@typecad/cuttlefish 1.0.0-alpha.7 → 1.0.0-alpha.8

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 (160) hide show
  1. package/dist/api/config.d.ts +32 -0
  2. package/dist/api/index.d.ts +1 -1
  3. package/dist/api/schema/types.d.ts +15 -0
  4. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  5. package/dist/api/shared/async-runtime-static.js +69 -24
  6. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  7. package/dist/api/shared/async-symbol-detector.js +140 -0
  8. package/dist/api/shared/async-types.d.ts +24 -0
  9. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  10. package/dist/api/shared/coop-scheduler.js +149 -0
  11. package/dist/api/shared/display-adapter.d.ts +2 -2
  12. package/dist/api/shared/display-adapter.js +9 -119
  13. package/dist/api/shared/display-adapters/sdl.js +29 -29
  14. package/dist/api/shared/display-profile.d.ts +38 -33
  15. package/dist/api/shared/display-profile.js +22 -170
  16. package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
  17. package/dist/api/shared/framework-manifest-registry.js +8 -6
  18. package/dist/api/shared/framework-manifest.d.ts +147 -28
  19. package/dist/api/shared/framework-manifest.js +35 -2
  20. package/dist/api/shared/graphics-strategy.d.ts +15 -6
  21. package/dist/api/shared/hal-op-ir.d.ts +340 -3
  22. package/dist/api/shared/hal-op-ir.js +34 -0
  23. package/dist/api/shared/index.d.ts +9 -1
  24. package/dist/api/shared/index.js +11 -1
  25. package/dist/api/shared/ir-core.d.ts +4 -0
  26. package/dist/api/shared/ir-declarations.d.ts +6 -0
  27. package/dist/api/shared/platform-strategy.d.ts +104 -2
  28. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  29. package/dist/api/shared/promise-runtime.d.ts +1 -1
  30. package/dist/api/shared/promise-runtime.js +17 -13
  31. package/dist/api/shared/toolchain-types.d.ts +17 -0
  32. package/dist/api/shared/validate-framework-manifest.js +77 -0
  33. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  34. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  35. package/dist/api/shared/worker-runtime.d.ts +69 -0
  36. package/dist/api/shared/worker-runtime.js +163 -0
  37. package/dist/cli.js +120 -121
  38. package/dist/config-loader.d.ts +7 -2
  39. package/dist/config-loader.js +20 -19
  40. package/dist/config-schema.d.ts +107 -65
  41. package/dist/config-schema.js +14 -0
  42. package/dist/contract/board-generator.d.ts +17 -0
  43. package/dist/contract/board-generator.js +57 -0
  44. package/dist/contract/contract-parser.d.ts +217 -0
  45. package/dist/contract/contract-parser.js +224 -0
  46. package/dist/contract/index.d.ts +22 -0
  47. package/dist/contract/index.js +68 -0
  48. package/dist/create/board-codegen.js +4 -4
  49. package/dist/create/board-generators.js +4 -5
  50. package/dist/create/board-spec.d.ts +74 -77
  51. package/dist/create/board-spec.js +0 -1
  52. package/dist/create/init-scaffold.d.ts +7 -3
  53. package/dist/create/init-scaffold.js +0 -62
  54. package/dist/create/init-templates.js +62 -16
  55. package/dist/create/init-wizard.js +32 -27
  56. package/dist/debug/preprocessor.js +178 -39
  57. package/dist/debug/types.d.ts +23 -0
  58. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  59. package/dist/diagnostics/mermaid-builder.js +34 -24
  60. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  61. package/dist/emit/compliance/arxml-writer.js +34 -0
  62. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  63. package/dist/emit/compliance/compliance-context.js +113 -0
  64. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  65. package/dist/emit/compliance/deviation-ledger.js +47 -0
  66. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  67. package/dist/emit/compliance/deviation-writer.js +37 -0
  68. package/dist/emit/compliance/index.d.ts +7 -0
  69. package/dist/emit/compliance/index.js +6 -0
  70. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  71. package/dist/emit/compliance/rule-engine.js +101 -0
  72. package/dist/emit/compliance/rules.d.ts +16 -0
  73. package/dist/emit/compliance/rules.js +191 -0
  74. package/dist/emit/compliance/types.d.ts +59 -0
  75. package/dist/emit/compliance/types.js +8 -0
  76. package/dist/emit/emitters/class-emitter.js +6 -1
  77. package/dist/emit/emitters/emitter-context.d.ts +23 -1
  78. package/dist/emit/emitters/function-emitter-impl.js +96 -6
  79. package/dist/emit/emitters/line-appender.js +19 -0
  80. package/dist/emit/emitters/line-marker.d.ts +38 -0
  81. package/dist/emit/emitters/line-marker.js +39 -0
  82. package/dist/emit/emitters/output-finalizer.js +80 -1
  83. package/dist/emit/emitters/setup.d.ts +17 -0
  84. package/dist/emit/emitters/setup.js +217 -36
  85. package/dist/emit/emitters/top-level-prep.js +6 -0
  86. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  87. package/dist/emit/emitters/ui-emitter.js +17 -8
  88. package/dist/emit/expression-renderer.d.ts +1 -1
  89. package/dist/emit/expression-renderer.js +81 -5
  90. package/dist/emit/route-hal-op.js +18 -5
  91. package/dist/emit/snprintf-helpers.js +7 -4
  92. package/dist/emit/statement-renderer.d.ts +10 -0
  93. package/dist/emit/statement-renderer.js +21 -5
  94. package/dist/emit/utils/async-state-machine.js +39 -12
  95. package/dist/framework-package.js +2 -0
  96. package/dist/framework-registry.d.ts +17 -0
  97. package/dist/ir/adc-range-validation.js +14 -1
  98. package/dist/ir/build-ir-state.d.ts +1 -0
  99. package/dist/ir/build-ir-state.js +14 -0
  100. package/dist/ir/build-ir.js +4 -4
  101. package/dist/ir/call-graph.js +16 -0
  102. package/dist/ir/expression-to-ir.js +150 -0
  103. package/dist/ir/function-builder.js +22 -0
  104. package/dist/ir/hal/hal-emitter.js +10 -6
  105. package/dist/ir/hal/hal-parser.js +16 -9
  106. package/dist/ir/hal/hal-plugins.js +164 -3
  107. package/dist/ir/identifier-collector.js +21 -4
  108. package/dist/ir/interrupt-analysis.d.ts +5 -1
  109. package/dist/ir/interrupt-analysis.js +8 -16
  110. package/dist/ir/ownership-analysis.js +6 -0
  111. package/dist/ir/pin-capability-validation.js +37 -0
  112. package/dist/ir/pin-mode-validation.d.ts +2 -2
  113. package/dist/ir/pin-mode-validation.js +49 -18
  114. package/dist/ir/program-analysis.d.ts +42 -0
  115. package/dist/ir/program-analysis.js +243 -6
  116. package/dist/ir/render-expr.js +11 -0
  117. package/dist/ir/timing-validation.js +11 -2
  118. package/dist/ir/transformers/call-statement.js +105 -0
  119. package/dist/ir/transformers/expressions.js +4 -0
  120. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  121. package/dist/ir/transformers/namespace-methods.js +17 -12
  122. package/dist/ir/transformers/ui-reactive.js +2 -2
  123. package/dist/ir/transformers/variables.js +51 -0
  124. package/dist/ir/type-resolution.js +18 -0
  125. package/dist/ir/ui-element-auto-wire.js +7 -5
  126. package/dist/ir/utils/map-statements.d.ts +4 -0
  127. package/dist/ir/utils/map-statements.js +79 -0
  128. package/dist/ir/validation-orchestrator.js +6 -1
  129. package/dist/ir/worker-analysis.d.ts +10 -0
  130. package/dist/ir/worker-analysis.js +261 -0
  131. package/dist/orchestrator/graph-builder.js +8 -7
  132. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  133. package/dist/platform/coop-scheduler-runtime.js +41 -0
  134. package/dist/platform/generic-strategy.d.ts +15 -3
  135. package/dist/platform/generic-strategy.js +49 -4
  136. package/dist/safety/safety-bridge.d.ts +11 -0
  137. package/dist/safety/safety-bridge.js +48 -0
  138. package/dist/safety/sidecar-bridge.d.ts +5 -0
  139. package/dist/safety/sidecar-bridge.js +22 -0
  140. package/dist/safety-hook.d.ts +79 -0
  141. package/dist/safety-hook.js +35 -0
  142. package/dist/stores/display-profile-store.d.ts +0 -1
  143. package/dist/stores/display-profile-store.js +0 -1
  144. package/dist/testing.d.ts +4 -0
  145. package/dist/testing.js +5 -0
  146. package/dist/transpile.js +124 -35
  147. package/dist/types.d.ts +13 -2
  148. package/dist/ui-hook.d.ts +0 -2
  149. package/dist/utils/cli.js +51 -3
  150. package/package.json +20 -5
  151. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  152. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  153. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  154. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  155. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  156. package/dist/api/shared/display-adapters/st7796.js +0 -132
  157. package/dist/libdef/idf-discovery.d.ts +0 -7
  158. package/dist/libdef/idf-discovery.js +0 -59
  159. package/dist/licenses.d.ts +0 -185
  160. package/dist/licenses.js +0 -963
