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

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 (167) 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 +125 -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/install/framework-catalog.d.ts +53 -0
  98. package/dist/install/framework-catalog.js +107 -0
  99. package/dist/install/handle-install.d.ts +35 -0
  100. package/dist/install/handle-install.js +177 -0
  101. package/dist/install/index.d.ts +4 -0
  102. package/dist/install/index.js +3 -0
  103. package/dist/ir/adc-range-validation.js +14 -1
  104. package/dist/ir/build-ir-state.d.ts +1 -0
  105. package/dist/ir/build-ir-state.js +14 -0
  106. package/dist/ir/build-ir.js +4 -4
  107. package/dist/ir/call-graph.js +16 -0
  108. package/dist/ir/expression-to-ir.js +150 -0
  109. package/dist/ir/function-builder.js +22 -0
  110. package/dist/ir/hal/hal-emitter.js +10 -6
  111. package/dist/ir/hal/hal-parser.js +16 -9
  112. package/dist/ir/hal/hal-plugins.js +164 -3
  113. package/dist/ir/identifier-collector.js +21 -4
  114. package/dist/ir/interrupt-analysis.d.ts +5 -1
  115. package/dist/ir/interrupt-analysis.js +8 -16
  116. package/dist/ir/ownership-analysis.js +6 -0
  117. package/dist/ir/pin-capability-validation.js +37 -0
  118. package/dist/ir/pin-mode-validation.d.ts +2 -2
  119. package/dist/ir/pin-mode-validation.js +49 -18
  120. package/dist/ir/program-analysis.d.ts +42 -0
  121. package/dist/ir/program-analysis.js +243 -6
  122. package/dist/ir/render-expr.js +11 -0
  123. package/dist/ir/timing-validation.js +11 -2
  124. package/dist/ir/transformers/call-statement.js +105 -0
  125. package/dist/ir/transformers/expressions.js +4 -0
  126. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  127. package/dist/ir/transformers/namespace-methods.js +17 -12
  128. package/dist/ir/transformers/ui-reactive.js +2 -2
  129. package/dist/ir/transformers/variables.js +51 -0
  130. package/dist/ir/type-resolution.js +18 -0
  131. package/dist/ir/ui-element-auto-wire.js +7 -5
  132. package/dist/ir/utils/map-statements.d.ts +4 -0
  133. package/dist/ir/utils/map-statements.js +79 -0
  134. package/dist/ir/validation-orchestrator.js +6 -1
  135. package/dist/ir/worker-analysis.d.ts +10 -0
  136. package/dist/ir/worker-analysis.js +261 -0
  137. package/dist/orchestrator/graph-builder.js +8 -7
  138. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  139. package/dist/platform/coop-scheduler-runtime.js +41 -0
  140. package/dist/platform/generic-strategy.d.ts +15 -3
  141. package/dist/platform/generic-strategy.js +49 -4
  142. package/dist/safety/safety-bridge.d.ts +11 -0
  143. package/dist/safety/safety-bridge.js +48 -0
  144. package/dist/safety/sidecar-bridge.d.ts +5 -0
  145. package/dist/safety/sidecar-bridge.js +22 -0
  146. package/dist/safety-hook.d.ts +79 -0
  147. package/dist/safety-hook.js +35 -0
  148. package/dist/stores/display-profile-store.d.ts +0 -1
  149. package/dist/stores/display-profile-store.js +0 -1
  150. package/dist/testing.d.ts +4 -0
  151. package/dist/testing.js +5 -0
  152. package/dist/transpile.js +124 -35
  153. package/dist/types.d.ts +22 -2
  154. package/dist/ui-hook.d.ts +0 -2
  155. package/dist/utils/cli.d.ts +2 -2
  156. package/dist/utils/cli.js +68 -3
  157. package/package.json +20 -5
  158. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  159. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  160. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  161. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  162. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  163. package/dist/api/shared/display-adapters/st7796.js +0 -132
  164. package/dist/libdef/idf-discovery.d.ts +0 -7
  165. package/dist/libdef/idf-discovery.js +0 -59
  166. package/dist/licenses.d.ts +0 -185
  167. package/dist/licenses.js +0 -963
