projectify-cli 1.0.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.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Projectify
2
+
3
+ > **Autonomous Code Analysis & Visualization Engine**
4
+
5
+ Projectify is a powerful, standalone Node.js tool designed to analyze codebase architectures, calculate dependency impacts ("Blast Radius"), and generate stunning, interactive visualizations. Built with TypeScript and powered by AI.
6
+
7
+ ![Projectify HTML Report](./graph.png)
8
+ ![Projectify HTML Report](./functions.png)
9
+
10
+ ## Features
11
+
12
+ - **AI-Powered Insights**: Uses OpenAI (LangChain) to summarize architecture and explain high-risk components.
13
+ - **Blast Radius Calculation**: Instantly identify the most critical files in your project based on dependency impact.
14
+ - **Interactive Visualization**: Generates a premium, Shadcn-style HTML report with physics-based graphs and search.
15
+ - **Multi-Language Support**:
16
+ - JavaScript / TypeScript (AST-based)
17
+ - Python (Regex/Heuristic-based)
18
+ - **Fast & Standalone**: optimized for speed, zero external Python dependencies.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ # Clone the repository
24
+ git clone https://github.com/your-repo/projectify.git
25
+
26
+ # Install dependencies
27
+ cd projectify-cli
28
+ npm install
29
+
30
+ # Build the project
31
+ npm run build
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ### Using npx (Recommended)
37
+
38
+ Run directly without installing:
39
+
40
+ ```bash
41
+ npx projectify-cli [path] [options]
42
+ ```
43
+
44
+ ### Global Installation
45
+
46
+ ```bash
47
+ npm install -g projectify-cli
48
+
49
+ # Run anywhere
50
+ projectify [path] [options]
51
+ ```
52
+
53
+ ### Local Development
54
+
55
+ Run the analyzer on any project directory:
56
+
57
+ ```bash
58
+ # Basic Analysis
59
+ npm start -- /path/to/target/project
60
+
61
+ # Analysis with AI Summary (Requires API Key)
62
+ export OPENAI_API_KEY=sk-your-key-here
63
+ export GEMINI_API_KEY=sk-your-key-here
64
+ export OLLAMA_API_KEY=sk-your-key-here
65
+ npm start -- /path/to/target/project --summary
66
+ ```
67
+
68
+ ### CLI Options
69
+ - `path`: (Optional) Path to the project to analyze. Defaults to current directory.
70
+ - `--summary`: Enable AI-powered project summarization.
71
+ - `--no-ai`: Disable individual file AI analysis.
72
+
73
+ ## Output
74
+
75
+ All reports are generated in the `projectify-cli` directory:
76
+
77
+ | File | Description |
78
+ |------|-------------|
79
+ | `analysis-report.html` | **Interactive Dashboard**. View dependency graphs, search nodes, and see detailed stats. |
80
+ | `analysis-report.json` | **Raw Data**. Complete dependency graph and metrics in JSON format. |
81
+ | `project-summary.md` | **AI Report**. High-level architecture overview and risk assessment. |
82
+
83
+ ## Architecture
84
+
85
+ - **Scanner**: Fast-glob based file traversal.
86
+ - **Analyzer**: Babel-based AST parsing for JS/TS; Custom regex parser for Python.
87
+ - **Graph Engine**: Directed graph construction with cycle detection and impact scoring.
88
+ - **UI**: Customized `vis-network` with Tailwind CSS (Shadcn/Zinc theme).
89
+
90
+ ## Developer
91
+
92
+ Developed by **GreenHacker**.
93
+
94
+
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CodeIntelligence = void 0;
4
+ const openai_1 = require("@langchain/openai");
5
+ const google_genai_1 = require("@langchain/google-genai");
6
+ const ollama_1 = require("@langchain/ollama");
7
+ const prebuilt_1 = require("@langchain/langgraph/prebuilt");
8
+ const langgraph_1 = require("@langchain/langgraph");
9
+ const messages_1 = require("@langchain/core/messages");
10
+ class CodeIntelligence {
11
+ constructor(provider, apiKey, modelName) {
12
+ let model;
13
+ // Initialize Model based on Provider
14
+ if (provider === 'openai') {
15
+ if (!apiKey)
16
+ throw new Error("OpenAI API Key required");
17
+ model = new openai_1.ChatOpenAI({
18
+ apiKey,
19
+ modelName: modelName || 'gpt-4o',
20
+ temperature: 0
21
+ });
22
+ }
23
+ else if (provider === 'gemini') {
24
+ if (!apiKey)
25
+ throw new Error("Gemini API Key required");
26
+ model = new google_genai_1.ChatGoogleGenerativeAI({
27
+ apiKey,
28
+ model: modelName || 'gemini-1.5-flash',
29
+ temperature: 0
30
+ });
31
+ }
32
+ else if (provider === 'ollama') {
33
+ model = new ollama_1.ChatOllama({
34
+ baseUrl: 'http://localhost:11434',
35
+ model: modelName || 'llama3',
36
+ temperature: 0
37
+ });
38
+ }
39
+ else {
40
+ throw new Error(`Unsupported provider: ${provider}`);
41
+ }
42
+ // Initialize Memory
43
+ const checkpointer = new langgraph_1.MemorySaver();
44
+ // Create Agent
45
+ this.agent = (0, prebuilt_1.createReactAgent)({
46
+ llm: model,
47
+ tools: [],
48
+ checkpointSaver: checkpointer
49
+ });
50
+ // Config for thread persistence
51
+ this.config = { configurable: { thread_id: "project-analysis-session" } };
52
+ }
53
+ async askAgent(systemPrompt, userContent) {
54
+ const result = await this.agent.invoke({
55
+ messages: [
56
+ new messages_1.SystemMessage(systemPrompt),
57
+ new messages_1.HumanMessage(userContent)
58
+ ]
59
+ }, this.config);
60
+ const lastMessage = result.messages[result.messages.length - 1];
61
+ return lastMessage.content;
62
+ }
63
+ async summarizeFile(fileName, content) {
64
+ return this.askAgent('You are an expert code analyst. Summarize the purpose of this file in 1-2 sentences.', `File: ${fileName}\n\nCode:\n${content.substring(0, 5000)}`);
65
+ }
66
+ async analyzeBlastRadius(node) {
67
+ return this.askAgent('You are an expert software architect. Explain why this file has a high blast radius and the risks involved.', `File: ${node.id} is imported by ${node.affectedFiles} other files (${node.blastRadius.toFixed(2)}% of the codebase).`);
68
+ }
69
+ async generateProjectSummary(fileCount, topRisks, files) {
70
+ return this.askAgent('You are a technical lead. Provide a high-level summary of this project based on the file statistics and high-risk components.', `
71
+ Project Statistics:
72
+ - Total Files: ${fileCount}
73
+
74
+ Key Components (High Dependency/Risk):
75
+ ${topRisks.map(n => `- ${n.id} (Impacts ${n.affectedFiles} files)`).join('\n')}
76
+
77
+ File List Sample:
78
+ ${files.slice(0, 50).join('\n')}
79
+
80
+ Please provide:
81
+ 1. An estimated architecture type.
82
+ 2. Key risks.
83
+ 3. Recommendations.
84
+ `);
85
+ }
86
+ async analyzeGitHistory(gitStats) {
87
+ return this.askAgent('You are a technical lead analyzing project evolution. Provide insights based on commit history.', `
88
+ Git History Statistics:
89
+ - Total Commits: ${gitStats.totalCommits}
90
+ - Active Authors: ${gitStats.authroStats.length}
91
+
92
+ Top Authors:
93
+ ${gitStats.authroStats.slice(0, 5).map((a) => `- ${a.name}: ${a.commits} commits`).join('\n')}
94
+
95
+ Recent Activity:
96
+ ${gitStats.recentActivity.join('\n')}
97
+ `);
98
+ }
99
+ }
100
+ exports.CodeIntelligence = CodeIntelligence;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GeminiProvider = void 0;
4
+ const generative_ai_1 = require("@google/generative-ai");
5
+ class GeminiProvider {
6
+ constructor(apiKey, modelName = 'gemini-1.5-flash') {
7
+ this.genAI = new generative_ai_1.GoogleGenerativeAI(apiKey);
8
+ this.model = this.genAI.getGenerativeModel({ model: modelName });
9
+ }
10
+ async generateText(prompt) {
11
+ const result = await this.model.generateContent(prompt);
12
+ const response = await result.response;
13
+ return response.text();
14
+ }
15
+ async summarizeFile(fileName, content) {
16
+ return this.generateText(`You are an expert code analyst. Summarize the purpose of this file in 1-2 sentencs.\n\nFile: ${fileName}\n\nCode:\n${content.substring(0, 10000)}`);
17
+ }
18
+ async analyzeBlastRadius(riskNode) {
19
+ return this.generateText(`You are an expert software architect. Explain why this file has a high blast radius and the risks involved.\n\nFile: ${riskNode.id} is imported by ${riskNode.affectedFiles} other files (${riskNode.blastRadius.toFixed(2)}% of the codebase).`);
20
+ }
21
+ async generateProjectSummary(fileCount, topRisks, files) {
22
+ return this.generateText(`You are a technical lead. Provide a high-level summary of this project based on the file statistics and high-risk components.\n\n
23
+ Project Statistics:
24
+ - Total Files: ${fileCount}
25
+
26
+ Key Components (High Dependency/Risk):
27
+ ${topRisks.map(n => `- ${n.id} (Impacts ${n.affectedFiles} files)`).join('\n')}
28
+
29
+ File List Sample:
30
+ ${files.slice(0, 50).join('\n')}
31
+
32
+ Please provide:
33
+ 1. An estimated architecture type (CLI, Web App, Library, etc.).
34
+ 2. Key risks based on the high-dependency components.
35
+ 3. Recommendations for improvement.`);
36
+ }
37
+ async analyzeGitHistory(gitStats) {
38
+ return this.generateText(`You are a technical lead analyzing project evolution. Provide insights based on commit history.
39
+
40
+ Git History Statistics:
41
+ - Total Commits: ${gitStats.totalCommits}
42
+ - Active Authors: ${gitStats.authroStats.length}
43
+
44
+ Top Authors:
45
+ ${gitStats.authroStats.slice(0, 5).map((a) => `- ${a.name}: ${a.commits} commits`).join('\n')}
46
+
47
+ Recent Activity:
48
+ ${gitStats.recentActivity.join('\n')}
49
+
50
+ Please provide:
51
+ 1. Assessment of project activity (Active/Stale).
52
+ 2. Key contributors analysis.
53
+ 3. Recent development focus based on commit messages.`);
54
+ }
55
+ }
56
+ exports.GeminiProvider = GeminiProvider;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OllamaProvider = void 0;
7
+ const node_fetch_1 = __importDefault(require("node-fetch")); // Ensure node-fetch is available or use global fetch if on Node 18+
8
+ class OllamaProvider {
9
+ constructor(modelName = 'llama3', baseUrl = 'http://localhost:11434') {
10
+ this.modelName = modelName;
11
+ this.baseUrl = baseUrl;
12
+ }
13
+ async generate(prompt) {
14
+ try {
15
+ const response = await (0, node_fetch_1.default)(`${this.baseUrl}/api/generate`, {
16
+ method: 'POST',
17
+ headers: { 'Content-Type': 'application/json' },
18
+ body: JSON.stringify({
19
+ model: this.modelName,
20
+ prompt: prompt,
21
+ stream: false
22
+ })
23
+ });
24
+ const data = await response.json();
25
+ return data.response;
26
+ }
27
+ catch (error) {
28
+ console.error('Ollama generation failed:', error);
29
+ return 'Ollama analysis failed. Ensure Ollama is running.';
30
+ }
31
+ }
32
+ async summarizeFile(fileName, content) {
33
+ return this.generate(`Summarize this file in 1-2 sentences:\nFile: ${fileName}\nCode:\n${content.substring(0, 2000)}`);
34
+ }
35
+ async analyzeBlastRadius(riskNode) {
36
+ return this.generate(`Explain high blast radius risks for: ${riskNode.id} (Imported by ${riskNode.affectedFiles} files).`);
37
+ }
38
+ async generateProjectSummary(fileCount, topRisks, files) {
39
+ return this.generate(`Summarize project architecture and risks based on:
40
+ ${fileCount} files.
41
+ Top Risks: ${topRisks.map(n => n.id).join(', ')}.`);
42
+ }
43
+ async analyzeGitHistory(gitStats) {
44
+ return this.generate(`Analyze project evolution based on git history:
45
+ Total Commits: ${gitStats.totalCommits}.
46
+ Recent Activity: ${gitStats.recentActivity.join('\n')}.
47
+ Provide insights on activity and focus.`);
48
+ }
49
+ }
50
+ exports.OllamaProvider = OllamaProvider;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenAIProvider = void 0;
4
+ const openai_1 = require("@langchain/openai");
5
+ const messages_1 = require("@langchain/core/messages");
6
+ class OpenAIProvider {
7
+ constructor(apiKey, modelName = 'gpt-4o') {
8
+ this.model = new openai_1.ChatOpenAI({
9
+ openAIApiKey: apiKey,
10
+ temperature: 0,
11
+ modelName
12
+ });
13
+ }
14
+ async summarizeFile(fileName, content) {
15
+ const response = await this.model.invoke([
16
+ new messages_1.SystemMessage('You are an expert code analyst. Summarize the purpose of this file in 1-2 sentencs.'),
17
+ new messages_1.HumanMessage(`File: ${fileName}\n\nCode:\n${content.substring(0, 4000)}`)
18
+ ]);
19
+ return response.content;
20
+ }
21
+ async analyzeBlastRadius(riskNode) {
22
+ const response = await this.model.invoke([
23
+ new messages_1.SystemMessage('You are an expert software architect. Explain why this file has a high blast radius and the risks involved.'),
24
+ new messages_1.HumanMessage(`File: ${riskNode.id} is imported by ${riskNode.affectedFiles} other files (${riskNode.blastRadius.toFixed(2)}% of the codebase).`)
25
+ ]);
26
+ return response.content;
27
+ }
28
+ async generateProjectSummary(fileCount, topRisks, files) {
29
+ const response = await this.model.invoke([
30
+ new messages_1.SystemMessage('You are a technical lead. Provide a high-level summary of this project based on the file statistics and high-risk components.'),
31
+ new messages_1.HumanMessage(`
32
+ Project Statistics:
33
+ - Total Files: ${fileCount}
34
+
35
+ Key Components (High Dependency/Risk):
36
+ ${topRisks.map(n => `- ${n.id} (Impacts ${n.affectedFiles} files)`).join('\n')}
37
+
38
+ File List Sample:
39
+ ${files.slice(0, 50).join('\n')}
40
+
41
+ Please provide:
42
+ 1. An estimated architecture type (CLI, Web App, Library, etc.).
43
+ 2. Key risks based on the high-dependency components.
44
+ 3. Recommendations for improvement.
45
+ `)
46
+ ]);
47
+ return response.content;
48
+ }
49
+ async analyzeGitHistory(gitStats) {
50
+ const response = await this.model.invoke([
51
+ new messages_1.SystemMessage('You are a technical lead analyzing project evolution. Provide insights based on commit history.'),
52
+ new messages_1.HumanMessage(`
53
+ Git History Statistics:
54
+ - Total Commits: ${gitStats.totalCommits}
55
+ - Active Authors: ${gitStats.authroStats.length}
56
+
57
+ Top Authors:
58
+ ${gitStats.authroStats.slice(0, 5).map((a) => `- ${a.name}: ${a.commits} commits`).join('\n')}
59
+
60
+ Recent Activity:
61
+ ${gitStats.recentActivity.join('\n')}
62
+
63
+ Please provide:
64
+ 1. Assessment of project activity (Active/Stale).
65
+ 2. Key contributors analysis.
66
+ 3. Recent development focus based on commit messages.
67
+ `)
68
+ ]);
69
+ return response.content;
70
+ }
71
+ }
72
+ exports.OpenAIProvider = OpenAIProvider;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.analyzeFiles = analyzeFiles;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fileUtils_1 = require("../utils/fileUtils");
9
+ const javascript_1 = require("./parsers/javascript");
10
+ const python_1 = require("./parsers/python");
11
+ async function analyzeFiles(filePaths) {
12
+ const analysis = {
13
+ fileCount: filePaths.length,
14
+ files: {}
15
+ };
16
+ for (const filePath of filePaths) {
17
+ const content = await (0, fileUtils_1.readFileSafe)(filePath);
18
+ if (!content)
19
+ continue;
20
+ const ext = path_1.default.extname(filePath);
21
+ let fileResult = { imports: [], exports: [], functions: [], classes: [] };
22
+ if (['.js', '.jsx', '.ts', '.tsx'].includes(ext)) {
23
+ fileResult = (0, javascript_1.parseJS)(content, filePath);
24
+ }
25
+ else if (['.py'].includes(ext)) {
26
+ fileResult = (0, python_1.parsePython)(content, filePath);
27
+ }
28
+ else {
29
+ // TODO: Add generic regex parser
30
+ }
31
+ analysis.files[filePath] = fileResult;
32
+ }
33
+ return analysis;
34
+ }
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.parseJS = parseJS;
40
+ const parser_1 = require("@babel/parser");
41
+ const traverse_1 = __importDefault(require("@babel/traverse"));
42
+ const t = __importStar(require("@babel/types"));
43
+ const path_1 = __importDefault(require("path"));
44
+ function parseJS(content, filePath) {
45
+ const analysis = {
46
+ imports: [],
47
+ exports: [],
48
+ functions: [],
49
+ classes: []
50
+ };
51
+ try {
52
+ const isTs = filePath.endsWith('.ts') || filePath.endsWith('.tsx');
53
+ const ast = (0, parser_1.parse)(content, {
54
+ sourceType: 'module',
55
+ errorRecovery: true,
56
+ plugins: [
57
+ 'jsx',
58
+ 'typescript',
59
+ 'asyncGenerators',
60
+ 'bigInt',
61
+ 'classProperties',
62
+ 'classPrivateProperties',
63
+ 'classPrivateMethods',
64
+ 'decorators-legacy',
65
+ 'doExpressions',
66
+ 'dynamicImport',
67
+ 'exportDefaultFrom',
68
+ 'exportNamespaceFrom',
69
+ 'functionBind',
70
+ 'functionSent',
71
+ 'importMeta',
72
+ 'logicalAssignment',
73
+ 'nullishCoalescingOperator',
74
+ 'numericSeparator',
75
+ 'objectRestSpread',
76
+ 'optionalCatchBinding',
77
+ 'optionalChaining',
78
+ 'partialApplication'
79
+ ]
80
+ });
81
+ (0, traverse_1.default)(ast, {
82
+ ImportDeclaration(path) {
83
+ analysis.imports.push(path.node.source.value);
84
+ },
85
+ ExportNamedDeclaration(path) {
86
+ if (path.node.declaration) {
87
+ if (t.isFunctionDeclaration(path.node.declaration) && path.node.declaration.id) {
88
+ analysis.exports.push(path.node.declaration.id.name);
89
+ }
90
+ else if (t.isVariableDeclaration(path.node.declaration)) {
91
+ path.node.declaration.declarations.forEach(d => {
92
+ if (t.isIdentifier(d.id)) {
93
+ analysis.exports.push(d.id.name);
94
+ }
95
+ });
96
+ }
97
+ else if (t.isClassDeclaration(path.node.declaration) && path.node.declaration.id) {
98
+ analysis.exports.push(path.node.declaration.id.name);
99
+ }
100
+ }
101
+ },
102
+ FunctionDeclaration(path) {
103
+ if (path.node.id && path.node.loc) {
104
+ const start = path.node.loc.start.line - 1;
105
+ const end = path.node.loc.end.line;
106
+ const code = content.split('\n').slice(start, end).join('\n');
107
+ const params = path.node.params.map(p => {
108
+ if (t.isIdentifier(p))
109
+ return p.name;
110
+ if (t.isAssignmentPattern(p) && t.isIdentifier(p.left))
111
+ return p.left.name;
112
+ return 'arg';
113
+ });
114
+ const doc = path.node.leadingComments
115
+ ? path.node.leadingComments.map(c => c.value.trim()).join('\n')
116
+ : undefined;
117
+ analysis.functions.push({
118
+ name: path.node.id.name,
119
+ line: path.node.loc.start.line,
120
+ params: params,
121
+ doc: doc,
122
+ code: code
123
+ });
124
+ }
125
+ },
126
+ ClassDeclaration(path) {
127
+ if (path.node.id) {
128
+ analysis.classes.push(path.node.id.name);
129
+ }
130
+ }
131
+ });
132
+ }
133
+ catch (error) {
134
+ console.warn(`⚠️ Parser warning in ${path_1.default.basename(filePath)}: ${error.message?.split('\n')[0]}`);
135
+ }
136
+ return analysis;
137
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parsePython = parsePython;
4
+ function parsePython(content, filePath) {
5
+ const analysis = {
6
+ imports: [],
7
+ exports: [],
8
+ functions: [],
9
+ classes: []
10
+ };
11
+ const lines = content.split('\n');
12
+ // Regex patterns
13
+ const importRegex = /^(?:from|import)\s+([\w\.]+)/;
14
+ const defRegex = /^\s*def\s+([a-zA-Z_]\w*)/;
15
+ const classRegex = /^\s*class\s+([a-zA-Z_]\w*)/;
16
+ const assignRegex = /^([a-zA-Z_]\w*)\s*=/; // Global assignments
17
+ lines.forEach((line, index) => {
18
+ // Imports
19
+ const importMatch = line.match(importRegex);
20
+ if (importMatch) {
21
+ analysis.imports.push(importMatch[1]);
22
+ }
23
+ // Functions
24
+ const defMatch = line.match(defRegex);
25
+ if (defMatch) {
26
+ analysis.functions.push({
27
+ name: defMatch[1],
28
+ line: index + 1,
29
+ params: [], // Regex parser doesn't extract params yet
30
+ doc: undefined,
31
+ code: undefined
32
+ });
33
+ // Python functions at module level are exports
34
+ analysis.exports.push(defMatch[1]);
35
+ }
36
+ // Classes
37
+ const classMatch = line.match(classRegex);
38
+ if (classMatch) {
39
+ analysis.classes.push(classMatch[1]);
40
+ analysis.exports.push(classMatch[1]);
41
+ }
42
+ });
43
+ return analysis;
44
+ }