@speclife/core 0.1.0

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 (69) hide show
  1. package/dist/adapters/claude-cli-adapter.d.ts +57 -0
  2. package/dist/adapters/claude-cli-adapter.d.ts.map +1 -0
  3. package/dist/adapters/claude-cli-adapter.js +161 -0
  4. package/dist/adapters/claude-cli-adapter.js.map +1 -0
  5. package/dist/adapters/claude-sdk-adapter.d.ts +49 -0
  6. package/dist/adapters/claude-sdk-adapter.d.ts.map +1 -0
  7. package/dist/adapters/claude-sdk-adapter.js +278 -0
  8. package/dist/adapters/claude-sdk-adapter.js.map +1 -0
  9. package/dist/adapters/cursor-adapter.d.ts +26 -0
  10. package/dist/adapters/cursor-adapter.d.ts.map +1 -0
  11. package/dist/adapters/cursor-adapter.js +54 -0
  12. package/dist/adapters/cursor-adapter.js.map +1 -0
  13. package/dist/adapters/environment-adapter.d.ts +153 -0
  14. package/dist/adapters/environment-adapter.d.ts.map +1 -0
  15. package/dist/adapters/environment-adapter.js +690 -0
  16. package/dist/adapters/environment-adapter.js.map +1 -0
  17. package/dist/adapters/git-adapter.d.ts +41 -0
  18. package/dist/adapters/git-adapter.d.ts.map +1 -0
  19. package/dist/adapters/git-adapter.js +95 -0
  20. package/dist/adapters/git-adapter.js.map +1 -0
  21. package/dist/adapters/github-adapter.d.ts +39 -0
  22. package/dist/adapters/github-adapter.d.ts.map +1 -0
  23. package/dist/adapters/github-adapter.js +129 -0
  24. package/dist/adapters/github-adapter.js.map +1 -0
  25. package/dist/adapters/index.d.ts +11 -0
  26. package/dist/adapters/index.d.ts.map +1 -0
  27. package/dist/adapters/index.js +13 -0
  28. package/dist/adapters/index.js.map +1 -0
  29. package/dist/adapters/openspec-adapter.d.ts +36 -0
  30. package/dist/adapters/openspec-adapter.d.ts.map +1 -0
  31. package/dist/adapters/openspec-adapter.js +182 -0
  32. package/dist/adapters/openspec-adapter.js.map +1 -0
  33. package/dist/config.d.ts +60 -0
  34. package/dist/config.d.ts.map +1 -0
  35. package/dist/config.js +112 -0
  36. package/dist/config.js.map +1 -0
  37. package/dist/index.d.ts +10 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +14 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/types.d.ts +105 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +28 -0
  44. package/dist/types.js.map +1 -0
  45. package/dist/workflows/implement.d.ts +28 -0
  46. package/dist/workflows/implement.d.ts.map +1 -0
  47. package/dist/workflows/implement.js +277 -0
  48. package/dist/workflows/implement.js.map +1 -0
  49. package/dist/workflows/index.d.ts +9 -0
  50. package/dist/workflows/index.d.ts.map +1 -0
  51. package/dist/workflows/index.js +9 -0
  52. package/dist/workflows/index.js.map +1 -0
  53. package/dist/workflows/init.d.ts +55 -0
  54. package/dist/workflows/init.d.ts.map +1 -0
  55. package/dist/workflows/init.js +195 -0
  56. package/dist/workflows/init.js.map +1 -0
  57. package/dist/workflows/merge.d.ts +40 -0
  58. package/dist/workflows/merge.d.ts.map +1 -0
  59. package/dist/workflows/merge.js +90 -0
  60. package/dist/workflows/merge.js.map +1 -0
  61. package/dist/workflows/status.d.ts +34 -0
  62. package/dist/workflows/status.d.ts.map +1 -0
  63. package/dist/workflows/status.js +53 -0
  64. package/dist/workflows/status.js.map +1 -0
  65. package/dist/workflows/submit.d.ts +44 -0
  66. package/dist/workflows/submit.d.ts.map +1 -0
  67. package/dist/workflows/submit.js +143 -0
  68. package/dist/workflows/submit.js.map +1 -0
  69. package/package.json +48 -0
