carto-md 1.0.1 → 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/agents/validator.js +88 -0
- package/src/extractors/frontend.js +1 -1
- package/src/extractors/imports.js +212 -0
- package/src/sync.js +35 -9
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,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* validateExtracted(data) → cleaned data
|
|
3
|
+
*
|
|
4
|
+
* Runs after extraction, before formatting.
|
|
5
|
+
* Drops anything that looks wrong. Never throws.
|
|
6
|
+
*/
|
|
7
|
+
function validateExtracted({ routes, models, functions, envVars, dbTables }) {
|
|
8
|
+
return {
|
|
9
|
+
routes: validateRoutes(routes || []),
|
|
10
|
+
models: validateModels(models || []),
|
|
11
|
+
functions: validateFunctions(functions || {}),
|
|
12
|
+
envVars: validateEnvVars(envVars || []),
|
|
13
|
+
dbTables: validateDBTables(dbTables || [])
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const VALID_METHODS = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']);
|
|
18
|
+
const VALID_HANDLER = /^[a-zA-Z_]\w*$|^\[anonymous\]$/;
|
|
19
|
+
const VALID_CLASS_NAME = /^[A-Z][a-zA-Z0-9]*$/;
|
|
20
|
+
const VALID_FIELD_NAME = /^[a-z_]\w*$/;
|
|
21
|
+
const VALID_FUNC_NAME = /^_?[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
22
|
+
const VALID_ENV_VAR = /^[A-Z][A-Z0-9_]*$/;
|
|
23
|
+
const VALID_TABLE_NAME = /^[a-z][a-z0-9_]*$/;
|
|
24
|
+
|
|
25
|
+
function validateRoutes(routes) {
|
|
26
|
+
return routes.filter(r => {
|
|
27
|
+
if (!r.method || !VALID_METHODS.has(r.method)) return false;
|
|
28
|
+
if (!r.path || (!r.path.startsWith('/') && r.path !== '[dynamic]' && r.path !== '[inferred]')) return false;
|
|
29
|
+
if (!r.functionName || !VALID_HANDLER.test(r.functionName)) return false;
|
|
30
|
+
return true;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function validateModels(models) {
|
|
35
|
+
return models
|
|
36
|
+
.map(m => {
|
|
37
|
+
if (!m.className || !VALID_CLASS_NAME.test(m.className)) return null;
|
|
38
|
+
if (!Array.isArray(m.fields)) return null;
|
|
39
|
+
|
|
40
|
+
const validFields = m.fields.filter(f => {
|
|
41
|
+
if (!f.name || !VALID_FIELD_NAME.test(f.name)) return false;
|
|
42
|
+
return true;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (validFields.length === 0) return null;
|
|
46
|
+
return { className: m.className, fields: validFields };
|
|
47
|
+
})
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateFunctions(functionsMap) {
|
|
52
|
+
const cleaned = {};
|
|
53
|
+
|
|
54
|
+
for (const [filename, funcs] of Object.entries(functionsMap)) {
|
|
55
|
+
const validFuncs = funcs.filter(f => {
|
|
56
|
+
// Name must be a valid identifier
|
|
57
|
+
if (!f.name || !VALID_FUNC_NAME.test(f.name)) return false;
|
|
58
|
+
// Name must not contain spaces, brackets, commas
|
|
59
|
+
if (/[\s\[\],]/.test(f.name)) return false;
|
|
60
|
+
// Params must not contain parse artifacts
|
|
61
|
+
if (f.params && (/\[\[/.test(f.params) || /\]\]/.test(f.params) || /Any\]\]/.test(f.params))) return false;
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (validFuncs.length > 0) {
|
|
66
|
+
cleaned[filename] = validFuncs;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return cleaned;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function validateEnvVars(envVars) {
|
|
74
|
+
return envVars.filter(v => {
|
|
75
|
+
if (!v.name || !VALID_ENV_VAR.test(v.name)) return false;
|
|
76
|
+
return true;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function validateDBTables(dbTables) {
|
|
81
|
+
return dbTables.filter(t => {
|
|
82
|
+
if (!t.tableName || !VALID_TABLE_NAME.test(t.tableName)) return false;
|
|
83
|
+
if (!t.modelName || !VALID_CLASS_NAME.test(t.modelName)) return false;
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { validateExtracted };
|
|
@@ -42,7 +42,7 @@ function extractFrontend(content) {
|
|
|
42
42
|
const dynamicCount = fetches.filter(f => f.url === '[dynamic]').length;
|
|
43
43
|
if (dynamicCount > 0) {
|
|
44
44
|
staticFetches.push({
|
|
45
|
-
url: `
|
|
45
|
+
url: `dynamic calls detected (${dynamicCount} unresolved)`,
|
|
46
46
|
method: '\u2014'
|
|
47
47
|
});
|
|
48
48
|
}
|
|
@@ -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
|
@@ -4,8 +4,10 @@ const { loadLanguagePlugins, getPluginForFile } = require('./extractors/loader')
|
|
|
4
4
|
const { formatSections } = require('./agents/formatter');
|
|
5
5
|
const { mergeIntoAgentsMd } = require('./agents/merger');
|
|
6
6
|
const { inferResponsibility } = require('./extractors/filemap');
|
|
7
|
+
const { validateExtracted } = require('./agents/validator');
|
|
8
|
+
const { buildImportGraph } = require('./extractors/imports');
|
|
7
9
|
|
|
8
|
-
const IGNORE_DIRS = new Set(['node_modules', '.git', '__pycache__', '.venv', 'venv', '.idea', '.vscode', '.carto']);
|
|
10
|
+
const IGNORE_DIRS = new Set(['node_modules', '.git', '__pycache__', '.venv', 'venv', '.idea', '.vscode', '.carto', 'AGENTS.md']);
|
|
9
11
|
|
|
10
12
|
// Load plugins once at module load
|
|
11
13
|
const plugins = loadLanguagePlugins();
|
|
@@ -143,16 +145,16 @@ async function runFullSync(config) {
|
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
// Global dedup: collapse dynamic fetches across all files into one summary row
|
|
146
|
-
const staticFetches = allFetches.filter(f => !f.url.startsWith('
|
|
148
|
+
const staticFetches = allFetches.filter(f => f.url !== '[dynamic]' && !f.url.startsWith('dynamic calls detected'));
|
|
147
149
|
let totalDynamic = 0;
|
|
148
150
|
for (const f of allFetches) {
|
|
149
151
|
if (f.url === '[dynamic]') totalDynamic++;
|
|
150
|
-
// Also count already-collapsed per-file rows
|
|
151
|
-
const m = f.url.match(
|
|
152
|
+
// Also count already-collapsed per-file rows
|
|
153
|
+
const m = f.url.match(/^dynamic calls detected \((\d+) unresolved\)$/);
|
|
152
154
|
if (m) totalDynamic += parseInt(m[1], 10);
|
|
153
155
|
}
|
|
154
156
|
if (totalDynamic > 0) {
|
|
155
|
-
staticFetches.push({ url: `
|
|
157
|
+
staticFetches.push({ url: `dynamic calls detected (${totalDynamic} unresolved)`, method: '\u2014' });
|
|
156
158
|
}
|
|
157
159
|
allFetches = staticFetches;
|
|
158
160
|
|
|
@@ -165,6 +167,20 @@ async function runFullSync(config) {
|
|
|
165
167
|
return true;
|
|
166
168
|
});
|
|
167
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
|
+
|
|
168
184
|
// Build file map
|
|
169
185
|
const fileMap = [];
|
|
170
186
|
for (const filePath of allCodeFiles) {
|
|
@@ -185,16 +201,26 @@ async function runFullSync(config) {
|
|
|
185
201
|
// Scan project structure
|
|
186
202
|
const structure = await scanStructure(config.projectRoot);
|
|
187
203
|
|
|
188
|
-
|
|
204
|
+
// Validate extracted data — drop anything malformed
|
|
205
|
+
const validated = validateExtracted({
|
|
189
206
|
routes: allRoutes,
|
|
190
207
|
models: allModels,
|
|
208
|
+
functions: functionsMap,
|
|
209
|
+
envVars,
|
|
210
|
+
dbTables: dbTableList
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const autoContent = formatSections({
|
|
214
|
+
routes: validated.routes,
|
|
215
|
+
models: validated.models,
|
|
191
216
|
frontend: { fetches: allFetches, storageKeys: allStorageKeys },
|
|
192
217
|
structure,
|
|
193
218
|
warnings,
|
|
194
219
|
fileMap,
|
|
195
|
-
functions:
|
|
196
|
-
dbTables:
|
|
197
|
-
envVars
|
|
220
|
+
functions: validated.functions,
|
|
221
|
+
dbTables: validated.dbTables,
|
|
222
|
+
envVars: validated.envVars,
|
|
223
|
+
importGraph
|
|
198
224
|
});
|
|
199
225
|
|
|
200
226
|
mergeIntoAgentsMd(config.output, autoContent);
|