@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +10 -10
  2. package/dist/api/board-types.d.ts +1 -1
  3. package/dist/api/config.d.ts +38 -2
  4. package/dist/api/index.d.ts +1 -1
  5. package/dist/api/schema/types.d.ts +15 -0
  6. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  7. package/dist/api/shared/async-runtime-static.js +69 -24
  8. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  9. package/dist/api/shared/async-symbol-detector.js +140 -0
  10. package/dist/api/shared/async-types.d.ts +24 -0
  11. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  12. package/dist/api/shared/coop-scheduler.js +149 -0
  13. package/dist/api/shared/display-adapter.d.ts +4 -3
  14. package/dist/api/shared/display-adapter.js +15 -118
  15. package/dist/api/shared/display-adapters/sdl.js +38 -31
  16. package/dist/api/shared/display-profile.d.ts +47 -25
  17. package/dist/api/shared/display-profile.js +31 -164
  18. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  19. package/dist/api/shared/framework-manifest-registry.js +27 -0
  20. package/dist/api/shared/framework-manifest.d.ts +595 -0
  21. package/dist/api/shared/framework-manifest.js +193 -0
  22. package/dist/api/shared/glcdfont.d.ts +12 -0
  23. package/dist/api/shared/glcdfont.js +124 -0
  24. package/dist/api/shared/graphics-strategy.d.ts +37 -0
  25. package/dist/api/shared/hal-op-ir.d.ts +764 -1
  26. package/dist/api/shared/hal-op-ir.js +129 -1
  27. package/dist/api/shared/index.d.ts +22 -2
  28. package/dist/api/shared/index.js +26 -1
  29. package/dist/api/shared/ir-core.d.ts +4 -0
  30. package/dist/api/shared/ir-declarations.d.ts +6 -0
  31. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  32. package/dist/api/shared/native-display-op-resolver.js +64 -0
  33. package/dist/api/shared/platform-strategy.d.ts +119 -2
  34. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  35. package/dist/api/shared/promise-runtime.d.ts +1 -1
  36. package/dist/api/shared/promise-runtime.js +95 -13
  37. package/dist/api/shared/spdx-licenses.d.ts +139 -0
  38. package/dist/api/shared/spdx-licenses.js +385 -0
  39. package/dist/api/shared/toolchain-types.d.ts +17 -0
  40. package/dist/api/shared/types.d.ts +8 -0
  41. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  42. package/dist/api/shared/validate-framework-manifest.js +494 -0
  43. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  44. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  45. package/dist/api/shared/worker-runtime.d.ts +69 -0
  46. package/dist/api/shared/worker-runtime.js +163 -0
  47. package/dist/cli-utils.d.ts +1 -0
  48. package/dist/cli-utils.js +3 -1
  49. package/dist/cli.js +249 -32
  50. package/dist/config-loader.d.ts +7 -2
  51. package/dist/config-loader.js +23 -3
  52. package/dist/config-schema.d.ts +112 -70
  53. package/dist/config-schema.js +14 -0
  54. package/dist/contract/board-generator.d.ts +17 -0
  55. package/dist/contract/board-generator.js +57 -0
  56. package/dist/contract/contract-parser.d.ts +217 -0
  57. package/dist/contract/contract-parser.js +224 -0
  58. package/dist/contract/index.d.ts +22 -0
  59. package/dist/contract/index.js +68 -0
  60. package/dist/create/board-codegen.js +4 -4
  61. package/dist/create/board-generators.js +4 -5
  62. package/dist/create/board-spec.d.ts +72 -75
  63. package/dist/create/board-spec.js +0 -1
  64. package/dist/create/framework-catalog.d.ts +64 -0
  65. package/dist/create/framework-catalog.js +136 -0
  66. package/dist/create/index.d.ts +5 -1
  67. package/dist/create/index.js +3 -1
  68. package/dist/create/init-scaffold.d.ts +12 -3
  69. package/dist/create/init-scaffold.js +49 -14
  70. package/dist/create/init-templates.d.ts +4 -0
  71. package/dist/create/init-templates.js +271 -17
  72. package/dist/create/init-wizard.js +41 -27
  73. package/dist/create/install-deps.d.ts +32 -0
  74. package/dist/create/install-deps.js +46 -0
  75. package/dist/debug/preprocessor.js +178 -39
  76. package/dist/debug/types.d.ts +23 -0
  77. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  78. package/dist/diagnostics/mermaid-builder.js +34 -24
  79. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  80. package/dist/emit/compliance/arxml-writer.js +34 -0
  81. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  82. package/dist/emit/compliance/compliance-context.js +113 -0
  83. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  84. package/dist/emit/compliance/deviation-ledger.js +47 -0
  85. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  86. package/dist/emit/compliance/deviation-writer.js +37 -0
  87. package/dist/emit/compliance/index.d.ts +7 -0
  88. package/dist/emit/compliance/index.js +6 -0
  89. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  90. package/dist/emit/compliance/rule-engine.js +101 -0
  91. package/dist/emit/compliance/rules.d.ts +16 -0
  92. package/dist/emit/compliance/rules.js +191 -0
  93. package/dist/emit/compliance/types.d.ts +59 -0
  94. package/dist/emit/compliance/types.js +8 -0
  95. package/dist/emit/cpp-emitter.js +4 -3
  96. package/dist/emit/emitters/class-emitter.js +6 -1
  97. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  98. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  99. package/dist/emit/emitters/line-appender.js +19 -0
  100. package/dist/emit/emitters/line-marker.d.ts +38 -0
  101. package/dist/emit/emitters/line-marker.js +39 -0
  102. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  103. package/dist/emit/emitters/output-finalizer.js +101 -12
  104. package/dist/emit/emitters/setup.d.ts +17 -0
  105. package/dist/emit/emitters/setup.js +372 -30
  106. package/dist/emit/emitters/top-level-prep.js +8 -0
  107. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  108. package/dist/emit/emitters/ui-emitter.js +33 -9
  109. package/dist/emit/expression-renderer.d.ts +1 -1
  110. package/dist/emit/expression-renderer.js +91 -6
  111. package/dist/emit/route-hal-op.js +18 -5
  112. package/dist/emit/snprintf-helpers.js +15 -4
  113. package/dist/emit/statement-renderer.d.ts +10 -0
  114. package/dist/emit/statement-renderer.js +34 -5
  115. package/dist/emit/utils/async-state-machine.js +221 -125
  116. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  117. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  118. package/dist/framework-package.js +2 -0
  119. package/dist/framework-registry.d.ts +17 -0
  120. package/dist/install/framework-catalog.d.ts +53 -0
  121. package/dist/install/framework-catalog.js +107 -0
  122. package/dist/install/handle-install.d.ts +35 -0
  123. package/dist/install/handle-install.js +177 -0
  124. package/dist/install/index.d.ts +4 -0
  125. package/dist/install/index.js +3 -0
  126. package/dist/ir/adc-range-validation.js +40 -26
  127. package/dist/ir/build-ir-state.d.ts +1 -0
  128. package/dist/ir/build-ir-state.js +14 -0
  129. package/dist/ir/build-ir.js +9 -5
  130. package/dist/ir/call-graph.js +16 -0
  131. package/dist/ir/expression-to-ir.js +207 -6
  132. package/dist/ir/feature-registry.js +7 -25
  133. package/dist/ir/function-builder.js +22 -0
  134. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  135. package/dist/ir/hal/hal-emitter.js +50 -18
  136. package/dist/ir/hal/hal-parser.d.ts +6 -0
  137. package/dist/ir/hal/hal-parser.js +81 -0
  138. package/dist/ir/hal/hal-plugins.js +733 -1
  139. package/dist/ir/identifier-collector.js +35 -0
  140. package/dist/ir/interrupt-analysis.d.ts +5 -1
  141. package/dist/ir/interrupt-analysis.js +16 -19
  142. package/dist/ir/memory-budget-validation.js +1 -0
  143. package/dist/ir/network-validation.d.ts +4 -0
  144. package/dist/ir/network-validation.js +184 -0
  145. package/dist/ir/ownership-analysis.js +39 -1
  146. package/dist/ir/peripheral-ownership.js +5 -0
  147. package/dist/ir/peripheral-validation.d.ts +1 -1
  148. package/dist/ir/peripheral-validation.js +6 -3
  149. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  150. package/dist/ir/pin-alias-conflict.js +2 -1
  151. package/dist/ir/pin-capability-validation.js +71 -32
  152. package/dist/ir/pin-mode-validation.d.ts +2 -2
  153. package/dist/ir/pin-mode-validation.js +54 -18
  154. package/dist/ir/pin-safety.d.ts +1 -1
  155. package/dist/ir/pin-safety.js +2 -1
  156. package/dist/ir/program-analysis.d.ts +81 -0
  157. package/dist/ir/program-analysis.js +429 -0
  158. package/dist/ir/pulldown-validation.d.ts +1 -1
  159. package/dist/ir/pulldown-validation.js +2 -1
  160. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  161. package/dist/ir/pwm-timer-sharing.js +2 -1
  162. package/dist/ir/render-expr.js +11 -0
  163. package/dist/ir/resource-analysis.js +2 -0
  164. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  165. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  166. package/dist/ir/timing-validation.d.ts +6 -1
  167. package/dist/ir/timing-validation.js +61 -13
  168. package/dist/ir/transformers/call-statement.js +105 -0
  169. package/dist/ir/transformers/expressions.js +62 -0
  170. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  171. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  172. package/dist/ir/transformers/namespace-methods.js +17 -12
  173. package/dist/ir/transformers/ui-reactive.js +2 -2
  174. package/dist/ir/transformers/variables.js +137 -19
  175. package/dist/ir/try-catch-validation.js +2 -0
  176. package/dist/ir/type-resolution.js +20 -2
  177. package/dist/ir/ui-element-auto-wire.js +7 -5
  178. package/dist/ir/unit-suspicion-validation.js +9 -7
  179. package/dist/ir/utils/map-statements.d.ts +4 -0
  180. package/dist/ir/utils/map-statements.js +79 -0
  181. package/dist/ir/validation-orchestrator.js +15 -8
  182. package/dist/ir/worker-analysis.d.ts +10 -0
  183. package/dist/ir/worker-analysis.js +261 -0
  184. package/dist/libdef/c-to-decl.d.ts +27 -0
  185. package/dist/libdef/c-to-decl.js +397 -0
  186. package/dist/libdef/component-decls.d.ts +2 -0
  187. package/dist/libdef/component-decls.js +6 -0
  188. package/dist/libdef/component-discovery.d.ts +43 -0
  189. package/dist/libdef/component-discovery.js +83 -0
  190. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  191. package/dist/libdef/cpp-to-decl.js +72 -0
  192. package/dist/libdef/registry.js +5 -2
  193. package/dist/lint-cache.d.ts +59 -0
  194. package/dist/lint-cache.js +257 -0
  195. package/dist/orchestrator/graph-builder.js +14 -9
  196. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  197. package/dist/platform/coop-scheduler-runtime.js +41 -0
  198. package/dist/platform/generic-strategy.d.ts +15 -3
  199. package/dist/platform/generic-strategy.js +49 -4
  200. package/dist/safety/safety-bridge.d.ts +11 -0
  201. package/dist/safety/safety-bridge.js +48 -0
  202. package/dist/safety/sidecar-bridge.d.ts +5 -0
  203. package/dist/safety/sidecar-bridge.js +22 -0
  204. package/dist/safety-hook.d.ts +79 -0
  205. package/dist/safety-hook.js +35 -0
  206. package/dist/testing.d.ts +8 -2
  207. package/dist/testing.js +9 -2
  208. package/dist/transpile.d.ts +3 -0
  209. package/dist/transpile.js +191 -49
  210. package/dist/types.d.ts +22 -3
  211. package/dist/ui-hook.d.ts +17 -3
  212. package/dist/utils/cli.js +123 -2
  213. package/dist/utils/fs.d.ts +13 -0
  214. package/dist/utils/fs.js +50 -0
  215. package/dist/utils/ui.js +3 -1
  216. package/package.json +23 -3
  217. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  218. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  219. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  220. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  221. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  222. package/dist/api/shared/display-adapters/st7796.js +0 -132
  223. package/dist/ir/heap-array-validation.d.ts +0 -24
  224. package/dist/ir/heap-array-validation.js +0 -29