@@ -0,0 +1,217 @@
1
+ import { z } from 'zod';
2
+ /** Contract schema version. Only 1 is currently defined. */
3
+ export declare const CONTRACT_VERSION: 1;
4
+ /**
5
+ * An external component on the same net as an MCU pin. Validated LENIENTLY:
6
+ * cuttlefish does not act on component metadata, so we only require the fields
7
+ * the firmware toolchain reads (reference + dnp) and accept unknown extras
8
+ * (voltage, wattage, tolerance, ...) without rejecting the contract. This
9
+ * keeps cuttlefish forward-compatible with typecad.net adding component fields.
10
+ */
11
+ declare const ContractComponentSchema: z.ZodObject<{
12
+ reference: z.ZodString;
13
+ dnp: z.ZodBoolean;
14
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
15
+ reference: z.ZodString;
16
+ dnp: z.ZodBoolean;
17
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
18
+ reference: z.ZodString;
19
+ dnp: z.ZodBoolean;
20
+ }, z.ZodTypeAny, "passthrough">>;
21
+ /**
22
+ * Info about a single connected MCU pin. The fields cuttlefish consumes
23
+ * (pinName, pinType, net) are required; boardName is optional (present only
24
+ * when typecad.net's Component.typehal map was set).
25
+ */
26
+ declare const ContractPinSchema: z.ZodObject<{
27
+ pinName: z.ZodString;
28
+ pinType: z.ZodString;
29
+ boardName: z.ZodOptional<z.ZodString>;
30
+ net: z.ZodString;
31
+ externalComponents: z.ZodDefault<z.ZodArray<z.ZodObject<{
32
+ reference: z.ZodString;
33
+ dnp: z.ZodBoolean;
34
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
35
+ reference: z.ZodString;
36
+ dnp: z.ZodBoolean;
37
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
38
+ reference: z.ZodString;
39
+ dnp: z.ZodBoolean;
40
+ }, z.ZodTypeAny, "passthrough">>, "many">>;
41
+ }, "strict", z.ZodTypeAny, {
42
+ pinName: string;
43
+ pinType: string;
44
+ net: string;
45
+ externalComponents: z.objectOutputType<{
46
+ reference: z.ZodString;
47
+ dnp: z.ZodBoolean;
48
+ }, z.ZodTypeAny, "passthrough">[];
49
+ boardName?: string | undefined;
50
+ }, {
51
+ pinName: string;
52
+ pinType: string;
53
+ net: string;
54
+ boardName?: string | undefined;
55
+ externalComponents?: z.objectInputType<{
56
+ reference: z.ZodString;
57
+ dnp: z.ZodBoolean;
58
+ }, z.ZodTypeAny, "passthrough">[] | undefined;
59
+ }>;
60
+ /** Which peripheral buses have all their required pins wired. */
61
+ declare const AvailablePeripheralsSchema: z.ZodObject<{
62
+ i2c: z.ZodBoolean;
63
+ spi: z.ZodBoolean;
64
+ uart: z.ZodBoolean;
65
+ }, "strict", z.ZodTypeAny, {
66
+ i2c: boolean;
67
+ spi: boolean;
68
+ uart: boolean;
69
+ }, {
70
+ i2c: boolean;
71
+ spi: boolean;
72
+ uart: boolean;
73
+ }>;
74
+ /**
75
+ * The top-level contract object. `.strict()` so a future v2 contract with new
76
+ * top-level keys is rejected clearly (complementing the version check) rather
77
+ * than silently accepted with the new fields dropped.
78
+ */
79
+ export declare const HwContractSchema: z.ZodObject<{
80
+ version: z.ZodLiteral<1>;
81
+ mcu: z.ZodObject<{
82
+ symbol: z.ZodString;
83
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
84
+ symbol: z.ZodString;
85
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
86
+ symbol: z.ZodString;
87
+ }, z.ZodTypeAny, "passthrough">>;
88
+ connectedPins: z.ZodRecord<z.ZodString, z.ZodObject<{
89
+ pinName: z.ZodString;
90
+ pinType: z.ZodString;
91
+ boardName: z.ZodOptional<z.ZodString>;
92
+ net: z.ZodString;
93
+ externalComponents: z.ZodDefault<z.ZodArray<z.ZodObject<{
94
+ reference: z.ZodString;
95
+ dnp: z.ZodBoolean;
96
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
97
+ reference: z.ZodString;
98
+ dnp: z.ZodBoolean;
99
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
100
+ reference: z.ZodString;
101
+ dnp: z.ZodBoolean;
102
+ }, z.ZodTypeAny, "passthrough">>, "many">>;
103
+ }, "strict", z.ZodTypeAny, {
104
+ pinName: string;
105
+ pinType: string;
106
+ net: string;
107
+ externalComponents: z.objectOutputType<{
108
+ reference: z.ZodString;
109
+ dnp: z.ZodBoolean;
110
+ }, z.ZodTypeAny, "passthrough">[];
111
+ boardName?: string | undefined;
112
+ }, {
113
+ pinName: string;
114
+ pinType: string;
115
+ net: string;
116
+ boardName?: string | undefined;
117
+ externalComponents?: z.objectInputType<{
118
+ reference: z.ZodString;
119
+ dnp: z.ZodBoolean;
120
+ }, z.ZodTypeAny, "passthrough">[] | undefined;
121
+ }>>;
122
+ availablePeripherals: z.ZodObject<{
123
+ i2c: z.ZodBoolean;
124
+ spi: z.ZodBoolean;
125
+ uart: z.ZodBoolean;
126
+ }, "strict", z.ZodTypeAny, {
127
+ i2c: boolean;
128
+ spi: boolean;
129
+ uart: boolean;
130
+ }, {
131
+ i2c: boolean;
132
+ spi: boolean;
133
+ uart: boolean;
134
+ }>;
135
+ }, "strict", z.ZodTypeAny, {
136
+ mcu: {
137
+ symbol: string;
138
+ } & {
139
+ [k: string]: unknown;
140
+ };
141
+ version: 1;
142
+ connectedPins: Record<string, {
143
+ pinName: string;
144
+ pinType: string;
145
+ net: string;
146
+ externalComponents: z.objectOutputType<{
147
+ reference: z.ZodString;
148
+ dnp: z.ZodBoolean;
149
+ }, z.ZodTypeAny, "passthrough">[];
150
+ boardName?: string | undefined;
151
+ }>;
152
+ availablePeripherals: {
153
+ i2c: boolean;
154
+ spi: boolean;
155
+ uart: boolean;
156
+ };
157
+ }, {
158
+ mcu: {
159
+ symbol: string;
160
+ } & {
161
+ [k: string]: unknown;
162
+ };
163
+ version: 1;
164
+ connectedPins: Record<string, {
165
+ pinName: string;
166
+ pinType: string;
167
+ net: string;
168
+ boardName?: string | undefined;
169
+ externalComponents?: z.objectInputType<{
170
+ reference: z.ZodString;
171
+ dnp: z.ZodBoolean;
172
+ }, z.ZodTypeAny, "passthrough">[] | undefined;
173
+ }>;
174
+ availablePeripherals: {
175
+ i2c: boolean;
176
+ spi: boolean;
177
+ uart: boolean;
178
+ };
179
+ }>;
180
+ export type ContractComponent = z.infer<typeof ContractComponentSchema>;
181
+ export type ContractPin = z.infer<typeof ContractPinSchema>;
182
+ export type AvailablePeripherals = z.infer<typeof AvailablePeripheralsSchema>;
183
+ export type HwContract = z.infer<typeof HwContractSchema>;
184
+ /**
185
+ * Parses a TypeCAD contract file.
186
+ *
187
+ * The version is checked first as a fast path so a v2 (or later) contract
188
+ * fails with "unsupported version, update cuttlefish" rather than a cascade
189
+ * of field-mismatch errors. After the version check, the full schema is
190
+ * applied for precise per-field validation.
191
+ *
192
+ * @throws on an unreadable file, invalid JSON, unsupported version, or
193
+ * schema-validation failure (with a path-annotated message).
194
+ */
195
+ export declare function parseContractFile(contractPath: string): HwContract;
196
+ /**
197
+ * Matches contract pins to canonical MCU pin names (e.g. 'PB5', 'PC4').
198
+ *
199
+ * Strategy (boardName-first, substring fallback):
200
+ * 1. If the contract pin carries a `boardName` (populated by typecad.net when
201
+ * Component.typehal was set) and that name appears in mcuPinNames, use it
202
+ * directly — it is the user-validated mapping.
203
+ * 2. Otherwise, substring-match the KiCAD `pinName` against mcuPinNames
204
+ * (e.g. "XTAL1/PB6" contains "PB6"). This covers contracts emitted without
205
+ * a typehal map.
206
+ *
207
+ * Power/ground pins are excluded. The result is de-duplicated.
208
+ */
209
+ export declare function matchConnectedPins(contract: HwContract, mcuPinNames: readonly string[]): string[];
210
+ /**
211
+ * Selects which MCU peripheral instance names to re-export based on the
212
+ * contract's availablePeripherals. If the contract says SPI isn't wired,
213
+ * SPI0 (and friends) is dropped from the narrowed board — so `SPI0.begin()`
214
+ * becomes a compile error on a board whose PCB doesn't route SPI.
215
+ */
216
+ export declare function selectPeripherals(contract: HwContract, mcuPeripheralNames: readonly string[]): string[];
217
+ export {};
@@ -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: {`);