@principal-ai/codebase-composition 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 (129) hide show
  1. package/README.md +67 -0
  2. package/dist/index.d.ts +9 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +23 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/modules/DependencyLayerModule.d.ts +44 -0
  7. package/dist/modules/DependencyLayerModule.d.ts.map +1 -0
  8. package/dist/modules/DependencyLayerModule.js +286 -0
  9. package/dist/modules/DependencyLayerModule.js.map +1 -0
  10. package/dist/modules/FileSystemModule.d.ts +30 -0
  11. package/dist/modules/FileSystemModule.d.ts.map +1 -0
  12. package/dist/modules/FileSystemModule.js +46 -0
  13. package/dist/modules/FileSystemModule.js.map +1 -0
  14. package/dist/modules/FileTypeLayerModule.d.ts +34 -0
  15. package/dist/modules/FileTypeLayerModule.d.ts.map +1 -0
  16. package/dist/modules/FileTypeLayerModule.js +169 -0
  17. package/dist/modules/FileTypeLayerModule.js.map +1 -0
  18. package/dist/modules/FrameworkLayerModule.d.ts +22 -0
  19. package/dist/modules/FrameworkLayerModule.d.ts.map +1 -0
  20. package/dist/modules/FrameworkLayerModule.js +388 -0
  21. package/dist/modules/FrameworkLayerModule.js.map +1 -0
  22. package/dist/modules/PackageLayerModule.d.ts +23 -0
  23. package/dist/modules/PackageLayerModule.d.ts.map +1 -0
  24. package/dist/modules/PackageLayerModule.js +810 -0
  25. package/dist/modules/PackageLayerModule.js.map +1 -0
  26. package/dist/modules/TypeExtractionModule.d.ts +37 -0
  27. package/dist/modules/TypeExtractionModule.d.ts.map +1 -0
  28. package/dist/modules/TypeExtractionModule.js +180 -0
  29. package/dist/modules/TypeExtractionModule.js.map +1 -0
  30. package/dist/modules/VersionControlLayerModule.d.ts +10 -0
  31. package/dist/modules/VersionControlLayerModule.d.ts.map +1 -0
  32. package/dist/modules/VersionControlLayerModule.js +32 -0
  33. package/dist/modules/VersionControlLayerModule.js.map +1 -0
  34. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.d.ts +4 -0
  35. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.d.ts.map +1 -0
  36. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.js +7 -0
  37. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.js.map +1 -0
  38. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.d.ts +15 -0
  39. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.d.ts.map +1 -0
  40. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.js +3 -0
  41. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.js.map +1 -0
  42. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.d.ts +34 -0
  43. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.d.ts.map +1 -0
  44. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.js +23 -0
  45. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.js.map +1 -0
  46. package/dist/modules/__fixtures__/typescript-packages/simple-types/index.d.ts +39 -0
  47. package/dist/modules/__fixtures__/typescript-packages/simple-types/index.d.ts.map +1 -0
  48. package/dist/modules/__fixtures__/typescript-packages/simple-types/index.js +39 -0
  49. package/dist/modules/__fixtures__/typescript-packages/simple-types/index.js.map +1 -0
  50. package/dist/modules/extractors/TypeScriptExtractor.d.ts +18 -0
  51. package/dist/modules/extractors/TypeScriptExtractor.d.ts.map +1 -0
  52. package/dist/modules/extractors/TypeScriptExtractor.js +361 -0
  53. package/dist/modules/extractors/TypeScriptExtractor.js.map +1 -0
  54. package/dist/modules/index.d.ts +13 -0
  55. package/dist/modules/index.d.ts.map +1 -0
  56. package/dist/modules/index.js +21 -0
  57. package/dist/modules/index.js.map +1 -0
  58. package/dist/providers/GitVersionControlProvider.d.ts +108 -0
  59. package/dist/providers/GitVersionControlProvider.d.ts.map +1 -0
  60. package/dist/providers/GitVersionControlProvider.js +380 -0
  61. package/dist/providers/GitVersionControlProvider.js.map +1 -0
  62. package/dist/providers/PackageManagerApiProvider.d.ts +78 -0
  63. package/dist/providers/PackageManagerApiProvider.d.ts.map +1 -0
  64. package/dist/providers/PackageManagerApiProvider.js +14 -0
  65. package/dist/providers/PackageManagerApiProvider.js.map +1 -0
  66. package/dist/providers/index.d.ts +4 -0
  67. package/dist/providers/index.d.ts.map +1 -0
  68. package/dist/providers/index.js +10 -0
  69. package/dist/providers/index.js.map +1 -0
  70. package/dist/services/FilesystemService.d.ts +59 -0
  71. package/dist/services/FilesystemService.d.ts.map +1 -0
  72. package/dist/services/FilesystemService.js +391 -0
  73. package/dist/services/FilesystemService.js.map +1 -0
  74. package/dist/services/index.d.ts +2 -0
  75. package/dist/services/index.d.ts.map +1 -0
  76. package/dist/services/index.js +7 -0
  77. package/dist/services/index.js.map +1 -0
  78. package/dist/types/file-system.d.ts +7 -0
  79. package/dist/types/file-system.d.ts.map +1 -0
  80. package/dist/types/file-system.js +7 -0
  81. package/dist/types/file-system.js.map +1 -0
  82. package/dist/types/index.d.ts +4 -0
  83. package/dist/types/index.d.ts.map +1 -0
  84. package/dist/types/index.js +3 -0
  85. package/dist/types/index.js.map +1 -0
  86. package/dist/types/layer-types.d.ts +187 -0
  87. package/dist/types/layer-types.d.ts.map +1 -0
  88. package/dist/types/layer-types.js +7 -0
  89. package/dist/types/layer-types.js.map +1 -0
  90. package/dist/types/version-control-layer.d.ts +53 -0
  91. package/dist/types/version-control-layer.d.ts.map +1 -0
  92. package/dist/types/version-control-layer.js +3 -0
  93. package/dist/types/version-control-layer.js.map +1 -0
  94. package/dist/types/workspace-boundaries.d.ts +17 -0
  95. package/dist/types/workspace-boundaries.d.ts.map +1 -0
  96. package/dist/types/workspace-boundaries.js +7 -0
  97. package/dist/types/workspace-boundaries.js.map +1 -0
  98. package/package.json +42 -0
  99. package/src/index.ts +62 -0
  100. package/src/modules/DependencyLayerModule.ts +329 -0
  101. package/src/modules/FileSystemModule.ts +65 -0
  102. package/src/modules/FileTypeLayerModule.ts +199 -0
  103. package/src/modules/FrameworkLayerModule.ts +437 -0
  104. package/src/modules/PackageLayerModule.ts +979 -0
  105. package/src/modules/TypeExtractionModule.test.ts +340 -0
  106. package/src/modules/TypeExtractionModule.ts +180 -0
  107. package/src/modules/VersionControlLayerModule.ts +31 -0
  108. package/src/modules/__fixtures__/typescript-packages/complex-types/package.json +6 -0
  109. package/src/modules/__fixtures__/typescript-packages/complex-types/src/index.ts +6 -0
  110. package/src/modules/__fixtures__/typescript-packages/complex-types/src/models/category.ts +15 -0
  111. package/src/modules/__fixtures__/typescript-packages/complex-types/src/models/product.ts +48 -0
  112. package/src/modules/__fixtures__/typescript-packages/javascript-only/index.js +18 -0
  113. package/src/modules/__fixtures__/typescript-packages/javascript-only/package.json +5 -0
  114. package/src/modules/__fixtures__/typescript-packages/simple-types/index.ts +53 -0
  115. package/src/modules/__fixtures__/typescript-packages/simple-types/package.json +6 -0
  116. package/src/modules/extractors/README.md +55 -0
  117. package/src/modules/extractors/TypeScriptExtractor.ts +409 -0
  118. package/src/modules/index.ts +13 -0
  119. package/src/providers/GitVersionControlProvider.ts +500 -0
  120. package/src/providers/PackageManagerApiProvider.ts +108 -0
  121. package/src/providers/README.md +88 -0
  122. package/src/providers/index.ts +17 -0
  123. package/src/services/FilesystemService.ts +530 -0
  124. package/src/services/index.ts +2 -0
  125. package/src/types/file-system.ts +11 -0
  126. package/src/types/index.ts +24 -0
  127. package/src/types/layer-types.ts +264 -0
  128. package/src/types/version-control-layer.ts +87 -0
  129. package/src/types/workspace-boundaries.ts +17 -0
