carto-md 1.0.1 → 1.0.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carto-md",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "The context layer for AI-native development.",
5
5
  "bin": {
6
6
  "carto": "src/cli/index.js"
@@ -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: `[dynamic \u00d7${dynamicCount}]`,
45
+ url: `dynamic calls detected (${dynamicCount} unresolved)`,
46
46
  method: '\u2014'
47
47
  });
48
48
  }
package/src/sync.js CHANGED
@@ -4,8 +4,9 @@ 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');
7
8
 
8
- const IGNORE_DIRS = new Set(['node_modules', '.git', '__pycache__', '.venv', 'venv', '.idea', '.vscode', '.carto']);
9
+ const IGNORE_DIRS = new Set(['node_modules', '.git', '__pycache__', '.venv', 'venv', '.idea', '.vscode', '.carto', 'AGENTS.md']);
9
10
 
10
11
  // Load plugins once at module load
11
12
  const plugins = loadLanguagePlugins();
@@ -143,16 +144,16 @@ async function runFullSync(config) {
143
144
  }
144
145
 
145
146
  // Global dedup: collapse dynamic fetches across all files into one summary row
146
- const staticFetches = allFetches.filter(f => !f.url.startsWith('[dynamic'));
147
+ const staticFetches = allFetches.filter(f => f.url !== '[dynamic]' && !f.url.startsWith('dynamic calls detected'));
147
148
  let totalDynamic = 0;
148
149
  for (const f of allFetches) {
149
150
  if (f.url === '[dynamic]') totalDynamic++;
150
- // Also count already-collapsed per-file rows like "[dynamic ×1]"
151
- const m = f.url.match(/^\[dynamic\s*\u00d7(\d+)\]$/);
151
+ // Also count already-collapsed per-file rows
152
+ const m = f.url.match(/^dynamic calls detected \((\d+) unresolved\)$/);
152
153
  if (m) totalDynamic += parseInt(m[1], 10);
153
154
  }
154
155
  if (totalDynamic > 0) {
155
- staticFetches.push({ url: `[dynamic \u00d7${totalDynamic}]`, method: '\u2014' });
156
+ staticFetches.push({ url: `dynamic calls detected (${totalDynamic} unresolved)`, method: '\u2014' });
156
157
  }
157
158
  allFetches = staticFetches;
158
159
 
@@ -185,16 +186,25 @@ async function runFullSync(config) {
185
186
  // Scan project structure
186
187
  const structure = await scanStructure(config.projectRoot);
187
188
 
188
- const autoContent = formatSections({
189
+ // Validate extracted data — drop anything malformed
190
+ const validated = validateExtracted({
189
191
  routes: allRoutes,
190
192
  models: allModels,
193
+ functions: functionsMap,
194
+ envVars,
195
+ dbTables: dbTableList
196
+ });
197
+
198
+ const autoContent = formatSections({
199
+ routes: validated.routes,
200
+ models: validated.models,
191
201
  frontend: { fetches: allFetches, storageKeys: allStorageKeys },
192
202
  structure,
193
203
  warnings,
194
204
  fileMap,
195
- functions: functionsMap,
196
- dbTables: dbTableList,
197
- envVars
205
+ functions: validated.functions,
206
+ dbTables: validated.dbTables,
207
+ envVars: validated.envVars
198
208
  });
199
209
 
200
210
  mergeIntoAgentsMd(config.output, autoContent);