archicore 0.1.5 → 0.1.7

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.
@@ -21,6 +21,7 @@ import { DeadCodeDetector } from '../../analyzers/dead-code.js';
21
21
  import { DuplicationDetector } from '../../analyzers/duplication.js';
22
22
  import { SecurityAnalyzer } from '../../analyzers/security.js';
23
23
  import { RefactoringEngine } from '../../refactoring/index.js';
24
+ import { GitHubService } from '../../github/github-service.js';
24
25
  // Singleton instance
25
26
  let instance = null;
26
27
  export class ProjectService {
@@ -65,15 +66,21 @@ export class ProjectService {
65
66
  Logger.error('Could not save projects:', error);
66
67
  }
67
68
  }
68
- async listProjects() {
69
- return Array.from(this.projects.values());
69
+ async listProjects(userId) {
70
+ const projects = Array.from(this.projects.values());
71
+ // Filter by userId if provided
72
+ if (userId) {
73
+ return projects.filter(p => p.ownerId === userId);
74
+ }
75
+ return projects;
70
76
  }
71
- async createProject(name, projectPath) {
77
+ async createProject(name, projectPath, ownerId) {
72
78
  const id = uuidv4();
73
79
  const project = {
74
80
  id,
75
81
  name,
76
82
  path: projectPath,
83
+ ownerId,
77
84
  createdAt: new Date().toISOString(),
78
85
  status: 'pending'
79
86
  };
@@ -81,11 +88,25 @@ export class ProjectService {
81
88
  await this.saveProjects();
82
89
  // Инициализируем данные проекта
83
90
  await this.initializeProjectData(id);
84
- Logger.success(`Created project: ${name} (${id})`);
91
+ Logger.success(`Created project: ${name} (${id}) for user ${ownerId}`);
92
+ return project;
93
+ }
94
+ async getProject(id, userId) {
95
+ const project = this.projects.get(id);
96
+ if (!project)
97
+ return null;
98
+ // Check ownership if userId provided
99
+ if (userId && project.ownerId !== userId) {
100
+ return null;
101
+ }
85
102
  return project;
86
103
  }
87
- async getProject(id) {
88
- return this.projects.get(id) || null;
104
+ /**
105
+ * Check if user owns project
106
+ */
107
+ async isProjectOwner(projectId, userId) {
108
+ const project = this.projects.get(projectId);
109
+ return project?.ownerId === userId;
89
110
  }
90
111
  async initializeProjectData(projectId) {
91
112
  const project = this.projects.get(projectId);
@@ -213,8 +234,8 @@ export class ProjectService {
213
234
  label: node.name,
214
235
  type: node.type,
215
236
  filePath: node.filePath,
216
- // Для визуализации - группировка по папкам
217
- group: node.filePath.split('/').slice(-2, -1)[0] || 'root'
237
+ // Для визуализации - группировка по папкам (кроссплатформенно)
238
+ group: node.filePath.split(/[/\\]/).slice(-2, -1)[0] || 'root'
218
239
  }));
219
240
  const edges = [];
220
241
  for (const [from, edgeList] of graph.edges) {
@@ -366,6 +387,17 @@ export class ProjectService {
366
387
  };
367
388
  }
368
389
  async deleteProject(projectId) {
390
+ // Disconnect GitHub repository if connected
391
+ try {
392
+ const githubService = new GitHubService();
393
+ const disconnected = await githubService.disconnectRepositoryByProjectId(projectId);
394
+ if (disconnected) {
395
+ Logger.info(`Disconnected GitHub repository for project: ${projectId}`);
396
+ }
397
+ }
398
+ catch (err) {
399
+ Logger.warn(`Failed to disconnect GitHub repository for project ${projectId}:`, err);
400
+ }
369
401
  this.projects.delete(projectId);
370
402
  this.projectData.delete(projectId);
371
403
  await this.saveProjects();
@@ -689,5 +721,125 @@ export class ProjectService {
689
721
  }
690
722
  return fileContents;
691
723
  }
724
+ /**
725
+ * Генерация документации по коду проекта
726
+ */
727
+ async generateDocumentation(projectId, options = {}) {
728
+ const project = this.projects.get(projectId);
729
+ if (!project) {
730
+ throw new Error(`Project not found: ${projectId}`);
731
+ }
732
+ const data = await this.getProjectData(projectId);
733
+ const fileContents = await this.getFileContents(projectId);
734
+ const format = options.format || 'markdown';
735
+ const language = options.language || 'en';
736
+ Logger.progress(`Generating ${format} documentation for: ${project.name}`);
737
+ // Собираем информацию о проекте
738
+ const stats = project.stats;
739
+ const symbols = data.symbols ? Array.from(data.symbols.values()) : [];
740
+ // graph is available via data.graph if needed for more detailed docs
741
+ // Группируем символы по типу (kind)
742
+ const classes = symbols.filter(s => s.kind === 'class');
743
+ const functions = symbols.filter(s => s.kind === 'function');
744
+ const interfaces = symbols.filter(s => s.kind === 'interface');
745
+ // Формируем структуру файлов
746
+ const files = fileContents ? Array.from(fileContents.keys()) : [];
747
+ const fileStructure = this.buildFileStructure(files);
748
+ // Генерируем документацию
749
+ let documentation = '';
750
+ if (format === 'markdown') {
751
+ const title = language === 'ru' ? 'Документация проекта' : 'Project Documentation';
752
+ const overview = language === 'ru' ? 'Обзор' : 'Overview';
753
+ const structure = language === 'ru' ? 'Структура проекта' : 'Project Structure';
754
+ const classesTitle = language === 'ru' ? 'Классы' : 'Classes';
755
+ const functionsTitle = language === 'ru' ? 'Функции' : 'Functions';
756
+ const interfacesTitle = language === 'ru' ? 'Интерфейсы' : 'Interfaces';
757
+ documentation = `# ${title}: ${project.name}
758
+
759
+ ## ${overview}
760
+
761
+ - **Files:** ${stats?.filesCount || files.length}
762
+ - **Symbols:** ${stats?.symbolsCount || symbols.length}
763
+ - **Classes:** ${classes.length}
764
+ - **Functions:** ${functions.length}
765
+ - **Interfaces:** ${interfaces.length}
766
+
767
+ ## ${structure}
768
+
769
+ \`\`\`
770
+ ${fileStructure}
771
+ \`\`\`
772
+
773
+ ${classes.length > 0 ? `## ${classesTitle}
774
+
775
+ ${classes.slice(0, 20).map(c => `### ${c.name}
776
+
777
+ - **File:** \`${c.filePath}\`
778
+ - **Line:** ${c.location?.startLine || 'N/A'}
779
+ `).join('\n')}
780
+ ` : ''}
781
+
782
+ ${interfaces.length > 0 ? `## ${interfacesTitle}
783
+
784
+ ${interfaces.slice(0, 20).map(i => `### ${i.name}
785
+
786
+ - **File:** \`${i.filePath}\`
787
+ - **Line:** ${i.location?.startLine || 'N/A'}
788
+ `).join('\n')}
789
+ ` : ''}
790
+
791
+ ${functions.length > 0 ? `## ${functionsTitle}
792
+
793
+ ${functions.slice(0, 30).map(f => `- \`${f.name}\` - ${f.filePath}:${f.location?.startLine || 'N/A'}`).join('\n')}
794
+ ` : ''}
795
+
796
+ ---
797
+ *Generated by ArchiCore*
798
+ `;
799
+ }
800
+ else {
801
+ // JSON format
802
+ documentation = JSON.stringify({
803
+ project: project.name,
804
+ stats,
805
+ classes: classes.map(c => ({ name: c.name, file: c.filePath, line: c.location?.startLine })),
806
+ functions: functions.map(f => ({ name: f.name, file: f.filePath, line: f.location?.startLine })),
807
+ interfaces: interfaces.map(i => ({ name: i.name, file: i.filePath, line: i.location?.startLine })),
808
+ files
809
+ }, null, 2);
810
+ }
811
+ Logger.success('Documentation generated');
812
+ return { documentation, format };
813
+ }
814
+ /**
815
+ * Построить текстовое представление структуры файлов
816
+ */
817
+ buildFileStructure(files) {
818
+ const tree = {};
819
+ for (const file of files) {
820
+ const parts = file.split(/[/\\]/);
821
+ let current = tree;
822
+ for (const part of parts) {
823
+ if (!current[part]) {
824
+ current[part] = {};
825
+ }
826
+ current = current[part];
827
+ }
828
+ }
829
+ const renderTree = (node, prefix = '') => {
830
+ const entries = Object.entries(node);
831
+ return entries.map(([name, children], i) => {
832
+ const isLast = i === entries.length - 1;
833
+ const connector = isLast ? '└── ' : '├── ';
834
+ const childPrefix = isLast ? ' ' : '│ ';
835
+ const childKeys = Object.keys(children);
836
+ if (childKeys.length === 0) {
837
+ return `${prefix}${connector}${name}`;
838
+ }
839
+ return `${prefix}${connector}${name}\n${renderTree(children, prefix + childPrefix)}`;
840
+ }).join('\n');
841
+ };
842
+ return renderTree(tree);
843
+ }
692
844
  }
693
845
  //# sourceMappingURL=project-service.js.map
@@ -1,6 +1,7 @@
1
1
  import { readFile, stat } from 'fs/promises';
2
- import { join, relative, extname } from 'path';
2
+ import { join, relative, extname, resolve } from 'path';
3
3
  import { glob } from 'glob';
4
+ import { Logger } from './logger.js';
4
5
  const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB max file size for parsing
5
6
  export class FileUtils {
6
7
  static async readFileContent(filePath) {
@@ -20,12 +21,38 @@ export class FileUtils {
20
21
  '**/*.php', // PHP
21
22
  '**/*.rb', // Ruby
22
23
  '**/*.cs', // C#
23
- '**/*.cpp', '**/*.c', '**/*.h', '**/*.hpp' // C/C++
24
+ '**/*.cpp', '**/*.c', '**/*.h', '**/*.hpp', // C/C++
25
+ '**/*.html', '**/*.htm', // HTML
26
+ '**/*.css', '**/*.scss', '**/*.sass', '**/*.less', // CSS
27
+ '**/*.json', // JSON
28
+ '**/*.yaml', '**/*.yml', // YAML
29
+ '**/*.xml', // XML
30
+ '**/*.sql', // SQL
31
+ '**/*.sh', '**/*.bash', // Shell
32
+ '**/*.md', '**/*.markdown' // Markdown (for documentation)
24
33
  ]) {
34
+ // Resolve to absolute path
35
+ const resolvedPath = resolve(rootDir);
36
+ // Normalize for glob (use forward slashes on all platforms)
37
+ const absoluteRootDir = resolvedPath.replace(/\\/g, '/');
38
+ Logger.info(`getAllFiles: rootDir=${rootDir}, resolvedPath=${resolvedPath}, absoluteRootDir=${absoluteRootDir}`);
39
+ // Check if directory exists and is accessible
40
+ try {
41
+ const dirStats = await stat(resolvedPath);
42
+ if (!dirStats.isDirectory()) {
43
+ Logger.error(`getAllFiles: ${resolvedPath} is not a directory`);
44
+ return [];
45
+ }
46
+ Logger.info(`getAllFiles: Directory exists and is accessible`);
47
+ }
48
+ catch (err) {
49
+ Logger.error(`getAllFiles: Cannot access directory ${resolvedPath}: ${err}`);
50
+ return [];
51
+ }
25
52
  const files = [];
26
53
  for (const pattern of patterns) {
27
54
  const matches = await glob(pattern, {
28
- cwd: rootDir,
55
+ cwd: absoluteRootDir,
29
56
  nodir: true, // Exclude directories that match file patterns (e.g., "404.php/")
30
57
  dot: true, // Include files in hidden directories (e.g., ".default/")
31
58
  ignore: [
@@ -96,6 +123,28 @@ export class FileUtils {
96
123
  });
97
124
  files.push(...matches);
98
125
  }
126
+ Logger.info(`getAllFiles: glob found ${files.length} raw matches`);
127
+ // If no files found, list directory contents for debugging
128
+ if (files.length === 0) {
129
+ try {
130
+ const { readdir } = await import('fs/promises');
131
+ const contents = await readdir(resolvedPath, { withFileTypes: true });
132
+ const listing = contents.map(d => `${d.isDirectory() ? '[DIR]' : '[FILE]'} ${d.name}`);
133
+ Logger.warn(`getAllFiles: No files found! Directory contents: ${listing.join(', ') || '(empty)'}`);
134
+ // If there's a single directory inside, it might be a GitHub ZIP inner folder
135
+ const dirs = contents.filter(d => d.isDirectory());
136
+ if (dirs.length === 1) {
137
+ Logger.info(`getAllFiles: Found single subdirectory "${dirs[0].name}", trying to scan inside...`);
138
+ const innerPath = join(resolvedPath, dirs[0].name);
139
+ const innerContents = await readdir(innerPath, { withFileTypes: true });
140
+ const innerListing = innerContents.slice(0, 10).map(d => `${d.isDirectory() ? '[DIR]' : '[FILE]'} ${d.name}`);
141
+ Logger.info(`getAllFiles: Inner directory contents (first 10): ${innerListing.join(', ')}`);
142
+ }
143
+ }
144
+ catch (listErr) {
145
+ Logger.error(`getAllFiles: Failed to list directory: ${listErr}`);
146
+ }
147
+ }
99
148
  // Filter out large files and directories
100
149
  const filteredFiles = [];
101
150
  for (const file of [...new Set(files)]) {
@@ -113,10 +162,17 @@ export class FileUtils {
113
162
  // Skip files we can't stat
114
163
  }
115
164
  }
165
+ Logger.info(`getAllFiles: returning ${filteredFiles.length} files after filtering`);
166
+ if (filteredFiles.length > 0 && filteredFiles.length <= 20) {
167
+ Logger.info(`Files found: ${filteredFiles.join(', ')}`);
168
+ }
169
+ else if (filteredFiles.length > 20) {
170
+ Logger.info(`First 20 files: ${filteredFiles.slice(0, 20).join(', ')}`);
171
+ }
116
172
  return filteredFiles;
117
173
  }
118
174
  static getLanguageFromExtension(filePath) {
119
- const ext = extname(filePath);
175
+ const ext = extname(filePath).toLowerCase();
120
176
  const map = {
121
177
  '.ts': 'typescript',
122
178
  '.tsx': 'typescript',
@@ -133,7 +189,22 @@ export class FileUtils {
133
189
  '.cpp': 'cpp',
134
190
  '.c': 'c',
135
191
  '.h': 'c',
136
- '.hpp': 'cpp'
192
+ '.hpp': 'cpp',
193
+ '.html': 'html',
194
+ '.htm': 'html',
195
+ '.css': 'css',
196
+ '.scss': 'scss',
197
+ '.sass': 'sass',
198
+ '.less': 'less',
199
+ '.json': 'json',
200
+ '.yaml': 'yaml',
201
+ '.yml': 'yaml',
202
+ '.xml': 'xml',
203
+ '.sql': 'sql',
204
+ '.sh': 'shell',
205
+ '.bash': 'shell',
206
+ '.md': 'markdown',
207
+ '.markdown': 'markdown'
137
208
  };
138
209
  return map[ext] || 'unknown';
139
210
  }
@@ -154,7 +225,8 @@ export class FileUtils {
154
225
  size: stats.size,
155
226
  lastModified: stats.mtime
156
227
  });
157
- const dirPath = join(rootDir, rel.split('/').slice(0, -1).join('/'));
228
+ const parts = rel.split(/[/\\]/);
229
+ const dirPath = join(rootDir, ...parts.slice(0, -1));
158
230
  structure.directories.add(dirPath);
159
231
  }
160
232
  return structure;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archicore",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "AI Software Architect - code analysis, impact prediction, semantic search",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -40,15 +40,16 @@
40
40
  "dependencies": {
41
41
  "@anthropic-ai/sdk": "^0.32.0",
42
42
  "@qdrant/js-client-rest": "^1.11.0",
43
+ "@types/unzipper": "^0.10.11",
43
44
  "adm-zip": "^0.5.16",
44
45
  "boxen": "^8.0.1",
45
46
  "chalk": "^5.4.1",
46
47
  "cli-table3": "^0.6.5",
47
48
  "commander": "^12.1.0",
48
- "figures": "^6.1.0",
49
49
  "cors": "^2.8.5",
50
50
  "dotenv": "^16.4.7",
51
51
  "express": "^5.2.1",
52
+ "figures": "^6.1.0",
52
53
  "glob": "^11.0.0",
53
54
  "mime-types": "^3.0.2",
54
55
  "multer": "^2.0.2",
@@ -58,6 +59,7 @@
58
59
  "tree-sitter-javascript": "^0.21.4",
59
60
  "tree-sitter-python": "^0.21.0",
60
61
  "tree-sitter-typescript": "^0.21.2",
62
+ "unzipper": "^0.12.3",
61
63
  "uuid": "^11.0.3",
62
64
  "zod": "^3.24.1"
63
65
  },