@@ -0,0 +1,690 @@
1
+ /**
2
+ * Environment adapters for language-specific worktree setup
3
+ *
4
+ * These adapters handle dependency bootstrapping when creating worktrees,
5
+ * ensuring the worktree is ready to build without manual intervention.
6
+ */
7
+ import { existsSync, lstatSync, symlinkSync, rmSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
8
+ import { join, dirname, relative } from 'node:path';
9
+ import { mkdir } from 'node:fs/promises';
10
+ /**
11
+ * Create an environment registry with optional initial adapters
12
+ */
13
+ export function createEnvironmentRegistry(initialAdapters) {
14
+ const adapters = new Map();
15
+ // Register initial adapters
16
+ if (initialAdapters) {
17
+ for (const adapter of initialAdapters) {
18
+ adapters.set(adapter.name, adapter);
19
+ }
20
+ }
21
+ return {
22
+ register(adapter) {
23
+ adapters.set(adapter.name, adapter);
24
+ },
25
+ getAdapters() {
26
+ return Array.from(adapters.values())
27
+ .sort((a, b) => b.priority - a.priority);
28
+ },
29
+ getAdapter(name) {
30
+ return adapters.get(name);
31
+ },
32
+ async detectEnvironments(projectRoot) {
33
+ const results = [];
34
+ for (const adapter of this.getAdapters()) {
35
+ const detection = await adapter.detect(projectRoot);
36
+ if (detection) {
37
+ results.push(detection);
38
+ }
39
+ }
40
+ return results.sort((a, b) => b.confidence - a.confidence);
41
+ },
42
+ async bootstrapAll(worktreePath, sourceRoot, strategy, onProgress) {
43
+ const results = [];
44
+ const detected = await this.detectEnvironments(sourceRoot);
45
+ for (const detection of detected) {
46
+ const adapter = adapters.get(detection.name);
47
+ if (adapter) {
48
+ onProgress?.({
49
+ type: 'step_completed',
50
+ message: `Bootstrapping ${adapter.displayName} environment`,
51
+ data: { environment: detection.name, strategy },
52
+ });
53
+ const result = await adapter.bootstrap(worktreePath, sourceRoot, strategy, onProgress);
54
+ results.push(result);
55
+ }
56
+ }
57
+ return results;
58
+ },
59
+ async cleanupAll(worktreePath) {
60
+ for (const adapter of this.getAdapters()) {
61
+ await adapter.cleanup(worktreePath);
62
+ }
63
+ },
64
+ };
65
+ }
66
+ // ============================================================================
67
+ // Built-in Adapters
68
+ // ============================================================================
69
+ /**
70
+ * Node.js environment adapter
71
+ * Detects: package.json, package-lock.json, yarn.lock, pnpm-lock.yaml
72
+ * Bootstraps: symlinks node_modules from source to worktree
73
+ *
74
+ * For monorepos: Detects workspace packages and auto-patches tsconfig.json
75
+ * with paths mappings to ensure TypeScript resolves to worktree source.
76
+ */
77
+ export function createNodejsAdapter() {
78
+ return {
79
+ name: 'nodejs',
80
+ displayName: 'Node.js',
81
+ priority: 100,
82
+ async detect(projectRoot) {
83
+ const markerFiles = [];
84
+ let packageManager;
85
+ // Check for package.json (required)
86
+ if (existsSync(join(projectRoot, 'package.json'))) {
87
+ markerFiles.push('package.json');
88
+ }
89
+ else {
90
+ return null;
91
+ }
92
+ // Detect package manager from lock files
93
+ if (existsSync(join(projectRoot, 'pnpm-lock.yaml'))) {
94
+ packageManager = 'pnpm';
95
+ markerFiles.push('pnpm-lock.yaml');
96
+ }
97
+ else if (existsSync(join(projectRoot, 'yarn.lock'))) {
98
+ packageManager = 'yarn';
99
+ markerFiles.push('yarn.lock');
100
+ }
101
+ else if (existsSync(join(projectRoot, 'package-lock.json'))) {
102
+ packageManager = 'npm';
103
+ markerFiles.push('package-lock.json');
104
+ }
105
+ else {
106
+ packageManager = 'npm'; // Default to npm
107
+ }
108
+ return {
109
+ name: 'nodejs',
110
+ confidence: 1.0,
111
+ packageManager,
112
+ markerFiles,
113
+ };
114
+ },
115
+ async bootstrap(worktreePath, sourceRoot, strategy, onProgress) {
116
+ const sourceModules = join(sourceRoot, 'node_modules');
117
+ const targetModules = join(worktreePath, 'node_modules');
118
+ // Detect monorepo structure
119
+ const monorepo = detectMonorepo(sourceRoot);
120
+ if (strategy === 'none') {
121
+ return {
122
+ environment: 'nodejs',
123
+ strategy: 'none',
124
+ success: true,
125
+ message: 'Node.js bootstrap skipped (strategy: none)',
126
+ monorepo,
127
+ };
128
+ }
129
+ // Check if source node_modules exists
130
+ if (!existsSync(sourceModules)) {
131
+ return {
132
+ environment: 'nodejs',
133
+ strategy,
134
+ success: false,
135
+ message: 'Source node_modules not found. Run npm install in the main project first.',
136
+ monorepo,
137
+ };
138
+ }
139
+ if (strategy === 'symlink') {
140
+ // Remove existing target if present
141
+ if (existsSync(targetModules)) {
142
+ rmSync(targetModules, { recursive: true });
143
+ }
144
+ // Create parent directory if needed
145
+ await mkdir(dirname(targetModules), { recursive: true });
146
+ // Create symlink
147
+ symlinkSync(sourceModules, targetModules, 'junction');
148
+ onProgress?.({
149
+ type: 'file_written',
150
+ message: `Symlinked node_modules`,
151
+ data: { source: sourceModules, target: targetModules },
152
+ });
153
+ // For monorepos with symlink strategy, patch tsconfig to fix local package resolution
154
+ let tsconfigPatched = false;
155
+ if (monorepo.isMonorepo && monorepo.workspacePackages.length > 0) {
156
+ onProgress?.({
157
+ type: 'step_completed',
158
+ message: `Detected ${monorepo.type} monorepo with ${monorepo.workspacePackages.length} local packages`,
159
+ data: { monorepo },
160
+ });
161
+ tsconfigPatched = patchTsconfigForMonorepo(worktreePath, monorepo, onProgress);
162
+ if (tsconfigPatched) {
163
+ onProgress?.({
164
+ type: 'step_completed',
165
+ message: 'Patched tsconfig.json files with local package paths',
166
+ data: { packages: monorepo.workspacePackages.map(p => p.name) },
167
+ });
168
+ }
169
+ }
170
+ return {
171
+ environment: 'nodejs',
172
+ strategy: 'symlink',
173
+ success: true,
174
+ message: tsconfigPatched
175
+ ? `Symlinked node_modules and patched tsconfig for ${monorepo.workspacePackages.length} local packages`
176
+ : `Symlinked node_modules from ${sourceRoot}`,
177
+ path: targetModules,
178
+ tsconfigPatched,
179
+ monorepo,
180
+ };
181
+ }
182
+ // strategy === 'install'
183
+ // For install strategy, we'd run the package manager
184
+ // This requires executing shell commands which we'll leave as a TODO
185
+ return {
186
+ environment: 'nodejs',
187
+ strategy: 'install',
188
+ success: false,
189
+ message: 'Install strategy not yet implemented. Use symlink for now.',
190
+ monorepo,
191
+ };
192
+ },
193
+ async cleanup(worktreePath) {
194
+ const targetModules = join(worktreePath, 'node_modules');
195
+ // Only remove if it's a symlink (don't delete real node_modules)
196
+ if (existsSync(targetModules)) {
197
+ try {
198
+ const stats = lstatSync(targetModules);
199
+ if (stats.isSymbolicLink()) {
200
+ rmSync(targetModules);
201
+ }
202
+ }
203
+ catch {
204
+ // Ignore cleanup errors
205
+ }
206
+ }
207
+ },
208
+ };
209
+ }
210
+ /**
211
+ * Python environment adapter
212
+ * Detects: requirements.txt, pyproject.toml, setup.py, Pipfile
213
+ * Bootstraps: symlinks .venv from source to worktree
214
+ */
215
+ export function createPythonAdapter() {
216
+ return {
217
+ name: 'python',
218
+ displayName: 'Python',
219
+ priority: 90,
220
+ async detect(projectRoot) {
221
+ const markerFiles = [];
222
+ let packageManager;
223
+ // Check for Python project indicators
224
+ if (existsSync(join(projectRoot, 'pyproject.toml'))) {
225
+ markerFiles.push('pyproject.toml');
226
+ // Could be poetry, uv, or standard setuptools
227
+ if (existsSync(join(projectRoot, 'poetry.lock'))) {
228
+ packageManager = 'poetry';
229
+ markerFiles.push('poetry.lock');
230
+ }
231
+ else if (existsSync(join(projectRoot, 'uv.lock'))) {
232
+ packageManager = 'uv';
233
+ markerFiles.push('uv.lock');
234
+ }
235
+ else {
236
+ packageManager = 'pip';
237
+ }
238
+ }
239
+ else if (existsSync(join(projectRoot, 'requirements.txt'))) {
240
+ markerFiles.push('requirements.txt');
241
+ packageManager = 'pip';
242
+ }
243
+ else if (existsSync(join(projectRoot, 'Pipfile'))) {
244
+ markerFiles.push('Pipfile');
245
+ packageManager = 'pipenv';
246
+ }
247
+ else if (existsSync(join(projectRoot, 'setup.py'))) {
248
+ markerFiles.push('setup.py');
249
+ packageManager = 'pip';
250
+ }
251
+ else {
252
+ return null;
253
+ }
254
+ return {
255
+ name: 'python',
256
+ confidence: 1.0,
257
+ packageManager,
258
+ markerFiles,
259
+ };
260
+ },
261
+ async bootstrap(worktreePath, sourceRoot, strategy, onProgress) {
262
+ const sourceVenv = join(sourceRoot, '.venv');
263
+ const targetVenv = join(worktreePath, '.venv');
264
+ if (strategy === 'none') {
265
+ return {
266
+ environment: 'python',
267
+ strategy: 'none',
268
+ success: true,
269
+ message: 'Python bootstrap skipped (strategy: none)',
270
+ };
271
+ }
272
+ // Check if source .venv exists
273
+ if (!existsSync(sourceVenv)) {
274
+ return {
275
+ environment: 'python',
276
+ strategy,
277
+ success: true, // Not a failure - just no venv to symlink
278
+ message: 'No .venv found in source. Python environment not bootstrapped.',
279
+ };
280
+ }
281
+ if (strategy === 'symlink') {
282
+ // Remove existing target if present
283
+ if (existsSync(targetVenv)) {
284
+ rmSync(targetVenv, { recursive: true });
285
+ }
286
+ // Create parent directory if needed
287
+ await mkdir(dirname(targetVenv), { recursive: true });
288
+ // Create symlink
289
+ symlinkSync(sourceVenv, targetVenv, 'junction');
290
+ onProgress?.({
291
+ type: 'file_written',
292
+ message: `Symlinked .venv`,
293
+ data: { source: sourceVenv, target: targetVenv },
294
+ });
295
+ return {
296
+ environment: 'python',
297
+ strategy: 'symlink',
298
+ success: true,
299
+ message: `Symlinked .venv from ${sourceRoot}`,
300
+ path: targetVenv,
301
+ };
302
+ }
303
+ // strategy === 'install'
304
+ return {
305
+ environment: 'python',
306
+ strategy: 'install',
307
+ success: false,
308
+ message: 'Install strategy not yet implemented. Use symlink for now.',
309
+ };
310
+ },
311
+ async cleanup(worktreePath) {
312
+ const targetVenv = join(worktreePath, '.venv');
313
+ if (existsSync(targetVenv)) {
314
+ try {
315
+ const stats = lstatSync(targetVenv);
316
+ if (stats.isSymbolicLink()) {
317
+ rmSync(targetVenv);
318
+ }
319
+ }
320
+ catch {
321
+ // Ignore cleanup errors
322
+ }
323
+ }
324
+ },
325
+ };
326
+ }
327
+ /**
328
+ * Go environment adapter
329
+ * Detects: go.mod
330
+ * Bootstraps: no-op (Go uses global module cache)
331
+ */
332
+ export function createGoAdapter() {
333
+ return {
334
+ name: 'go',
335
+ displayName: 'Go',
336
+ priority: 80,
337
+ async detect(projectRoot) {
338
+ if (existsSync(join(projectRoot, 'go.mod'))) {
339
+ return {
340
+ name: 'go',
341
+ confidence: 1.0,
342
+ packageManager: 'go',
343
+ markerFiles: ['go.mod'],
344
+ };
345
+ }
346
+ return null;
347
+ },
348
+ async bootstrap(_worktreePath, _sourceRoot, strategy, _onProgress) {
349
+ // Go uses a global module cache, no per-project setup needed
350
+ return {
351
+ environment: 'go',
352
+ strategy,
353
+ success: true,
354
+ message: 'Go uses global module cache. No worktree setup needed.',
355
+ };
356
+ },
357
+ async cleanup(_worktreePath) {
358
+ // Nothing to clean up for Go
359
+ },
360
+ };
361
+ }
362
+ /**
363
+ * Rust environment adapter
364
+ * Detects: Cargo.toml
365
+ * Bootstraps: no-op (Rust uses global cargo cache)
366
+ */
367
+ export function createRustAdapter() {
368
+ return {
369
+ name: 'rust',
370
+ displayName: 'Rust',
371
+ priority: 80,
372
+ async detect(projectRoot) {
373
+ if (existsSync(join(projectRoot, 'Cargo.toml'))) {
374
+ return {
375
+ name: 'rust',
376
+ confidence: 1.0,
377
+ packageManager: 'cargo',
378
+ markerFiles: ['Cargo.toml'],
379
+ };
380
+ }
381
+ return null;
382
+ },
383
+ async bootstrap(_worktreePath, _sourceRoot, strategy, _onProgress) {
384
+ // Rust uses a global cargo cache, no per-project setup needed
385
+ return {
386
+ environment: 'rust',
387
+ strategy,
388
+ success: true,
389
+ message: 'Rust uses global cargo cache. No worktree setup needed.',
390
+ };
391
+ },
392
+ async cleanup(_worktreePath) {
393
+ // Nothing to clean up for Rust
394
+ },
395
+ };
396
+ }
397
+ /**
398
+ * Create the default environment registry with all built-in adapters
399
+ */
400
+ export function createDefaultEnvironmentRegistry() {
401
+ return createEnvironmentRegistry([
402
+ createNodejsAdapter(),
403
+ createPythonAdapter(),
404
+ createGoAdapter(),
405
+ createRustAdapter(),
406
+ ]);
407
+ }
408
+ // ============================================================================
409
+ // Monorepo Detection and TypeScript Patching
410
+ // ============================================================================
411
+ /**
412
+ * Detect if a project is a monorepo and identify workspace packages
413
+ */
414
+ export function detectMonorepo(projectRoot) {
415
+ const packageJsonPath = join(projectRoot, 'package.json');
416
+ if (!existsSync(packageJsonPath)) {
417
+ return { isMonorepo: false, rootPackageJson: packageJsonPath, workspacePackages: [] };
418
+ }
419
+ try {
420
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
421
+ // Check for npm/yarn workspaces
422
+ if (packageJson.workspaces) {
423
+ const workspacePatterns = Array.isArray(packageJson.workspaces)
424
+ ? packageJson.workspaces
425
+ : packageJson.workspaces.packages ?? [];
426
+ const packages = resolveWorkspacePackages(projectRoot, workspacePatterns);
427
+ return {
428
+ isMonorepo: true,
429
+ type: 'npm-workspaces',
430
+ rootPackageJson: packageJsonPath,
431
+ workspacePackages: packages,
432
+ };
433
+ }
434
+ // Check for pnpm workspaces
435
+ const pnpmWorkspacePath = join(projectRoot, 'pnpm-workspace.yaml');
436
+ if (existsSync(pnpmWorkspacePath)) {
437
+ // Simple YAML parsing for packages array
438
+ const content = readFileSync(pnpmWorkspacePath, 'utf-8');
439
+ const patterns = parsePnpmWorkspaceYaml(content);
440
+ const packages = resolveWorkspacePackages(projectRoot, patterns);
441
+ return {
442
+ isMonorepo: true,
443
+ type: 'pnpm-workspaces',
444
+ rootPackageJson: packageJsonPath,
445
+ workspacePackages: packages,
446
+ };
447
+ }
448
+ // Check for lerna
449
+ const lernaPath = join(projectRoot, 'lerna.json');
450
+ if (existsSync(lernaPath)) {
451
+ const lernaConfig = JSON.parse(readFileSync(lernaPath, 'utf-8'));
452
+ const patterns = lernaConfig.packages ?? ['packages/*'];
453
+ const packages = resolveWorkspacePackages(projectRoot, patterns);
454
+ return {
455
+ isMonorepo: true,
456
+ type: 'lerna',
457
+ rootPackageJson: packageJsonPath,
458
+ workspacePackages: packages,
459
+ };
460
+ }
461
+ }
462
+ catch {
463
+ // Ignore JSON parse errors
464
+ }
465
+ return { isMonorepo: false, rootPackageJson: packageJsonPath, workspacePackages: [] };
466
+ }
467
+ /**
468
+ * Parse pnpm-workspace.yaml to extract package patterns
469
+ * Simple parser that handles common cases
470
+ */
471
+ function parsePnpmWorkspaceYaml(content) {
472
+ const patterns = [];
473
+ const lines = content.split('\n');
474
+ let inPackages = false;
475
+ for (const line of lines) {
476
+ const trimmed = line.trim();
477
+ if (trimmed === 'packages:') {
478
+ inPackages = true;
479
+ continue;
480
+ }
481
+ if (inPackages) {
482
+ if (trimmed.startsWith('-')) {
483
+ // Extract pattern from "- packages/*" or "- 'packages/*'"
484
+ let pattern = trimmed.slice(1).trim();
485
+ pattern = pattern.replace(/^['"]|['"]$/g, '');
486
+ if (pattern) {
487
+ patterns.push(pattern);
488
+ }
489
+ }
490
+ else if (!trimmed.startsWith('#') && trimmed.length > 0 && !trimmed.startsWith('-')) {
491
+ // End of packages section
492
+ break;
493
+ }
494
+ }
495
+ }
496
+ return patterns;
497
+ }
498
+ /**
499
+ * Resolve workspace patterns to actual package directories
500
+ */
501
+ function resolveWorkspacePackages(projectRoot, patterns) {
502
+ const packages = [];
503
+ for (const pattern of patterns) {
504
+ // Handle simple glob patterns like "packages/*"
505
+ if (pattern.endsWith('/*')) {
506
+ const baseDir = pattern.slice(0, -2);
507
+ const fullPath = join(projectRoot, baseDir);
508
+ if (existsSync(fullPath)) {
509
+ try {
510
+ const entries = readdirSync(fullPath, { withFileTypes: true });
511
+ for (const entry of entries) {
512
+ if (entry.isDirectory()) {
513
+ const pkgPath = join(fullPath, entry.name);
514
+ const pkgJsonPath = join(pkgPath, 'package.json');
515
+ if (existsSync(pkgJsonPath)) {
516
+ try {
517
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
518
+ const relativePath = relative(projectRoot, pkgPath);
519
+ // Find TypeScript entry point
520
+ const entryPoint = findTypeScriptEntryPoint(pkgPath);
521
+ packages.push({
522
+ name: pkgJson.name,
523
+ path: relativePath,
524
+ absolutePath: pkgPath,
525
+ entryPoint,
526
+ });
527
+ }
528
+ catch {
529
+ // Skip packages with invalid package.json
530
+ }
531
+ }
532
+ }
533
+ }
534
+ }
535
+ catch {
536
+ // Ignore directory read errors
537
+ }
538
+ }
539
+ }
540
+ else if (!pattern.includes('*')) {
541
+ // Direct path like "packages/core"
542
+ const fullPath = join(projectRoot, pattern);
543
+ const pkgJsonPath = join(fullPath, 'package.json');
544
+ if (existsSync(pkgJsonPath)) {
545
+ try {
546
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
547
+ const entryPoint = findTypeScriptEntryPoint(fullPath);
548
+ packages.push({
549
+ name: pkgJson.name,
550
+ path: pattern,
551
+ absolutePath: fullPath,
552
+ entryPoint,
553
+ });
554
+ }
555
+ catch {
556
+ // Skip packages with invalid package.json
557
+ }
558
+ }
559
+ }
560
+ }
561
+ return packages;
562
+ }
563
+ /**
564
+ * Find the TypeScript entry point for a package
565
+ */
566
+ function findTypeScriptEntryPoint(packagePath) {
567
+ // Common entry points in order of preference
568
+ const candidates = [
569
+ 'src/index.ts',
570
+ 'src/index.tsx',
571
+ 'lib/index.ts',
572
+ 'index.ts',
573
+ ];
574
+ for (const candidate of candidates) {
575
+ if (existsSync(join(packagePath, candidate))) {
576
+ return candidate;
577
+ }
578
+ }
579
+ return undefined;
580
+ }
581
+ /**
582
+ * Patch tsconfig files in a worktree to add paths for local workspace packages
583
+ * This ensures TypeScript resolves to worktree source, not the symlinked main repo
584
+ */
585
+ export function patchTsconfigForMonorepo(worktreePath, monorepo, onProgress) {
586
+ if (!monorepo.isMonorepo || monorepo.workspacePackages.length === 0) {
587
+ return false;
588
+ }
589
+ let patched = false;
590
+ // Find all tsconfig files in the worktree
591
+ const tsconfigPaths = findTsconfigFiles(worktreePath);
592
+ for (const tsconfigPath of tsconfigPaths) {
593
+ try {
594
+ const pathedPackages = patchSingleTsconfig(tsconfigPath, worktreePath, monorepo);
595
+ if (pathedPackages > 0) {
596
+ patched = true;
597
+ onProgress?.({
598
+ type: 'file_written',
599
+ message: `Patched ${tsconfigPath} with ${pathedPackages} local package paths`,
600
+ data: { tsconfigPath, packageCount: pathedPackages },
601
+ });
602
+ }
603
+ }
604
+ catch {
605
+ // Skip files that can't be patched
606
+ }
607
+ }
608
+ return patched;
609
+ }
610
+ /**
611
+ * Find all tsconfig.json files in a directory tree
612
+ */
613
+ function findTsconfigFiles(rootPath) {
614
+ const tsconfigs = [];
615
+ function scan(dirPath, depth) {
616
+ if (depth > 5)
617
+ return; // Limit depth to avoid traversing too deep
618
+ try {
619
+ const entries = readdirSync(dirPath, { withFileTypes: true });
620
+ for (const entry of entries) {
621
+ const fullPath = join(dirPath, entry.name);
622
+ if (entry.isFile() && entry.name === 'tsconfig.json') {
623
+ tsconfigs.push(fullPath);
624
+ }
625
+ else if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
626
+ scan(fullPath, depth + 1);
627
+ }
628
+ }
629
+ }
630
+ catch {
631
+ // Ignore directory read errors
632
+ }
633
+ }
634
+ scan(rootPath, 0);
635
+ return tsconfigs;
636
+ }
637
+ /**
638
+ * Patch a single tsconfig.json file with paths for local packages
639
+ */
640
+ function patchSingleTsconfig(tsconfigPath, worktreePath, monorepo) {
641
+ const content = readFileSync(tsconfigPath, 'utf-8');
642
+ // Parse tsconfig (handle comments by stripping them)
643
+ const jsonContent = stripJsonComments(content);
644
+ const tsconfig = JSON.parse(jsonContent);
645
+ // Ensure compilerOptions exists
646
+ if (!tsconfig.compilerOptions) {
647
+ tsconfig.compilerOptions = {};
648
+ }
649
+ // Calculate relative path from tsconfig location to worktree root
650
+ const tsconfigDir = dirname(tsconfigPath);
651
+ const relativeToRoot = relative(tsconfigDir, worktreePath);
652
+ // Set baseUrl if not already set
653
+ if (!tsconfig.compilerOptions.baseUrl) {
654
+ tsconfig.compilerOptions.baseUrl = '.';
655
+ }
656
+ // Build paths mapping for local packages
657
+ if (!tsconfig.compilerOptions.paths) {
658
+ tsconfig.compilerOptions.paths = {};
659
+ }
660
+ let addedCount = 0;
661
+ for (const pkg of monorepo.workspacePackages) {
662
+ // Skip if path already exists for this package
663
+ if (tsconfig.compilerOptions.paths[pkg.name]) {
664
+ continue;
665
+ }
666
+ // Calculate relative path from tsconfig to package source
667
+ const packageSrcPath = pkg.entryPoint
668
+ ? join(relativeToRoot, pkg.path, pkg.entryPoint)
669
+ : join(relativeToRoot, pkg.path, 'src/index.ts');
670
+ // Add path mapping
671
+ tsconfig.compilerOptions.paths[pkg.name] = [packageSrcPath];
672
+ addedCount++;
673
+ }
674
+ if (addedCount > 0) {
675
+ // Write back with pretty formatting
676
+ writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + '\n', 'utf-8');
677
+ }
678
+ return addedCount;
679
+ }
680
+ /**
681
+ * Strip JSON comments for parsing (single-line // and multi-line block comments)
682
+ */
683
+ function stripJsonComments(content) {
684
+ // Remove single-line comments
685
+ let result = content.replace(/\/\/.*$/gm, '');
686
+ // Remove multi-line comments
687
+ result = result.replace(/\/\*[\s\S]*?\*\//g, '');
688
+ return result;
689
+ }
690
+ //# sourceMappingURL=environment-adapter.js.map