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

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 (222) 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 +32 -0
  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 +20 -2
  28. package/dist/api/shared/index.js +25 -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/toolchain-types.d.ts +17 -0
  38. package/dist/api/shared/types.d.ts +8 -0
  39. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  40. package/dist/api/shared/validate-framework-manifest.js +494 -0
  41. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  42. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  43. package/dist/api/shared/worker-runtime.d.ts +69 -0
  44. package/dist/api/shared/worker-runtime.js +163 -0
  45. package/dist/cli-utils.d.ts +1 -0
  46. package/dist/cli-utils.js +3 -1
  47. package/dist/cli.js +249 -32
  48. package/dist/config-loader.d.ts +7 -2
  49. package/dist/config-loader.js +23 -3
  50. package/dist/config-schema.d.ts +112 -70
  51. package/dist/config-schema.js +14 -0
  52. package/dist/contract/board-generator.d.ts +17 -0
  53. package/dist/contract/board-generator.js +57 -0
  54. package/dist/contract/contract-parser.d.ts +217 -0
  55. package/dist/contract/contract-parser.js +224 -0
  56. package/dist/contract/index.d.ts +22 -0
  57. package/dist/contract/index.js +68 -0
  58. package/dist/create/board-codegen.js +4 -4
  59. package/dist/create/board-generators.js +4 -5
  60. package/dist/create/board-spec.d.ts +72 -75
  61. package/dist/create/board-spec.js +0 -1
  62. package/dist/create/framework-catalog.d.ts +64 -0
  63. package/dist/create/framework-catalog.js +136 -0
  64. package/dist/create/index.d.ts +5 -1
  65. package/dist/create/index.js +3 -1
  66. package/dist/create/init-scaffold.d.ts +12 -3
  67. package/dist/create/init-scaffold.js +49 -14
  68. package/dist/create/init-templates.d.ts +4 -0
  69. package/dist/create/init-templates.js +263 -17
  70. package/dist/create/init-wizard.js +41 -27
  71. package/dist/create/install-deps.d.ts +32 -0
  72. package/dist/create/install-deps.js +46 -0
  73. package/dist/debug/preprocessor.js +178 -39
  74. package/dist/debug/types.d.ts +23 -0
  75. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  76. package/dist/diagnostics/mermaid-builder.js +34 -24
  77. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  78. package/dist/emit/compliance/arxml-writer.js +34 -0
  79. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  80. package/dist/emit/compliance/compliance-context.js +113 -0
  81. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  82. package/dist/emit/compliance/deviation-ledger.js +47 -0
  83. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  84. package/dist/emit/compliance/deviation-writer.js +37 -0
  85. package/dist/emit/compliance/index.d.ts +7 -0
  86. package/dist/emit/compliance/index.js +6 -0
  87. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  88. package/dist/emit/compliance/rule-engine.js +101 -0
  89. package/dist/emit/compliance/rules.d.ts +16 -0
  90. package/dist/emit/compliance/rules.js +191 -0
  91. package/dist/emit/compliance/types.d.ts +59 -0
  92. package/dist/emit/compliance/types.js +8 -0
  93. package/dist/emit/cpp-emitter.js +4 -3
  94. package/dist/emit/emitters/class-emitter.js +6 -1
  95. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  96. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  97. package/dist/emit/emitters/line-appender.js +19 -0
  98. package/dist/emit/emitters/line-marker.d.ts +38 -0
  99. package/dist/emit/emitters/line-marker.js +39 -0
  100. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  101. package/dist/emit/emitters/output-finalizer.js +101 -12
  102. package/dist/emit/emitters/setup.d.ts +17 -0
  103. package/dist/emit/emitters/setup.js +372 -30
  104. package/dist/emit/emitters/top-level-prep.js +8 -0
  105. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  106. package/dist/emit/emitters/ui-emitter.js +33 -9
  107. package/dist/emit/expression-renderer.d.ts +1 -1
  108. package/dist/emit/expression-renderer.js +91 -6
  109. package/dist/emit/route-hal-op.js +18 -5
  110. package/dist/emit/snprintf-helpers.js +15 -4
  111. package/dist/emit/statement-renderer.d.ts +10 -0
  112. package/dist/emit/statement-renderer.js +34 -5
  113. package/dist/emit/utils/async-state-machine.js +221 -125
  114. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  115. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  116. package/dist/framework-package.js +2 -0
  117. package/dist/framework-registry.d.ts +17 -0
  118. package/dist/install/framework-catalog.d.ts +53 -0
  119. package/dist/install/framework-catalog.js +107 -0
  120. package/dist/install/handle-install.d.ts +35 -0
  121. package/dist/install/handle-install.js +177 -0
  122. package/dist/install/index.d.ts +4 -0
  123. package/dist/install/index.js +3 -0
  124. package/dist/ir/adc-range-validation.js +40 -26
  125. package/dist/ir/build-ir-state.d.ts +1 -0
  126. package/dist/ir/build-ir-state.js +14 -0
  127. package/dist/ir/build-ir.js +9 -5
  128. package/dist/ir/call-graph.js +16 -0
  129. package/dist/ir/expression-to-ir.js +207 -6
  130. package/dist/ir/feature-registry.js +7 -25
  131. package/dist/ir/function-builder.js +22 -0
  132. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  133. package/dist/ir/hal/hal-emitter.js +50 -18
  134. package/dist/ir/hal/hal-parser.d.ts +6 -0
  135. package/dist/ir/hal/hal-parser.js +81 -0
  136. package/dist/ir/hal/hal-plugins.js +733 -1
  137. package/dist/ir/identifier-collector.js +35 -0
  138. package/dist/ir/interrupt-analysis.d.ts +5 -1
  139. package/dist/ir/interrupt-analysis.js +16 -19
  140. package/dist/ir/memory-budget-validation.js +1 -0
  141. package/dist/ir/network-validation.d.ts +4 -0
  142. package/dist/ir/network-validation.js +184 -0
  143. package/dist/ir/ownership-analysis.js +39 -1
  144. package/dist/ir/peripheral-ownership.js +5 -0
  145. package/dist/ir/peripheral-validation.d.ts +1 -1
  146. package/dist/ir/peripheral-validation.js +6 -3
  147. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  148. package/dist/ir/pin-alias-conflict.js +2 -1
  149. package/dist/ir/pin-capability-validation.js +71 -32
  150. package/dist/ir/pin-mode-validation.d.ts +2 -2
  151. package/dist/ir/pin-mode-validation.js +54 -18
  152. package/dist/ir/pin-safety.d.ts +1 -1
  153. package/dist/ir/pin-safety.js +2 -1
  154. package/dist/ir/program-analysis.d.ts +81 -0
  155. package/dist/ir/program-analysis.js +429 -0
  156. package/dist/ir/pulldown-validation.d.ts +1 -1
  157. package/dist/ir/pulldown-validation.js +2 -1
  158. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  159. package/dist/ir/pwm-timer-sharing.js +2 -1
  160. package/dist/ir/render-expr.js +11 -0
  161. package/dist/ir/resource-analysis.js +2 -0
  162. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  163. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  164. package/dist/ir/timing-validation.d.ts +6 -1
  165. package/dist/ir/timing-validation.js +61 -13
  166. package/dist/ir/transformers/call-statement.js +105 -0
  167. package/dist/ir/transformers/expressions.js +62 -0
  168. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  169. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  170. package/dist/ir/transformers/namespace-methods.js +17 -12
  171. package/dist/ir/transformers/ui-reactive.js +2 -2
  172. package/dist/ir/transformers/variables.js +137 -19
  173. package/dist/ir/try-catch-validation.js +2 -0
  174. package/dist/ir/type-resolution.js +20 -2
  175. package/dist/ir/ui-element-auto-wire.js +7 -5
  176. package/dist/ir/unit-suspicion-validation.js +9 -7
  177. package/dist/ir/utils/map-statements.d.ts +4 -0
  178. package/dist/ir/utils/map-statements.js +79 -0
  179. package/dist/ir/validation-orchestrator.js +15 -8
  180. package/dist/ir/worker-analysis.d.ts +10 -0
  181. package/dist/ir/worker-analysis.js +261 -0
  182. package/dist/libdef/c-to-decl.d.ts +27 -0
  183. package/dist/libdef/c-to-decl.js +397 -0
  184. package/dist/libdef/component-decls.d.ts +2 -0
  185. package/dist/libdef/component-decls.js +6 -0
  186. package/dist/libdef/component-discovery.d.ts +43 -0
  187. package/dist/libdef/component-discovery.js +83 -0
  188. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  189. package/dist/libdef/cpp-to-decl.js +72 -0
  190. package/dist/libdef/registry.js +5 -2
  191. package/dist/lint-cache.d.ts +59 -0
  192. package/dist/lint-cache.js +257 -0
  193. package/dist/orchestrator/graph-builder.js +14 -9
  194. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  195. package/dist/platform/coop-scheduler-runtime.js +41 -0
  196. package/dist/platform/generic-strategy.d.ts +15 -3
  197. package/dist/platform/generic-strategy.js +49 -4
  198. package/dist/safety/safety-bridge.d.ts +11 -0
  199. package/dist/safety/safety-bridge.js +48 -0
  200. package/dist/safety/sidecar-bridge.d.ts +5 -0
  201. package/dist/safety/sidecar-bridge.js +22 -0
  202. package/dist/safety-hook.d.ts +79 -0
  203. package/dist/safety-hook.js +35 -0
  204. package/dist/testing.d.ts +8 -2
  205. package/dist/testing.js +9 -2
  206. package/dist/transpile.d.ts +3 -0
  207. package/dist/transpile.js +191 -49
  208. package/dist/types.d.ts +22 -3
  209. package/dist/ui-hook.d.ts +17 -3
  210. package/dist/utils/cli.js +122 -2
  211. package/dist/utils/fs.d.ts +13 -0
  212. package/dist/utils/fs.js +50 -0
  213. package/dist/utils/ui.js +3 -1
  214. package/package.json +23 -3
  215. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  216. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  217. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  218. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  219. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  220. package/dist/api/shared/display-adapters/st7796.js +0 -132
  221. package/dist/ir/heap-array-validation.d.ts +0 -24
  222. package/dist/ir/heap-array-validation.js +0 -29
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Persistent cache for the ESLint build gate.
3
+ *
4
+ * ESLint is a mandatory correctness gate: it excludes non-AOT code patterns
5
+ * that the transpiler cannot accept. Running it on every `cuttlefish build`
6
+ * costs ~3s (38% of a small-project build) and almost always finds nothing on
7
+ * repeat runs. Its result (zero errors) is a whole-program boolean that
8
+ * depends only on a small set of inputs, so it is cleanly cacheable.
9
+ *
10
+ * Soundness contract:
11
+ * - The cache ONLY records a successful (zero-error) lint result.
12
+ * - A failing lint never persists (the build aborts anyway).
13
+ * - On ANY input change the entry is invalidated and ESLint runs for real.
14
+ * - `options.force` and the `CUTTLEFISH_NO_CACHE` env var bypass entirely.
15
+ *
16
+ * This mirrors the historical `.cuttlefish-cache.json` timestamp+hash cache
17
+ * that used to live in this package. It is scoped to the ESLint gate (and the
18
+ * type-check gate) rather than the transpile-IR pass, because — unlike IR
19
+ * lowering — these gates' outcomes are whole-program booleans with no
20
+ * cross-module rehydration requirement.
21
+ */
22
+ /** Resolve the eslint config the same way runEslintCheck does, to avoid drift. */
23
+ export declare function resolveEslintConfigPath(projectRoot: string): string | undefined;
24
+ /**
25
+ * Compute the inputs that the ESLint result depends on. Two builds with the
26
+ * same fingerprint are guaranteed to produce the same lint outcome.
27
+ */
28
+ export interface LintFingerprint {
29
+ digest: string;
30
+ /** Absolute paths that were hashed into the digest (for debugging). */
31
+ inputs: string[];
32
+ }
33
+ export declare function computeLintFingerprint(projectRoot: string, srcDir: string): LintFingerprint | null;
34
+ export interface GateCacheResult {
35
+ /** True when the gate can be skipped because the recorded success still holds. */
36
+ hit: boolean;
37
+ /** The fingerprint to record after a successful gate run. */
38
+ fingerprint: LintFingerprint | null;
39
+ }
40
+ /**
41
+ * Decide whether the ESLint gate can be skipped for this project.
42
+ *
43
+ * Returns `hit: true` only when:
44
+ * - caching is not disabled, not force-bypassed,
45
+ * - a fingerprint can be computed (config + eslint resolvable), and
46
+ * - the on-disk cache records a success for that exact fingerprint.
47
+ *
48
+ * `force` mirrors TranspileOptions.force and bypasses the cache.
49
+ */
50
+ export declare function checkLintCache(projectRoot: string, srcDir: string, opts?: {
51
+ force?: boolean;
52
+ }): GateCacheResult;
53
+ /**
54
+ * Record a successful ESLint run (zero errors). Never call this after a
55
+ * failing run — a failing build must not persist a "clean" marker.
56
+ */
57
+ export declare function recordLintSuccess(projectRoot: string, fingerprint: LintFingerprint): void;
58
+ /** Drop the lint entry (used when the gate is skipped entirely / nothing to cache). */
59
+ export declare function invalidateLint(projectRoot: string): void;
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Persistent cache for the ESLint build gate.
3
+ *
4
+ * ESLint is a mandatory correctness gate: it excludes non-AOT code patterns
5
+ * that the transpiler cannot accept. Running it on every `cuttlefish build`
6
+ * costs ~3s (38% of a small-project build) and almost always finds nothing on
7
+ * repeat runs. Its result (zero errors) is a whole-program boolean that
8
+ * depends only on a small set of inputs, so it is cleanly cacheable.
9
+ *
10
+ * Soundness contract:
11
+ * - The cache ONLY records a successful (zero-error) lint result.
12
+ * - A failing lint never persists (the build aborts anyway).
13
+ * - On ANY input change the entry is invalidated and ESLint runs for real.
14
+ * - `options.force` and the `CUTTLEFISH_NO_CACHE` env var bypass entirely.
15
+ *
16
+ * This mirrors the historical `.cuttlefish-cache.json` timestamp+hash cache
17
+ * that used to live in this package. It is scoped to the ESLint gate (and the
18
+ * type-check gate) rather than the transpile-IR pass, because — unlike IR
19
+ * lowering — these gates' outcomes are whole-program booleans with no
20
+ * cross-module rehydration requirement.
21
+ */
22
+ import fs from "node:fs";
23
+ import path from "node:path";
24
+ import crypto from "node:crypto";
25
+ import { createRequire } from "node:module";
26
+ import { fileURLToPath } from "node:url";
27
+ const require = createRequire(import.meta.url);
28
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
29
+ const CACHE_VERSION = 2;
30
+ const DEFAULT_CACHE_NAME = ".cuttlefish-cache.json";
31
+ /** Bypass the cache entirely when set (debugging / CI cold runs). */
32
+ function isCacheDisabled() {
33
+ return process.env.CUTTLEFISH_NO_CACHE === "1" || process.env.CUTTLEFISH_NO_CACHE === "true";
34
+ }
35
+ /**
36
+ * Hash a file's content. Returns null if the file cannot be read (treated as
37
+ * "changed" by callers because the input set is no longer what we recorded).
38
+ */
39
+ function hashFile(absPath) {
40
+ try {
41
+ const content = fs.readFileSync(absPath, "utf8");
42
+ return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ /**
49
+ * Hash size + mtime for a file. Used for inputs (eslint config, eslint package)
50
+ * that we don't want to fully read on every build, and whose identity is
51
+ * sufficiently captured by size+mtime. Returns null if the file is missing.
52
+ */
53
+ function fingerprintStat(absPath) {
54
+ try {
55
+ const stat = fs.statSync(absPath);
56
+ return `${stat.size}:${stat.mtimeMs}`;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ /**
63
+ * Transpiler-self fingerprint: invalidate every cache entry when the
64
+ * transpiler's own compiled sources change. Mirrors the historical
65
+ * computeToolchainFingerprint() in incremental-cache.ts.
66
+ */
67
+ let cachedToolchainFingerprint;
68
+ function computeToolchainFingerprint() {
69
+ if (cachedToolchainFingerprint !== undefined)
70
+ return cachedToolchainFingerprint;
71
+ const candidates = [
72
+ "transpile.js",
73
+ "lint-cache.js",
74
+ "eslint-check.js",
75
+ path.join("emit", "cpp-emitter.js"),
76
+ path.join("ir", "build-ir.js"),
77
+ path.join("ir", "ownership-analysis.js"),
78
+ path.join("ir", "validation-orchestrator.js"),
79
+ ];
80
+ const signature = candidates
81
+ .map((rel) => path.join(__dirname, rel))
82
+ .filter((p) => fs.existsSync(p))
83
+ .map((p) => {
84
+ const stat = fs.statSync(p);
85
+ return `${path.basename(p)}:${stat.size}:${stat.mtimeMs}`;
86
+ })
87
+ .join("|");
88
+ cachedToolchainFingerprint = crypto.createHash("sha256").update(signature).digest("hex").slice(0, 16);
89
+ return cachedToolchainFingerprint;
90
+ }
91
+ /** Resolve the eslint config the same way runEslintCheck does, to avoid drift. */
92
+ export function resolveEslintConfigPath(projectRoot) {
93
+ const cuttlefishConfig = path.join(projectRoot, ".cuttlefish", "eslint.config.mjs");
94
+ if (fs.existsSync(cuttlefishConfig))
95
+ return cuttlefishConfig;
96
+ for (const name of ["eslint.config.mjs", "eslint.config.js", "eslint.config.cjs"]) {
97
+ const candidate = path.join(projectRoot, name);
98
+ if (fs.existsSync(candidate))
99
+ return candidate;
100
+ }
101
+ return undefined;
102
+ }
103
+ /** Resolve the eslint package version that runEslintCheck would load. */
104
+ function resolveEslintIdentity(projectRoot) {
105
+ const resolvers = [
106
+ createRequire(path.join(projectRoot, "package.json")),
107
+ require,
108
+ ];
109
+ for (const r of resolvers) {
110
+ try {
111
+ const eslintPath = r.resolve("eslint");
112
+ const pkgPath = r.resolve("eslint/package.json");
113
+ const version = JSON.parse(fs.readFileSync(pkgPath, "utf8")).version ?? "unknown";
114
+ return { path: eslintPath, version };
115
+ }
116
+ catch {
117
+ // try next resolver
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ /** Recursively gather .ts/.tsx/.ui files under a directory (the lint input set). */
123
+ function gatherSourceFiles(srcDir) {
124
+ const out = [];
125
+ if (!fs.existsSync(srcDir) || !fs.statSync(srcDir).isDirectory())
126
+ return out;
127
+ const stack = [srcDir];
128
+ while (stack.length > 0) {
129
+ const dir = stack.pop();
130
+ let entries;
131
+ try {
132
+ entries = fs.readdirSync(dir, { withFileTypes: true });
133
+ }
134
+ catch {
135
+ continue;
136
+ }
137
+ for (const entry of entries) {
138
+ const full = path.join(dir, entry.name);
139
+ if (entry.isDirectory()) {
140
+ if (entry.name === "node_modules" || entry.name === "out" || entry.name.startsWith("out-"))
141
+ continue;
142
+ stack.push(full);
143
+ }
144
+ else if (entry.isFile()) {
145
+ const ext = entry.name.toLowerCase();
146
+ if (ext.endsWith(".ts") || ext.endsWith(".tsx") || ext.endsWith(".ui")) {
147
+ out.push(full);
148
+ }
149
+ }
150
+ }
151
+ }
152
+ out.sort();
153
+ return out;
154
+ }
155
+ export function computeLintFingerprint(projectRoot, srcDir) {
156
+ const configPath = resolveEslintConfigPath(projectRoot);
157
+ if (!configPath)
158
+ return null; // no config => runEslintCheck returns [] without loading eslint
159
+ const eslint = resolveEslintIdentity(projectRoot);
160
+ if (!eslint)
161
+ return null; // eslint unresolvable => runEslintCheck returns []
162
+ const parts = [];
163
+ parts.push(["toolchain", computeToolchainFingerprint()]);
164
+ parts.push(["eslint", `${eslint.version}@${eslint.path}`]);
165
+ parts.push(["config", `${configPath}:${fingerprintStat(configPath) ?? "missing"}`]);
166
+ const sources = gatherSourceFiles(srcDir);
167
+ for (const f of sources) {
168
+ const h = hashFile(f);
169
+ parts.push(["src", `${f}:${h ?? "missing"}`]);
170
+ }
171
+ const digest = crypto
172
+ .createHash("sha256")
173
+ .update(parts.map((p) => p.join("=")).join("\n"))
174
+ .digest("hex")
175
+ .slice(0, 32);
176
+ return { digest, inputs: parts.map((p) => p[1]) };
177
+ }
178
+ function cachePathFor(projectRoot) {
179
+ return path.join(projectRoot, DEFAULT_CACHE_NAME);
180
+ }
181
+ function loadCacheFile(projectRoot) {
182
+ const cachePath = cachePathFor(projectRoot);
183
+ try {
184
+ if (!fs.existsSync(cachePath))
185
+ return null;
186
+ const data = JSON.parse(fs.readFileSync(cachePath, "utf8"));
187
+ if (data.version === CACHE_VERSION &&
188
+ data.toolchainFingerprint === computeToolchainFingerprint() &&
189
+ path.resolve(data.rootDir) === path.resolve(projectRoot)) {
190
+ return data;
191
+ }
192
+ }
193
+ catch {
194
+ // Corrupt or unreadable — treat as empty.
195
+ }
196
+ return null;
197
+ }
198
+ function saveCacheFile(projectRoot, data) {
199
+ data.updatedAt = Date.now();
200
+ try {
201
+ fs.writeFileSync(cachePathFor(projectRoot), JSON.stringify(data, null, 2), "utf8");
202
+ }
203
+ catch {
204
+ // Non-fatal: caching is best-effort. Next build just re-runs the gate.
205
+ }
206
+ }
207
+ function freshCacheFile(projectRoot) {
208
+ return {
209
+ version: CACHE_VERSION,
210
+ toolchainFingerprint: computeToolchainFingerprint(),
211
+ rootDir: path.resolve(projectRoot),
212
+ gates: {},
213
+ createdAt: Date.now(),
214
+ updatedAt: Date.now(),
215
+ };
216
+ }
217
+ /**
218
+ * Decide whether the ESLint gate can be skipped for this project.
219
+ *
220
+ * Returns `hit: true` only when:
221
+ * - caching is not disabled, not force-bypassed,
222
+ * - a fingerprint can be computed (config + eslint resolvable), and
223
+ * - the on-disk cache records a success for that exact fingerprint.
224
+ *
225
+ * `force` mirrors TranspileOptions.force and bypasses the cache.
226
+ */
227
+ export function checkLintCache(projectRoot, srcDir, opts = {}) {
228
+ const fingerprint = computeLintFingerprint(projectRoot, srcDir);
229
+ if (fingerprint === null)
230
+ return { hit: false, fingerprint: null };
231
+ if (opts.force || isCacheDisabled())
232
+ return { hit: false, fingerprint };
233
+ const data = loadCacheFile(projectRoot);
234
+ if (!data)
235
+ return { hit: false, fingerprint };
236
+ const entry = data.gates.lint;
237
+ return { hit: entry?.digest === fingerprint.digest, fingerprint };
238
+ }
239
+ /**
240
+ * Record a successful ESLint run (zero errors). Never call this after a
241
+ * failing run — a failing build must not persist a "clean" marker.
242
+ */
243
+ export function recordLintSuccess(projectRoot, fingerprint) {
244
+ if (isCacheDisabled())
245
+ return;
246
+ const data = loadCacheFile(projectRoot) ?? freshCacheFile(projectRoot);
247
+ data.gates.lint = { digest: fingerprint.digest, recordedAt: Date.now() };
248
+ saveCacheFile(projectRoot, data);
249
+ }
250
+ /** Drop the lint entry (used when the gate is skipped entirely / nothing to cache). */
251
+ export function invalidateLint(projectRoot) {
252
+ const data = loadCacheFile(projectRoot);
253
+ if (data && data.gates.lint) {
254
+ delete data.gates.lint;
255
+ saveCacheFile(projectRoot, data);
256
+ }
257
+ }
@@ -121,7 +121,7 @@ export function collectTranspileGraph(entryFile, boardPackage) {
121
121
  }
122
122
  if (!moduleSpecifier)
123
123
  continue;
124
- if (moduleSpecifier === "@typecad/expect" || moduleSpecifier === "@typecad/ui")
124
+ if (moduleSpecifier === "@typecad/expect" || moduleSpecifier === "@typecad/ui" || moduleSpecifier === "@typecad/safety")
125
125
  continue;
126
126
  if (moduleSpecifier.startsWith("@typecad/"))
127
127
  continue;
@@ -159,22 +159,27 @@ export function collectTranspileGraph(entryFile, boardPackage) {
159
159
  if (moduleSpecifier === "@typecad/expect") {
160
160
  continue;
161
161
  }
162
- // Skip @typecad/ui (and the future @typecad/ui rename) it provides
163
- // compile-time authoring stubs only. ui.mount/signal/bind calls are
164
- // intercepted by tryResolveUICall and lowered to IR; the package itself
165
- // must NOT be emitted as a C++ module (it would synthesize a bogus
166
- // _ui_t struct for the `ui` namespace value).
167
- if (moduleSpecifier === "@typecad/ui" || moduleSpecifier === "@typecad/ui") {
162
+ // Skip @typecad/ui and @typecad/safetythey provide compile-time
163
+ // authoring stubs only. Their calls (ui.mount/signal/bind, safe.read)
164
+ // are intercepted at IR-build time and lowered to IR; the packages
165
+ // themselves must NOT be emitted as C++ modules (they would synthesize
166
+ // bogus _ui_t / _safe_t structs). All runtime definitions the user code
167
+ // needs (enums, structs, shims) are provided by the safety polyfill.
168
+ if (moduleSpecifier === "@typecad/ui" || moduleSpecifier === "@typecad/safety") {
168
169
  continue;
169
170
  }
170
- // Skip @typecad/board, @typecad/board-*, @typecad/mcu-*, and
171
- // @typecad/framework-* — these packages ship src/ for HAL metadata
171
+ // Skip @typecad/board, @typecad/board-*, @typecad/mcu-*, @typecad/hal,
172
+ // and @typecad/framework-* — these packages ship src/ for HAL metadata
172
173
  // introspection (hal-parser.ts, board-resolver.ts) but their source
173
174
  // must NOT be transpiled to C++. The HAL resolver loads class/method
174
175
  // metadata from these files separately; emitting them as C++ produces
175
176
  // thousands of lines of stub functions (board(), gpioWrite(), etc.)
176
177
  // and pulls in unsupported types (Promise, variant, Object.freeze).
178
+ // Skipping @typecad/hal is especially important: its 28 source files
179
+ // (gpio.ts, i2c.ts, spi.ts, etc.) were all walked through full
180
+ // buildProgramIR, adding ~40 seconds to every transpile.
177
181
  if (moduleSpecifier === "@typecad/board"
182
+ || moduleSpecifier === "@typecad/hal"
178
183
  || moduleSpecifier.startsWith("@typecad/board-")
179
184
  || moduleSpecifier.startsWith("@typecad/mcu-")
180
185
  || moduleSpecifier.startsWith("@typecad/framework-")) {
@@ -0,0 +1,19 @@
1
+ import type { ProgramIR } from "../api/index.js";
2
+ import type { RuntimePolyfillIR } from "../api/shared/index.js";
3
+ import type { AsyncRuntimeConfig } from "../api/shared/index.js";
4
+ /**
5
+ * Build a RuntimePolyfillIR for the no-STL cooperative scheduler, when the
6
+ * program has async/timer work AND the strategy has opted into priority/time-
7
+ * budget scheduling via AsyncRuntimeConfig.
8
+ *
9
+ * The polyfill carries only the scheduler namespace + CoopSched_run() alias.
10
+ * Per-program work-unit registration + dispatch lines are emitted separately
11
+ * by each strategy's asyncLoopInjection() via buildCoopSchedInjection().
12
+ *
13
+ * Why a polyfill and not shimLines(): polyfills are dead-code-eliminated
14
+ * (filterPolyfillHelpers) and feature-gated centrally, matching how
15
+ * async_runtime / timer_methods are handled. The scheduler is STL-free, so it
16
+ * links on minimal-libc targets (Zephyr) where the std::function Promise
17
+ * runtime does not.
18
+ */
19
+ export declare function buildCoopSchedulerPolyfill(_program: ProgramIR, config: AsyncRuntimeConfig, currentTimeExpr?: string): RuntimePolyfillIR | null;
@@ -0,0 +1,41 @@
1
+ import { generateCoopScheduler } from "../api/shared/index.js";
2
+ /**
3
+ * Build a RuntimePolyfillIR for the no-STL cooperative scheduler, when the
4
+ * program has async/timer work AND the strategy has opted into priority/time-
5
+ * budget scheduling via AsyncRuntimeConfig.
6
+ *
7
+ * The polyfill carries only the scheduler namespace + CoopSched_run() alias.
8
+ * Per-program work-unit registration + dispatch lines are emitted separately
9
+ * by each strategy's asyncLoopInjection() via buildCoopSchedInjection().
10
+ *
11
+ * Why a polyfill and not shimLines(): polyfills are dead-code-eliminated
12
+ * (filterPolyfillHelpers) and feature-gated centrally, matching how
13
+ * async_runtime / timer_methods are handled. The scheduler is STL-free, so it
14
+ * links on minimal-libc targets (Zephyr) where the std::function Promise
15
+ * runtime does not.
16
+ */
17
+ export function buildCoopSchedulerPolyfill(_program, config, currentTimeExpr = "millis()") {
18
+ if (!config.enablePriority && !config.enableTimeBudget)
19
+ return null;
20
+ return {
21
+ kind: "polyfill",
22
+ id: "coop_scheduler",
23
+ domain: "embedded",
24
+ // STL-free: only needs a 32-bit unsigned time source (millis() /
25
+ // k_uptime_get_32()) and fixed-width types, both available everywhere.
26
+ requiredIncludes: [],
27
+ forwardDeclarations: [],
28
+ helperStructs: [
29
+ generateCoopScheduler({
30
+ capacity: 8,
31
+ priorities: config.schedulerPriorities ?? 2,
32
+ enableTimeBudget: config.enableTimeBudget ?? false,
33
+ timeBudgetMs: config.timeBudgetMs ?? 5,
34
+ currentTimeExpr,
35
+ }),
36
+ ],
37
+ helperFunctions: [],
38
+ shimMacros: [],
39
+ dependencies: [],
40
+ };
41
+ }
@@ -17,7 +17,9 @@ export declare class GenericStrategy implements PlatformStrategy {
17
17
  overrideBaseName(originalBaseName: string, _outDirBaseName: string, _isEntryFile: boolean, _isNpmPackage: boolean): string;
18
18
  effectiveEmitMode(requestedMode: string, _isNpmPackage: boolean): string;
19
19
  normalizeCppType(typeName: string): string;
20
- defaultNumericType(): string;
20
+ defaultNumericType(compliance?: {
21
+ isBanned(ruleId: string): boolean;
22
+ }): string;
21
23
  mapReturnType(_functionName: string, returnType: string): string;
22
24
  isStringLikeType(cppType: string): boolean;
23
25
  isPointerType(cppType: string): boolean;
@@ -54,8 +56,8 @@ export declare class GenericStrategy implements PlatformStrategy {
54
56
  structFieldInitializer(_fieldValue: ExpressionIR, _compiletimeVarNames: Set<string>, _renderExpr: (e: ExpressionIR) => string): string | undefined;
55
57
  getAsyncRuntimeConfig(): AsyncRuntimeConfig;
56
58
  generateNativePolyfills(program: ProgramIR, ctx?: PlatformContext): RuntimePolyfillIR[];
57
- asyncLoopInjection(_taskVarNames: string[], config: AsyncRuntimeConfig): string[];
58
- asyncLoopInjection(_taskVarNames: string[], hasPromiseRuntime: boolean): string[];
59
+ asyncLoopInjection(taskVarNames: string[], config: AsyncRuntimeConfig): string[];
60
+ asyncLoopInjection(taskVarNames: string[], hasPromiseRuntime: boolean): string[];
59
61
  asyncDriverFunctionName(): string;
60
62
  shouldSkipTypeAlias(_cppType: string): boolean;
61
63
  emitDiagnostics(_emitMode: string): Diagnostic[];
@@ -68,6 +70,7 @@ export declare class GenericStrategy implements PlatformStrategy {
68
70
  close: string;
69
71
  } | undefined;
70
72
  getStdLibSupport(_architecture?: string): StdLibSupport;
73
+ debugMode(_target?: string): 'gdb' | 'printf';
71
74
  generateDebugInitCode(): string[];
72
75
  generateDebugBreakpointCode(params: {
73
76
  fileName: string;
@@ -98,4 +101,13 @@ export declare class GenericStrategy implements PlatformStrategy {
98
101
  supportedDisplayDrivers(): ReadonlySet<string>;
99
102
  colorFormat(): "rgb565" | "rgb666" | "rgb888" | "mono";
100
103
  graphicsCapacity(): GraphicsCapacity;
104
+ readDigitalPin(_pin: string): string;
105
+ readAnalogPin(_pin: string): string;
106
+ writeDigitalPin(_pin: string, _val: string): string;
107
+ setPinMode(_pin: string, _mode: string): string;
108
+ delayMs(_ms: string): string;
109
+ delayMicroseconds(_us: string): string;
110
+ halCallNames(): ReadonlySet<string>;
111
+ isHalCall(_name: string): boolean;
112
+ analogReadCallNames(): ReadonlySet<string>;
101
113
  }
@@ -42,10 +42,14 @@ export class GenericStrategy {
42
42
  // ── Type normalisation ──────────────────────────────────────────────────
43
43
  normalizeCppType(typeName) {
44
44
  if (typeName === "auto")
45
- return "int";
45
+ return this.defaultNumericType();
46
46
  return typeName;
47
47
  }
48
- defaultNumericType() { return "int"; }
48
+ defaultNumericType(compliance) {
49
+ // A3-9-1: fixed-width integers under AUTOSAR. Legacy 'int' spelling
50
+ // preserved when autosar is off so default output is byte-identical.
51
+ return compliance?.isBanned("A3-9-1") ? "int32_t" : "int";
52
+ }
49
53
  mapReturnType(_functionName, returnType) {
50
54
  return this.normalizeCppType(returnType);
51
55
  }
@@ -185,17 +189,23 @@ export class GenericStrategy {
185
189
  helpers.push(asyncRuntime);
186
190
  return helpers;
187
191
  }
188
- asyncLoopInjection(_taskVarNames, configOrBool) {
192
+ asyncLoopInjection(taskVarNames, configOrBool) {
189
193
  let hasPromiseRuntime;
190
- if (typeof configOrBool === 'boolean') {
194
+ if (typeof configOrBool === "boolean") {
191
195
  hasPromiseRuntime = configOrBool;
192
196
  }
193
197
  else {
194
198
  hasPromiseRuntime = configOrBool.hasPromiseRuntime;
195
199
  }
200
+ // Drive every async state-machine task once per driver-function iteration.
201
+ // Task globals auto-start on their first .run() (STATE_0 runs
202
+ // unconditionally); the state machine no-ops in its terminal/cyclic state.
196
203
  const lines = [];
197
204
  if (hasPromiseRuntime)
198
205
  lines.push(" cuttlefish_pump_microtasks();");
206
+ for (const n of taskVarNames) {
207
+ lines.push(` ${n}.run();`);
208
+ }
199
209
  return lines;
200
210
  }
201
211
  asyncDriverFunctionName() { return "main"; }
@@ -213,6 +223,9 @@ export class GenericStrategy {
213
223
  enumApiGuard(_enumName) { return undefined; }
214
224
  getStdLibSupport(_architecture) { return DEFAULT_STDLIB_SUPPORT; }
215
225
  // ── Debug code generation ─────────────────────────────────────────────────
226
+ debugMode(_target) {
227
+ return 'printf';
228
+ }
216
229
  generateDebugInitCode() {
217
230
  return [
218
231
  '// === DEBUG: Initialize ===',
@@ -286,4 +299,36 @@ export class GenericStrategy {
286
299
  nodeStorage: "flash",
287
300
  };
288
301
  }
302
+ // ── HAL (generic fallback) ──────────────────────────────────────────────
303
+ // The generic/host target has no GPIO or timing hardware. These return
304
+ // documented no-op stubs so emit stays deterministic rather than throwing
305
+ // mid-codegen. Host tests that exercise pin-polling paths use a mock
306
+ // PlatformStrategy (see tests/packages/cuttlefish/async-runtime-static.test.ts).
307
+ readDigitalPin(_pin) {
308
+ return "/* gpio unavailable on generic target */ 0";
309
+ }
310
+ readAnalogPin(_pin) {
311
+ return "/* adc unavailable on generic target */ 0";
312
+ }
313
+ writeDigitalPin(_pin, _val) {
314
+ return "/* gpio unavailable on generic target */";
315
+ }
316
+ setPinMode(_pin, _mode) {
317
+ return "/* gpio unavailable on generic target */";
318
+ }
319
+ delayMs(_ms) {
320
+ return "/* delay unavailable on generic target */";
321
+ }
322
+ delayMicroseconds(_us) {
323
+ return "/* delay unavailable on generic target */";
324
+ }
325
+ halCallNames() {
326
+ return new Set();
327
+ }
328
+ isHalCall(_name) {
329
+ return false;
330
+ }
331
+ analogReadCallNames() {
332
+ return new Set();
333
+ }
289
334
  }
@@ -0,0 +1,11 @@
1
+ /** Dynamically load @typecad/safety/engine and register the hook.
2
+ * Called by transpile.ts at the start of each transpile run.
3
+ * Safe to call when @typecad/safety is absent (hook stays null). */
4
+ export declare function loadSafetyEngine(): Promise<void>;
5
+ /** Reset the safety bridge: forget a load was attempted and clear the hook.
6
+ * Mirrors resetUIEngine() so each transpile run / test starts clean. */
7
+ export declare function resetSafetyEngine(): void;
8
+ /** Test-only: force the bridge into the "no @typecad/safety" state — mark the
9
+ * load as already attempted and leave the hook null, exactly as if the
10
+ * dynamic import had failed because the package is not installed. */
11
+ export declare function __simulateSafetyAbsentForTest(): void;
@@ -0,0 +1,48 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Safety bridge — loads the safety engine from @typecad/safety and registers it.
3
+ //
4
+ // transpile.ts calls loadSafetyEngine() at the start of transpileFile(). If
5
+ // @typecad/safety is not installed, the import fails gracefully and the hook
6
+ // stays null — cuttlefish works as a pure TS→C++ transpiler with no safety
7
+ // support.
8
+ // ---------------------------------------------------------------------------
9
+ import { setSafetyHook } from "../safety-hook.js";
10
+ let loaded = false;
11
+ /** Dynamically load @typecad/safety/engine and register the hook.
12
+ * Called by transpile.ts at the start of each transpile run.
13
+ * Safe to call when @typecad/safety is absent (hook stays null). */
14
+ export async function loadSafetyEngine() {
15
+ if (loaded)
16
+ return;
17
+ loaded = true;
18
+ try {
19
+ // Dynamic import — @typecad/safety is optional. The module specifier is
20
+ // routed through a variable (rather than a string literal) so tsc types
21
+ // the result as `any` and never resolves @typecad/safety's declaration
22
+ // files while type-checking this file. A literal specifier would make tsc
23
+ // load safety's dist/engine-index.d.ts, which imports back
24
+ // @typecad/cuttlefish/safety-hook-types — a self-referencing package path
25
+ // that resolves into this package's own dist/ output and causes TS5055
26
+ // ("would overwrite input file") on every rebuild where dist/ exists.
27
+ const safetyEnginePath = "@typecad/safety/engine";
28
+ const engine = await import(safetyEnginePath);
29
+ const hook = engine.registerSafetyEngine();
30
+ setSafetyHook(hook);
31
+ }
32
+ catch {
33
+ // @typecad/safety is not installed — cuttlefish works without safety.
34
+ }
35
+ }
36
+ /** Reset the safety bridge: forget a load was attempted and clear the hook.
37
+ * Mirrors resetUIEngine() so each transpile run / test starts clean. */
38
+ export function resetSafetyEngine() {
39
+ loaded = false;
40
+ setSafetyHook(null);
41
+ }
42
+ /** Test-only: force the bridge into the "no @typecad/safety" state — mark the
43
+ * load as already attempted and leave the hook null, exactly as if the
44
+ * dynamic import had failed because the package is not installed. */
45
+ export function __simulateSafetyAbsentForTest() {
46
+ loaded = true;
47
+ setSafetyHook(null);
48
+ }
@@ -0,0 +1,5 @@
1
+ import type { SafetyMetadata } from "../safety-hook.js";
2
+ /** Dynamically import the safety package's sidecar renderer.
3
+ * Uses a variable-held specifier so tsc doesn't try to resolve the
4
+ * @typecad/safety types at build time (avoids circular dependency). */
5
+ export declare function renderSafetySidecar(metadata: SafetyMetadata, toolVersion: string): Promise<string>;
@@ -0,0 +1,22 @@
1
+ /** Dynamically import the safety package's sidecar renderer.
2
+ * Uses a variable-held specifier so tsc doesn't try to resolve the
3
+ * @typecad/safety types at build time (avoids circular dependency). */
4
+ export async function renderSafetySidecar(metadata, toolVersion) {
5
+ const spec = "@typecad/safety/iso26262/sidecar-writer";
6
+ try {
7
+ const mod = await import(spec);
8
+ return mod.renderSafetySidecar(metadata, toolVersion);
9
+ }
10
+ catch {
11
+ // Fallback: inline the renderer so the sidecar is produced even if
12
+ // the subpath export isn't configured.
13
+ return JSON.stringify({
14
+ schemaVersion: "1.0.0",
15
+ standard: "ISO 26262 Part 6",
16
+ tool: "cuttlefish (@typecad/safety)",
17
+ toolVersion,
18
+ generatedAt: new Date().toISOString(),
19
+ safetyFunctions: metadata.functions,
20
+ }, null, 2);
21
+ }
22
+ }