carrick 0.3.53

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 (114) hide show
  1. package/LICENSE.md +99 -0
  2. package/README.md +79 -0
  3. package/bin/carrick.mjs +168 -0
  4. package/dist/channel.d.ts +16 -0
  5. package/dist/channel.js +37 -0
  6. package/dist/channel.js.map +1 -0
  7. package/dist/cli.d.ts +29 -0
  8. package/dist/cli.js +60 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/contract.d.ts +154 -0
  11. package/dist/contract.js +141 -0
  12. package/dist/contract.js.map +1 -0
  13. package/dist/diagnostics.d.ts +54 -0
  14. package/dist/diagnostics.js +199 -0
  15. package/dist/diagnostics.js.map +1 -0
  16. package/dist/hook/post-edit.d.ts +13 -0
  17. package/dist/hook/post-edit.js +76 -0
  18. package/dist/hook/post-edit.js.map +1 -0
  19. package/dist/hook/session-start.d.ts +2 -0
  20. package/dist/hook/session-start.js +44 -0
  21. package/dist/hook/session-start.js.map +1 -0
  22. package/dist/init/identity.d.ts +20 -0
  23. package/dist/init/identity.js +60 -0
  24. package/dist/init/identity.js.map +1 -0
  25. package/dist/init/repos.d.ts +19 -0
  26. package/dist/init/repos.js +46 -0
  27. package/dist/init/repos.js.map +1 -0
  28. package/dist/init/run.d.ts +11 -0
  29. package/dist/init/run.js +228 -0
  30. package/dist/init/run.js.map +1 -0
  31. package/dist/init/settings.d.ts +50 -0
  32. package/dist/init/settings.js +138 -0
  33. package/dist/init/settings.js.map +1 -0
  34. package/dist/log.d.ts +2 -0
  35. package/dist/log.js +35 -0
  36. package/dist/log.js.map +1 -0
  37. package/dist/native.d.ts +48 -0
  38. package/dist/native.js +127 -0
  39. package/dist/native.js.map +1 -0
  40. package/dist/render.d.ts +65 -0
  41. package/dist/render.js +334 -0
  42. package/dist/render.js.map +1 -0
  43. package/dist/root.d.ts +30 -0
  44. package/dist/root.js +82 -0
  45. package/dist/root.js.map +1 -0
  46. package/dist/server.d.ts +2 -0
  47. package/dist/server.js +255 -0
  48. package/dist/server.js.map +1 -0
  49. package/dist/templates.d.ts +18 -0
  50. package/dist/templates.js +66 -0
  51. package/dist/templates.js.map +1 -0
  52. package/package.json +72 -0
  53. package/plugin/.claude-plugin/plugin.json +6 -0
  54. package/plugin/.lsp.json +14 -0
  55. package/plugin/hooks/hooks.json +27 -0
  56. package/sidecar/dist/src/bundler.d.ts +141 -0
  57. package/sidecar/dist/src/bundler.js +680 -0
  58. package/sidecar/dist/src/capture/anchors.d.ts +61 -0
  59. package/sidecar/dist/src/capture/anchors.js +1132 -0
  60. package/sidecar/dist/src/capture/api.d.ts +378 -0
  61. package/sidecar/dist/src/capture/api.js +10 -0
  62. package/sidecar/dist/src/capture/augmentations.d.ts +20 -0
  63. package/sidecar/dist/src/capture/augmentations.js +60 -0
  64. package/sidecar/dist/src/capture/check-classify.d.ts +58 -0
  65. package/sidecar/dist/src/capture/check-classify.js +189 -0
  66. package/sidecar/dist/src/capture/check-deep.d.ts +32 -0
  67. package/sidecar/dist/src/capture/check-deep.js +91 -0
  68. package/sidecar/dist/src/capture/check-poison.d.ts +40 -0
  69. package/sidecar/dist/src/capture/check-poison.js +155 -0
  70. package/sidecar/dist/src/capture/check-probe.d.ts +70 -0
  71. package/sidecar/dist/src/capture/check-probe.js +147 -0
  72. package/sidecar/dist/src/capture/check-scrub.d.ts +31 -0
  73. package/sidecar/dist/src/capture/check-scrub.js +71 -0
  74. package/sidecar/dist/src/capture/check-workspace.d.ts +52 -0
  75. package/sidecar/dist/src/capture/check-workspace.js +218 -0
  76. package/sidecar/dist/src/capture/check.d.ts +39 -0
  77. package/sidecar/dist/src/capture/check.js +450 -0
  78. package/sidecar/dist/src/capture/deep-walk.d.ts +62 -0
  79. package/sidecar/dist/src/capture/deep-walk.js +243 -0
  80. package/sidecar/dist/src/capture/index.d.ts +36 -0
  81. package/sidecar/dist/src/capture/index.js +477 -0
  82. package/sidecar/dist/src/capture/lockfile.d.ts +48 -0
  83. package/sidecar/dist/src/capture/lockfile.js +490 -0
  84. package/sidecar/dist/src/capture/machinery.d.ts +59 -0
  85. package/sidecar/dist/src/capture/machinery.js +160 -0
  86. package/sidecar/dist/src/capture/node-builder.d.ts +37 -0
  87. package/sidecar/dist/src/capture/node-builder.js +123 -0
  88. package/sidecar/dist/src/capture/paths-rewrite.d.ts +34 -0
  89. package/sidecar/dist/src/capture/paths-rewrite.js +100 -0
  90. package/sidecar/dist/src/capture/self-check.d.ts +38 -0
  91. package/sidecar/dist/src/capture/self-check.js +317 -0
  92. package/sidecar/dist/src/capture/specifiers.d.ts +39 -0
  93. package/sidecar/dist/src/capture/specifiers.js +56 -0
  94. package/sidecar/dist/src/definition-resolver.d.ts +57 -0
  95. package/sidecar/dist/src/definition-resolver.js +153 -0
  96. package/sidecar/dist/src/index.d.ts +14 -0
  97. package/sidecar/dist/src/index.js +564 -0
  98. package/sidecar/dist/src/monorepo-builder.d.ts +129 -0
  99. package/sidecar/dist/src/monorepo-builder.js +584 -0
  100. package/sidecar/dist/src/project-loader.d.ts +130 -0
  101. package/sidecar/dist/src/project-loader.js +399 -0
  102. package/sidecar/dist/src/type-inferrer.d.ts +940 -0
  103. package/sidecar/dist/src/type-inferrer.js +3540 -0
  104. package/sidecar/dist/src/type-structural-expander.d.ts +61 -0
  105. package/sidecar/dist/src/type-structural-expander.js +283 -0
  106. package/sidecar/dist/src/type-text-canonicalizer.d.ts +40 -0
  107. package/sidecar/dist/src/type-text-canonicalizer.js +297 -0
  108. package/sidecar/dist/src/types.d.ts +669 -0
  109. package/sidecar/dist/src/types.js +5 -0
  110. package/sidecar/dist/src/validators.d.ts +2214 -0
  111. package/sidecar/dist/src/validators.js +336 -0
  112. package/sidecar/package.json +6 -0
  113. package/templates/carrick.json +7 -0
  114. package/templates/carrick.yml +45 -0
