feat: add cmos nor circuitikz family

This commit is contained in:
aliyun1121003339 2026-07-07 01:16:32 +08:00
parent 6081a2d93f
commit 9c515d4fa4
16 changed files with 273 additions and 22 deletions

File diff suppressed because one or more lines are too long

View file

@ -167,6 +167,7 @@ The repository now includes maintainer fixtures for every supported golden famil
| `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json` | `common-source-amplifier` |
| `docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json` | `cmos-inverter` |
| `docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json` | `cmos-nand2` |
| `docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json` | `cmos-nor2` |
Run every fixture through the same explicit renderer configuration with:
@ -216,6 +217,7 @@ This is not a generic TikZ generator. The current prototype supports only golden
| `common-source-amplifier` | `common-source-nmos-v1` | NMOS common-source amplifier with `R_D`, `VDD`, `vin`, `vout`, and grounded source |
| `cmos-inverter` | `cmos-inverter-v1` | PMOS-over-NMOS CMOS inverter with shared gate input and shared drain output |
| `cmos-nand2` | `cmos-nand2-v1` | Two-input CMOS NAND with parallel PMOS pull-up devices, series NMOS pull-down devices, `va`, `vb`, and `vout` |
| `cmos-nor2` | `cmos-nor2-v1` | Two-input CMOS NOR with series PMOS pull-up devices, parallel NMOS pull-down devices, `va`, `vb`, and `vout` |
The adapter validates the structural invariant first, then emits a fixed layout. For example, the CMOS inverter requires:
@ -228,6 +230,8 @@ The adapter validates the structural invariant first, then emits a fixed layout.
The CMOS NAND template adds a stronger digital-logic invariant: `MPA` and `MPB` must be PMOS devices in the parallel pull-up network from `VDD` to `vout`; `MNA` and `MNB` must be NMOS devices in a series pull-down stack from `vout` to `GND`; `va` must drive `MPA.G` and `MNA.G`; and `vb` must drive `MPB.G` and `MNB.G`. `layoutHints.inputSide` and `layoutHints.outputSide` only move the input/output ports and presentation routing; they do not change the topology signature.
The CMOS NOR template mirrors the digital-logic constraint in the opposite networks: `MPA` and `MPB` must be PMOS devices in a series pull-up stack from `VDD` to `vout`; `MNA` and `MNB` must be NMOS devices in the parallel pull-down network from `vout` to `GND`; `va` must drive `MPA.G` and `MNA.G`; and `vb` must drive `MPB.G` and `MNB.G`. The same layout-hint projection rule applies: port placement can move, but the topology signature cannot.
## Why `CircuitSpec` Is Separate
`DiagramSpec` is intentionally not widened for circuit diagrams yet. Circuit diagrams need topology, terminal references, layout lanes, and package conventions that generic flowcharts and data charts do not use.
@ -284,4 +288,4 @@ The tests verify:
## Non-Goals
This prototype does not bundle LaTeX, call TikZJax as an Obsidian runtime dependency, OCR path-only glyph text, run precise pixel-level overlap detection, cover unsupported SVG path geometry, or use rendered-image feedback for automatic repair. Those are later gates. It also does not accept arbitrary natural-language circuit requests. The important current claim is narrower: validated `CircuitSpec` input can produce stable, readable circuitikz for two high-value golden families, existing compile logs can be converted into actionable diagnostics, and an explicitly configured local renderer can be executed without shell-specific command parsing while optionally proving that a concrete output artifact was created and, for SVG or PNG output, structurally renderable enough for later visual inspection. SVG output now includes transform-aware geometry for common SVG transforms, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, positioned `tspan` label geometry, path-only label classification, path-only glyph overlap detection, and conservative label-vs-drawing overlap detection; PNG output exposes foreground bounds, foreground density, and format-specific unsupported-export guidance needed to reject obvious canvas clipping, first-pass pixel crowding, Adam7 interlaced PNGs, and unsupported indexed-color bit depths before a topology-preserving repair loop.
This prototype does not bundle LaTeX, call TikZJax as an Obsidian runtime dependency, OCR path-only glyph text, run precise pixel-level overlap detection, cover unsupported SVG path geometry, or use rendered-image feedback for automatic repair. Those are later gates. It also does not accept arbitrary natural-language circuit requests. The important current claim is narrower: validated `CircuitSpec` input can produce stable, readable circuitikz for four golden families, existing compile logs can be converted into actionable diagnostics, and an explicitly configured local renderer can be executed without shell-specific command parsing while optionally proving that a concrete output artifact was created and, for SVG or PNG output, structurally renderable enough for later visual inspection. SVG output now includes transform-aware geometry for common SVG transforms, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, positioned `tspan` label geometry, path-only label classification, path-only glyph overlap detection, and conservative label-vs-drawing overlap detection; PNG output exposes foreground bounds, foreground density, and format-specific unsupported-export guidance needed to reject obvious canvas clipping, first-pass pixel crowding, Adam7 interlaced PNGs, and unsupported indexed-color bit depths before a topology-preserving repair loop.

View file