@@ -0,0 +1,224 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Contract parser — reads a TypeCAD hardware contract (*.contract.json)
3
+ //
4
+ // A contract is produced by the sister project typecad.net (TypeScript +
5
+ // KiCAD). It describes the physical connections of an MCU on a designed PCB:
6
+ // which pins are wired, to what nets, and which peripherals (I2C/SPI/UART)
7
+ // are available. This module parses that file and narrows it against an MCU
8
+ // package's TypeCADManifest to find the concrete MCU pin/peripheral names
9
+ // that the firmware is allowed to touch.
10
+ //
11
+ // The matched names feed board-generator.ts, which writes a narrowed
12
+ // `.cuttlefish/board.ts` so that `import { ... } from '@typecad/board'`
13
+ // exposes only the pins the actual PCB has wired — a pin used elsewhere on
14
+ // the board becomes a compile error in the firmware.
15
+ //
16
+ // Zod is used (rather than a plain JSON.parse + cast) because the contract is
17
+ // a cross-project interchange format: it crosses a trust boundary from a
18
+ // different codebase (typecad.net) with its own release cadence. Precise
19
+ // validation at this boundary turns silent "board generated with no pins"
20
+ // failures into clear "expected boolean, received string at
21
+ // availablePeripherals.i2c" errors. zod is already a runtime dependency of
22
+ // cuttlefish (config-schema.ts).
23
+ // ---------------------------------------------------------------------------
24
+ import fs from 'node:fs';
25
+ import { z } from 'zod';
26
+ // ---------------------------------------------------------------------------
27
+ // Contract schema — the authoritative shape of a *.contract.json file.
28
+ //
29
+ // The types exported below (HwContract, ContractPin, ...) are DERIVED from
30
+ // this schema via z.infer, so the runtime validation and the compile-time
31
+ // type cannot drift. This mirrors the shape produced by
32
+ // typeCAD/@typecad/typecad/src/contract.ts; the schema is the local source of
33
+ // truth so cuttlefish pins to a stable contract format regardless of
34
+ // typecad.net's internal evolution.
35
+ // ---------------------------------------------------------------------------
36
+ /** Contract schema version. Only 1 is currently defined. */
37
+ export const CONTRACT_VERSION = 1;
38
+ /**
39
+ * An external component on the same net as an MCU pin. Validated LENIENTLY:
40
+ * cuttlefish does not act on component metadata, so we only require the fields
41
+ * the firmware toolchain reads (reference + dnp) and accept unknown extras
42
+ * (voltage, wattage, tolerance, ...) without rejecting the contract. This
43
+ * keeps cuttlefish forward-compatible with typecad.net adding component fields.
44
+ */
45
+ const ContractComponentSchema = z.object({
46
+ reference: z.string(),
47
+ dnp: z.boolean(),
48
+ }).passthrough();
49
+ /**
50
+ * Info about a single connected MCU pin. The fields cuttlefish consumes
51
+ * (pinName, pinType, net) are required; boardName is optional (present only
52
+ * when typecad.net's Component.typehal map was set).
53
+ */
54
+ const ContractPinSchema = z.object({
55
+ pinName: z.string(),
56
+ pinType: z.string(),
57
+ boardName: z.string().optional(),
58
+ net: z.string(),
59
+ externalComponents: z.array(ContractComponentSchema).default([]),
60
+ }).strict();
61
+ /** Which peripheral buses have all their required pins wired. */
62
+ const AvailablePeripheralsSchema = z.object({
63
+ i2c: z.boolean(),
64
+ spi: z.boolean(),
65
+ uart: z.boolean(),
66
+ }).strict();
67
+ /** MCU identifying metadata. Only `symbol` is load-bearing for cuttlefish. */
68
+ const ContractMcuSchema = z.object({
69
+ symbol: z.string(),
70
+ }).passthrough();
71
+ /**
72
+ * The top-level contract object. `.strict()` so a future v2 contract with new
73
+ * top-level keys is rejected clearly (complementing the version check) rather
74
+ * than silently accepted with the new fields dropped.
75
+ */
76
+ export const HwContractSchema = z.object({
77
+ version: z.literal(CONTRACT_VERSION),
78
+ mcu: ContractMcuSchema,
79
+ connectedPins: z.record(z.string(), ContractPinSchema),
80
+ availablePeripherals: AvailablePeripheralsSchema,
81
+ }).strict();
82
+ // ---------------------------------------------------------------------------
83
+ // KiCAD electrical-type and net-name tables for power-pin exclusion.
84
+ // ---------------------------------------------------------------------------
85
+ /** KiCAD pin types that are not user GPIO and must not be re-exported as digital pins. */
86
+ const NON_GPIO_PIN_TYPES = new Set([
87
+ 'power_in',
88
+ 'power_out',
89
+ 'passive', // typically decoupling/nc ties
90
+ ]);
91
+ /** Net names that indicate a power/ground rail, not a signal. */
92
+ const POWER_NET_NAMES = new Set(['VCC', 'GND', 'VDD', 'VSS', '+3V3', '+5V', 'AVCC', 'AREF']);
93
+ // ---------------------------------------------------------------------------
94
+ // Parsing
95
+ // ---------------------------------------------------------------------------
96
+ /**
97
+ * Reformats a ZodError into a single readable, path-annotated message.
98
+ * Zod's default .message is a JSON blob; users want "at availablePeripherals.i2c:
99
+ * expected boolean, received string".
100
+ */
101
+ function formatZodError(err, contractPath) {
102
+ const issues = err.issues.map((issue) => {
103
+ const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
104
+ return ` at ${path}: ${issue.message}`;
105
+ });
106
+ return `Contract ${contractPath} failed validation:\n${issues.join('\n')}`;
107
+ }
108
+ /**
109
+ * Parses a TypeCAD contract file.
110
+ *
111
+ * The version is checked first as a fast path so a v2 (or later) contract
112
+ * fails with "unsupported version, update cuttlefish" rather than a cascade
113
+ * of field-mismatch errors. After the version check, the full schema is
114
+ * applied for precise per-field validation.
115
+ *
116
+ * @throws on an unreadable file, invalid JSON, unsupported version, or
117
+ * schema-validation failure (with a path-annotated message).
118
+ */
119
+ export function parseContractFile(contractPath) {
120
+ let content;
121
+ try {
122
+ content = fs.readFileSync(contractPath, 'utf-8');
123
+ }
124
+ catch (err) {
125
+ throw new Error(`Could not read contract file at ${contractPath}: ${err.message}`);
126
+ }
127
+ let parsed;
128
+ try {
129
+ parsed = JSON.parse(content);
130
+ }
131
+ catch (err) {
132
+ throw new Error(`Contract file ${contractPath} is not valid JSON: ${err.message}`);
133
+ }
134
+ // Fast path: reject an unsupported version before the full schema runs, so
135
+ // the error names the real problem rather than listing every field that
136
+ // differs from v1.
137
+ const version = parsed?.version;
138
+ if (version !== CONTRACT_VERSION) {
139
+ throw new Error(`Unsupported contract version in ${contractPath}: expected ${CONTRACT_VERSION}, got ${String(version)}. ` +
140
+ `This may be a newer contract format; update @typecad/cuttlefish.`);
141
+ }
142
+ // Full schema validation.
143
+ try {
144
+ return HwContractSchema.parse(parsed);
145
+ }
146
+ catch (err) {
147
+ if (err instanceof z.ZodError) {
148
+ throw new Error(formatZodError(err, contractPath));
149
+ }
150
+ throw err;
151
+ }
152
+ }
153
+ // ---------------------------------------------------------------------------
154
+ // Matching — narrows the contract against an MCU package's manifest.
155
+ // ---------------------------------------------------------------------------
156
+ /**
157
+ * Returns true if a contract pin represents a power/ground/decoupling connection
158
+ * rather than a usable GPIO. Such pins must not be re-exported as digital pins.
159
+ */
160
+ function isPowerPin(pin) {
161
+ if (NON_GPIO_PIN_TYPES.has(pin.pinType))
162
+ return true;
163
+ if (POWER_NET_NAMES.has(pin.net))
164
+ return true;
165
+ // KiCAD auto-generated nets like "net6" are signal nets; named power rails are not.
166
+ return false;
167
+ }
168
+ /**
169
+ * Matches contract pins to canonical MCU pin names (e.g. 'PB5', 'PC4').
170
+ *
171
+ * Strategy (boardName-first, substring fallback):
172
+ * 1. If the contract pin carries a `boardName` (populated by typecad.net when
173
+ * Component.typehal was set) and that name appears in mcuPinNames, use it
174
+ * directly — it is the user-validated mapping.
175
+ * 2. Otherwise, substring-match the KiCAD `pinName` against mcuPinNames
176
+ * (e.g. "XTAL1/PB6" contains "PB6"). This covers contracts emitted without
177
+ * a typehal map.
178
+ *
179
+ * Power/ground pins are excluded. The result is de-duplicated.
180
+ */
181
+ export function matchConnectedPins(contract, mcuPinNames) {
182
+ const mcuSet = new Set(mcuPinNames);
183
+ const matched = new Set();
184
+ for (const pin of Object.values(contract.connectedPins)) {
185
+ if (isPowerPin(pin))
186
+ continue;
187
+ // (1) boardName-first: explicit, firmware-friendly mapping.
188
+ if (pin.boardName && mcuSet.has(pin.boardName)) {
189
+ matched.add(pin.boardName);
190
+ continue;
191
+ }
192
+ // (2) substring fallback: the MCU port name appears inside the KiCAD pin name.
193
+ // Compound KiCAD names like "XTAL1/PB6" or "~{RESET}/PC6" are split on "/"
194
+ // and each segment is checked so we don't false-match on a prefix.
195
+ const segments = pin.pinName.replace(/[~{}]/g, '').split(/[/-]/);
196
+ for (const mcuPin of mcuPinNames) {
197
+ if (segments.includes(mcuPin)) {
198
+ matched.add(mcuPin);
199
+ break;
200
+ }
201
+ }
202
+ }
203
+ return Array.from(matched);
204
+ }
205
+ /**
206
+ * Selects which MCU peripheral instance names to re-export based on the
207
+ * contract's availablePeripherals. If the contract says SPI isn't wired,
208
+ * SPI0 (and friends) is dropped from the narrowed board — so `SPI0.begin()`
209
+ * becomes a compile error on a board whose PCB doesn't route SPI.
210
+ */
211
+ export function selectPeripherals(contract, mcuPeripheralNames) {
212
+ const avail = contract.availablePeripherals ?? { i2c: false, spi: false, uart: false };
213
+ return mcuPeripheralNames.filter((name) => {
214
+ // Match instance-family prefixes: I2C0/I2C1 → "i2c", SPI0/SPI1 → "spi", UART0 → "uart".
215
+ if (/^I2C\d/.test(name))
216
+ return avail.i2c;
217
+ if (/^SPI\d/.test(name))
218
+ return avail.spi;
219
+ if (/^UART\d/.test(name))
220
+ return avail.uart;
221
+ // Unknown peripheral families are preserved (don't silently drop something we don't recognize).
222
+ return true;
223
+ });
224
+ }
@@ -0,0 +1,22 @@
1
+ import type { ResolvedCuttlefishConfig } from '../config-loader.js';
2
+ export type { HwContract, ContractPin, ContractComponent, AvailablePeripherals, } from './contract-parser.js';
3
+ export { parseContractFile, matchConnectedPins, selectPeripherals } from './contract-parser.js';
4
+ export { generateBoardFile, CUTTLEFISH_DIR } from './board-generator.js';
5
+ export type { GenerateBoardOptions } from './board-generator.js';
6
+ /**
7
+ * Orchestrates contract-based board generation for a resolved config:
8
+ * 1. Reads the contract file at `config.contract` (resolved relative to the
9
+ * project root, i.e. the dir containing cuttlefish.config.ts).
10
+ * 2. Dynamically imports the MCU package's `TypeCADManifest` to discover the
11
+ * canonical pin and peripheral names.
12
+ * 3. Matches contract pins → MCU pin names and selects peripherals.
13
+ * 4. Writes the narrowed `.cuttlefish/board.ts`.
14
+ *
15
+ * This is the step that re-opens the typecad.net → cuttlefish interop. After it
16
+ * runs, the existing `export * from './board.js'` in cuttlefish-env.d.ts
17
+ * resolves to a board exposing only the pins the actual PCB has wired.
18
+ *
19
+ * @throws on a missing/unreadable/unparseable contract, an unsupported version,
20
+ * or if the MCU package can't be loaded for its manifest.
21
+ */
22
+ export declare function generateContractBoard(config: ResolvedCuttlefishConfig): Promise<string>;
@@ -0,0 +1,68 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Contract module barrel — entry point for contract-based board generation.
3
+ //
4
+ // Exports the parser, the board generator, and the `generateContractBoard`
5
+ // orchestrator that the CLI calls when `config.contract` is set. The env.d.ts
6
+ // emitted by config-loader.ts points `@typecad/board` at `./board.js`, and this
7
+ // module is what writes that file.
8
+ // ---------------------------------------------------------------------------
9
+ import path from 'node:path';
10
+ import { parseContractFile, matchConnectedPins, selectPeripherals } from './contract-parser.js';
11
+ import { generateBoardFile } from './board-generator.js';
12
+ export { parseContractFile, matchConnectedPins, selectPeripherals } from './contract-parser.js';
13
+ export { generateBoardFile, CUTTLEFISH_DIR } from './board-generator.js';
14
+ /**
15
+ * Orchestrates contract-based board generation for a resolved config:
16
+ * 1. Reads the contract file at `config.contract` (resolved relative to the
17
+ * project root, i.e. the dir containing cuttlefish.config.ts).
18
+ * 2. Dynamically imports the MCU package's `TypeCADManifest` to discover the
19
+ * canonical pin and peripheral names.
20
+ * 3. Matches contract pins → MCU pin names and selects peripherals.
21
+ * 4. Writes the narrowed `.cuttlefish/board.ts`.
22
+ *
23
+ * This is the step that re-opens the typecad.net → cuttlefish interop. After it
24
+ * runs, the existing `export * from './board.js'` in cuttlefish-env.d.ts
25
+ * resolves to a board exposing only the pins the actual PCB has wired.
26
+ *
27
+ * @throws on a missing/unreadable/unparseable contract, an unsupported version,
28
+ * or if the MCU package can't be loaded for its manifest.
29
+ */
30
+ export async function generateContractBoard(config) {
31
+ if (!config.contract) {
32
+ throw new Error('generateContractBoard called without config.contract');
33
+ }
34
+ if (!config.mcu) {
35
+ throw new Error(`A 'contract' config requires an 'mcu' package to narrow against. ` +
36
+ `Add e.g. mcu: '@typecad/mcu-atmega328p' to cuttlefish.config.ts.`);
37
+ }
38
+ const projectDir = path.dirname(config.configPath);
39
+ const contractPath = path.isAbsolute(config.contract)
40
+ ? config.contract
41
+ : path.resolve(projectDir, config.contract);
42
+ // (1) Parse the contract.
43
+ const contract = parseContractFile(contractPath);
44
+ // (2) Load the MCU manifest. The MCU package is a peer at runtime — import
45
+ // it dynamically so cuttlefish doesn't hard-depend on any single MCU.
46
+ let manifest;
47
+ try {
48
+ const mod = (await import(config.mcu));
49
+ if (!mod.TypeCADManifest) {
50
+ throw new Error(`'${config.mcu}' does not export a TypeCADManifest.`);
51
+ }
52
+ manifest = mod.TypeCADManifest;
53
+ }
54
+ catch (err) {
55
+ throw new Error(`Could not load TypeCADManifest from MCU package '${config.mcu}' for contract-based ` +
56
+ `board generation: ${err.message}`);
57
+ }
58
+ // (3) Match + select.
59
+ const connectedPins = matchConnectedPins(contract, manifest.pinNames);
60
+ const peripherals = selectPeripherals(contract, manifest.peripheralNames);
61
+ // (4) Emit the narrowed board.
62
+ return generateBoardFile({
63
+ projectDir,
64
+ mcuPackage: config.mcu,
65
+ connectedPins,
66
+ peripherals,
67
+ });
68
+ }
@@ -53,8 +53,8 @@ function insertAfter(filePath, marker, insertion, searchForId) {
53
53
  export function scaffoldBoardPackages(spec, opts = {}) {
54
54
  const arch = spec.architecture;
55
55
  const rootDir = opts.rootDir ? path.resolve(opts.rootDir) : findMonorepoRoot(process.cwd());
56
- const mcuDir = path.join(rootDir, 'packages', `mcu-${arch}`);
57
- const boardDir = path.join(rootDir, 'packages', `board-${arch}`);
56
+ const mcuDir = path.join(rootDir, 'mcus', `mcu-${arch}`);
57
+ const boardDir = path.join(rootDir, 'boards', `board-${arch}`);
58
58
  // Overwrite protection
59
59
  const mcuExists = fs.existsSync(mcuDir);
60
60
  const boardExists = fs.existsSync(boardDir);
@@ -93,8 +93,8 @@ export function scaffoldBoardPackages(spec, opts = {}) {
93
93
  if (fs.existsSync(rootPjPath)) {
94
94
  try {
95
95
  const rootPj = JSON.parse(fs.readFileSync(rootPjPath, 'utf8'));
96
- if (Array.isArray(rootPj.workspaces) && !rootPj.workspaces.includes(`packages/mcu-${arch}`)) {
97
- rootPj.workspaces.push(`packages/mcu-${arch}`, `packages/board-${arch}`);
96
+ if (Array.isArray(rootPj.workspaces) && !rootPj.workspaces.includes(`mcus/mcu-${arch}`)) {
97
+ rootPj.workspaces.push(`mcus/mcu-${arch}`, `boards/board-${arch}`);
98
98
  fs.writeFileSync(rootPjPath, JSON.stringify(rootPj, null, 2) + '\n', 'utf8');
99
99
  }
100
100
  }
@@ -3,8 +3,8 @@
3
3
  // tool. Each function takes a BoardSpec and returns the file content as a
4
4
  // string. No filesystem I/O — the orchestrator (board-codegen.ts) writes files.
5
5
  //
6
- // Golden reference: the hand-written packages/mcu-esp32c6/ and
7
- // packages/board-esp32c6/ packages. These generators reproduce them from a
6
+ // Golden reference: the hand-written mcus/mcu-esp32c6/ and
7
+ // boards/board-esp32c6/ packages. These generators reproduce them from a
8
8
  // C6 spec.
9
9
  // ---------------------------------------------------------------------------
10
10
  // ---------------------------------------------------------------------------
@@ -71,7 +71,7 @@ export function genMcuTsconfig(_spec) {
71
71
  experimentalDecorators: true, emitDecoratorMetadata: true,
72
72
  },
73
73
  include: ['src/**/*.ts'],
74
- references: [{ path: '../hal' }, { path: '../cuttlefish' }],
74
+ references: [{ path: '../../packages/hal' }, { path: '../../packages/cuttlefish' }],
75
75
  }, null, 2) + '\n';
76
76
  }
77
77
  export function genMcuPins(spec) {
@@ -408,7 +408,7 @@ export function genBoardTsconfig(spec) {
408
408
  },
409
409
  include: ['src/**/*.ts'],
410
410
  references: [
411
- { path: '../hal' }, { path: '../cuttlefish' }, { path: `../mcu-${spec.architecture}` },
411
+ { path: '../../packages/hal' }, { path: '../../packages/cuttlefish' }, { path: `../../mcus/mcu-${spec.architecture}` },
412
412
  ],
413
413
  }, null, 2) + '\n';
414
414
  }