@@ -1,13 +1,26 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // Op routing — dispatch a HALOpIR to the right strategy resolver.
3
3
  //
4
- // display.* ops go to resolveDisplayOp (graphics-specific); everything else
5
- // goes to resolveHALOperation (the existing generic seam). Keeps the consumer
6
- // sites (expression-renderer, statement-renderer, render-expr) DRY.
4
+ // display.* ops go to resolveDisplayOp (graphics-specific).
5
+ // safety.* ops go to the safety hook's resolveSafetyOp (when @typecad/safety
6
+ // is loaded) — NEVER to a per-target strategy, because the safety
7
+ // package is MCU-agnostic by construction.
8
+ // everything else goes to resolveHALOperation (the existing generic seam).
9
+ // Keeps the consumer sites (expression-renderer, statement-renderer,
10
+ // render-expr) DRY.
7
11
  // ---------------------------------------------------------------------------
12
+ import { getSafetyHook } from "../safety-hook.js";
8
13
  export function routeHALOp(op, strategy) {
9
- if (typeof op.operation === "string" && op.operation.startsWith("display.")) {
10
- return strategy.resolveDisplayOp?.(op);
14
+ if (typeof op.operation === "string") {
15
+ if (op.operation.startsWith("display.")) {
16
+ return strategy.resolveDisplayOp?.(op);
17
+ }
18
+ if (op.operation.startsWith("safety.")) {
19
+ // Safety ops are resolved by the @typecad/safety package via hook.
20
+ // Returns undefined if the package is not loaded (caller emits an
21
+ // unhandled-op warning).
22
+ return getSafetyHook()?.resolveSafetyOp?.(op);
23
+ }
11
24
  }
12
25
  return strategy.resolveHALOperation?.(op);
13
26
  }
@@ -236,11 +236,14 @@ export function inferSnprintfArg(expr, strategy, scopeState, renderExpression, p
236
236
  return { format: "%d", arg: renderExpression(expr), estimatedLength: 12, preludeLines: [] };
237
237
  }
238
238
  }