@ -167,6 +167,7 @@ runner 位于 `src/diagram/adapters/circuitikz/circuitikzCompileRunner.ts`。它
| `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json` | `common-source-amplifier` |
| `docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json` | `cmos-inverter` |
| `docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json` | `cmos-nand2` |
| `docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json` | `cmos-nor2` |
使用同一套显式 renderer 配置跑完所有 fixture
@ -216,6 +217,7 @@ npm run diagram:smoke-circuitikz -- \
| `common-source-amplifier` | `common-source-nmos-v1` | 带 `R_D`、`VDD`、`vin`、`vout` 和源极接地的 NMOS 共源放大器 |
| `cmos-inverter` | `cmos-inverter-v1` | PMOS 在上、NMOS 在下,共用 gate 输入、共用 drain 输出的 CMOS 反相器 |
| `cmos-nand2` | `cmos-nand2-v1` | 两输入 CMOS NAND包含并联 PMOS 上拉、串联 NMOS 下拉、`va`、`vb` 与 `vout` |
| `cmos-nor2` | `cmos-nor2-v1` | 两输入 CMOS NOR包含串联 PMOS 上拉、并联 NMOS 下拉、`va`、`vb` 与 `vout` |
adapter 会先验证结构 invariant再输出固定布局。例如 CMOS 反相器要求:
@ -228,6 +230,8 @@ adapter 会先验证结构 invariant再输出固定布局。例如 CMOS 反
CMOS NAND template 进一步固定数字逻辑 invariant`MPA` 与 `MPB` 必须是从 `VDD``vout` 的并联 PMOS pull-up network`MNA` 与 `MNB` 必须是从 `vout``GND` 的串联 NMOS pull-down stack`va` 必须同时驱动 `MPA.G``MNA.G``vb` 必须同时驱动 `MPB.G``MNB.G`。`layoutHints.inputSide` 与 `layoutHints.outputSide` 只移动输入/输出端口和展示 routing不改变 topology signature。
CMOS NOR template 则锁定互补的数字逻辑 invariant`MPA` 与 `MPB` 必须是从 `VDD``vout` 的串联 PMOS pull-up stack`MNA` 与 `MNB` 必须是从 `vout``GND` 的并联 NMOS pull-down network`va` 必须同时驱动 `MPA.G``MNA.G``vb` 必须同时驱动 `MPB.G``MNB.G`。同样只允许通过 layout hints 移动端口展示位置,不能改变 topology signature。
## 为什么 `CircuitSpec` 独立存在
当前不会为了电路图过早扩展 `DiagramSpec`。电路图需要拓扑、terminal references、layout lanes 和 package conventions这些字段并不属于普通 flowchart 或 data chart。
@ -284,4 +288,4 @@ npm test -- --runInBand src/tests/circuitikzExporter.test.ts src/tests/circuitik
## 非目标
这个原型不捆绑 LaTeX、不把 TikZJax 作为 Obsidian runtime 依赖调用、不 OCR 识别 path-only glyph text、不做精确 pixel-level overlap detection、不覆盖尚未支持的 SVG path geometry也不使用渲染图像反馈进行自动修复。这些是后续 gate。它也不接受任意自然语言电路请求。当前重要声明更窄经过验证的 `CircuitSpec` 输入可以为两个高价值 golden families 生成稳定、可读的 circuitikz已有 compile logs 可以转换为 actionable diagnostics并且显式配置的本地 renderer 可以在不走 shell-specific command parsing 的情况下执行,同时可选证明具体输出 artifact 已经创建;如果输出是 SVG 或 PNG还能证明它具备进入后续视觉检查的基本结构。SVG 输出现在包含常见 SVG transforms 的 transform-aware geometry、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、positioned `tspan` label geometry、path-only label classification、path-only glyph overlap detection 与保守 label-vs-drawing overlap detectionPNG 输出则会暴露前景包围盒、前景密度与 format-specific unsupported-export guidance用于在 topology-preserving repair loop 之前拒绝明显画布裁剪、第一层 pixel crowding、Adam7 interlaced PNG 和 unsupported indexed-color bit depths。
这个原型不捆绑 LaTeX、不把 TikZJax 作为 Obsidian runtime 依赖调用、不 OCR 识别 path-only glyph text、不做精确 pixel-level overlap detection、不覆盖尚未支持的 SVG path geometry也不使用渲染图像反馈进行自动修复。这些是后续 gate。它也不接受任意自然语言电路请求。当前重要声明更窄经过验证的 `CircuitSpec` 输入可以为四个 golden families 生成稳定、可读的 circuitikz已有 compile logs 可以转换为 actionable diagnostics并且显式配置的本地 renderer 可以在不走 shell-specific command parsing 的情况下执行,同时可选证明具体输出 artifact 已经创建;如果输出是 SVG 或 PNG还能证明它具备进入后续视觉检查的基本结构。SVG 输出现在包含常见 SVG transforms 的 transform-aware geometry、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、positioned `tspan` label geometry、path-only label classification、path-only glyph overlap detection 与保守 label-vs-drawing overlap detectionPNG 输出则会暴露前景包围盒、前景密度与 format-specific unsupported-export guidance用于在 topology-preserving repair loop 之前拒绝明显画布裁剪、第一层 pixel crowding、Adam7 interlaced PNG 和 unsupported indexed-color bit depths。

View file

@ -140,7 +140,7 @@ Screenshot feedback can be manual first. Automated screenshot checks should star
Phase A is documented. Phase B/C now have a constrained repository prototype:
- `src/diagram/adapters/circuitikz/circuitSpec.ts` defines the separate circuit-only spec boundary.
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` validates topology and emits deterministic `circuitikz` LaTeX for `common-source-amplifier`, `cmos-inverter`, and `cmos-nand2` / `cmos-nand2-v1`. It now projects `layoutHints.inputSide` and `layoutHints.outputSide` into constrained port placement for those golden templates, so layout-only repair candidates can move input/output ports without changing topology. The CMOS NAND template locks the parallel PMOS pull-up network, the series NMOS pull-down stack, and dual input nets `va` / `vb` before export.
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` validates topology and emits deterministic `circuitikz` LaTeX for `common-source-amplifier`, `cmos-inverter`, `cmos-nand2` / `cmos-nand2-v1`, and `cmos-nor2` / `cmos-nor2-v1`. It now projects `layoutHints.inputSide` and `layoutHints.outputSide` into constrained port placement for those golden templates, so layout-only repair candidates can move input/output ports without changing topology. The CMOS NAND template locks the parallel PMOS pull-up network, the series NMOS pull-down stack, and dual input nets `va` / `vb` before export; the CMOS NOR template locks the complementary series PMOS pull-up stack and parallel NMOS pull-down network before export.
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` now also exposes `createCircuitTopologySignature` and `assertCircuitTopologyUnchanged` so topology-preserving repair can reject electrical drift while allowing label and layout changes.
- `scripts/export-circuitikz.js` and `npm run diagram:export-circuitikz` provide the offline export command, including `--topology-reference` for repair candidates.
- `src/diagram/adapters/circuitikz/circuitikzRepairBrief.ts`, `--repair-brief-output`, and `--repair-brief` produce and consume schema `notemd.circuitikz.repair-brief.v1`, a topology-preserving repair handoff that carries the source `CircuitSpec`, topology signature, compile/render diagnostics, allowed changes, prohibited topology changes, next verification steps, and a structured `repairPrompt`. The prompt role is `topology-preserving-circuitikz-repair`; it includes `diagnosticFocus` items derived from diagnostics and `acceptanceCriteria` that require candidate validation plus fresh compile and render-smoke checks. Candidate specs can be checked against the brief signature before output is written, and `--repair-brief` now returns `repairAcceptance` evidence with schema `notemd.circuitikz.repair-acceptance.v1`, `readyForVisualAcceptance`, gate-level status, blocking diagnostics, and `remainingChecks`. `--repair-acceptance-output` can persist the same evidence as a JSON file for CI or release records.

View file

@ -140,7 +140,7 @@ screenshot feedback 可以先保持人工执行。自动 screenshot 检查应从
Phase A 已经文档化。Phase B/C 现在有了受约束的仓库内原型:
- `src/diagram/adapters/circuitikz/circuitSpec.ts` 定义独立的 circuit-only spec 边界。
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` 会验证拓扑,并为 `common-source-amplifier`、`cmos-inverter``cmos-nand2` / `cmos-nand2-v1` 输出确定性的 `circuitikz` LaTeX。它现在会把 `layoutHints.inputSide``layoutHints.outputSide` 投影为这些 golden templates 的受约束 port placement因此 layout-only repair candidates 可以移动输入/输出端口而不改变拓扑。CMOS NAND template 会在导出前锁定并联 PMOS pull-up network、串联 NMOS pull-down stack 和双输入 nets `va` / `vb`
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` 会验证拓扑,并为 `common-source-amplifier`、`cmos-inverter`、`cmos-nand2` / `cmos-nand2-v1``cmos-nor2` / `cmos-nor2-v1` 输出确定性的 `circuitikz` LaTeX。它现在会把 `layoutHints.inputSide``layoutHints.outputSide` 投影为这些 golden templates 的受约束 port placement因此 layout-only repair candidates 可以移动输入/输出端口而不改变拓扑。CMOS NAND template 会在导出前锁定并联 PMOS pull-up network、串联 NMOS pull-down stack 和双输入 nets `va` / `vb`CMOS NOR template 会在导出前锁定互补的串联 PMOS pull-up stack 与并联 NMOS pull-down network
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` 现在还暴露 `createCircuitTopologySignature``assertCircuitTopologyUnchanged`,让 topology-preserving repair 可以拒绝电气拓扑漂移,同时允许 label 与 layout 变化。
- `scripts/export-circuitikz.js``npm run diagram:export-circuitikz` 提供离线导出命令,并通过 `--topology-reference` 支持 repair candidate。
- `src/diagram/adapters/circuitikz/circuitikzRepairBrief.ts`、`--repair-brief-output` 与 `--repair-brief` 会生成并消费 schema `notemd.circuitikz.repair-brief.v1`,作为 topology-preserving repair handoff携带 source `CircuitSpec`、topology signature、compile/render diagnostics、允许修改项、禁止拓扑修改项、下一步验证步骤和结构化 `repairPrompt`。prompt role 是 `topology-preserving-circuitikz-repair`;它包含从 diagnostics 派生的 `diagnosticFocus` 项,以及要求 candidate validation、重新 compile 和 render-smoke checks 的 `acceptanceCriteria`。candidate spec 可以在写出 output 前按 brief signature 执行校验,`--repair-brief` 现在还会返回 schema `notemd.circuitikz.repair-acceptance.v1``repairAcceptance` 证据,包含 `readyForVisualAcceptance`、gate-level status、blocking diagnostics 与 `remainingChecks`。`--repair-acceptance-output` 可以把同一份 evidence 持久化为 JSON 文件,供 CI 或 release 记录使用。

