carto-md 1.0.2 → 1.0.3
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/package.json +1 -1
- package/src/agents/formatter.js +20 -5
- package/src/extractors/imports.js +212 -0
- package/src/sync.js +17 -1
package/package.json
CHANGED
package/src/agents/formatter.js
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* 5. Functions (auto)
|
|
10
10
|
* 6. Database Tables (auto)
|
|
11
11
|
* 7. Environment Variables (auto)
|
|
12
|
-
* 8.
|
|
13
|
-
* 9. Frontend
|
|
12
|
+
* 8. File Relationships (auto)
|
|
13
|
+
* 9. Frontend API Calls (auto)
|
|
14
|
+
* 10. Frontend Storage Keys (auto)
|
|
14
15
|
*/
|
|
15
|
-
function formatSections({ routes, models, frontend, structure, warnings, fileMap, functions, dbTables, envVars }) {
|
|
16
|
+
function formatSections({ routes, models, frontend, structure, warnings, fileMap, functions, dbTables, envVars, importGraph }) {
|
|
16
17
|
const sections = [];
|
|
17
18
|
|
|
18
19
|
// 1. Project Structure
|
|
@@ -114,7 +115,21 @@ function formatSections({ routes, models, frontend, structure, warnings, fileMap
|
|
|
114
115
|
sections.push('_No environment variables detected._');
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
// 8.
|
|
118
|
+
// 8. File Relationships
|
|
119
|
+
sections.push('\n## File Relationships (auto)\n');
|
|
120
|
+
if (importGraph && Object.keys(importGraph).length > 0) {
|
|
121
|
+
const sortedFiles = Object.keys(importGraph).sort();
|
|
122
|
+
for (const file of sortedFiles) {
|
|
123
|
+
const deps = importGraph[file];
|
|
124
|
+
if (deps.length > 0) {
|
|
125
|
+
sections.push(`${file} \u2192 ${deps.join(', ')}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
sections.push('_No file relationships detected._');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 9. Frontend API Calls
|
|
118
133
|
sections.push('\n## Frontend API Calls (auto)\n');
|
|
119
134
|
if (frontend.fetches.length > 0) {
|
|
120
135
|
sections.push('| Method | URL |');
|
|
@@ -126,7 +141,7 @@ function formatSections({ routes, models, frontend, structure, warnings, fileMap
|
|
|
126
141
|
sections.push('_No fetch calls found._');
|
|
127
142
|
}
|
|
128
143
|
|
|
129
|
-
//
|
|
144
|
+
// 10. Frontend Storage Keys
|
|
130
145
|
sections.push('\n## Frontend Storage Keys (auto)\n');
|
|
131
146
|
if (frontend.storageKeys.length > 0) {
|
|
132
147
|
sections.push('| Operation | Key |');
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* extractImports(content, filePath, projectRoot) → Array<string>
|
|
6
|
+
*
|
|
7
|
+
* Extracts relative import paths from a source file.
|
|
8
|
+
* Returns resolved relative paths (from project root) of local dependencies.
|
|
9
|
+
*
|
|
10
|
+
* JS/TS patterns:
|
|
11
|
+
* import X from './Y'
|
|
12
|
+
* import { X } from './Y'
|
|
13
|
+
* import './Y'
|
|
14
|
+
* const X = require('./Y')
|
|
15
|
+
* require('./Y')
|
|
16
|
+
*
|
|
17
|
+
* Python patterns:
|
|
18
|
+
* from .module import X (relative)
|
|
19
|
+
* from ..module import X (relative)
|
|
20
|
+
* from app.module import X (local package — resolved if file exists)
|
|
21
|
+
* import .module (relative)
|
|
22
|
+
*
|
|
23
|
+
* Only includes paths that resolve to actual files in the project.
|
|
24
|
+
* Skips: node_modules, non-code files, anything that doesn't resolve.
|
|
25
|
+
*/
|
|
26
|
+
function extractImports(content, filePath, projectRoot) {
|
|
27
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
28
|
+
const fileDir = path.dirname(filePath);
|
|
29
|
+
|
|
30
|
+
let rawImports = [];
|
|
31
|
+
|
|
32
|
+
if (['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'].includes(ext)) {
|
|
33
|
+
rawImports = extractJSImports(content);
|
|
34
|
+
} else if (ext === '.py') {
|
|
35
|
+
rawImports = extractPythonImports(content, filePath, projectRoot);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Resolve and deduplicate
|
|
39
|
+
const resolved = new Set();
|
|
40
|
+
|
|
41
|
+
for (const imp of rawImports) {
|
|
42
|
+
const resolvedPath = resolveImportPath(imp, fileDir, projectRoot, ext);
|
|
43
|
+
if (resolvedPath) {
|
|
44
|
+
// Store as relative to project root
|
|
45
|
+
const rel = path.relative(projectRoot, resolvedPath);
|
|
46
|
+
resolved.add(rel);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return [...resolved].sort();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extract import paths from JS/TS content. Relative paths only.
|
|
55
|
+
*/
|
|
56
|
+
function extractJSImports(content) {
|
|
57
|
+
const imports = [];
|
|
58
|
+
|
|
59
|
+
// import ... from './path' or import './path'
|
|
60
|
+
const importPattern = /import\s+(?:[\s\S]*?\s+from\s+)?['"](\.[^'"]+)['"]/g;
|
|
61
|
+
let match;
|
|
62
|
+
while ((match = importPattern.exec(content)) !== null) {
|
|
63
|
+
imports.push(match[1]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// require('./path')
|
|
67
|
+
const requirePattern = /require\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
|
|
68
|
+
while ((match = requirePattern.exec(content)) !== null) {
|
|
69
|
+
imports.push(match[1]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return imports;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extract import paths from Python content. Relative imports only.
|
|
77
|
+
*/
|
|
78
|
+
function extractPythonImports(content, filePath, projectRoot) {
|
|
79
|
+
const imports = [];
|
|
80
|
+
const fileDir = path.dirname(filePath);
|
|
81
|
+
|
|
82
|
+
// from .module import X or from ..module import X
|
|
83
|
+
const fromRelPattern = /^from\s+(\.+\w*(?:\.\w+)*)\s+import/gm;
|
|
84
|
+
let match;
|
|
85
|
+
while ((match = fromRelPattern.exec(content)) !== null) {
|
|
86
|
+
const resolved = resolvePythonRelativeImport(match[1], fileDir);
|
|
87
|
+
if (resolved) imports.push(resolved);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// from app.module import X — try to resolve as local file
|
|
91
|
+
const fromAbsPattern = /^from\s+(\w+(?:\.\w+)+)\s+import/gm;
|
|
92
|
+
while ((match = fromAbsPattern.exec(content)) !== null) {
|
|
93
|
+
const modulePath = match[1].replace(/\./g, path.sep);
|
|
94
|
+
// Try from project root
|
|
95
|
+
const resolved = tryResolvePythonModule(modulePath, projectRoot);
|
|
96
|
+
if (resolved) {
|
|
97
|
+
imports.push(resolved);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// Try from file's directory (for cases like `from app.models` when inside aws-risk-agent/)
|
|
101
|
+
const fromFileDir = tryResolvePythonModule(modulePath, fileDir);
|
|
102
|
+
if (fromFileDir) {
|
|
103
|
+
imports.push(fromFileDir);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// Try from parent directories up to project root
|
|
107
|
+
let searchDir = path.dirname(fileDir);
|
|
108
|
+
while (searchDir.startsWith(projectRoot) && searchDir !== projectRoot) {
|
|
109
|
+
const fromParent = tryResolvePythonModule(modulePath, searchDir);
|
|
110
|
+
if (fromParent) {
|
|
111
|
+
imports.push(fromParent);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
searchDir = path.dirname(searchDir);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return imports;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Try to resolve a dotted Python module path from a base directory.
|
|
123
|
+
*/
|
|
124
|
+
function tryResolvePythonModule(modulePath, baseDir) {
|
|
125
|
+
const asFile = path.join(baseDir, modulePath + '.py');
|
|
126
|
+
if (fs.existsSync(asFile)) return asFile;
|
|
127
|
+
const asInit = path.join(baseDir, modulePath, '__init__.py');
|
|
128
|
+
if (fs.existsSync(asInit)) return asInit;
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Resolve a Python relative import like '.models' or '..utils' to an absolute path.
|
|
134
|
+
*/
|
|
135
|
+
function resolvePythonRelativeImport(importStr, fileDir) {
|
|
136
|
+
// Count leading dots
|
|
137
|
+
let dots = 0;
|
|
138
|
+
while (dots < importStr.length && importStr[dots] === '.') dots++;
|
|
139
|
+
|
|
140
|
+
const modulePart = importStr.substring(dots);
|
|
141
|
+
|
|
142
|
+
// Go up (dots - 1) directories from fileDir
|
|
143
|
+
let baseDir = fileDir;
|
|
144
|
+
for (let i = 1; i < dots; i++) {
|
|
145
|
+
baseDir = path.dirname(baseDir);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!modulePart) return null;
|
|
149
|
+
|
|
150
|
+
const modulePath = modulePart.replace(/\./g, path.sep);
|
|
151
|
+
const asFile = path.join(baseDir, modulePath + '.py');
|
|
152
|
+
if (fs.existsSync(asFile)) return asFile;
|
|
153
|
+
|
|
154
|
+
const asInit = path.join(baseDir, modulePath, '__init__.py');
|
|
155
|
+
if (fs.existsSync(asInit)) return asInit;
|
|
156
|
+
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Resolve a JS/TS import path to an actual file.
|
|
162
|
+
* Tries: exact, .js, .ts, .jsx, .tsx, /index.js, /index.ts
|
|
163
|
+
*/
|
|
164
|
+
function resolveImportPath(importPath, fileDir, projectRoot, sourceExt) {
|
|
165
|
+
// For Python, importPath is already absolute
|
|
166
|
+
if (path.isAbsolute(importPath)) {
|
|
167
|
+
return fs.existsSync(importPath) ? importPath : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const base = path.resolve(fileDir, importPath);
|
|
171
|
+
|
|
172
|
+
// Try exact
|
|
173
|
+
if (fs.existsSync(base) && fs.statSync(base).isFile()) return base;
|
|
174
|
+
|
|
175
|
+
// Try extensions
|
|
176
|
+
const extensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
|
|
177
|
+
for (const ext of extensions) {
|
|
178
|
+
const withExt = base + ext;
|
|
179
|
+
if (fs.existsSync(withExt)) return withExt;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Try index files
|
|
183
|
+
for (const ext of extensions) {
|
|
184
|
+
const indexFile = path.join(base, 'index' + ext);
|
|
185
|
+
if (fs.existsSync(indexFile)) return indexFile;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* buildImportGraph(fileContents, projectRoot) → { 'relative/path.js': ['relative/dep.js', ...] }
|
|
193
|
+
*
|
|
194
|
+
* fileContents: Array of { filePath, content } (absolute paths)
|
|
195
|
+
* Returns a map of relative file paths to their relative dependencies.
|
|
196
|
+
* Only includes files that have at least one resolved dependency.
|
|
197
|
+
*/
|
|
198
|
+
function buildImportGraph(fileContents, projectRoot) {
|
|
199
|
+
const graph = {};
|
|
200
|
+
|
|
201
|
+
for (const { filePath, content } of fileContents) {
|
|
202
|
+
const deps = extractImports(content, filePath, projectRoot);
|
|
203
|
+
if (deps.length > 0) {
|
|
204
|
+
const relPath = path.relative(projectRoot, filePath);
|
|
205
|
+
graph[relPath] = deps;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return graph;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = { extractImports, buildImportGraph };
|
package/src/sync.js
CHANGED
|
@@ -5,6 +5,7 @@ const { formatSections } = require('./agents/formatter');
|
|
|
5
5
|
const { mergeIntoAgentsMd } = require('./agents/merger');
|
|
6
6
|
const { inferResponsibility } = require('./extractors/filemap');
|
|
7
7
|
const { validateExtracted } = require('./agents/validator');
|
|
8
|
+
const { buildImportGraph } = require('./extractors/imports');
|
|
8
9
|
|
|
9
10
|
const IGNORE_DIRS = new Set(['node_modules', '.git', '__pycache__', '.venv', 'venv', '.idea', '.vscode', '.carto', 'AGENTS.md']);
|
|
10
11
|
|
|
@@ -166,6 +167,20 @@ async function runFullSync(config) {
|
|
|
166
167
|
return true;
|
|
167
168
|
});
|
|
168
169
|
|
|
170
|
+
// Build import graph from all processed files
|
|
171
|
+
const fileContentsForImports = [];
|
|
172
|
+
const allProcessedPaths = [...new Set([...allCodeFiles, ...allFrontendFiles])];
|
|
173
|
+
// Re-read is avoided — collect during processing. Use a second pass for simplicity.
|
|
174
|
+
for (const filePath of allProcessedPaths) {
|
|
175
|
+
try {
|
|
176
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
177
|
+
fileContentsForImports.push({ filePath, content });
|
|
178
|
+
} catch {
|
|
179
|
+
// skip — already warned during extraction
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const importGraph = buildImportGraph(fileContentsForImports, config.projectRoot);
|
|
183
|
+
|
|
169
184
|
// Build file map
|
|
170
185
|
const fileMap = [];
|
|
171
186
|
for (const filePath of allCodeFiles) {
|
|
@@ -204,7 +219,8 @@ async function runFullSync(config) {
|
|
|
204
219
|
fileMap,
|
|
205
220
|
functions: validated.functions,
|
|
206
221
|
dbTables: validated.dbTables,
|
|
207
|
-
envVars: validated.envVars
|
|
222
|
+
envVars: validated.envVars,
|
|
223
|
+
importGraph
|
|
208
224
|
});
|
|
209
225
|
|
|
210
226
|
mergeIntoAgentsMd(config.output, autoContent);
|