239
- const arduinoIntFunctions = new Set([
240
- "digitalRead", "analogRead", "pulseIn", "pulseInLong",
241
- "Wire_available", "Serial_available",
239
+ const halIntFunctions = new Set([
240
+ ...(strategy?.halCallNames?.() ?? new Set()),
241
+ // pulseIn/pulseInLong and the Wire_available/Serial_available forms
242
+ // are framework-specific suffix conventions; frameworks may add them
243
+ // to halCallNames. Kept as a fallback here until all frameworks do.
244
+ "pulseIn", "pulseInLong", "Wire_available", "Serial_available",
242
245
  ]);
243
- if (arduinoIntFunctions.has(funcName)) {
246
+ if (halIntFunctions.has(funcName)) {
244
247
  return { format: "%d", arg: renderExpression(expr), estimatedLength: 12, preludeLines: [] };
245
248
  }
246
249
  }
@@ -63,6 +63,14 @@ interface StatementRendererContext {
63
63
  * list. Optional so ad-hoc/test constructions still work.
64
64
  */
65
65
  diagnostics?: Diagnostic[];
66
+ /**
67
+ * Optional compliance context for A3-9-1 (fixed-width integer default).
68
+ * When provided and A3-9-1 is enforced, defaultNumericType() returns a
69
+ * fixed-width type instead of the legacy 'int'.
70
+ */
71
+ compliance?: {
72
+ isBanned(ruleId: string): boolean;
73
+ };
66
74
  }
67
75
  /**
68
76
  * Renders StatementIR nodes to C++ code strings.
@@ -83,6 +91,8 @@ export declare class StatementRenderer {
83
91
  private readonly interfaceFieldTypes;
84
92
  /** Shared emit-time diagnostics sink (see StatementRendererContext.diagnostics). */
85
93
  private readonly _diagnostics;
94
+ /** Compliance context for A3-9-1 fixed-width integer default. */
95
+ private readonly _compliance?;
86
96
  constructor(context: StatementRendererContext);
87
97
  /**
88
98
  * Gets the underlying expression renderer for direct use.
@@ -116,6 +116,7 @@ export class StatementRenderer {
116
116
  this.namespaceNames = context.namespaceNames ?? new Set();
117
117
  this.interfaceFieldTypes = context.interfaceFieldTypes ?? new Map();
118
118
  this._diagnostics = context.diagnostics ?? [];
119
+ this._compliance = context.compliance;
119
120
  // Create expression renderer with shared context
120
121
  this.expressionRenderer = new ExpressionRenderer({
121
122
  strategy: context.strategy,
@@ -324,7 +325,7 @@ export class StatementRenderer {
324
325
  this.expressionRenderer.pushPrelude([
325
326
  `const char* ${idxVar}_keys[] = { ${keysArr} };`,
326
327
  ]);
327
- return `for (${this.strategy.defaultNumericType()} ${idxVar} = 0; ${idxVar} < ${statement.keys.length}; ${idxVar}++)`;
328
+ return `for (${this.strategy.defaultNumericType(this._compliance)} ${idxVar} = 0; ${idxVar} < ${statement.keys.length}; ${idxVar}++)`;
328
329
  }
329
330
  const varDecl = statement.variable;
330
331
  if (varDecl.kind === "var_decl") {
@@ -485,6 +486,15 @@ export class StatementRenderer {
485
486
  const rawStmt = statement.callee.slice('__RAW_STMT__'.length);
486
487
  return forHeader ? rawStmt : `${rawStmt.endsWith(';') ? rawStmt : rawStmt + ';'}`;
487
488
  }
489
+ // Handle expression statements whose IR must render at EMIT time (not the
490
+ // build-time renderExprAsText). The safe.read().ok().fail() chain uses this:
491
+ // its lambda args can only render via the emit-time renderLambda (which
492
+ // produces [&](){...}), so the structured chain IR is carried as args[0]
493
+ // and rendered here via the expression renderer.
494
+ if (statement.callee === '__EXPR_STMT__' && statement.args.length >= 1) {
495
+ const exprText = this.expressionRenderer.render(statement.args[0], undefined, knownVariableTypes);
496
+ return forHeader ? exprText : `${exprText};`;
497
+ }
488
498
  // Awaited network markers (__WIFI_WAIT__/__HTTP_WAIT__) reaching the
489
499
  // plain renderer means the await sits outside an async state machine
490
500
  // (top-level await, or a position the state-machine splitter doesn't
@@ -658,13 +668,13 @@ export class StatementRenderer {
658
668
  // that don't support std::vector → emit as a plain C-style array.
659
669
  // Mutable arrays (.push/.pop/.indexOf) are rewritten to StaticArray<int> in
660
670
  // the IR builder (via mutableArrayVars) and never reach this branch.
661
- const elementType = parsedElementString(rawType) ?? this.strategy.defaultNumericType();
671
+ const elementType = parsedElementString(rawType) ?? this.strategy.defaultNumericType(this._compliance);
662
672
  return forHeader
663
673
  ? `${elementType} ${safeArrName}[] = { ${elements} }`
664
674
  : `${elementType} ${safeArrName}[] = { ${elements} };`;
665
675
  }
666
676
  // Use "int" for "auto" element type since C arrays need explicit types
667
- const arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType() : statement.initializer.elementType;
677
+ const arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType(this._compliance) : statement.initializer.elementType;
668
678
  return forHeader
669
679
  ? `${arrayType} ${safeArrName}[] = { ${elements} }`
670
680
  : `${arrayType} ${safeArrName}[] = { ${elements} };`;
@@ -739,7 +749,7 @@ export class StatementRenderer {
739
749
  }
740
750
  // Handle spread array initializers
741
751
  if (statement.initializer.kind === "spread_array") {
742
- let arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType() : statement.initializer.elementType;
752
+ let arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType(this._compliance) : statement.initializer.elementType;
743
753
  const spreadName = this.expressionRenderer.render(statement.initializer.spreadExpr, calleeTransformer, knownVariableTypes);
744
754
  if (statement.initializer.elementType === "auto" && knownVariableTypes && statement.initializer.spreadExpr.kind === "identifier") {
745
755
  const srcInfo = knownVariableTypes.get(statement.initializer.spreadExpr.value);
@@ -892,11 +902,17 @@ export class StatementRenderer {
892
902
  if (this.stringEnumNames.has(typeName)) {
893
903
  return this.strategy.normalizeCppType("const char*");
894
904
  }
905
+ // A3-9-1: under autosar, substitute the platform's default numeric type
906
+ // for the legacy "int" spelling. The IR layer hardcodes "int" for number
907
+ // literals, so we catch it here at the renderer boundary.
908
+ if (typeName === "int" && this._compliance?.isBanned("A3-9-1")) {
909
+ return this.strategy.defaultNumericType(this._compliance);
910
+ }
895
911
  return this.strategy.normalizeCppType(typeName);
896
912
  }
897
913
  inferFieldType(value, parentName, fieldName) {
898
914
  return inferObjectFieldType(value, this.pointerVarTypes, this.knownFunctionReturnTypes, undefined, undefined, undefined, // largeEnumNames - would need to pass through
899
- parentName, fieldName, this.strategy.defaultNumericType(), (o, n) => this.strategy.resolvePinType?.(o, n));
915
+ parentName, fieldName, this.strategy.defaultNumericType(this._compliance), (o, n) => this.strategy.resolvePinType?.(o, n));
900
916
  }
901
917
  /**
902
918
  * Transform console.log/error/warn calls based on target platform.
@@ -76,11 +76,23 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
76
76
  segments.push({ preStatements: currentPre, awaitedArgs: [] });
77
77
  const awaitCount = segments.filter((s) => s.awaitedCallee !== undefined).length;
78
78
  const stateCount = awaitCount + 1; // STATE_0 … STATE_{awaitCount}; cyclic loops back, linear adds STATE_DONE
79
+ // State enum is emitted as `enum class State` (AUTOSAR A7-2-1), so all
80
+ // references must be scope-qualified as `State::STATE_X`. The stateEnumList
81
+ // (used inside `enum class State { ... }`) uses the bare names; everywhere
82
+ // else (assignments, case labels, comparisons) uses the qualified form.
79
83
  const stateNames = [];
80
- for (let i = 0; i < stateCount; i++)
84
+ const qualifiedStateNames = [];
85
+ for (let i = 0; i < stateCount; i++) {
81
86
  stateNames.push(`STATE_${i}`);
82
- if (!isCyclic)
87
+ qualifiedStateNames.push(`State::STATE_${i}`);
88
+ }
89
+ if (!isCyclic) {
83
90
  stateNames.push("STATE_DONE");
91
+ qualifiedStateNames.push("State::STATE_DONE");
92
+ }
93
+ const Q = (i) => qualifiedStateNames[i];
94
+ const Q_DONE = "State::STATE_DONE";
95
+ const Q_0 = "State::STATE_0";
84
96
  // Helper: render a non-await statement as a single C++ line
85
97
  const renderStmt = (stmt) => renderStatement(stmt, false, strategy, undefined, undefined, knownFunctionReturnTypes);
86
98
  // Collect edge-detection markers from segments
@@ -138,14 +150,14 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
138
150
  const seg = segments[i];
139
151
  const lines = [];
140
152
  if (seg.awaitedCallee === undefined) {
141
- lines.push(`${pad}_state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
153
+ lines.push(`${pad}_state = ${isCyclic ? Q_0 : Q_DONE};`);
142
154
  return lines;
143
155
  }
144
156
  const edge = edgeInfoMap.get(i);
145
157
  const tap = tapInfoMap.get(i);
146
158
  const net = netInfoMap.get(i);
147
159
  if (edge) {
148
- lines.push(`${pad}_edgePrev_p${edge.pin} = digitalRead(${edge.pin});`);
160
+ lines.push(`${pad}_edgePrev_p${edge.pin} = ${strategy.readDigitalPin?.(String(edge.pin)) ?? `digitalRead(${edge.pin})`};`);
149
161
  if (edge.timeout !== null) {
150
162
  lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${edge.timeout};`);
151
163
  }
@@ -165,13 +177,13 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
165
177
  const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
166
178
  lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
167
179
  }
168
- lines.push(`${pad}_state = STATE_${i + 1};`);
180
+ lines.push(`${pad}_state = ${Q(i + 1)};`);
169
181
  return lines;
170
182
  };
171
183
  const caseLines = [];
172
184
  for (let i = 0; i < segments.length; i++) {
173
185
  const seg = segments[i];
174
- const stateName = `STATE_${i}`;
186
+ const stateName = Q(i);
175
187
  const body = [];
176
188
  const edgePoll = i > 0 ? edgeInfoMap.get(i - 1) : undefined;
177
189
  const tapPoll = i > 0 ? tapInfoMap.get(i - 1) : undefined;
@@ -194,7 +206,7 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
194
206
  ? `(${cond}) || ${strategy.currentTimeMillis()} >= _waitUntil`
195
207
  : cond;
196
208
  body.push(` {`);
197
- body.push(` int _cur = digitalRead(${edgePoll.pin});`);
209
+ body.push(` int _cur = ${strategy.readDigitalPin?.(String(edgePoll.pin)) ?? `digitalRead(${edgePoll.pin})`};`);
198
210
  body.push(` if (${fullCond}) {`);
199
211
  body.push(` ${prevVar} = _cur;`);
200
212
  body.push(...runSegment(` `));
@@ -242,13 +254,13 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
242
254
  caseLines.push(` case ${stateName}:`, ` {`, ...body, ` }`, ` break;`);
243
255
  }
244
256
  if (!isCyclic)
245
- caseLines.push(` case STATE_DONE:`, ` break;`);
257
+ caseLines.push(` case ${Q_DONE}:`, ` break;`);
246
258
  const stateEnumList = stateNames.join(", ");
247
- const isCompleteExpr = isCyclic ? "false" : "_state == STATE_DONE";
259
+ const isCompleteExpr = isCyclic ? "false" : `_state == ${Q_DONE}`;
248
260
  // Build constructor initializer list and edge/tap member declarations
249
261
  const edgeMemberArr = Array.from(edgeMembers);
250
262
  const tapMemberArr = Array.from(tapMembers.keys());
251
- const inits = [`_state(STATE_0)`, `_waitUntil(0)`];
263
+ const inits = [`_state(${Q_0})`, `_waitUntil(0)`];
252
264
  for (const m of edgeMemberArr)
253
265
  inits.push(`${m}(LOW)`);
254
266
  for (const m of tapMemberArr)
@@ -263,7 +275,7 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
263
275
  `// Async state machine for ${fnName}`,
264
276
  `class ${className} {`,
265
277
  `public:`,
266
- ` enum State { ${stateEnumList} };`,
278
+ ` enum class State { ${stateEnumList} };`,
267
279
  ` ${className}() : ${ctorInitList} {}`,
268
280
  ` void run() {`,
269
281
  ` switch (_state) {`,
@@ -271,7 +283,7 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
271
283
  ` }`,
272
284
  ` }`,
273
285
  ` bool isComplete() const { return ${isCompleteExpr}; }`,
274
- ` void reset() { _state = STATE_0; _waitUntil = 0;${edgeResetList}${tapResetList} }`,
286
+ ` void reset() { _state = ${Q_0}; _waitUntil = 0;${edgeResetList}${tapResetList} }`,
275
287
  `private:`,
276
288
  ` State _state;`,
277
289
  ` unsigned long _waitUntil;`,
@@ -394,6 +406,21 @@ function netWaitInfo(op, strategy) {
394
406
  }
395
407
  break;
396
408
  }
409
+ case "worker.submit": {
410
+ // Worker offload (Phase 3). `await worker.submit(fn, arg)` rewrites to:
411
+ // submit the worker (start), then poll worker.done on the same handle.
412
+ // The submit op is emitted verbatim as the start line; the poll predicate
413
+ // is worker.done(handle) carrying the SAME handleId. This is a predicate-
414
+ // poll resume (same per-frame cost as net/timer awaits), NOT an ISR-driven
415
+ // resume — that defers with DMA-backed awaits.
416
+ const submit = routeHALOp(op, strategy);
417
+ // Carry the handle through to the poll so submit + done agree on the slot.
418
+ const poll = route({ operation: "worker.done", handleId: o.handleId })?.expression ?? null;
419
+ if (submit?.code && poll) {
420
+ return { startLines: [submit.code], pollCond: poll, timeoutExpr: null };
421
+ }
422
+ break;
423
+ }
397
424
  }
398
425
  // Fallback: run the blocking form immediately and complete on the next tick.
399
426
  const blocking = routeHALOp(op, strategy);
@@ -50,6 +50,8 @@ export function loadFrameworkPackage(packageName, fromDir = process.cwd()) {
50
50
  } : undefined,
51
51
  classNameMapBuilder: typeof mod.buildClassNameMap === "function" ? mod.buildClassNameMap : undefined,
52
52
  libDeclGenerator: typeof mod.tryGenerateLibDecl === "function" ? mod.tryGenerateLibDecl : undefined,
53
+ doctor: typeof mod.doctor === "function" ? mod.doctor : undefined,
54
+ licenses: typeof mod.licenses === "function" ? mod.licenses : undefined,
53
55
  };
54
56
  setLoadedFramework(framework);
55
57
  registerPlatformStrategy(strategy);
@@ -14,6 +14,16 @@ export interface LoadedFramework {
14
14
  classNameMapBuilder?: (imports: any[]) => Map<string, string>;
15
15
  /** Framework library .d.ts declaration generation */
16
16
  libDeclGenerator?: (modulePath: string, file: string) => string | undefined;
17
+ /**
18
+ * Optional subcommand presenters owned by the framework. Cuttlefish dispatches
19
+ * `cuttlefish doctor` / `cuttlefish licenses` to these when the loaded
20
+ * framework provides them; otherwise it prints a no-support message. Each
21
+ * framework decides what (if anything) these do — e.g. framework-arduino
22
+ * checks arduino-cli + board core (doctor) and scans Arduino library licenses
23
+ * (licenses).
24
+ */
25
+ doctor?: () => void;
26
+ licenses?: (strict: boolean, all: boolean) => void;
17
27
  }
18
28
  /**
19
29
  * Resolves framework-specific library imports (e.g., Arduino libraries).
@@ -32,6 +42,13 @@ export interface FrameworkToolchain {
32
42
  compile(options: ToolchainOptions): CompileResult;
33
43
  upload?(options: ToolchainOptions): UploadResult;
34
44
  monitor?(options: ToolchainOptions): void;
45
+ /**
46
+ * Optional: launch an interactive debugger session for the last build
47
+ * (e.g. `west debug`). Not invoked by the standard build/compile flow;
48
+ * powers an explicit debug-attach entry point. Frameworks that support
49
+ * source-level debugging implement this to spawn their native debugger.
50
+ */
51
+ debug?(options: ToolchainOptions): void;
35
52
  }
