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,130 @@
1
+ /**
2
+ * Project Loader - Loads TypeScript projects using ts-morph
3
+ *
4
+ * This module handles initialization of the ts-morph Project,
5
+ * including finding and loading tsconfig.json files.
6
+ *
7
+ * Supports:
8
+ * - Traditional tsconfig.json file loading
9
+ * - Tsconfig snapshot (closed/merged extends chains) for synthetic monorepo
10
+ * - Pinned dependency snapshots for deterministic builds
11
+ */
12
+ import { Project } from 'ts-morph';
13
+ import type { TsconfigSnapshot, PinnedDependencySnapshot } from './types.js';
14
+ /**
15
+ * Options for ProjectLoader construction
16
+ */
17
+ export interface ProjectLoaderOptions {
18
+ /** The repository root directory (absolute path) */
19
+ repoRoot: string;
20
+ /** Optional path to tsconfig.json (relative to repo root or absolute) */
21
+ tsconfigPath?: string;
22
+ /** Optional tsconfig snapshot (closed/merged) - preferred over tsconfigPath */
23
+ tsconfigSnapshot?: TsconfigSnapshot;
24
+ /** Optional pinned dependencies for this repo */
25
+ pinnedDependencies?: PinnedDependencySnapshot;
26
+ }
27
+ /**
28
+ * Result of project loading
29
+ */
30
+ export interface LoadResult {
31
+ success: boolean;
32
+ error?: string;
33
+ initTimeMs?: number;
34
+ }
35
+ /**
36
+ * ProjectLoader - Manages ts-morph Project initialization and access
37
+ *
38
+ * `load()` decides what the project will be built from and returns; the
39
+ * ts-morph project itself is built on first use and memoised. Readiness is a
40
+ * gate on the whole type layer — when it was gated on program construction, a
41
+ * repo with its dependencies installed could walk past the caller's budget and
42
+ * lose types for every service at once (carrick#749). Building on demand puts
43
+ * that cost inside the request that needs it, where a failure costs one
44
+ * service instead of all of them.
45
+ *
46
+ * Usage:
47
+ * const loader = new ProjectLoader({ repoRoot: '/path/to/repo' });
48
+ * const result = loader.load();
49
+ * if (result.success) {
50
+ * const project = loader.getProject();
51
+ * }
52
+ */
53
+ export declare class ProjectLoader {
54
+ private project;
55
+ /** Set by `load()`; builds the ts-morph project on first `getProject()`. */
56
+ private buildProject;
57
+ private readonly repoRoot;
58
+ private readonly tsconfigPath;
59
+ private readonly tsconfigSnapshot;
60
+ private readonly pinnedDependencies;
61
+ private initialized;
62
+ private initError;
63
+ private initTimeMs;
64
+ constructor(options: ProjectLoaderOptions);
65
+ /**
66
+ * Decide what the project will be built from.
67
+ *
68
+ * Validates the repo root and resolves the tsconfig, both cheap; the
69
+ * ts-morph project is built by the first `getProject()`.
70
+ *
71
+ * @returns LoadResult indicating success or failure
72
+ */
73
+ load(): LoadResult;
74
+ /**
75
+ * Convert a TsconfigSnapshot to ts-morph CompilerOptions
76
+ */
77
+ private snapshotToCompilerOptions;
78
+ /**
79
+ * Get the ts-morph Project, building it on first call.
80
+ *
81
+ * @returns The Project instance
82
+ * @throws Error if load() has not succeeded, or if the build fails
83
+ */
84
+ getProject(): Project;
85
+ /**
86
+ * Check if the project has been successfully initialized
87
+ */
88
+ isInitialized(): boolean;
89
+ /**
90
+ * Get initialization time in milliseconds
91
+ */
92
+ getInitTimeMs(): number | null;
93
+ /**
94
+ * Get the last initialization error, if any
95
+ */
96
+ getInitError(): string | null;
97
+ /**
98
+ * Get the repository root path
99
+ */
100
+ getRepoRoot(): string;
101
+ /**
102
+ * Get the pinned dependencies, if any
103
+ */
104
+ getPinnedDependencies(): PinnedDependencySnapshot | undefined;
105
+ /**
106
+ * Find the tsconfig.json file to use
107
+ *
108
+ * @returns Absolute path to tsconfig.json, or undefined if not found
109
+ */
110
+ private findTsConfig;
111
+ /**
112
+ * Add source files from common project locations when no tsconfig is found.
113
+ *
114
+ * Files are globbed and added one by one rather than through
115
+ * `addSourceFilesAtPaths`, which registers every descendant directory of any
116
+ * directory a pattern hit in. One `.ts` file at the repo root is enough to
117
+ * make that walk the entire installed tree: on a large monorepo with
118
+ * dependencies installed it cost 40 s and added nothing the compiler needs
119
+ * (carrick#749).
120
+ */
121
+ private addDefaultSourceFiles;
122
+ /**
123
+ * Log a message to stderr (stdout is reserved for JSON responses)
124
+ */
125
+ private log;
126
+ /**
127
+ * Log an error message to stderr
128
+ */
129
+ private logError;
130
+ }
@@ -0,0 +1,399 @@
1
+ /**
2
+ * Project Loader - Loads TypeScript projects using ts-morph
3
+ *
4
+ * This module handles initialization of the ts-morph Project,
5
+ * including finding and loading tsconfig.json files.
6
+ *
7
+ * Supports:
8
+ * - Traditional tsconfig.json file loading
9
+ * - Tsconfig snapshot (closed/merged extends chains) for synthetic monorepo
10
+ * - Pinned dependency snapshots for deterministic builds
11
+ */
12
+ import { Project } from 'ts-morph';
13
+ import * as path from 'node:path';
14
+ import * as fs from 'node:fs';
15
+ /**
16
+ * Source-file patterns used when the repo declares no tsconfig, relative to
17
+ * the repo root. `node_modules` is excluded explicitly: a glob that matches
18
+ * anything at the repo root would otherwise pull the whole installed tree in.
19
+ */
20
+ const DEFAULT_SOURCE_PATTERNS = [
21
+ 'src/**/*.ts',
22
+ 'src/**/*.tsx',
23
+ 'lib/**/*.ts',
24
+ 'app/**/*.ts',
25
+ 'app/**/*.tsx',
26
+ '*.ts',
27
+ ];
28
+ /**
29
+ * Default compiler options used when no tsconfig.json is found
30
+ */
31
+ const DEFAULT_COMPILER_OPTIONS = {
32
+ target: 99, // ESNext
33
+ module: 99, // ESNext
34
+ moduleResolution: 99, // NodeNext
35
+ strict: true,
36
+ esModuleInterop: true,
37
+ skipLibCheck: true,
38
+ declaration: true,
39
+ allowJs: true,
40
+ checkJs: false,
41
+ resolveJsonModule: true,
42
+ isolatedModules: true,
43
+ };
44
+ /**
45
+ * Map string module values to ts-morph enum values
46
+ */
47
+ const MODULE_MAP = {
48
+ 'CommonJS': 1,
49
+ 'AMD': 2,
50
+ 'UMD': 3,
51
+ 'System': 4,
52
+ 'ES2015': 5,
53
+ 'ES2020': 6,
54
+ 'ES2022': 7,
55
+ 'ESNext': 99,
56
+ 'Node16': 100,
57
+ 'NodeNext': 199,
58
+ 'Preserve': 200,
59
+ };
60
+ /**
61
+ * Map string moduleResolution values to ts-morph enum values
62
+ */
63
+ const MODULE_RESOLUTION_MAP = {
64
+ 'Classic': 1,
65
+ 'Node': 2,
66
+ 'Node10': 2,
67
+ 'Node16': 3,
68
+ 'NodeNext': 99,
69
+ 'Bundler': 100,
70
+ };
71
+ /**
72
+ * Map string target values to ts-morph enum values
73
+ */
74
+ const TARGET_MAP = {
75
+ 'ES3': 0,
76
+ 'ES5': 1,
77
+ 'ES2015': 2,
78
+ 'ES2016': 3,
79
+ 'ES2017': 4,
80
+ 'ES2018': 5,
81
+ 'ES2019': 6,
82
+ 'ES2020': 7,
83
+ 'ES2021': 8,
84
+ 'ES2022': 9,
85
+ 'ES2023': 10,
86
+ 'ESNext': 99,
87
+ };
88
+ /**
89
+ * ProjectLoader - Manages ts-morph Project initialization and access
90
+ *
91
+ * `load()` decides what the project will be built from and returns; the
92
+ * ts-morph project itself is built on first use and memoised. Readiness is a
93
+ * gate on the whole type layer — when it was gated on program construction, a
94
+ * repo with its dependencies installed could walk past the caller's budget and
95
+ * lose types for every service at once (carrick#749). Building on demand puts
96
+ * that cost inside the request that needs it, where a failure costs one
97
+ * service instead of all of them.
98
+ *
99
+ * Usage:
100
+ * const loader = new ProjectLoader({ repoRoot: '/path/to/repo' });
101
+ * const result = loader.load();
102
+ * if (result.success) {
103
+ * const project = loader.getProject();
104
+ * }
105
+ */
106
+ export class ProjectLoader {
107
+ project = null;
108
+ /** Set by `load()`; builds the ts-morph project on first `getProject()`. */
109
+ buildProject = null;
110
+ repoRoot;
111
+ tsconfigPath;
112
+ tsconfigSnapshot;
113
+ pinnedDependencies;
114
+ initialized = false;
115
+ initError = null;
116
+ initTimeMs = null;
117
+ constructor(options) {
118
+ // Normalize the repo root to an absolute path
119
+ this.repoRoot = path.isAbsolute(options.repoRoot)
120
+ ? options.repoRoot
121
+ : path.resolve(process.cwd(), options.repoRoot);
122
+ // Resolve tsconfig path if provided
123
+ if (options.tsconfigPath) {
124
+ this.tsconfigPath = path.isAbsolute(options.tsconfigPath)
125
+ ? options.tsconfigPath
126
+ : path.resolve(this.repoRoot, options.tsconfigPath);
127
+ }
128
+ // Store snapshot if provided
129
+ this.tsconfigSnapshot = options.tsconfigSnapshot;
130
+ this.pinnedDependencies = options.pinnedDependencies;
131
+ }
132
+ /**
133
+ * Decide what the project will be built from.
134
+ *
135
+ * Validates the repo root and resolves the tsconfig, both cheap; the
136
+ * ts-morph project is built by the first `getProject()`.
137
+ *
138
+ * @returns LoadResult indicating success or failure
139
+ */
140
+ load() {
141
+ const startTime = performance.now();
142
+ try {
143
+ // Validate repo root exists
144
+ if (!fs.existsSync(this.repoRoot)) {
145
+ const error = `Repository root does not exist: ${this.repoRoot}`;
146
+ this.logError(error);
147
+ this.initError = error;
148
+ return { success: false, error };
149
+ }
150
+ if (!fs.statSync(this.repoRoot).isDirectory()) {
151
+ const error = `Repository root is not a directory: ${this.repoRoot}`;
152
+ this.logError(error);
153
+ this.initError = error;
154
+ return { success: false, error };
155
+ }
156
+ // Priority 1: Use tsconfig snapshot if provided (for synthetic monorepo)
157
+ if (this.tsconfigSnapshot) {
158
+ const snapshot = this.tsconfigSnapshot;
159
+ this.log('Project will load with tsconfig snapshot');
160
+ this.buildProject = () => {
161
+ const project = new Project({
162
+ compilerOptions: this.snapshotToCompilerOptions(snapshot),
163
+ skipAddingFilesFromTsConfig: true,
164
+ });
165
+ this.addDefaultSourceFiles(project);
166
+ return project;
167
+ };
168
+ }
169
+ // Priority 2: Use tsconfig.json file
170
+ else {
171
+ const tsconfigPath = this.findTsConfig();
172
+ if (tsconfigPath) {
173
+ this.log(`Project will load with tsconfig: ${tsconfigPath}`);
174
+ this.buildProject = () => new Project({
175
+ tsConfigFilePath: tsconfigPath,
176
+ skipAddingFilesFromTsConfig: false,
177
+ });
178
+ }
179
+ else {
180
+ this.log('No tsconfig.json found, using default compiler options');
181
+ this.buildProject = () => {
182
+ const project = new Project({
183
+ compilerOptions: DEFAULT_COMPILER_OPTIONS,
184
+ skipAddingFilesFromTsConfig: true,
185
+ });
186
+ // Add source files from common locations
187
+ this.addDefaultSourceFiles(project);
188
+ return project;
189
+ };
190
+ }
191
+ }
192
+ // Log pinned dependencies if provided
193
+ if (this.pinnedDependencies) {
194
+ const depCount = Object.keys(this.pinnedDependencies).length;
195
+ this.log(`Using ${depCount} pinned dependencies`);
196
+ }
197
+ this.initialized = true;
198
+ this.initTimeMs = Math.round(performance.now() - startTime);
199
+ this.log(`Project resolved in ${this.initTimeMs}ms (built on first use)`);
200
+ return {
201
+ success: true,
202
+ initTimeMs: this.initTimeMs,
203
+ };
204
+ }
205
+ catch (err) {
206
+ const error = err instanceof Error ? err.message : String(err);
207
+ this.logError(`Failed to load project: ${error}`);
208
+ this.initError = error;
209
+ return {
210
+ success: false,
211
+ error,
212
+ initTimeMs: Math.round(performance.now() - startTime),
213
+ };
214
+ }
215
+ }
216
+ /**
217
+ * Convert a TsconfigSnapshot to ts-morph CompilerOptions
218
+ */
219
+ snapshotToCompilerOptions(snapshot) {
220
+ const opts = snapshot.compilerOptions;
221
+ const result = {};
222
+ // Map module
223
+ if (opts.module) {
224
+ const moduleValue = MODULE_MAP[opts.module];
225
+ if (moduleValue !== undefined) {
226
+ result.module = moduleValue;
227
+ }
228
+ }
229
+ // Map moduleResolution
230
+ if (opts.moduleResolution) {
231
+ const moduleResValue = MODULE_RESOLUTION_MAP[opts.moduleResolution];
232
+ if (moduleResValue !== undefined) {
233
+ result.moduleResolution = moduleResValue;
234
+ }
235
+ }
236
+ // Map target
237
+ if (opts.target) {
238
+ const targetValue = TARGET_MAP[opts.target];
239
+ if (targetValue !== undefined) {
240
+ result.target = targetValue;
241
+ }
242
+ }
243
+ // Pass through other options directly
244
+ if (opts.lib)
245
+ result.lib = opts.lib;
246
+ if (opts.types)
247
+ result.types = opts.types;
248
+ if (opts.typeRoots)
249
+ result.typeRoots = opts.typeRoots;
250
+ if (opts.strict !== undefined)
251
+ result.strict = opts.strict;
252
+ if (opts.esModuleInterop !== undefined)
253
+ result.esModuleInterop = opts.esModuleInterop;
254
+ if (opts.skipLibCheck !== undefined)
255
+ result.skipLibCheck = opts.skipLibCheck;
256
+ if (opts.declaration !== undefined)
257
+ result.declaration = opts.declaration;
258
+ if (opts.declarationMap !== undefined)
259
+ result.declarationMap = opts.declarationMap;
260
+ if (opts.paths)
261
+ result.paths = opts.paths;
262
+ if (opts.baseUrl)
263
+ result.baseUrl = opts.baseUrl;
264
+ // Map jsx if present
265
+ if (opts.jsx) {
266
+ const jsxMap = {
267
+ 'preserve': 1,
268
+ 'react': 2,
269
+ 'react-native': 3,
270
+ 'react-jsx': 4,
271
+ 'react-jsxdev': 5,
272
+ };
273
+ const jsxValue = jsxMap[opts.jsx.toLowerCase()];
274
+ if (jsxValue !== undefined) {
275
+ result.jsx = jsxValue;
276
+ }
277
+ }
278
+ return result;
279
+ }
280
+ /**
281
+ * Get the ts-morph Project, building it on first call.
282
+ *
283
+ * @returns The Project instance
284
+ * @throws Error if load() has not succeeded, or if the build fails
285
+ */
286
+ getProject() {
287
+ if (!this.initialized || !this.buildProject) {
288
+ throw new Error('Project not initialized. Call load() first and ensure it succeeds.');
289
+ }
290
+ if (!this.project) {
291
+ const startTime = performance.now();
292
+ this.project = this.buildProject();
293
+ const buildTimeMs = Math.round(performance.now() - startTime);
294
+ this.log(`Project built in ${buildTimeMs}ms ` +
295
+ `(${this.project.getSourceFiles().length} source files)`);
296
+ }
297
+ return this.project;
298
+ }
299
+ /**
300
+ * Check if the project has been successfully initialized
301
+ */
302
+ isInitialized() {
303
+ return this.initialized;
304
+ }
305
+ /**
306
+ * Get initialization time in milliseconds
307
+ */
308
+ getInitTimeMs() {
309
+ return this.initTimeMs;
310
+ }
311
+ /**
312
+ * Get the last initialization error, if any
313
+ */
314
+ getInitError() {
315
+ return this.initError;
316
+ }
317
+ /**
318
+ * Get the repository root path
319
+ */
320
+ getRepoRoot() {
321
+ return this.repoRoot;
322
+ }
323
+ /**
324
+ * Get the pinned dependencies, if any
325
+ */
326
+ getPinnedDependencies() {
327
+ return this.pinnedDependencies;
328
+ }
329
+ /**
330
+ * Find the tsconfig.json file to use
331
+ *
332
+ * @returns Absolute path to tsconfig.json, or undefined if not found
333
+ */
334
+ findTsConfig() {
335
+ // If a specific path was provided, try to use it
336
+ if (this.tsconfigPath) {
337
+ if (fs.existsSync(this.tsconfigPath)) {
338
+ return this.tsconfigPath;
339
+ }
340
+ this.log(`Specified tsconfig not found: ${this.tsconfigPath}`);
341
+ }
342
+ // Try common tsconfig locations
343
+ const candidates = [
344
+ path.join(this.repoRoot, 'tsconfig.json'),
345
+ path.join(this.repoRoot, 'tsconfig.build.json'),
346
+ path.join(this.repoRoot, 'tsconfig.app.json'),
347
+ ];
348
+ for (const candidate of candidates) {
349
+ if (fs.existsSync(candidate)) {
350
+ return candidate;
351
+ }
352
+ }
353
+ return undefined;
354
+ }
355
+ /**
356
+ * Add source files from common project locations when no tsconfig is found.
357
+ *
358
+ * Files are globbed and added one by one rather than through
359
+ * `addSourceFilesAtPaths`, which registers every descendant directory of any
360
+ * directory a pattern hit in. One `.ts` file at the repo root is enough to
361
+ * make that walk the entire installed tree: on a large monorepo with
362
+ * dependencies installed it cost 40 s and added nothing the compiler needs
363
+ * (carrick#749).
364
+ */
365
+ addDefaultSourceFiles(project) {
366
+ const globs = DEFAULT_SOURCE_PATTERNS.map((pattern) => path.join(this.repoRoot, pattern));
367
+ // Negated last so it applies to every pattern above it.
368
+ globs.push(`!${path.join(this.repoRoot, '**/node_modules/**')}`);
369
+ let filePaths = [];
370
+ try {
371
+ filePaths = project.getFileSystem().globSync(globs);
372
+ }
373
+ catch (err) {
374
+ this.logError(`Default source file glob failed: ${err instanceof Error ? err.message : String(err)}`);
375
+ return;
376
+ }
377
+ for (const filePath of filePaths) {
378
+ try {
379
+ project.addSourceFileAtPath(filePath);
380
+ }
381
+ catch {
382
+ // A file that disappeared between glob and read is not fatal.
383
+ }
384
+ }
385
+ this.log(`Added ${project.getSourceFiles().length} source files from default patterns`);
386
+ }
387
+ /**
388
+ * Log a message to stderr (stdout is reserved for JSON responses)
389
+ */
390
+ log(message) {
391
+ console.error(`[sidecar:project-loader] ${message}`);
392
+ }
393
+ /**
394
+ * Log an error message to stderr
395
+ */
396
+ logError(message) {
397
+ console.error(`[sidecar:project-loader:error] ${message}`);
398
+ }
399
+ }