@@ -0,0 +1,500 @@
1
+ import ignore from 'ignore';
2
+ import type { Ignore } from 'ignore';
3
+
4
+ import {
5
+ VersionControlProvider,
6
+ VersionControlLayer,
7
+ VersionControlIgnorePattern,
8
+ } from '../types/version-control-layer';
9
+
10
+ // Minimal file system interface needed by this provider and electron-react
11
+ export interface FileSystemAdapter {
12
+ readFile(path: string): Promise<{ content: string } | null>;
13
+ fileExists?(path: string): Promise<boolean>;
14
+ isDirectory?(path: string): Promise<boolean>;
15
+ readDirectory?(path: string): Promise<string[]>;
16
+ getFileStats?(path: string): Promise<{
17
+ size: number;
18
+ isDirectory: boolean;
19
+ lastModified: Date;
20
+ } | null>;
21
+
22
+ /**
23
+ * Fast filtered file tree building - adapters must implement this efficiently
24
+ * for their environment (e.g., using fdir in Node.js, batch APIs for remote, etc.)
25
+ * @returns paths: Array of relative paths from directoryPath (directories end with '/')
26
+ * @returns stats: Optional map of path to file stats for performance
27
+ */
28
+ buildFilteredFileTree(
29
+ directoryPath: string,
30
+ patterns?: string[], // Array of gitignore-style patterns to exclude
31
+ sourceDirectory?: string, // Optional source directory for scoped filtering
32
+ ): Promise<{
33
+ paths: string[];
34
+ stats?: Map<
35
+ string,
36
+ {
37
+ size: number;
38
+ isDirectory: boolean;
39
+ lastModified: Date;
40
+ }
41
+ >;
42
+ }>;
43
+ }
44
+
45
+ // Git interface needed by this provider and electron-react
46
+ export interface GitAdapter {
47
+ getCurrentBranch?(repoPath: string): Promise<string | null>;
48
+ getCurrentCommit?(repoPath: string): Promise<string | null>;
49
+ getRemotes?(
50
+ repoPath: string,
51
+ ): Promise<Array<{ name: string; url: string; type?: 'push' | 'fetch' | 'both' }>>;
52
+ getStatus?(
53
+ repoPath: string,
54
+ ): Promise<{ modified: string[]; untracked: string[]; staged: string[] }>;
55
+
56
+ // Extended methods for electron-react
57
+ detectRepository?(path: string): Promise<{
58
+ isGitRepository: boolean;
59
+ currentBranch?: string;
60
+ owner?: string;
61
+ repo?: string;
62
+ } | null>;
63
+ watchGitRepository?(path: string): Promise<boolean>;
64
+ stopWatchingGit?(): Promise<void>;
65
+ onGitStatusChange?(
66
+ callback: (data: {
67
+ changedFiles: Array<{
68
+ path: string;
69
+ status: 'added' | 'modified' | 'deleted' | 'renamed';
70
+ lastModified?: Date;
71
+ }>;
72
+ }) => void,
73
+ ): () => void;
74
+ }
75
+
76
+ // Shell adapter interface for electron-react
77
+ export interface ShellAdapter {
78
+ openExternal(url: string): Promise<void>;
79
+ }
80
+
81
+ // Path utilities that work in both Node and browser environments
82
+ const path = {
83
+ join: (...parts: string[]) => {
84
+ const joined = parts.filter(p => p).join('/');
85
+ return joined.replace(/\/+/g, '/');
86
+ },
87
+ dirname: (p: string) => {
88
+ const parts = p.split('/');
89
+ parts.pop();
90
+ return parts.join('/') || '/';
91
+ },
92
+ relative: (from: string, to: string) => {
93
+ if (!from || from === '.') return to;
94
+ if (to.startsWith(from + '/')) return to.slice(from.length + 1);
95
+ if (to === from) return '.';
96
+ return to;
97
+ },
98
+ basename: (p: string) => {
99
+ const parts = p.split('/');
100
+ return parts[parts.length - 1] || '';
101
+ },
102
+ };
103
+
104
+ /**
105
+ * Git implementation of VersionControlProvider for core library
106
+ * Handles .gitignore parsing and Git-specific operations
107
+ */
108
+ export class GitVersionControlProvider implements VersionControlProvider {
109
+ constructor(
110
+ private fileSystemAdapter: FileSystemAdapter,
111
+ private gitAdapter?: GitAdapter,
112
+ ) {}
113
+
114
+ async detect(directoryPath: string): Promise<boolean> {
115
+ // Check if .git directory exists
116
+ let currentPath = directoryPath;
117
+
118
+ while (currentPath && currentPath !== '/' && currentPath !== '.') {
119
+ const gitPath = path.join(currentPath, '.git');
120
+
121
+ // Try using isDirectory if available, otherwise check for .git/HEAD file
122
+ if (this.fileSystemAdapter.isDirectory) {
123
+ if (await this.fileSystemAdapter.isDirectory(gitPath)) {
124
+ return true;
125
+ }
126
+ } else if (this.fileSystemAdapter.fileExists) {
127
+ // Fallback: check for .git/HEAD file
128
+ const headPath = path.join(gitPath, 'HEAD');
129
+ if (await this.fileSystemAdapter.fileExists(headPath)) {
130
+ return true;
131
+ }
132
+ }
133
+
134
+ // Move up one directory
135
+ const parent = path.dirname(currentPath);
136
+ if (parent === currentPath) break; // Reached root
137
+ currentPath = parent;
138
+ }
139
+
140
+ return false;
141
+ }
142
+
143
+ async getRepositoryRoot(directoryPath: string): Promise<string | null> {
144
+ let currentPath = directoryPath;
145
+
146
+ while (currentPath && currentPath !== '/' && currentPath !== '.') {
147
+ const gitPath = path.join(currentPath, '.git');
148
+
149
+ // Check using same logic as detect
150
+ let isGitRepo = false;
151
+ if (this.fileSystemAdapter.isDirectory) {
152
+ isGitRepo = await this.fileSystemAdapter.isDirectory(gitPath);
153
+ } else if (this.fileSystemAdapter.fileExists) {
154
+ const headPath = path.join(gitPath, 'HEAD');
155
+ isGitRepo = await this.fileSystemAdapter.fileExists(headPath);
156
+ }
157
+
158
+ if (isGitRepo) {
159
+ return currentPath;
160
+ }
161
+
162
+ // Move up one directory
163
+ const parent = path.dirname(currentPath);
164
+ if (parent === currentPath) break; // Reached root
165
+ currentPath = parent;
166
+ }
167
+
168
+ return null;
169
+ }
170
+
171
+ async createLayer(repositoryRoot: string): Promise<VersionControlLayer> {
172
+ // PERFORMANCE OPTIMIZATION: Return minimal git metadata since buildFilteredFileTree
173
+ // now handles all filtering. This avoids expensive file reads for .git/HEAD, .git/config, etc.
174
+
175
+ const ignorePatterns = await this.extractIgnorePatterns(repositoryRoot);
176
+
177
+ // Return default values instead of reading git files
178
+ const currentBranch = 'main'; // Default branch name
179
+ const currentRevision = 'unknown'; // Skip expensive git file reads
180
+ const remotes: Array<{ name: string; url: string; type: 'push' | 'fetch' | 'both' }> = []; // Empty remotes
181
+
182
+ return {
183
+ id: `vcs-git-${repositoryRoot.replace(/[^a-zA-Z0-9]/g, '-')}`,
184
+ name: 'Git Version Control',
185
+ type: 'version-control',
186
+ enabled: true,
187
+ workspaceRoot: repositoryRoot,
188
+ vcsData: {
189
+ system: 'git',
190
+ repositoryRoot,
191
+ currentBranch: currentBranch || 'unknown',
192
+ currentRevision: currentRevision || 'unknown',
193
+ remotes: remotes || [],
194
+ ignorePatterns,
195
+ },
196
+ derivedFrom: {
197
+ fileSets: [
198
+ {
199
+ id: 'git-files',
200
+ name: '.git directory and .gitignore files',
201
+ patterns: [
202
+ { type: 'glob', pattern: '.git/**' },
203
+ { type: 'glob', pattern: '**/.gitignore' },
204
+ ],
205
+ },
206
+ ],
207
+ derivationType: 'content',
208
+ description: 'Git version control metadata and ignore patterns',
209
+ },
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Extract all .gitignore patterns from the repository
215
+ */
216
+ async extractIgnorePatterns(repositoryRoot: string): Promise<VersionControlIgnorePattern[]> {
217
+ const patterns: VersionControlIgnorePattern[] = [];
218
+
219
+ // Always add .git directory as ignored
220
+ patterns.push({
221
+ pattern: '.git',
222
+ type: 'exact',
223
+ source: 'implicit',
224
+ scope: 'repository',
225
+ negated: false,
226
+ description: 'Git metadata directory',
227
+ });
228
+
229
+ // Parse root .gitignore
230
+ const rootGitignorePath = path.join(repositoryRoot, '.gitignore');
231
+ const rootPatterns = await this.parseGitignoreFile(rootGitignorePath, repositoryRoot);
232
+ patterns.push(...rootPatterns);
233
+
234
+ // Find and parse subdirectory .gitignore files
235
+ if (this.fileSystemAdapter.readDirectory) {
236
+ const subdirPatterns = await this.findAndParseGitignoreFiles(repositoryRoot, repositoryRoot);
237
+ patterns.push(...subdirPatterns);
238
+ }
239
+
240
+ return patterns;
241
+ }
242
+
243
+ /**
244
+ * Recursively find and parse .gitignore files
245
+ */
246
+ private async findAndParseGitignoreFiles(
247
+ currentPath: string,
248
+ repositoryRoot: string,
249
+ ): Promise<VersionControlIgnorePattern[]> {
250
+ const patterns: VersionControlIgnorePattern[] = [];
251
+
252
+ if (!this.fileSystemAdapter.readDirectory) {
253
+ return patterns;
254
+ }
255
+
256
+ try {
257
+ const entries = await this.fileSystemAdapter.readDirectory(currentPath);
258
+
259
+ for (const entry of entries) {
260
+ const fullPath = path.join(currentPath, entry);
261
+
262
+ // Skip .git directory
263
+ if (entry === '.git') continue;
264
+
265
+ // Check for .gitignore file
266
+ if (entry === '.gitignore' && currentPath !== repositoryRoot) {
267
+ const gitignorePatterns = await this.parseGitignoreFile(fullPath, repositoryRoot);
268
+ patterns.push(...gitignorePatterns);
269
+ }
270
+
271
+ // Recursively check subdirectories
272
+ if (
273
+ this.fileSystemAdapter.isDirectory &&
274
+ (await this.fileSystemAdapter.isDirectory(fullPath))
275
+ ) {
276
+ const subdirPatterns = await this.findAndParseGitignoreFiles(fullPath, repositoryRoot);
277
+ patterns.push(...subdirPatterns);
278
+ }
279
+ }
280
+ } catch (error) {
281
+ console.warn(`Error reading directory ${currentPath}:`, error);
282
+ }
283
+
284
+ return patterns;
285
+ }
286
+
287
+ /**
288
+ * Parse a .gitignore file and return patterns using the ignore library
289
+ */
290
+ private async parseGitignoreFile(
291
+ gitignorePath: string,
292
+ repositoryRoot: string,
293
+ ): Promise<VersionControlIgnorePattern[]> {
294
+ const patterns: VersionControlIgnorePattern[] = [];
295
+
296
+ try {
297
+ const result = await this.fileSystemAdapter.readFile(gitignorePath);
298
+ if (!result) return patterns;
299
+
300
+ const content = result.content;
301
+ const lines = content.split('\n');
302
+ const relativePath = path.relative(repositoryRoot, path.dirname(gitignorePath));
303
+ // For root gitignore, use "./" prefix to show it's in the current directory
304
+ const source = relativePath === '.' ? './.gitignore' : `${relativePath}/.gitignore`;
305
+
306
+ // Use ignore library to validate patterns as we process them
307
+ const ig = ignore();
308
+
309
+ for (const line of lines) {
310
+ const trimmed = line.trim();
311
+
312
+ // Skip empty lines and comments
313
+ if (!trimmed || trimmed.startsWith('#')) continue;
314
+
315
+ // Handle negation patterns
316
+ const negated = trimmed.startsWith('!');
317
+ const pattern = negated ? trimmed.slice(1) : trimmed;
318
+
319
+ // Validate pattern using ignore library
320
+ try {
321
+ // Test if the pattern is valid by trying to add it to ignore
322
+ const testIgnore = ignore();
323
+ testIgnore.add([trimmed]); // Add the original line (with ! if present)
324
+
325
+ // Determine pattern type based on content
326
+ const hasGlobChars = /[*?[\]]/.test(pattern);
327
+ const hasSlash = pattern.includes('/');
328
+ const patternType = hasGlobChars || hasSlash ? 'glob' : 'exact';
329
+
330
+ // Determine scope
331
+ const scope = source === '.gitignore' ? 'repository' : 'directory';
332
+
333
+ patterns.push({
334
+ pattern: pattern,
335
+ type: patternType,
336
+ source,
337
+ scope,
338
+ negated,
339
+ description: `From ${source}`,
340
+ });
341
+
342
+ // Add valid patterns to our ignore instance for potential future use
343
+ ig.add([trimmed]);
344
+ } catch (err) {
345
+ console.warn(`Invalid gitignore pattern: ${trimmed} in ${source}`, err);
346
+ }
347
+ }
348
+ } catch (error) {
349
+ console.warn(`Error reading gitignore file ${gitignorePath}:`, error);
350
+ }
351
+
352
+ return patterns;
353
+ }
354
+
355
+ /**
356
+ * Create an ignore instance from patterns for efficient filtering
357
+ * This can be used by FilesystemService or other components
358
+ */
359
+ createIgnoreFromPatterns(patterns: VersionControlIgnorePattern[]): Ignore | null {
360
+ const ig = ignore();
361
+
362
+ // Convert patterns back to gitignore format
363
+ const gitignoreLines = patterns.map(p => (p.negated ? `!${p.pattern}` : p.pattern));
364
+
365
+ if (gitignoreLines.length > 0) {
366
+ ig.add(gitignoreLines);
367
+ }
368
+
369
+ return ig;
370
+ }
371
+
372
+ /**
373
+ * Get current branch name
374
+ */
375
+ async getCurrentBranch(repositoryRoot: string): Promise<string | null> {
376
+ // First try using GitAdapter if available
377
+ if (this.gitAdapter?.getCurrentBranch) {
378
+ try {
379
+ return await this.gitAdapter.getCurrentBranch(repositoryRoot);
380
+ } catch (error) {
381
+ console.warn('Error getting branch via GitAdapter:', error);
382
+ }
383
+ }
384
+
385
+ // Fallback to reading .git/HEAD file
386
+ try {
387
+ const headPath = path.join(repositoryRoot, '.git', 'HEAD');
388
+ const result = await this.fileSystemAdapter.readFile(headPath);
389
+ if (!result) return null;
390
+
391
+ const content = result.content.trim();
392
+ if (content.startsWith('ref: refs/heads/')) {
393
+ return content.replace('ref: refs/heads/', '');
394
+ }
395
+
396
+ // Detached HEAD state
397
+ return 'HEAD';
398
+ } catch (error) {
399
+ console.warn('Error reading .git/HEAD:', error);
400
+ return null;
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Get current commit SHA
406
+ */
407
+ async getCurrentRevision(repositoryRoot: string): Promise<string | null> {
408
+ // First try using GitAdapter if available
409
+ if (this.gitAdapter?.getCurrentCommit) {
410
+ try {
411
+ return await this.gitAdapter.getCurrentCommit(repositoryRoot);
412
+ } catch (error) {
413
+ console.warn('Error getting commit via GitAdapter:', error);
414
+ }
415
+ }
416
+
417
+ // Fallback to reading from .git files
418
+ try {
419
+ const headPath = path.join(repositoryRoot, '.git', 'HEAD');
420
+ const headResult = await this.fileSystemAdapter.readFile(headPath);
421
+ if (!headResult) return null;
422
+
423
+ const headContent = headResult.content.trim();
424
+
425
+ // If HEAD points to a ref, read that ref
426
+ if (headContent.startsWith('ref: ')) {
427
+ const ref = headContent.replace('ref: ', '');
428
+ const refPath = path.join(repositoryRoot, '.git', ref);
429
+ const refResult = await this.fileSystemAdapter.readFile(refPath);
430
+ if (refResult) {
431
+ return refResult.content.trim().substring(0, 7); // Short SHA
432
+ }
433
+ } else {
434
+ // Direct commit SHA in HEAD (detached state)
435
+ return headContent.substring(0, 7);
436
+ }
437
+ } catch (error) {
438
+ console.warn('Error reading git revision:', error);
439
+ }
440
+
441
+ return null;
442
+ }
443
+
444
+ /**
445
+ * Get remote repositories
446
+ */
447
+ async getRemotes(
448
+ repositoryRoot: string,
449
+ ): Promise<Array<{ name: string; url: string; type: 'push' | 'fetch' | 'both' }>> {
450
+ // First try using GitAdapter if available
451
+ if (this.gitAdapter?.getRemotes) {
452
+ try {
453
+ const remotes = await this.gitAdapter.getRemotes(repositoryRoot);
454
+ // Ensure all remotes have a type, default to 'both' if not specified
455
+ return remotes.map(remote => ({
456
+ name: remote.name,
457
+ url: remote.url,
458
+ type: remote.type || 'both',
459
+ }));
460
+ } catch (error) {
461
+ console.warn('Error getting remotes via GitAdapter:', error);
462
+ }
463
+ }
464
+
465
+ // Fallback to reading .git/config file
466
+ try {
467
+ const configPath = path.join(repositoryRoot, '.git', 'config');
468
+ const result = await this.fileSystemAdapter.readFile(configPath);
469
+ if (!result) return [];
470
+
471
+ const remotes: Array<{ name: string; url: string; type: 'push' | 'fetch' | 'both' }> = [];
472
+ const lines = result.content.split('\n');
473
+ let currentRemote: string | null = null;
474
+
475
+ for (const line of lines) {
476
+ const trimmed = line.trim();
477
+
478
+ // Check for remote section
479
+ const remoteMatch = trimmed.match(/^\[remote "(.+)"\]$/);
480
+ if (remoteMatch) {
481
+ currentRemote = remoteMatch[1];
482
+ continue;
483
+ }
484
+
485
+ // Extract URL for current remote
486
+ if (currentRemote && trimmed.startsWith('url = ')) {
487
+ const url = trimmed.replace('url = ', '');
488
+ // Git remotes are typically used for both fetch and push unless specified otherwise
489
+ remotes.push({ name: currentRemote, url, type: 'both' });
490
+ currentRemote = null;
491
+ }
492
+ }
493
+
494
+ return remotes;
495
+ } catch (error) {
496
+ console.warn('Error reading .git/config:', error);
497
+ return [];
498
+ }
499
+ }
500
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Package management API provider for checking versions, vulnerabilities, and licenses
3
+ */
4
+
5
+ export type PackageManager = 'npm' | 'pnpm' | 'yarn';
6
+
7
+ export interface PackageVersionInfo {
8
+ name: string;
9
+ currentVersion: string;
10
+ }
11
+
12
+ export interface VersionCheckResult {
13
+ packageName: string;
14
+ currentVersion: string;
15
+ latestVersion?: string;
16
+ updateType?: 'major' | 'minor' | 'patch' | 'none';
17
+ isOutdated: boolean;
18
+ isDeprecated?: boolean;
19
+ deprecationMessage?: string;
20
+ error?: string;
21
+ }
22
+
23
+ export interface VulnerabilityInfo {
24
+ id: string;
25
+ severity: 'low' | 'moderate' | 'high' | 'critical';
26
+ title: string;
27
+ description?: string;
28
+ fixAvailable?: boolean;
29
+ }
30
+
31
+ export interface VulnerabilityCheckResult {
32
+ packageName: string;
33
+ version: string;
34
+ vulnerabilities: VulnerabilityInfo[];
35
+ error?: string;
36
+ }
37
+
38
+ export interface LicenseInfo {
39
+ license: string;
40
+ licenseType: 'permissive' | 'copyleft' | 'proprietary' | 'unknown';
41
+ requiresAttribution: boolean;
42
+ requiresShareAlike: boolean;
43
+ allowsCommercialUse: boolean;
44
+ }
45
+
46
+ export interface LicenseCheckResult {
47
+ packageName: string;
48
+ version: string;
49
+ license?: LicenseInfo;
50
+ error?: string;
51
+ }
52
+
53
+ export interface DependencyCheckProgress {
54
+ phase:
55
+ | 'idle'
56
+ | 'counting'
57
+ | 'checking-versions'
58
+ | 'checking-vulnerabilities'
59
+ | 'checking-licenses'
60
+ | 'complete';
61
+ current: number;
62
+ total: number;
63
+ message?: string;
64
+ }
65
+
66
+ export interface BatchCheckOptions {
67
+ batchSize?: number; // How many packages to check at once
68
+ maxConcurrent?: number; // Max concurrent API calls
69
+ skipCache?: boolean; // Force fresh checks
70
+ }
71
+
72
+ /**
73
+ * Abstract provider for package manager API operations
74
+ * Implementations can use direct API calls, server proxies, or mock data
75
+ */
76
+ export abstract class PackageManagerApiProvider {
77
+ /**
78
+ * Check versions for multiple packages, yielding results as they complete
79
+ */
80
+ abstract checkVersions(
81
+ packages: PackageVersionInfo[],
82
+ packageManager: PackageManager,
83
+ options?: BatchCheckOptions,
84
+ ): AsyncGenerator<VersionCheckResult, void, unknown>;
85
+
86
+ /**
87
+ * Check vulnerabilities for multiple packages, yielding results as they complete
88
+ */
89
+ abstract checkVulnerabilities(
90
+ packages: PackageVersionInfo[],
91
+ packageManager: PackageManager,
92
+ options?: BatchCheckOptions,
93
+ ): AsyncGenerator<VulnerabilityCheckResult, void, unknown>;
94
+
95
+ /**
96
+ * Check licenses for multiple packages, yielding results as they complete
97
+ */
98
+ abstract checkLicenses(
99
+ packages: PackageVersionInfo[],
100
+ packageManager: PackageManager,
101
+ options?: BatchCheckOptions,
102
+ ): AsyncGenerator<LicenseCheckResult, void, unknown>;
103
+
104
+ /**
105
+ * Get overall progress of the current check operation
106
+ */
107
+ abstract getProgress(): DependencyCheckProgress;
108
+ }
@@ -0,0 +1,88 @@
1
+ # Layer Providers
2
+
3
+ This directory contains provider implementations for the layer system.
4
+
5
+ ## GitVersionControlProvider
6
+
7
+ A Git implementation of the `VersionControlProvider` interface that:
8
+
9
+ - Detects Git repositories by looking for `.git` directories
10
+ - Parses `.gitignore` files throughout the repository
11
+ - Extracts branch, commit, and remote information
12
+ - Creates `VersionControlLayer` objects with Git metadata
13
+
14
+ ### Usage with Electron-React
15
+
16
+ ```typescript
17
+ import {
18
+ VersionControlLayerFactory,
19
+ GitVersionControlProvider,
20
+ FileSystemModule,
21
+ } from 'core/layers';
22
+ import { ElectronPlatformAdapters } from '../adapters';
23
+
24
+ // Use electron's existing adapters
25
+ const adapters = new ElectronPlatformAdapters();
26
+
27
+ // Create Git provider using electron's adapters
28
+ const gitProvider = new GitVersionControlProvider(
29
+ adapters.fileSystem, // ElectronFileSystemAdapter
30
+ adapters.git, // ElectronGitAdapter (optional)
31
+ );
32
+
33
+ // Register with factory
34
+ const vcsFactory = new VersionControlLayerFactory();
35
+ vcsFactory.registerProvider('git', gitProvider);
36
+
37
+ // Use in FileSystemModule
38
+ const fsModule = new FileSystemModule({
39
+ directoryPath: '/path/to/repo',
40
+ buildFileSystemTree: async (path, filters) => {
41
+ // ... your implementation
42
+ },
43
+ versionControlLayerFactory: vcsFactory,
44
+ });
45
+
46
+ // Load filesystem with VCS awareness
47
+ const result = await fsModule.loadFileSystemTree();
48
+ // result.versionControlLayer will contain Git metadata
49
+ ```
50
+
51
+ ### Adapter Requirements
52
+
53
+ The `GitVersionControlProvider` requires a `FileSystemAdapter` with these methods:
54
+
55
+ - `readFile(path)` - Read file contents
56
+ - `fileExists(path)` - Check if file exists (optional)
57
+ - `isDirectory(path)` - Check if path is directory (optional)
58
+ - `readDirectory(path)` - List directory contents (optional)
59
+
60
+ Optionally, you can provide a `GitAdapter` for more accurate Git operations:
61
+
62
+ - `getCurrentBranch(repoPath)` - Get current branch name
63
+ - `getCurrentCommit(repoPath)` - Get current commit SHA
64
+ - `getRemotes(repoPath)` - Get remote repositories
65
+ - `getStatus(repoPath)` - Get working tree status
66
+
67
+ ### Features
68
+
69
+ - **Repository Detection**: Walks up directory tree to find `.git` directory
70
+ - **Ignore Pattern Parsing**: Recursively finds and parses all `.gitignore` files
71
+ - **Pattern Types**: Supports glob, exact, and regex patterns
72
+ - **Pattern Scopes**: Repository-wide or directory-specific patterns
73
+ - **Negation Support**: Handles `!` negation patterns in `.gitignore`
74
+ - **Fallback Logic**: Falls back to file reading when Git adapter not available
75
+
76
+ ### Integration with Shared-Lib
77
+
78
+ This provider is compatible with the same interfaces used by `markdown-slides-shared-lib/workspace`,
79
+ making it easy to migrate from shared-lib's `GitVersionControlProvider` to this one:
80
+
81
+ ```typescript
82
+ // Before (using shared-lib)
83
+ import { GitVersionControlProvider } from 'markdown-slides-shared-lib/workspace';
84
+
85
+ // After (using core)
86
+ import { GitVersionControlProvider } from 'core/layers';
87
+ // Same usage, same interface!
88
+ ```