@@ -535,7 +535,6 @@ export function genBoardIndex(spec) {
535
535
  // Build config
536
536
  lines.push(` build: {`);
537
537
  lines.push(` frameworks: {`);
538
- lines.push(` platformio: '${spec.platformioTarget}',`);
539
538
  lines.push(` arduino: '${spec.fqbn}',`);
540
539
  lines.push(` },`);
541
540
  lines.push(` defines: {`);
@@ -10,7 +10,6 @@ export declare const BoardSpecSchema: z.ZodObject<{
10
10
  description: z.ZodOptional<z.ZodString>;
11
11
  clockSpeed: z.ZodNumber;
12
12
  fqbn: z.ZodString;
13
- platformioTarget: z.ZodString;
14
13
  arduinoDefine: z.ZodString;
15
14
  moduleFlash: z.ZodNullable<z.ZodNumber>;
16
15
  externalRam: z.ZodNullable<z.ZodNumber>;
@@ -262,11 +261,11 @@ export declare const BoardSpecSchema: z.ZodObject<{
262
261
  channels: z.ZodNumber;
263
262
  pins: z.ZodArray<z.ZodString, "many">;
264
263
  }, "strip", z.ZodTypeAny, {
265
- channels: number;
266
264
  pins: string[];
267
- }, {
268
265
  channels: number;
266
+ }, {
269
267
  pins: string[];
268
+ channels: number;
270
269
  }>>>;
271
270
  timers: z.ZodArray<z.ZodObject<{
272
271
  instance: z.ZodNumber;
@@ -276,12 +275,12 @@ export declare const BoardSpecSchema: z.ZodObject<{
276
275
  }, "strict", z.ZodTypeAny, {
277
276
  type: "general" | "high_speed" | "rtc" | "sys";
278
277
  instance: number;
279
- bits: 16 | 8 | 32 | 64;
278
+ bits: 8 | 16 | 32 | 64;
280
279
  features?: ("pwm" | "capture" | "compare" | "interrupt" | "dma")[] | undefined;
281
280
  }, {
282
281
  type: "general" | "high_speed" | "rtc" | "sys";
283
282
  instance: number;
284
- bits: 16 | 8 | 32 | 64;
283
+ bits: 8 | 16 | 32 | 64;
285
284
  features?: ("pwm" | "capture" | "compare" | "interrupt" | "dma")[] | undefined;
286
285
  }>, "many">;
287
286
  wifi: z.ZodObject<{
@@ -366,6 +365,32 @@ export declare const BoardSpecSchema: z.ZodObject<{
366
365
  }>;
367
366
  }, "strict", z.ZodTypeAny, {
368
367
  architecture: string;
368
+ mcuId: string;
369
+ mcuName: string;
370
+ boardId: string;
371
+ boardName: string;
372
+ vendor: string;
373
+ clockSpeed: number;
374
+ fqbn: string;
375
+ arduinoDefine: string;
376
+ moduleFlash: number | null;
377
+ externalRam: number | null;
378
+ gpioRange: [number, number];
379
+ excludedGpio: number[];
380
+ pins: {
381
+ gpio: number;
382
+ capabilities: "FULL_GPIO" | "FULL_GPIO_ANALOG" | "FULL_GPIO_TOUCH" | "FULL_GPIO_ANALOG_TOUCH" | "FULL_GPIO_DAC" | "INPUT_ONLY";
383
+ functions?: {
384
+ type: "i2c" | "spi" | "uart" | "adc" | "dac" | "pwm" | "touch" | "usb";
385
+ instance: number;
386
+ role: string;
387
+ }[] | undefined;
388
+ alt?: string[] | undefined;
389
+ warnings?: string[] | undefined;
390
+ unsafe?: boolean | undefined;
391
+ notes?: string | undefined;
392
+ onboardLed?: boolean | undefined;
393
+ }[];
369
394
  i2c: Record<string, {
370
395
  sda?: string | undefined;
371
396
  scl?: string | undefined;
@@ -414,39 +439,7 @@ export declare const BoardSpecSchema: z.ZodObject<{
414
439
  vid: string;
415
440
  pid: string;
416
441
  };
417
- wifi: {
418
- type: "wifi" | "wifi6";
419
- supportsStation: boolean;
420
- supportsAp: boolean;
421
- };
422
442
  unsafe: string[];
423
- mcuId: string;
424
- mcuName: string;
425
- boardId: string;
426
- boardName: string;
427
- vendor: string;
428
- clockSpeed: number;
429
- fqbn: string;
430
- platformioTarget: string;
431
- arduinoDefine: string;
432
- moduleFlash: number | null;
433
- externalRam: number | null;
434
- gpioRange: [number, number];
435
- excludedGpio: number[];
436
- pins: {
437
- gpio: number;
438
- capabilities: "FULL_GPIO" | "FULL_GPIO_ANALOG" | "FULL_GPIO_TOUCH" | "FULL_GPIO_ANALOG_TOUCH" | "FULL_GPIO_DAC" | "INPUT_ONLY";
439
- functions?: {
440
- type: "i2c" | "spi" | "uart" | "adc" | "dac" | "pwm" | "touch" | "usb";
441
- instance: number;
442
- role: string;
443
- }[] | undefined;
444
- alt?: string[] | undefined;
445
- warnings?: string[] | undefined;
446
- unsafe?: boolean | undefined;
447
- notes?: string | undefined;
448
- onboardLed?: boolean | undefined;
449
- }[];
450
443
  analog: string[];
451
444
  peripheralInstances: {
452
445
  i2c: {
@@ -468,7 +461,7 @@ export declare const BoardSpecSchema: z.ZodObject<{
468
461
  timers: {
469
462
  type: "general" | "high_speed" | "rtc" | "sys";
470
463
  instance: number;
471
- bits: 16 | 8 | 32 | 64;
464
+ bits: 8 | 16 | 32 | 64;
472
465
  features?: ("pwm" | "capture" | "compare" | "interrupt" | "dma")[] | undefined;
473
466
  }[];
474
467
  features: {
@@ -480,6 +473,11 @@ export declare const BoardSpecSchema: z.ZodObject<{
480
473
  hardwareRng: boolean;
481
474
  fpu: boolean;
482
475
  };
476
+ wifi: {
477
+ type: "wifi" | "wifi6";
478
+ supportsStation: boolean;
479
+ supportsAp: boolean;
480
+ };
483
481
  bluetooth: {
484
482
  type: "classic" | "ble" | "dual";
485
483
  version: string;
@@ -491,13 +489,39 @@ export declare const BoardSpecSchema: z.ZodObject<{
491
489
  externalRam?: number | undefined;
492
490
  rtcMemory?: number | undefined;
493
491
  };
492
+ description?: string | undefined;
494
493
  touch?: {
495
- channels: number;
496
494
  pins: string[];
495
+ channels: number;
497
496
  } | null | undefined;
498
- description?: string | undefined;
499
497
  }, {
500
498
  architecture: string;
499
+ mcuId: string;
500
+ mcuName: string;
501
+ boardId: string;
502
+ boardName: string;
503
+ vendor: string;
504
+ clockSpeed: number;
505
+ fqbn: string;
506
+ arduinoDefine: string;
507
+ moduleFlash: number | null;
508
+ externalRam: number | null;
509
+ gpioRange: [number, number];
510
+ excludedGpio: number[];
511
+ pins: {
512
+ gpio: number;
513
+ capabilities: "FULL_GPIO" | "FULL_GPIO_ANALOG" | "FULL_GPIO_TOUCH" | "FULL_GPIO_ANALOG_TOUCH" | "FULL_GPIO_DAC" | "INPUT_ONLY";
514
+ functions?: {
515
+ type: "i2c" | "spi" | "uart" | "adc" | "dac" | "pwm" | "touch" | "usb";
516
+ instance: number;
517
+ role: string;
518
+ }[] | undefined;
519
+ alt?: string[] | undefined;
520
+ warnings?: string[] | undefined;
521
+ unsafe?: boolean | undefined;
522
+ notes?: string | undefined;
523
+ onboardLed?: boolean | undefined;
524
+ }[];
501
525
  i2c: Record<string, {
502
526
  sda?: string | undefined;
503
527
  scl?: string | undefined;
@@ -546,39 +570,7 @@ export declare const BoardSpecSchema: z.ZodObject<{
546
570
  vid: string;
547
571
  pid: string;
548
572
  };
549
- wifi: {
550
- type: "wifi" | "wifi6";
551
- supportsStation: boolean;
552
- supportsAp: boolean;
553
- };
554
573
  unsafe: string[];
555
- mcuId: string;
556
- mcuName: string;
557
- boardId: string;
558
- boardName: string;
559
- vendor: string;
560
- clockSpeed: number;
561
- fqbn: string;
562
- platformioTarget: string;
563
- arduinoDefine: string;
564
- moduleFlash: number | null;
565
- externalRam: number | null;
566
- gpioRange: [number, number];
567
- excludedGpio: number[];
568
- pins: {
569
- gpio: number;
570
- capabilities: "FULL_GPIO" | "FULL_GPIO_ANALOG" | "FULL_GPIO_TOUCH" | "FULL_GPIO_ANALOG_TOUCH" | "FULL_GPIO_DAC" | "INPUT_ONLY";
571
- functions?: {
572
- type: "i2c" | "spi" | "uart" | "adc" | "dac" | "pwm" | "touch" | "usb";
573
- instance: number;
574
- role: string;
575
- }[] | undefined;
576
- alt?: string[] | undefined;
577
- warnings?: string[] | undefined;
578
- unsafe?: boolean | undefined;
579
- notes?: string | undefined;
580
- onboardLed?: boolean | undefined;
581
- }[];
582
574
  analog: string[];
583
575
  peripheralInstances: {
584
576
  i2c: {
@@ -600,7 +592,7 @@ export declare const BoardSpecSchema: z.ZodObject<{
600
592
  timers: {
601
593
  type: "general" | "high_speed" | "rtc" | "sys";
602
594
  instance: number;
603
- bits: 16 | 8 | 32 | 64;
595
+ bits: 8 | 16 | 32 | 64;
604
596
  features?: ("pwm" | "capture" | "compare" | "interrupt" | "dma")[] | undefined;
605
597
  }[];
606
598
  features: {
@@ -612,6 +604,11 @@ export declare const BoardSpecSchema: z.ZodObject<{
612
604
  hardwareRng: boolean;
613
605
  fpu: boolean;
614
606
  };
607
+ wifi: {
608
+ type: "wifi" | "wifi6";
609
+ supportsStation: boolean;
610
+ supportsAp: boolean;
611
+ };
615
612
  bluetooth: {
616
613
  type: "classic" | "ble" | "dual";
617
614
  version: string;
@@ -623,11 +620,11 @@ export declare const BoardSpecSchema: z.ZodObject<{
623
620
  externalRam?: number | undefined;
624
621
  rtcMemory?: number | undefined;
625
622
  };
623
+ description?: string | undefined;
626
624
  touch?: {
627
- channels: number;
628
625
  pins: string[];
626
+ channels: number;
629
627
  } | null | undefined;
630
- description?: string | undefined;
631
628
  }>;
632
629
  export type BoardSpec = z.infer<typeof BoardSpecSchema>;
633
630
  export declare function parseBoardSpec(jsoncText: string): BoardSpec;
@@ -111,7 +111,6 @@ export const BoardSpecSchema = z.object({
111
111
  // Build
112
112
  clockSpeed: z.number().int(),
113
113
  fqbn: z.string().min(1),
114
- platformioTarget: z.string().min(1),
115
114
  arduinoDefine: z.string().min(1),
116
115
  // Memory (module-level; silicon memory is derived)
117
116
  moduleFlash: z.number().int().nullable(),