View file

@ -0,0 +1,34 @@
{
"circuitKind": "cmos-nor2",
"title": "Two Input CMOS NOR Gate",
"goldenReferenceId": "cmos-nor2-v1",
"style": {
"package": "circuitikz",
"voltageConvention": "american voltages"
},
"nets": ["VDD", "GND", "va", "vb", "vout", "pull_up_mid"],
"components": [
{ "id": "MPA", "type": "pmos", "label": "$M_{PA}$", "terminals": { "S": "VDD", "G": "va", "D": "pull_up_mid" } },
{ "id": "MPB", "type": "pmos", "label": "$M_{PB}$", "terminals": { "S": "pull_up_mid", "G": "vb", "D": "vout" } },
{ "id": "MNA", "type": "nmos", "label": "$M_{NA}$", "terminals": { "D": "vout", "G": "va", "S": "GND" } },
{ "id": "MNB", "type": "nmos", "label": "$M_{NB}$", "terminals": { "D": "vout", "G": "vb", "S": "GND" } }
],
"connections": [
{ "from": "VDD", "to": "MPA.S" },
{ "from": "MPA.D", "to": "MPB.S" },
{ "from": "MPB.D", "to": "vout" },
{ "from": "MNA.D", "to": "vout" },
{ "from": "MNB.D", "to": "vout" },
{ "from": "MNA.S", "to": "GND" },
{ "from": "MNB.S", "to": "GND" },
{ "from": "va", "to": "MPA.G" },
{ "from": "va", "to": "MNA.G" },
{ "from": "vb", "to": "MPB.G" },
{ "from": "vb", "to": "MNB.G" }
],
"layoutHints": {
"inputSide": "left",
"outputSide": "right",
"routingStyle": "orthogonal"
}
}

View file

@ -1,7 +1,8 @@
export const SUPPORTED_CIRCUIT_KINDS = [
'common-source-amplifier',
'cmos-inverter',
'cmos-nand2'
'cmos-nand2',
'cmos-nor2'
] as const;
export type CircuitKind = typeof SUPPORTED_CIRCUIT_KINDS[number];

View file