@@ -0,0 +1,584 @@
1
+ /**
2
+ * Monorepo Builder - Builds synthetic monorepo workspaces for type compatibility checking
3
+ *
4
+ * This module implements the "Synthetic Monorepo (Stub Snapshot)" architecture:
5
+ * - Creates a workspace with stub packages for each repo
6
+ * - Each stub package has its own isolated node_modules with pinned dependencies
7
+ * - Generates path mappings so @carrick/{repoName}/{spec} resolves correctly
8
+ * - Creates a checker package that imports surfaces and asserts type compatibility
9
+ *
10
+ * Key design decisions:
11
+ * - Uses pnpm for better isolation between packages
12
+ * - Does NOT copy full repos - only uses captured artifacts
13
+ * - Deterministic: uses pinned dependency snapshots
14
+ */
15
+ import * as fs from 'node:fs';
16
+ import * as path from 'node:path';
17
+ import { execSync } from 'node:child_process';
18
+ /**
19
+ * Default workspace root path
20
+ */
21
+ const DEFAULT_WORKSPACE_ROOT = '.carrick/workspace';
22
+ /**
23
+ * MonorepoBuilder - Creates synthetic workspaces for cross-repo type checking
24
+ *
25
+ * Usage:
26
+ * const builder = new MonorepoBuilder();
27
+ * const result = builder.build(repos);
28
+ * const checkResult = builder.checkCompatibility(workspacePath, checks);
29
+ */
30
+ export class MonorepoBuilder {
31
+ /**
32
+ * Build the synthetic monorepo workspace
33
+ *
34
+ * @param repos - Metadata for each repository to include
35
+ * @param workspaceRoot - Root directory for the workspace (defaults to .carrick/workspace)
36
+ * @returns WorkspaceBuildResult
37
+ */
38
+ build(repos, workspaceRoot) {
39
+ const root = workspaceRoot || DEFAULT_WORKSPACE_ROOT;
40
+ const errors = [];
41
+ const stubPackages = [];
42
+ try {
43
+ // Phase 1: Clean and create workspace root
44
+ this.log(`Creating workspace at: ${root}`);
45
+ this.ensureCleanDirectory(root);
46
+ // Phase 2: Create root workspace files
47
+ this.createRootPackageJson(root);
48
+ this.createPnpmWorkspaceYaml(root);
49
+ this.createNpmrc(root);
50
+ // Phase 3: Create stub packages for each repo
51
+ for (const repo of repos) {
52
+ const packagePath = this.createStubPackage(root, repo);
53
+ stubPackages.push(packagePath);
54
+ }
55
+ // Phase 4: Create checker package
56
+ const checkerPath = this.createCheckerPackage(root, repos);
57
+ // Phase 5: Install dependencies
58
+ this.installDependencies(root);
59
+ this.log(`Workspace built successfully at: ${root}`);
60
+ return {
61
+ success: true,
62
+ workspace_path: path.resolve(root),
63
+ stub_packages: stubPackages.map((p) => path.resolve(p)),
64
+ checker_path: path.resolve(checkerPath),
65
+ };
66
+ }
67
+ catch (err) {
68
+ const error = err instanceof Error ? err.message : String(err);
69
+ this.logError(`Workspace build failed: ${error}`);
70
+ errors.push(error);
71
+ return {
72
+ success: false,
73
+ errors,
74
+ };
75
+ }
76
+ }
77
+ /**
78
+ * Run type compatibility checks in the workspace
79
+ *
80
+ * @param workspaceRoot - Path to the workspace root
81
+ * @param checks - Compatibility checks to run
82
+ * @returns CompatibilityCheckResult
83
+ */
84
+ checkCompatibility(workspaceRoot, checks) {
85
+ const errors = [];
86
+ const results = [];
87
+ const diagnostics = [];
88
+ try {
89
+ // Generate check assertions file
90
+ const checkerDir = path.join(workspaceRoot, 'checker');
91
+ const checkFilePath = path.join(checkerDir, 'src', 'checks.ts');
92
+ this.generateCheckFile(checkFilePath, checks);
93
+ // Run tsc on the checker package
94
+ const tscResult = this.runTypeCheck(checkerDir);
95
+ // Parse results
96
+ for (const check of checks) {
97
+ const result = this.parseCheckResult(check, tscResult);
98
+ results.push(result);
99
+ if (!result.compatible && result.diagnostic) {
100
+ diagnostics.push(result.diagnostic);
101
+ }
102
+ }
103
+ return {
104
+ success: true,
105
+ results,
106
+ diagnostics: diagnostics.length > 0 ? diagnostics : undefined,
107
+ };
108
+ }
109
+ catch (err) {
110
+ const error = err instanceof Error ? err.message : String(err);
111
+ this.logError(`Compatibility check failed: ${error}`);
112
+ errors.push(error);
113
+ return {
114
+ success: false,
115
+ errors,
116
+ };
117
+ }
118
+ }
119
+ // ===========================================================================
120
+ // Workspace Creation
121
+ // ===========================================================================
122
+ /**
123
+ * Ensure a clean directory exists at the given path
124
+ */
125
+ ensureCleanDirectory(dirPath) {
126
+ if (fs.existsSync(dirPath)) {
127
+ fs.rmSync(dirPath, { recursive: true, force: true });
128
+ }
129
+ fs.mkdirSync(dirPath, { recursive: true });
130
+ }
131
+ /**
132
+ * Create the root package.json for the workspace
133
+ */
134
+ createRootPackageJson(root) {
135
+ const packageJson = {
136
+ name: '@carrick/workspace',
137
+ version: '0.0.0',
138
+ private: true,
139
+ description: 'Synthetic workspace for Carrick type compatibility checking',
140
+ workspaces: [
141
+ 'packages/*',
142
+ 'checker',
143
+ ],
144
+ };
145
+ const filePath = path.join(root, 'package.json');
146
+ fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2), 'utf-8');
147
+ this.log(`Created: ${filePath}`);
148
+ }
149
+ /**
150
+ * Create pnpm-workspace.yaml
151
+ */
152
+ createPnpmWorkspaceYaml(root) {
153
+ const content = `# Carrick synthetic workspace
154
+ packages:
155
+ - 'packages/*'
156
+ - 'checker'
157
+ `;
158
+ const filePath = path.join(root, 'pnpm-workspace.yaml');
159
+ fs.writeFileSync(filePath, content, 'utf-8');
160
+ this.log(`Created: ${filePath}`);
161
+ }
162
+ /**
163
+ * Create .npmrc to prevent hoisting that would blur package boundaries
164
+ */
165
+ createNpmrc(root) {
166
+ const content = `# Prevent hoisting to maintain per-package isolation
167
+ node-linker=isolated
168
+ hoist=false
169
+ shamefully-hoist=false
170
+ `;
171
+ const filePath = path.join(root, '.npmrc');
172
+ fs.writeFileSync(filePath, content, 'utf-8');
173
+ this.log(`Created: ${filePath}`);
174
+ }
175
+ // ===========================================================================
176
+ // Stub Package Creation
177
+ // ===========================================================================
178
+ /**
179
+ * Create a stub package for a repository
180
+ *
181
+ * @returns Path to the created package
182
+ */
183
+ createStubPackage(root, repo) {
184
+ const packageDir = path.join(root, 'packages', repo.repoName);
185
+ const srcDir = path.join(packageDir, 'src');
186
+ // Create directories
187
+ fs.mkdirSync(srcDir, { recursive: true });
188
+ // Create package.json from pinned dependencies
189
+ this.createStubPackageJson(packageDir, repo);
190
+ // Create tsconfig.json from snapshot
191
+ this.createStubTsconfig(packageDir, repo);
192
+ // Create surface.d.ts if provided
193
+ if (repo.surfaceContent) {
194
+ this.createSurfaceFile(srcDir, repo.surfaceContent);
195
+ }
196
+ else {
197
+ // Create placeholder
198
+ this.createPlaceholderSurface(srcDir, repo.repoName);
199
+ }
200
+ this.log(`Created stub package: ${packageDir}`);
201
+ return packageDir;
202
+ }
203
+ /**
204
+ * Create package.json for a stub package
205
+ */
206
+ createStubPackageJson(packageDir, repo) {
207
+ const packageJson = {
208
+ name: `@carrick/${repo.repoName}`,
209
+ version: '0.0.0',
210
+ private: true,
211
+ description: `Stub package for ${repo.repoName} type checking`,
212
+ main: './src/surface.d.ts',
213
+ types: './src/surface.d.ts',
214
+ dependencies: {},
215
+ };
216
+ // Add pinned dependencies
217
+ if (repo.dependencies) {
218
+ packageJson.dependencies = { ...repo.dependencies };
219
+ }
220
+ const filePath = path.join(packageDir, 'package.json');
221
+ fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2), 'utf-8');
222
+ }
223
+ /**
224
+ * Create tsconfig.json for a stub package from the snapshot
225
+ */
226
+ createStubTsconfig(packageDir, repo) {
227
+ const snapshot = repo.tsconfig || this.getDefaultTsconfigSnapshot();
228
+ const tsconfig = {
229
+ compilerOptions: {
230
+ ...snapshot.compilerOptions,
231
+ // Override for surface checking
232
+ declaration: true,
233
+ declarationMap: false,
234
+ emitDeclarationOnly: true,
235
+ noEmit: false,
236
+ skipLibCheck: true,
237
+ // Ensure we can import the surface
238
+ rootDir: './src',
239
+ outDir: './dist',
240
+ },
241
+ include: ['src/**/*'],
242
+ exclude: ['node_modules'],
243
+ };
244
+ const filePath = path.join(packageDir, 'tsconfig.json');
245
+ fs.writeFileSync(filePath, JSON.stringify(tsconfig, null, 2), 'utf-8');
246
+ }
247
+ /**
248
+ * Create the surface.d.ts file
249
+ */
250
+ createSurfaceFile(srcDir, content) {
251
+ const filePath = path.join(srcDir, 'surface.d.ts');
252
+ fs.writeFileSync(filePath, content, 'utf-8');
253
+ }
254
+ /**
255
+ * Create a placeholder surface file
256
+ */
257
+ createPlaceholderSurface(srcDir, repoName) {
258
+ const content = `// Placeholder surface for ${repoName}
259
+ // This file should be replaced with actual extracted types
260
+
261
+ export type Placeholder = unknown;
262
+ `;
263
+ const filePath = path.join(srcDir, 'surface.d.ts');
264
+ fs.writeFileSync(filePath, content, 'utf-8');
265
+ }
266
+ /**
267
+ * Get default tsconfig snapshot
268
+ */
269
+ getDefaultTsconfigSnapshot() {
270
+ return {
271
+ compilerOptions: {
272
+ module: 'NodeNext',
273
+ moduleResolution: 'NodeNext',
274
+ target: 'ES2022',
275
+ lib: ['ES2022'],
276
+ strict: true,
277
+ esModuleInterop: true,
278
+ skipLibCheck: true,
279
+ declaration: true,
280
+ },
281
+ };
282
+ }
283
+ // ===========================================================================
284
+ // Checker Package Creation
285
+ // ===========================================================================
286
+ /**
287
+ * Create the checker package that runs type compatibility checks
288
+ *
289
+ * @returns Path to the checker package
290
+ */
291
+ createCheckerPackage(root, repos) {
292
+ const checkerDir = path.join(root, 'checker');
293
+ const srcDir = path.join(checkerDir, 'src');
294
+ // Create directories
295
+ fs.mkdirSync(srcDir, { recursive: true });
296
+ // Create package.json
297
+ this.createCheckerPackageJson(checkerDir, repos);
298
+ // Create tsconfig.json with path mappings
299
+ this.createCheckerTsconfig(checkerDir, repos);
300
+ // Create initial checks file
301
+ this.createInitialChecksFile(srcDir);
302
+ this.log(`Created checker package: ${checkerDir}`);
303
+ return checkerDir;
304
+ }
305
+ /**
306
+ * Create package.json for the checker package
307
+ */
308
+ createCheckerPackageJson(checkerDir, repos) {
309
+ const dependencies = {};
310
+ // Add workspace dependencies to each stub package
311
+ for (const repo of repos) {
312
+ dependencies[`@carrick/${repo.repoName}`] = 'workspace:*';
313
+ }
314
+ const packageJson = {
315
+ name: '@carrick/checker',
316
+ version: '0.0.0',
317
+ private: true,
318
+ description: 'Type compatibility checker',
319
+ dependencies,
320
+ devDependencies: {
321
+ typescript: '^5.0.0',
322
+ },
323
+ scripts: {
324
+ check: 'tsc --noEmit',
325
+ },
326
+ };
327
+ const filePath = path.join(checkerDir, 'package.json');
328
+ fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2), 'utf-8');
329
+ }
330
+ /**
331
+ * Create tsconfig.json for the checker package with path mappings
332
+ */
333
+ createCheckerTsconfig(checkerDir, repos) {
334
+ const paths = {};
335
+ for (const repo of repos) {
336
+ const repoName = repo.repoName;
337
+ // Surface import: @carrick/{repoName}/surface -> packages/{repoName}/src/surface.d.ts
338
+ paths[`@carrick/${repoName}/surface`] = [
339
+ `../packages/${repoName}/src/surface.d.ts`,
340
+ ];
341
+ // Unscoped packages: @carrick/{repoName}/* -> packages/{repoName}/node_modules/*
342
+ paths[`@carrick/${repoName}/*`] = [
343
+ `../packages/${repoName}/node_modules/*`,
344
+ `../packages/${repoName}/node_modules/*/index.d.ts`,
345
+ `../packages/${repoName}/node_modules/@types/*`,
346
+ ];
347
+ // Scoped packages: @carrick/{repoName}/@*/* -> packages/{repoName}/node_modules/@*/*
348
+ paths[`@carrick/${repoName}/@*/*`] = [
349
+ `../packages/${repoName}/node_modules/@*/*`,
350
+ `../packages/${repoName}/node_modules/@*/*/index.d.ts`,
351
+ ];
352
+ }
353
+ const tsconfig = {
354
+ compilerOptions: {
355
+ module: 'NodeNext',
356
+ moduleResolution: 'NodeNext',
357
+ target: 'ES2022',
358
+ lib: ['ES2022'],
359
+ strict: true,
360
+ esModuleInterop: true,
361
+ skipLibCheck: true,
362
+ noEmit: true,
363
+ baseUrl: '.',
364
+ paths,
365
+ },
366
+ include: ['src/**/*'],
367
+ exclude: ['node_modules'],
368
+ };
369
+ const filePath = path.join(checkerDir, 'tsconfig.json');
370
+ fs.writeFileSync(filePath, JSON.stringify(tsconfig, null, 2), 'utf-8');
371
+ }
372
+ /**
373
+ * Create initial checks.ts file
374
+ */
375
+ createInitialChecksFile(srcDir) {
376
+ const content = `// Carrick Type Compatibility Checks
377
+ // This file is auto-generated - do not edit manually
378
+
379
+ // Type assertion helpers
380
+ type Assert<T extends true> = T;
381
+ type IsAssignable<X, Y> = [X] extends [Y] ? true : false;
382
+ type IsBidirectional<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false;
383
+
384
+ // Checks will be generated here
385
+ export {};
386
+ `;
387
+ const filePath = path.join(srcDir, 'checks.ts');
388
+ fs.writeFileSync(filePath, content, 'utf-8');
389
+ }
390
+ // ===========================================================================
391
+ // Dependency Installation
392
+ // ===========================================================================
393
+ /**
394
+ * Install dependencies using pnpm
395
+ */
396
+ installDependencies(root) {
397
+ this.log('Installing dependencies with pnpm...');
398
+ try {
399
+ // Check if pnpm is available
400
+ execSync('pnpm --version', { stdio: 'pipe' });
401
+ }
402
+ catch {
403
+ this.log('pnpm not found, trying npm...');
404
+ this.installWithNpm(root);
405
+ return;
406
+ }
407
+ try {
408
+ execSync('pnpm install --frozen-lockfile=false', {
409
+ cwd: root,
410
+ stdio: 'pipe',
411
+ env: { ...process.env, CI: 'false' },
412
+ });
413
+ this.log('Dependencies installed successfully');
414
+ }
415
+ catch {
416
+ // If frozen lockfile fails, try without it
417
+ try {
418
+ execSync('pnpm install', {
419
+ cwd: root,
420
+ stdio: 'pipe',
421
+ env: { ...process.env, CI: 'false' },
422
+ });
423
+ this.log('Dependencies installed successfully');
424
+ }
425
+ catch (installErr) {
426
+ const error = installErr instanceof Error ? installErr.message : String(installErr);
427
+ throw new Error(`Failed to install dependencies: ${error}`);
428
+ }
429
+ }
430
+ }
431
+ /**
432
+ * Fallback: Install dependencies using npm
433
+ */
434
+ installWithNpm(root) {
435
+ try {
436
+ // Remove pnpm-workspace.yaml since npm doesn't understand it
437
+ const workspaceFile = path.join(root, 'pnpm-workspace.yaml');
438
+ if (fs.existsSync(workspaceFile)) {
439
+ fs.unlinkSync(workspaceFile);
440
+ }
441
+ execSync('npm install', {
442
+ cwd: root,
443
+ stdio: 'pipe',
444
+ env: { ...process.env, CI: 'false' },
445
+ });
446
+ this.log('Dependencies installed successfully with npm');
447
+ }
448
+ catch (err) {
449
+ const error = err instanceof Error ? err.message : String(err);
450
+ throw new Error(`Failed to install dependencies with npm: ${error}`);
451
+ }
452
+ }
453
+ // ===========================================================================
454
+ // Compatibility Checking
455
+ // ===========================================================================
456
+ /**
457
+ * Generate the checks.ts file with type assertions
458
+ */
459
+ generateCheckFile(filePath, checks) {
460
+ const lines = [
461
+ '// Carrick Type Compatibility Checks',
462
+ '// Auto-generated - do not edit manually',
463
+ '',
464
+ '// Type assertion helpers',
465
+ 'type Assert<T extends true> = T;',
466
+ 'type IsAssignable<X, Y> = [X] extends [Y] ? true : false;',
467
+ 'type IsBidirectional<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false;',
468
+ '',
469
+ ];
470
+ // Generate imports
471
+ const repoImports = new Map();
472
+ for (const check of checks) {
473
+ if (!repoImports.has(check.source_repo)) {
474
+ repoImports.set(check.source_repo, new Set());
475
+ }
476
+ repoImports.get(check.source_repo).add(check.source_alias);
477
+ if (!repoImports.has(check.target_repo)) {
478
+ repoImports.set(check.target_repo, new Set());
479
+ }
480
+ repoImports.get(check.target_repo).add(check.target_alias);
481
+ }
482
+ for (const [repo, aliases] of repoImports) {
483
+ const aliasesArray = Array.from(aliases);
484
+ // Create unique import names to avoid conflicts
485
+ const importNames = aliasesArray.map((alias) => `${alias} as ${this.makeImportName(repo, alias)}`);
486
+ lines.push(`import type { ${importNames.join(', ')} } from '@carrick/${repo}/surface';`);
487
+ }
488
+ lines.push('');
489
+ lines.push('// Compatibility checks');
490
+ // Generate type assertions
491
+ for (let i = 0; i < checks.length; i++) {
492
+ const check = checks[i];
493
+ const sourceType = this.makeImportName(check.source_repo, check.source_alias);
494
+ const targetType = this.makeImportName(check.target_repo, check.target_alias);
495
+ const checkName = `_check${i}`;
496
+ lines.push('');
497
+ lines.push(`// Check: ${check.source_repo}/${check.source_alias} vs ${check.target_repo}/${check.target_alias}`);
498
+ switch (check.direction) {
499
+ case 'source_extends_target':
500
+ lines.push(`type ${checkName} = Assert<IsAssignable<${sourceType}, ${targetType}>>;`);
501
+ break;
502
+ case 'target_extends_source':
503
+ lines.push(`type ${checkName} = Assert<IsAssignable<${targetType}, ${sourceType}>>;`);
504
+ break;
505
+ case 'bidirectional':
506
+ lines.push(`type ${checkName} = Assert<IsBidirectional<${sourceType}, ${targetType}>>;`);
507
+ break;
508
+ }
509
+ }
510
+ lines.push('');
511
+ lines.push('export {};');
512
+ const content = lines.join('\n');
513
+ fs.writeFileSync(filePath, content, 'utf-8');
514
+ this.log(`Generated check file: ${filePath}`);
515
+ }
516
+ /**
517
+ * Create a unique import name for a repo/alias combination
518
+ */
519
+ makeImportName(repo, alias) {
520
+ const safeRepo = repo.replace(/[^a-zA-Z0-9]/g, '_');
521
+ const safeAlias = alias.replace(/[^a-zA-Z0-9]/g, '_');
522
+ return `${safeRepo}_${safeAlias}`;
523
+ }
524
+ /**
525
+ * Run TypeScript type checking on the checker package
526
+ *
527
+ * @returns Object with success status and any error output
528
+ */
529
+ runTypeCheck(checkerDir) {
530
+ try {
531
+ execSync('npx tsc --noEmit', {
532
+ cwd: checkerDir,
533
+ stdio: 'pipe',
534
+ encoding: 'utf-8',
535
+ });
536
+ return { success: true, output: '' };
537
+ }
538
+ catch (err) {
539
+ const error = err;
540
+ const output = error.stdout || error.stderr || error.message || 'Unknown error';
541
+ return { success: false, output };
542
+ }
543
+ }
544
+ /**
545
+ * Parse the result of a type check for a specific compatibility check
546
+ */
547
+ parseCheckResult(check, tscResult) {
548
+ // If tsc succeeded, all checks passed
549
+ if (tscResult.success) {
550
+ return {
551
+ source_repo: check.source_repo,
552
+ source_alias: check.source_alias,
553
+ target_repo: check.target_repo,
554
+ target_alias: check.target_alias,
555
+ compatible: true,
556
+ };
557
+ }
558
+ // Look for errors related to this specific check
559
+ const escSrc = check.source_alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
560
+ const escTgt = check.target_alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
561
+ const checkPattern = new RegExp(`${escSrc}.*${escTgt}|${escTgt}.*${escSrc}`, 'i');
562
+ const isRelated = checkPattern.test(tscResult.output);
563
+ // If tsc failed but the error doesn't mention our aliases, treat as
564
+ // incompatible rather than silently reporting compatible (the check
565
+ // was not actually validated).
566
+ return {
567
+ source_repo: check.source_repo,
568
+ source_alias: check.source_alias,
569
+ target_repo: check.target_repo,
570
+ target_alias: check.target_alias,
571
+ compatible: false,
572
+ diagnostic: tscResult.output,
573
+ };
574
+ }
575
+ // ===========================================================================
576
+ // Logging
577
+ // ===========================================================================
578
+ log(message) {
579
+ console.error(`[sidecar:monorepo-builder] ${message}`);
580
+ }
581
+ logError(message) {
582
+ console.error(`[sidecar:monorepo-builder:error] ${message}`);
583
+ }
584
+ }