36
53
  export declare function setLoadedFramework(framework: LoadedFramework): void;
37
54
  export declare function getLoadedFramework(): LoadedFramework;
@@ -0,0 +1,53 @@
1
+ /** A framework family that ships as an installable @typecad/framework-* package. */
2
+ export interface FrameworkCatalogEntry {
3
+ /** Short id, e.g. "arduino". Matches the suffix of @typecad/framework-<id>. */
4
+ id: string;
5
+ /** Full npm package name, e.g. "@typecad/framework-arduino". */
6
+ packageName: string;
7
+ /** Human-readable label shown in the install prompt. */
8
+ label: string;
9
+ /**
10
+ * Whether a real package exists on the registry. Used to filter the prompt:
11
+ * "esp-idf" is a known family with no published package yet, so it must not
12
+ * be offered for install (it would fail at the package-manager step).
13
+ */
14
+ installable: boolean;
15
+ }
16
+ /**
17
+ * The installable frameworks. Kept aligned with the packages/ directory:
18
+ * framework-arduino, framework-native, framework-zephyr all ship real packages.
19
+ * esp-idf is intentionally absent (no published package in this repo).
20
+ */
21
+ export declare const FRAMEWORK_CATALOG: readonly FrameworkCatalogEntry[];
22
+ /** Structural shape we need from a board/target. Keeps this module decoupled
23
+ * from the create/ module's KnownTarget (and trivially testable with literals). */
24
+ export interface BoardLike {
25
+ isNative?: boolean;
26
+ architecture?: string;
27
+ }
28
+ /** Look up a catalog entry by framework id (e.g. "arduino"). */
29
+ export declare function frameworkCatalogEntry(id: string): FrameworkCatalogEntry | undefined;
30
+ /**
31
+ * The framework ids compatible with a board. Native boards map to ["native"];
32
+ * embedded boards map via ARCHITECTURE_FRAMEWORKS (falling back to arduino for
33
+ * unknown architectures). Order is preserved as the catalog order so the most
34
+ * common framework is offered first in the prompt.
35
+ */
36
+ export declare function frameworksForTarget(target: BoardLike): FrameworkCatalogEntry[];
37
+ export type PackageManager = "npm" | "pnpm" | "yarn";
38
+ /**
39
+ * Detect the package manager for a directory. Priority:
40
+ * 1. package.json#packageManager field (the strongest signal, Corepack-style)
41
+ * 2. lockfile presence (pnpm-lock.yaml / yarn.lock → otherwise npm)
42
+ * 3. default "npm"
43
+ * Never throws — unreadable/missing files fall through to the next signal.
44
+ */
45
+ export declare function detectPackageManager(cwd: string): PackageManager;
46
+ /**
47
+ * Build the package-manager invocation that installs `packageName` into the
48
+ * current project (project-local, never global — per the install command spec).
49
+ */
50
+ export declare function buildInstallCommand(pm: PackageManager, packageName: string): {
51
+ bin: string;
52
+ args: string[];
53
+ };
@@ -0,0 +1,107 @@
1
+ // Framework catalog + board→framework compatibility for `cuttlefish install`.
2
+ //
3
+ // A "framework" is a `@typecad/framework-<id>` npm package. This module is the
4
+ // single source of truth for which frameworks exist as installable packages and
5
+ // which are compatible with a given board architecture. It is deliberately
6
+ // free of side effects (no fs/process beyond lockfile/package.json reads in
7
+ // detectPackageManager) so it unit-tests cleanly.
8
+ import path from "node:path";
9
+ import fs from "node:fs";
10
+ /**
11
+ * The installable frameworks. Kept aligned with the packages/ directory:
12
+ * framework-arduino, framework-native, framework-zephyr all ship real packages.
13
+ * esp-idf is intentionally absent (no published package in this repo).
14
+ */
15
+ export const FRAMEWORK_CATALOG = [
16
+ { id: "arduino", packageName: "@typecad/framework-arduino", label: "Arduino (digitalWrite, Wire, SPI)", installable: true },
17
+ { id: "zephyr", packageName: "@typecad/framework-zephyr", label: "Zephyr RTOS", installable: true },
18
+ { id: "native", packageName: "@typecad/framework-native", label: "Native (Windows/Linux executable)", installable: true },
19
+ ];
20
+ /**
21
+ * Architecture → compatible framework ids. Derived from each framework
22
+ * package's framework.manifest.ts `profile.targets`:
23
+ * - arduino: avr, esp32 family, rp2040/rp2350, samd, stm32
24
+ * - zephyr: nrf52 (xiao_ble), esp32, esp32s3
25
+ * - native: desktop only
26
+ * Unknown embedded architectures fall back to [arduino] (the broadest core).
27
+ */
28
+ const ARCHITECTURE_FRAMEWORKS = {
29
+ avr: ["arduino"],
30
+ esp32: ["arduino", "zephyr"],
31
+ esp32s2: ["arduino"],
32
+ esp32s3: ["arduino", "zephyr"],
33
+ esp32c3: ["arduino"],
34
+ esp32c6: ["arduino"],
35
+ rp2040: ["arduino"],
36
+ rp2350: ["arduino"],
37
+ samd: ["arduino"],
38
+ stm32: ["arduino"],
39
+ nrf52: ["zephyr"],
40
+ };
41
+ const FALLBACK_FRAMEWORKS = ["arduino"];
42
+ /** Look up a catalog entry by framework id (e.g. "arduino"). */
43
+ export function frameworkCatalogEntry(id) {
44
+ return FRAMEWORK_CATALOG.find((f) => f.id === id);
45
+ }
46
+ /**
47
+ * The framework ids compatible with a board. Native boards map to ["native"];
48
+ * embedded boards map via ARCHITECTURE_FRAMEWORKS (falling back to arduino for
49
+ * unknown architectures). Order is preserved as the catalog order so the most
50
+ * common framework is offered first in the prompt.
51
+ */
52
+ export function frameworksForTarget(target) {
53
+ let ids;
54
+ if (target.isNative) {
55
+ ids = ["native"];
56
+ }
57
+ else {
58
+ ids = ARCHITECTURE_FRAMEWORKS[target.architecture ?? ""] ?? FALLBACK_FRAMEWORKS;
59
+ }
60
+ // Re-map ids → catalog entries in catalog order (stable ordering), dropping
61
+ // any id that has no catalog entry (defensive — keeps the prompt clean).
62
+ return FRAMEWORK_CATALOG.filter((entry) => ids.includes(entry.id));
63
+ }
64
+ /**
65
+ * Detect the package manager for a directory. Priority:
66
+ * 1. package.json#packageManager field (the strongest signal, Corepack-style)
67
+ * 2. lockfile presence (pnpm-lock.yaml / yarn.lock → otherwise npm)
68
+ * 3. default "npm"
69
+ * Never throws — unreadable/missing files fall through to the next signal.
70
+ */
71
+ export function detectPackageManager(cwd) {
72
+ try {
73
+ const pkgPath = path.join(cwd, "package.json");
74
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
75
+ const pm = typeof pkg.packageManager === "string" ? pkg.packageManager : "";
76
+ if (pm.startsWith("pnpm"))
77
+ return "pnpm";
78
+ if (pm.startsWith("yarn"))
79
+ return "yarn";
80
+ if (pm.startsWith("npm"))
81
+ return "npm";
82
+ }
83
+ catch {
84
+ // no package.json or unparseable JSON — fall through to lockfile detection
85
+ }
86
+ if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml")))
87
+ return "pnpm";
88
+ if (fs.existsSync(path.join(cwd, "yarn.lock")))
89
+ return "yarn";
90
+ // package-lock.json implies npm; absence also defaults to npm.
91
+ return "npm";
92
+ }
93
+ /**
94
+ * Build the package-manager invocation that installs `packageName` into the
95
+ * current project (project-local, never global — per the install command spec).
96
+ */
97
+ export function buildInstallCommand(pm, packageName) {
98
+ switch (pm) {
99
+ case "pnpm":
100
+ return { bin: "pnpm", args: ["add", packageName] };
101
+ case "yarn":
102
+ return { bin: "yarn", args: ["add", packageName] };
103
+ case "npm":
104
+ default:
105
+ return { bin: "npm", args: ["install", packageName] };
106
+ }
107
+ }
@@ -0,0 +1,35 @@
1
+ import type { InstallCommandOptions } from "../types.js";
2
+ import { type FrameworkCatalogEntry, type PackageManager } from "./framework-catalog.js";
3
+ interface InstallCommand {
4
+ bin: string;
5
+ args: string[];
6
+ cwd: string;
7
+ }
8
+ interface InstallRunResult {
9
+ /** Exit status; null when the process could not be launched (ENOENT, etc.). */
10
+ status: number | null;
11
+ /** Populated only when the binary could not be launched. */
12
+ launchError?: string;
13
+ }
14
+ type InstallRunner = (cmd: InstallCommand) => InstallRunResult;
15
+ /**
16
+ * FOR TESTS ONLY. Replaces the real spawn-based installer with `runner`.
17
+ * Pass `undefined` to restore the real executor.
18
+ */
19
+ export declare function __setInstallRunnerForTest(runner: InstallRunner | undefined): void;
20
+ export interface InstallResult {
21
+ pm: PackageManager;
22
+ bin: string;
23
+ args: string[];
24
+ }
25
+ /**
26
+ * Resolve the package manager and run the install for `entry`'s package. In
27
+ * dry-run mode the command is printed but not executed. Throws on launch
28
+ * failure or non-zero exit so the CLI surfaces a clear error.
29
+ */
30
+ export declare function installFrameworkPackage(entry: FrameworkCatalogEntry, opts: {
31
+ cwd: string;
32
+ dryRun?: boolean;
33
+ }): InstallResult;
34
+ export declare function handleInstall(options: InstallCommandOptions): Promise<void>;
35
+ export {};