@ -46,12 +46,12 @@ function extendedPortX(side: 'left' | 'right'): string {
return side === 'left' ? '0.8' : '5.2';
}
function nandInputPortX(side: 'left' | 'right'): string {
function dualInputPortX(side: 'left' | 'right'): string {
return side === 'left' ? '0.8' : '5.2';
}
function requiredNetsForCircuitKind(circuitKind: string): string[] {
if (circuitKind === 'cmos-nand2') {
if (circuitKind === 'cmos-nand2' || circuitKind === 'cmos-nor2') {
return ['VDD', 'GND', 'va', 'vb', 'vout'];
}
@ -286,6 +286,43 @@ function validateCmosNand2Template(
}
}
function validateCmosNor2Template(
spec: CircuitSpec,
componentById: Map<string, CircuitComponent>,
errors: string[]
): void {
if (spec.goldenReferenceId !== 'cmos-nor2-v1') {
errors.push('CMOS NOR requires goldenReferenceId "cmos-nor2-v1".');
}
requireComponent(componentById, 'MPA', 'pmos', errors);
requireComponent(componentById, 'MPB', 'pmos', errors);
requireComponent(componentById, 'MNA', 'nmos', errors);
requireComponent(componentById, 'MNB', 'nmos', errors);
if (!hasConnection(spec.connections, 'VDD', 'MPA.S')) {
errors.push('CMOS NOR requires MPA.S to connect to VDD at the top of the pull-up stack.');
}
if (!hasConnection(spec.connections, 'MPA.D', 'MPB.S')) {
errors.push('CMOS NOR requires MPA.D to connect to MPB.S for the series pull-up stack.');
}
if (!hasConnection(spec.connections, 'MPB.D', 'vout')) {
errors.push('CMOS NOR requires MPB.D to connect to vout at the bottom of the pull-up stack.');
}
if (!hasConnection(spec.connections, 'MNA.D', 'vout') || !hasConnection(spec.connections, 'MNB.D', 'vout')) {
errors.push('CMOS NOR requires both NMOS drains to connect to vout for the parallel pull-down network.');
}
if (!hasConnection(spec.connections, 'MNA.S', 'GND') || !hasConnection(spec.connections, 'MNB.S', 'GND')) {
errors.push('CMOS NOR requires both NMOS sources to connect to GND for the parallel pull-down network.');
}
if (!hasConnection(spec.connections, 'va', 'MPA.G') || !hasConnection(spec.connections, 'va', 'MNA.G')) {
errors.push('CMOS NOR requires va to drive both MPA.G and MNA.G.');
}
if (!hasConnection(spec.connections, 'vb', 'MPB.G') || !hasConnection(spec.connections, 'vb', 'MNB.G')) {
errors.push('CMOS NOR requires vb to drive both MPB.G and MNB.G.');
}
}
export function validateCircuitSpec(spec: CircuitSpec): CircuitSpecValidationResult {
const errors: string[] = [];
@ -328,6 +365,8 @@ export function validateCircuitSpec(spec: CircuitSpec): CircuitSpecValidationRes
validateCmosInverterTemplate(spec, componentById, errors);
} else if (spec.circuitKind === 'cmos-nand2') {
validateCmosNand2Template(spec, componentById, errors);
} else if (spec.circuitKind === 'cmos-nor2') {
validateCmosNor2Template(spec, componentById, errors);
}
return {
@ -434,23 +473,23 @@ function renderCmosNand2Template(spec: CircuitSpec): string {
? ` (MPA.G) to [short] (1.7,4.2)
(MNA.G) to [short] (1.7,2.7)
(1.7,4.2) to [short] (1.7,2.7)
to [short, -o] (${nandInputPortX(inputSide)},3.45)
to [short, -o] (${dualInputPortX(inputSide)},3.45)
node[left]{$v_A$}
(MPB.G) to [short] (1.3,4.2)
(MNB.G) to [short] (1.3,1.8)
(1.3,4.2) to [short] (1.3,1.8)
to [short, -o] (${nandInputPortX(inputSide)},2.7)
to [short, -o] (${dualInputPortX(inputSide)},2.7)
node[left]{$v_B$};`
: ` (MPA.G) to [short] (4.1,4.2)
(MNA.G) to [short] (4.1,2.7)
(4.1,4.2) to [short] (4.1,2.7)
to [short] (4.5,2.7)
to [short, -o] (${nandInputPortX(inputSide)},4.15)
to [short, -o] (${dualInputPortX(inputSide)},4.15)
node[right]{$v_A$}
(MPB.G) to [short] (4.5,4.2)
(MNB.G) to [short] (4.5,1.8)
(4.5,4.2) to [short] (4.5,1.8)
to [short, -o] (${nandInputPortX(inputSide)},1.85)
to [short, -o] (${dualInputPortX(inputSide)},1.85)
node[right]{$v_B$};`;
return `\\usepackage{circuitikz}
@ -482,6 +521,68 @@ ${inputRoute}
`;
}
function renderCmosNor2Template(spec: CircuitSpec): string {
const mpa = spec.components.find(component => component.id === 'MPA');
const mpb = spec.components.find(component => component.id === 'MPB');
const mna = spec.components.find(component => component.id === 'MNA');
const mnb = spec.components.find(component => component.id === 'MNB');
const mpaLabel = normalizeLabel(mpa?.label, '$M_{PA}$');
const mpbLabel = normalizeLabel(mpb?.label, '$M_{PB}$');
const mnaLabel = normalizeLabel(mna?.label, '$M_{NA}$');
const mnbLabel = normalizeLabel(mnb?.label, '$M_{NB}$');
const inputSide = layoutSide(spec.layoutHints?.inputSide, 'left');
const outputSide = layoutSide(spec.layoutHints?.outputSide, 'right');
const inputRoute = inputSide === 'left'
? ` (MPA.G) to [short] (1.7,4.35)
(MNA.G) to [short] (1.7,1.95)
(1.7,4.35) to [short] (1.7,1.95)
to [short, -o] (${dualInputPortX(inputSide)},4.35)
node[left]{$v_A$}
(MPB.G) to [short] (1.3,3.35)
(MNB.G) to [short] (1.3,1.45)
(1.3,3.35) to [short] (1.3,1.45)
to [short, -o] (${dualInputPortX(inputSide)},1.95)
node[left]{$v_B$};`
: ` (MPA.G) to [short] (4.1,4.35)
(MNA.G) to [short] (4.1,1.95)
(4.1,4.35) to [short] (4.1,1.95)
to [short] (4.5,1.95)
to [short, -o] (${dualInputPortX(inputSide)},4.35)
node[right]{$v_A$}
(MPB.G) to [short] (4.5,3.35)
(MNB.G) to [short] (4.5,1.45)
(4.5,3.35) to [short] (4.5,1.45)
to [short, -o] (${dualInputPortX(inputSide)},1.95)
node[right]{$v_B$};`;
return `\\usepackage{circuitikz}
\\begin{document}
\\begin{circuitikz}[${spec.style.voltageConvention}]
\\draw
(3,5) node[vcc]{$V_{DD}$}
to [short] (3,4.8)
node[pmos, anchor=S] (MPA) {${mpaLabel}}
(MPA.D) to [short] (3,3.75)
node[pmos, anchor=S] (MPB) {${mpbLabel}}
(MPB.D) to [short] (3,2.75);
\\draw
(3,2.75) to [short, *-o] (${commonPortX(outputSide)},2.75) node[${outputSide}]{$v_{out}$};
\\draw
(3,2.75) to [short] (2.5,2.2)
node[nmos, anchor=D] (MNA) {${mnaLabel}}
(MNA.S) to [short] (2.5,0.7)
(3,2.75) to [short] (3.5,2.2)
node[nmos, anchor=D] (MNB) {${mnbLabel}}
(MNB.S) to [short] (3.5,0.7)
(2.5,0.7) to [short] (3.5,0.7)
(3,0.7) node[ground]{};
\\draw
${inputRoute}
\\end{circuitikz}
\\end{document}
`;
}
export function exportCircuitSpecToCircuitikz(spec: CircuitSpec): string {
assertValidCircuitSpec(spec);
@ -492,6 +593,8 @@ export function exportCircuitSpecToCircuitikz(spec: CircuitSpec): string {
return renderCmosInverterTemplate(spec);
case 'cmos-nand2':
return renderCmosNand2Template(spec);
case 'cmos-nor2':
return renderCmosNor2Template(spec);
default:
throw new ValidationError('Unsupported circuitKind.', 'INVALID_INPUT');
}

View file

@ -41,12 +41,15 @@ describe('circuitikz export docs contract', () => {
expect(doc).toContain('cmos-inverter-v1');
expect(doc).toContain('cmos-nand2');
expect(doc).toContain('cmos-nand2-v1');
expect(doc).toContain('cmos-nor2');
expect(doc).toContain('cmos-nor2-v1');
expect(doc).toContain('layoutHints.inputSide');
expect(doc).toContain('layoutHints.outputSide');
expect(doc).toContain('port placement');
expect(doc).toContain('docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json');
expect(doc).toContain('docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json');
expect(doc).toContain('docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json');
expect(doc).toContain('docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json');
expect(doc).toContain('UTF-8');
expect(doc).toContain('BOM');
expect(doc).toContain('--compile-log');
@ -131,6 +134,8 @@ describe('circuitikz export docs contract', () => {
expect(doc).toContain('CircuitSpec -> circuitikz');
expect(doc).toContain('cmos-nand2');
expect(doc).toContain('cmos-nand2-v1');
expect(doc).toContain('cmos-nor2');
expect(doc).toContain('cmos-nor2-v1');
expect(doc).toContain('layoutHints.inputSide');
expect(doc).toContain('layoutHints.outputSide');
expect(doc).toContain('port placement');
@ -217,6 +222,8 @@ describe('circuitikz export docs contract', () => {
expect(doc).toContain('circuitikzRepairBrief.ts');
expect(doc).toContain('cmos-nand2');
expect(doc).toContain('cmos-nand2-v1');
expect(doc).toContain('cmos-nor2');
expect(doc).toContain('cmos-nor2-v1');
expect(doc).toContain('layoutHints.inputSide');
expect(doc).toContain('layoutHints.outputSide');
expect(doc).toContain('port placement');

View file

@ -104,6 +104,43 @@ function createCmosNand2Spec(): CircuitSpec {
};
}
function createCmosNor2Spec(): CircuitSpec {
return {
circuitKind: 'cmos-nor2' as CircuitSpec['circuitKind'],
title: 'Two Input CMOS NOR Gate',
goldenReferenceId: 'cmos-nor2-v1',
style: {
package: 'circuitikz',
voltageConvention: 'american voltages'
},
nets: ['VDD', 'GND', 'va', 'vb', 'vout', 'pull_up_mid'],
components: [
{ id: 'MPA', type: 'pmos', label: '$M_{PA}$', terminals: { S: 'VDD', G: 'va', D: 'pull_up_mid' } },
{ id: 'MPB', type: 'pmos', label: '$M_{PB}$', terminals: { S: 'pull_up_mid', G: 'vb', D: 'vout' } },
{ id: 'MNA', type: 'nmos', label: '$M_{NA}$', terminals: { D: 'vout', G: 'va', S: 'GND' } },
{ id: 'MNB', type: 'nmos', label: '$M_{NB}$', terminals: { D: 'vout', G: 'vb', S: 'GND' } }
],
connections: [
{ from: 'VDD', to: 'MPA.S' },
{ from: 'MPA.D', to: 'MPB.S' },
{ from: 'MPB.D', to: 'vout' },
{ from: 'MNA.D', to: 'vout' },
{ from: 'MNB.D', to: 'vout' },
{ from: 'MNA.S', to: 'GND' },
{ from: 'MNB.S', to: 'GND' },
{ from: 'va', to: 'MPA.G' },
{ from: 'va', to: 'MNA.G' },
{ from: 'vb', to: 'MPB.G' },
{ from: 'vb', to: 'MNB.G' }
],
layoutHints: {
inputSide: 'left',
outputSide: 'right',
routingStyle: 'orthogonal'
}
};
}
describe('circuitikz exporter', () => {
test('validates and exports the common-source golden reference template deterministically', () => {
const spec = createCommonSourceSpec();
@ -209,6 +246,52 @@ describe('circuitikz exporter', () => {
expect(() => exportCircuitSpecToCircuitikz(spec)).toThrow(/series pull-down stack/);
});
test('validates and exports a constrained two-input CMOS NOR template deterministically', () => {
const spec = createCmosNor2Spec();
const output = exportCircuitSpecToCircuitikz(spec);
expect(assertValidCircuitSpec(spec)).toBe(spec);
expect(output).toContain('\\begin{circuitikz}[american voltages]');
expect(output).toContain('node[pmos, anchor=S] (MPA) {$M_{PA}$}');
expect(output).toContain('node[pmos, anchor=S] (MPB) {$M_{PB}$}');
expect(output).toContain('node[nmos, anchor=D] (MNA) {$M_{NA}$}');
expect(output).toContain('node[nmos, anchor=D] (MNB) {$M_{NB}$}');
expect(output).toContain('(MPA.D) to [short] (3,3.75)');
expect(output).toContain('(MPB.D) to [short] (3,2.75)');
expect(output).toContain('(3,2.75) to [short, *-o] (5,2.75) node[right]{$v_{out}$};');
expect(output).toContain('node[left]{$v_A$}');
expect(output).toContain('node[left]{$v_B$};');
});
test('projects CMOS NOR layout hints into deterministic input and output ports', () => {
const reference = createCmosNor2Spec();
const spec = createCmosNor2Spec();
spec.layoutHints = {
inputSide: 'right',
outputSide: 'left',
routingStyle: 'orthogonal'
};
const output = exportCircuitSpecToCircuitikz(spec);
expect(createCircuitTopologySignature(spec)).toBe(createCircuitTopologySignature(reference));
expect(output).toContain('(3,2.75) to [short, *-o] (0.8,2.75) node[left]{$v_{out}$};');
expect(output).toContain('to [short, -o] (5.2,4.35)');
expect(output).toContain('node[right]{$v_A$}');
expect(output).toContain('to [short, -o] (5.2,1.95)');
expect(output).toContain('node[right]{$v_B$};');
});
test('rejects CMOS NOR specs that do not keep the PMOS pull-up stack in series', () => {
const spec = createCmosNor2Spec();
spec.connections = spec.connections.filter(connection => connection.from !== 'MPA.D' || connection.to !== 'MPB.S');
expect(validateCircuitSpec(spec).errors).toContain(
'CMOS NOR requires MPA.D to connect to MPB.S for the series pull-up stack.'
);
expect(() => exportCircuitSpecToCircuitikz(spec)).toThrow(/series pull-up stack/);
});
test('projects common-source layout hints into deterministic input and output ports', () => {
const reference = createCommonSourceSpec();
const spec = createCommonSourceSpec();

View file

@ -22,6 +22,7 @@ describe('circuitikz smoke fixtures CLI', () => {
expect(fixtureNames).toEqual([
'cmos-inverter-v1.json',
'cmos-nand2-v1.json',
'cmos-nor2-v1.json',
'common-source-nmos-v1.json'
]);
@ -58,7 +59,7 @@ describe('circuitikz smoke fixtures CLI', () => {
const report = JSON.parse(result.stdout);
expect(report).toEqual(expect.objectContaining({
ok: false,
fixtureCount: 3,
fixtureCount: 4,
reportOutputPath: reportPath,
rendererAvailability: expect.objectContaining({
ok: false,
@ -137,12 +138,13 @@ fs.writeFileSync(
const report = JSON.parse(stdout);
expect(report).toEqual(expect.objectContaining({
ok: true,
fixtureCount: 3,
fixtureCount: 4,
outputDirectory
}));
expect(report.fixtures.map((fixture: any) => fixture.name).sort()).toEqual([
'cmos-inverter-v1',
'cmos-nand2-v1',
'cmos-nor2-v1',
'common-source-nmos-v1'
]);
@ -162,7 +164,7 @@ fs.writeFileSync(
.trim()
.split(/\r?\n/)
.map(line => JSON.parse(line));
expect(invocations).toHaveLength(3);
expect(invocations).toHaveLength(4);
for (const invocation of invocations) {
expect(invocation[0]).toBe(compilerPath);
expect(invocation).toContain('--tex');

View file

@ -61,6 +61,8 @@ describe('website documentation contract', () => {
expect(content).toContain('cmos-inverter-v1');
expect(content).toContain('cmos-nand2');
expect(content).toContain('cmos-nand2-v1');
expect(content).toContain('cmos-nor2');
expect(content).toContain('cmos-nor2-v1');
expect(content).toContain('layoutHints.inputSide');
expect(content).toContain('layoutHints.outputSide');
expect(content).toContain('port placement');
@ -139,6 +141,7 @@ describe('website documentation contract', () => {
expect(content).toContain('docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json');
expect(content).toContain('docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json');
expect(content).toContain('docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json');
expect(content).toContain('docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json');
}
});

View file

@ -213,13 +213,14 @@ Notemd now includes the first constrained repository prototype for this directio
npm run diagram:export-circuitikz -- --input cmos-inverter.json --output cmos-inverter.tex
```
The prototype adds a separate `CircuitSpec` boundary and deterministic exporter for three golden-reference families:
The prototype adds a separate `CircuitSpec` boundary and deterministic exporter for four golden-reference families:
| Circuit kind | Golden reference | Current guarantee |
|--------------|------------------|-------------------|
| `common-source-amplifier` | `common-source-nmos-v1` | validates `VDD -> R_D -> M1.D`, `vin -> M1.G`, `M1.S -> GND`, and `M1.D -> vout` before writing LaTeX |
| `cmos-inverter` | `cmos-inverter-v1` | validates PMOS-over-NMOS topology, shared gate input, shared drain output, `VDD -> MP.S`, and `MN.S -> GND` before writing LaTeX |
| `cmos-nand2` | `cmos-nand2-v1` | validates parallel PMOS pull-up, series NMOS pull-down, dual inputs `va` / `vb`, and `vout` before writing LaTeX |
| `cmos-nor2` | `cmos-nor2-v1` | validates series PMOS pull-up, parallel NMOS pull-down, dual inputs `va` / `vb`, and `vout` before writing LaTeX |
This is not a general TikZ generator yet. It does not compile LaTeX, call TikZJax, inspect screenshots, or run automated image-feedback repair. Those remain later gates.
@ -295,7 +296,7 @@ For release or maintainer evidence, run every supported golden family through th
npm run diagram:smoke-circuitikz -- --output-dir docs/export/circuitikz-smoke --compile-executable pdflatex --compile-arg -interaction=nonstopmode --compile-arg -halt-on-error --compile-arg -output-directory={outputDir} --compile-arg {tex} --expected-artifact {outputDir}/{jobName}.pdf
```
The runner uses `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json`, `docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json`, and `docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json`, calls the same shell-free exporter path for each fixture, and returns an aggregate JSON report with per-fixture `compileExecution` and `compileDiagnostics`. It is still a maintainer command, not a plugin runtime dependency.
The runner uses `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json`, `docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json`, `docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json`, and `docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json`, calls the same shell-free exporter path for each fixture, and returns an aggregate JSON report with per-fixture `compileExecution` and `compileDiagnostics`. It is still a maintainer command, not a plugin runtime dependency.
When a maintainer machine has no renderer configured yet, run the same fixture command without `--compile-executable` and persist the environment gate explicitly:
@ -344,7 +345,7 @@ For a CMOS inverter, the prompt should request an explicit topology plus layout
| General diagrams | Spec-first pipeline implemented for Mermaid, JSON Canvas, Vega-Lite, HTML | Keep expanding semantic verification coverage |
| Editable figures | `editable-html-svg`, Draw.io XML, and Drawnix JSON artifact boundaries implemented | Add richer primitives only after tests prove editability |
| CLI support | `npm run diagram:export-artifact` exports editable HTML/SVG, Draw.io, and Drawnix from one `DiagramSpec` | Add target-specific smoke fixtures when new targets ship |
| circuitikz | `CircuitSpec -> circuitikz` prototype exports common-source, CMOS inverter, and `cmos-nand2` / `cmos-nand2-v1` golden templates, projects `layoutHints.inputSide` and `layoutHints.outputSide` into deterministic input/output port placement without changing topology, rejects repair topology drift through `--topology-reference`, emits topology-preserving repair briefs through `--repair-brief-output` and schema `notemd.circuitikz.repair-brief.v1`, includes structured `repairPrompt` handoff content with `diagnosticFocus`, `acceptanceCriteria`, and role `topology-preserving-circuitikz-repair`, validates repair candidates through `--repair-brief`, returns `repairAcceptance` gate evidence through schema `notemd.circuitikz.repair-acceptance.v1` with `readyForVisualAcceptance` and `remainingChecks`, persists that evidence through `--repair-acceptance-output`, parses compile logs, can run explicit local renderers plus `--expected-artifact`, SVG `--expected-svg-text`, accessibility metadata checks through `aria-label`, `<title>`, and `<desc>`, hidden/transparent SVG element exclusion, `render-svg-text-path-only` / `pathOnlyGlyphUseCount` classification for path-only labels, path-only glyph placement checks for `<use href="#...">`, path-only glyph overlap diagnostics through `render-svg-path-glyph-overlap`, close-path current-point handling for `Z/z`, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` drawing geometry checks, positioned `tspan` label geometry, `text-anchor`-aware positioned text geometry, transform-aware geometry for SVG bounded-canvas/text-overlap and label-vs-drawing smoke through `render-svg-label-overlap`, and PNG nonblank / clipped / dense-foreground screenshot smoke checks, including indexed-color palette alpha, grayscale/RGB tRNS transparent samples, and format-specific `render-png-unsupported` guidance for Adam7 interlaced PNGs and indexed bit-depth failures, through `foregroundBounds`, `foregroundDensity`, `render-png-content-clipped`, and `render-png-foreground-dense` without shell parsing, includes aggregate maintainer smoke fixtures through `npm run diagram:smoke-circuitikz`, records missing renderer configuration through `rendererAvailability.status: "missing-configuration"` and `compile-executable-invalid`, and has generic preview diagnostics, diagnostic summary counts, diagnostics-aware history entries, and source-only fallback through `RenderArtifact.diagnostics` and the preview modal | Add OCR-level label recognition for path-only visual text, precise pixel-level overlap checks, broader SVG path coverage where needed, automatic renderer installation/discovery only if it can remain optional, and automated topology-preserving repair execution |
| circuitikz | `CircuitSpec -> circuitikz` prototype exports common-source, CMOS inverter, `cmos-nand2` / `cmos-nand2-v1`, and `cmos-nor2` / `cmos-nor2-v1` golden templates, projects `layoutHints.inputSide` and `layoutHints.outputSide` into deterministic input/output port placement without changing topology, rejects repair topology drift through `--topology-reference`, emits topology-preserving repair briefs through `--repair-brief-output` and schema `notemd.circuitikz.repair-brief.v1`, includes structured `repairPrompt` handoff content with `diagnosticFocus`, `acceptanceCriteria`, and role `topology-preserving-circuitikz-repair`, validates repair candidates through `--repair-brief`, returns `repairAcceptance` gate evidence through schema `notemd.circuitikz.repair-acceptance.v1` with `readyForVisualAcceptance` and `remainingChecks`, persists that evidence through `--repair-acceptance-output`, parses compile logs, can run explicit local renderers plus `--expected-artifact`, SVG `--expected-svg-text`, accessibility metadata checks through `aria-label`, `<title>`, and `<desc>`, hidden/transparent SVG element exclusion, `render-svg-text-path-only` / `pathOnlyGlyphUseCount` classification for path-only labels, path-only glyph placement checks for `<use href="#...">`, path-only glyph overlap diagnostics through `render-svg-path-glyph-overlap`, close-path current-point handling for `Z/z`, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` drawing geometry checks, positioned `tspan` label geometry, `text-anchor`-aware positioned text geometry, transform-aware geometry for SVG bounded-canvas/text-overlap and label-vs-drawing smoke through `render-svg-label-overlap`, and PNG nonblank / clipped / dense-foreground screenshot smoke checks, including indexed-color palette alpha, grayscale/RGB tRNS transparent samples, and format-specific `render-png-unsupported` guidance for Adam7 interlaced PNGs and indexed bit-depth failures, through `foregroundBounds`, `foregroundDensity`, `render-png-content-clipped`, and `render-png-foreground-dense` without shell parsing, includes aggregate maintainer smoke fixtures through `npm run diagram:smoke-circuitikz`, records missing renderer configuration through `rendererAvailability.status: "missing-configuration"` and `compile-executable-invalid`, and has generic preview diagnostics, diagnostic summary counts, diagnostics-aware history entries, and source-only fallback through `RenderArtifact.diagnostics` and the preview modal | Add OCR-level label recognition for path-only visual text, precise pixel-level overlap checks, broader SVG path coverage where needed, automatic renderer installation/discovery only if it can remain optional, and automated topology-preserving repair execution |
| TikZJax integration | Candidate render host for Obsidian-side display | Keep it optional; do not make TikZJax a hard plugin runtime dependency |
## Configuration

View file

@ -213,13 +213,14 @@ Notemd 现在已经包含这个方向的第一个受约束仓库内原型。它
npm run diagram:export-circuitikz -- --input cmos-inverter.json --output cmos-inverter.tex
```
这个原型新增了独立的 `CircuitSpec` 边界,并为个 golden-reference families 提供确定性 exporter
这个原型新增了独立的 `CircuitSpec` 边界,并为个 golden-reference families 提供确定性 exporter
| Circuit kind | Golden reference | Current guarantee |
|--------------|------------------|-------------------|
| `common-source-amplifier` | `common-source-nmos-v1` | 写出 LaTeX 前验证 `VDD -> R_D -> M1.D`、`vin -> M1.G`、`M1.S -> GND` 和 `M1.D -> vout` |
| `cmos-inverter` | `cmos-inverter-v1` | 写出 LaTeX 前验证 PMOS-over-NMOS 拓扑、shared gate input、shared drain output、`VDD -> MP.S` 和 `MN.S -> GND` |
| `cmos-nand2` | `cmos-nand2-v1` | 写出 LaTeX 前验证并联 PMOS pull-up、串联 NMOS pull-down、双输入 `va` / `vb` 和 `vout` |
| `cmos-nor2` | `cmos-nor2-v1` | 写出 LaTeX 前验证串联 PMOS pull-up、并联 NMOS pull-down、双输入 `va` / `vb` 和 `vout` |
这还不是通用 TikZ 生成器。它不会编译 LaTeX、调用 TikZJax、检查截图或运行自动化图像反馈修复这些仍是后续 gate。
@ -295,7 +296,7 @@ npm run diagram:export-circuitikz -- --input repaired-cmos-inverter.json --repai
npm run diagram:smoke-circuitikz -- --output-dir docs/export/circuitikz-smoke --compile-executable pdflatex --compile-arg -interaction=nonstopmode --compile-arg -halt-on-error --compile-arg -output-directory={outputDir} --compile-arg {tex} --expected-artifact {outputDir}/{jobName}.pdf
```
runner 使用 `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json`、`docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json` 和 `docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json`,对每个 fixture 调用同一条 shell-free exporter path并返回包含每个 fixture 的 `compileExecution` 与 `compileDiagnostics` 的聚合 JSON report。它仍然是 maintainer command不是插件 runtime 依赖。
runner 使用 `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json`、`docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json`、`docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json` 和 `docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json`,对每个 fixture 调用同一条 shell-free exporter path并返回包含每个 fixture 的 `compileExecution` 与 `compileDiagnostics` 的聚合 JSON report。它仍然是 maintainer command不是插件 runtime 依赖。
如果维护者机器尚未配置 renderer可以不传 `--compile-executable`,用同一个 fixture command 把环境 gate 明确持久化:
@ -344,7 +345,7 @@ npm run diagram:smoke-circuitikz -- --output-dir docs/export/circuitikz-smoke --
| General diagrams | Mermaid、JSON Canvas、Vega-Lite、HTML 的 spec-first pipeline 已实现 | 继续扩展 semantic verification coverage |
| Editable figures | `editable-html-svg`、Draw.io XML、Drawnix JSON artifact boundaries 已实现 | 只有在测试证明 editability 后再加入更丰富 primitives |
| CLI support | `npm run diagram:export-artifact` 可从同一个 `DiagramSpec` 导出 editable HTML/SVG、Draw.io、Drawnix | 新 target 发布时增加 target-specific smoke fixtures |
| circuitikz | `CircuitSpec -> circuitikz` 原型已经可通过 `npm run diagram:export-circuitikz` 导出 common-source、CMOS inverter 和 `cmos-nand2` / `cmos-nand2-v1` golden templates、把 `layoutHints.inputSide` 与 `layoutHints.outputSide` 投影为不改变拓扑的确定性 input/output port placement、通过 `--topology-reference` 拒绝 repair topology drift、通过 `--repair-brief-output` 和 schema `notemd.circuitikz.repair-brief.v1` 写出 topology-preserving repair briefs、内含带 `diagnosticFocus`、`acceptanceCriteria` 与 role `topology-preserving-circuitikz-repair` 的结构化 `repairPrompt` handoff、通过 `--repair-brief` 校验 repair candidates、通过 schema `notemd.circuitikz.repair-acceptance.v1` 返回带 `readyForVisualAcceptance` 与 `remainingChecks` 的 `repairAcceptance` gate evidence、通过 `--repair-acceptance-output` 持久化这份 evidence、解析 compile logs并在不经过 shell parsing 的情况下运行显式本地 renderer、`--expected-artifact`、SVG `--expected-svg-text`、通过 `aria-label`、`<title>` 与 `<desc>` 执行 accessibility metadata checks、hidden/transparent SVG element exclusion、用于 path-only labels 的 `render-svg-text-path-only` / `pathOnlyGlyphUseCount` 分类、针对 `<use href="#...">` 的 path-only glyph placement checks、通过 `render-svg-path-glyph-overlap` 执行的 path-only glyph overlap diagnostics、针对 `Z/z` 的 close-path current-point handling、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` drawing geometry checks、positioned `tspan` label geometry、`text-anchor`-aware positioned text geometry、带 transform-aware geometry 的 SVG bounded-canvas / text-overlap 以及通过 `render-svg-label-overlap` 报告的 label-vs-drawing smoke以及通过 `foregroundBounds`、`foregroundDensity`、`render-png-content-clipped` 和 `render-png-foreground-dense` 执行的 PNG nonblank / clipped / dense-foreground screenshot smoke checks包含 indexed-color palette alpha、grayscale/RGB tRNS transparent samples以及针对 Adam7 interlaced PNG 与 indexed bit-depth failures 的 format-specific `render-png-unsupported` guidance现在还通过 `npm run diagram:smoke-circuitikz` 提供聚合 maintainer smoke fixtures通过 `rendererAvailability.status: "missing-configuration"` 与 `compile-executable-invalid` 记录缺失 renderer 配置,并通过 `RenderArtifact.diagnostics` 和 preview modal 具备通用 preview diagnostics、diagnostic summary counts、diagnostics-aware history entries 与 source-only fallback | 增加 path-only visual text 的 OCR-level label recognition、精确 pixel-level overlap checks、按需补齐更完整 SVG path coverage只有能保持可选时才考虑 automatic renderer installation/discovery继续推进自动化 topology-preserving repair execution |
| circuitikz | `CircuitSpec -> circuitikz` 原型已经可通过 `npm run diagram:export-circuitikz` 导出 common-source、CMOS inverter、`cmos-nand2` / `cmos-nand2-v1` 和 `cmos-nor2` / `cmos-nor2-v1` golden templates、把 `layoutHints.inputSide` 与 `layoutHints.outputSide` 投影为不改变拓扑的确定性 input/output port placement、通过 `--topology-reference` 拒绝 repair topology drift、通过 `--repair-brief-output` 和 schema `notemd.circuitikz.repair-brief.v1` 写出 topology-preserving repair briefs、内含带 `diagnosticFocus`、`acceptanceCriteria` 与 role `topology-preserving-circuitikz-repair` 的结构化 `repairPrompt` handoff、通过 `--repair-brief` 校验 repair candidates、通过 schema `notemd.circuitikz.repair-acceptance.v1` 返回带 `readyForVisualAcceptance` 与 `remainingChecks` 的 `repairAcceptance` gate evidence、通过 `--repair-acceptance-output` 持久化这份 evidence、解析 compile logs并在不经过 shell parsing 的情况下运行显式本地 renderer、`--expected-artifact`、SVG `--expected-svg-text`、通过 `aria-label`、`<title>` 与 `<desc>` 执行 accessibility metadata checks、hidden/transparent SVG element exclusion、用于 path-only labels 的 `render-svg-text-path-only` / `pathOnlyGlyphUseCount` 分类、针对 `<use href="#...">` 的 path-only glyph placement checks、通过 `render-svg-path-glyph-overlap` 执行的 path-only glyph overlap diagnostics、针对 `Z/z` 的 close-path current-point handling、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` drawing geometry checks、positioned `tspan` label geometry、`text-anchor`-aware positioned text geometry、带 transform-aware geometry 的 SVG bounded-canvas / text-overlap 以及通过 `render-svg-label-overlap` 报告的 label-vs-drawing smoke以及通过 `foregroundBounds`、`foregroundDensity`、`render-png-content-clipped` 和 `render-png-foreground-dense` 执行的 PNG nonblank / clipped / dense-foreground screenshot smoke checks包含 indexed-color palette alpha、grayscale/RGB tRNS transparent samples以及针对 Adam7 interlaced PNG 与 indexed bit-depth failures 的 format-specific `render-png-unsupported` guidance现在还通过 `npm run diagram:smoke-circuitikz` 提供聚合 maintainer smoke fixtures通过 `rendererAvailability.status: "missing-configuration"` 与 `compile-executable-invalid` 记录缺失 renderer 配置,并通过 `RenderArtifact.diagnostics` 和 preview modal 具备通用 preview diagnostics、diagnostic summary counts、diagnostics-aware history entries 与 source-only fallback | 增加 path-only visual text 的 OCR-level label recognition、精确 pixel-level overlap checks、按需补齐更完整 SVG path coverage只有能保持可选时才考虑 automatic renderer installation/discovery继续推进自动化 topology-preserving repair execution |
| TikZJax integration | Obsidian-side display 的候选 render host | 保持可选,不把 TikZJax 变成硬 runtime 依赖 |
## Configuration

View file

@ -359,6 +359,7 @@ function auditDiagramDocs() {
assertContains(englishDiagrams, 'CircuitSpec', 'English diagrams doc');
assertContains(englishDiagrams, 'cmos-inverter-v1', 'English diagrams doc');
assertContains(englishDiagrams, 'cmos-nand2-v1', 'English diagrams doc');
assertContains(englishDiagrams, 'cmos-nor2-v1', 'English diagrams doc');
assertContains(englishDiagrams, '--compile-log', 'English diagrams doc');
assertContains(englishDiagrams, '--diagnostics-output', 'English diagrams doc');
assertContains(englishDiagrams, '--compile-executable', 'English diagrams doc');
@ -382,6 +383,7 @@ function auditDiagramDocs() {
assertContains(zhCnDiagrams, 'CircuitSpec', 'zh-CN diagrams doc');
assertContains(zhCnDiagrams, 'cmos-inverter-v1', 'zh-CN diagrams doc');
assertContains(zhCnDiagrams, 'cmos-nand2-v1', 'zh-CN diagrams doc');
assertContains(zhCnDiagrams, 'cmos-nor2-v1', 'zh-CN diagrams doc');
assertContains(zhCnDiagrams, '--compile-log', 'zh-CN diagrams doc');
assertContains(zhCnDiagrams, '--diagnostics-output', 'zh-CN diagrams doc');
assertContains(zhCnDiagrams, '--compile-executable', 'zh-CN diagrams doc');