@nac3/forge-cli 1.0.43 → 1.0.46

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.
Files changed (48) hide show
  1. package/dist/chat/claude_cli.d.ts.map +1 -1
  2. package/dist/chat/claude_cli.js +24 -21
  3. package/dist/chat/claude_cli.js.map +1 -1
  4. package/dist/chat/panel.d.ts.map +1 -1
  5. package/dist/chat/panel.js +2 -0
  6. package/dist/chat/panel.js.map +1 -1
  7. package/dist/chat/server.d.ts +9 -1
  8. package/dist/chat/server.d.ts.map +1 -1
  9. package/dist/chat/server.js +65 -17
  10. package/dist/chat/server.js.map +1 -1
  11. package/dist/core/project_identity.d.ts +29 -0
  12. package/dist/core/project_identity.d.ts.map +1 -0
  13. package/dist/core/project_identity.js +118 -0
  14. package/dist/core/project_identity.js.map +1 -0
  15. package/dist/core/repo_state.d.ts +4 -0
  16. package/dist/core/repo_state.d.ts.map +1 -1
  17. package/dist/core/repo_state.js +19 -0
  18. package/dist/core/repo_state.js.map +1 -1
  19. package/dist/relay/board_snapshot.d.ts +1 -1
  20. package/dist/relay/board_snapshot.d.ts.map +1 -1
  21. package/dist/relay/board_snapshot.js +3 -1
  22. package/dist/relay/board_snapshot.js.map +1 -1
  23. package/dist/relay/command_consumer.d.ts +8 -2
  24. package/dist/relay/command_consumer.d.ts.map +1 -1
  25. package/dist/relay/command_consumer.js +4 -2
  26. package/dist/relay/command_consumer.js.map +1 -1
  27. package/dist/semantic_graph/data_model.d.ts +29 -0
  28. package/dist/semantic_graph/data_model.d.ts.map +1 -0
  29. package/dist/semantic_graph/data_model.js +288 -0
  30. package/dist/semantic_graph/data_model.js.map +1 -0
  31. package/dist/semantic_graph/derive.d.ts +3 -0
  32. package/dist/semantic_graph/derive.d.ts.map +1 -1
  33. package/dist/semantic_graph/derive.js +143 -41
  34. package/dist/semantic_graph/derive.js.map +1 -1
  35. package/dist/semantic_graph/py_imports.d.ts +30 -0
  36. package/dist/semantic_graph/py_imports.d.ts.map +1 -0
  37. package/dist/semantic_graph/py_imports.js +77 -0
  38. package/dist/semantic_graph/py_imports.js.map +1 -0
  39. package/dist/semantic_graph/render.d.ts.map +1 -1
  40. package/dist/semantic_graph/render.js +2 -1
  41. package/dist/semantic_graph/render.js.map +1 -1
  42. package/dist/semantic_graph/store.js +1 -1
  43. package/dist/semantic_graph/store.js.map +1 -1
  44. package/dist/semantic_graph/types.d.ts +2 -2
  45. package/dist/semantic_graph/types.d.ts.map +1 -1
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.js +1 -1
  48. package/package.json +1 -1
@@ -0,0 +1,288 @@
1
+ /**
2
+ * data_model.ts -- data-model extraction for the semantic graph
3
+ * (Pablo 2026-06-23).
4
+ *
5
+ * The deriver indexed code modules + libraries + tooling, but NOT the
6
+ * project's DATA MODEL -- the tables/entities the app is actually about.
7
+ * In a large RPA/CRM project the schema IS the backbone (e.g. OSDE: the
8
+ * LLM had hand-noted "BD del demo HITL (6 tablas)" because the graph could
9
+ * not see them). This module discovers entities + their relationships from
10
+ * the common ways teams declare a schema, GENERICALLY (ships to customers;
11
+ * nothing hardcoded), and emits them as `datamodel` nodes + `references`
12
+ * edges so the panel/mobile graph + the prompt show the real domain model.
13
+ *
14
+ * Supported declarations (regex extraction, no per-language parser):
15
+ * - SQL : CREATE TABLE [schema.]name (...), FOREIGN KEY/REFERENCES.
16
+ * - SQLAlchemy : class X(Base) + __tablename__ + Column(...) + ForeignKey.
17
+ * - Django ORM : class X(models.Model) + <f> = models.ForeignKey(...).
18
+ * - Pydantic/dataclass: class X(BaseModel) / @dataclass -> entity + fields.
19
+ * - Prisma : model X { ... } + relation fields.
20
+ * - TypeORM : @Entity() class X + @Column / @ManyToOne(() => Y).
21
+ *
22
+ * Bounded: a file/byte/entity cap keeps a pathological repo cheap; when a
23
+ * cap trips it is reported in stats (no silent truncation).
24
+ *
25
+ * ASCII-only.
26
+ */
27
+ import { promises as fs } from 'node:fs';
28
+ import path from 'node:path';
29
+ const EXCLUDED_DIRS = new Set([
30
+ 'node_modules', 'dist', 'build', 'out', 'coverage',
31
+ '__pycache__', '.pytest_cache', '.mypy_cache', '.ruff_cache',
32
+ 'venv', '.venv', 'env', '.tox', 'site-packages', 'Lib',
33
+ '.git', '.svn', '.hg', '.next', '.expo', '.expo-shared',
34
+ '.gradle', '.idea', '.vscode', 'target', 'bin', 'obj',
35
+ '__snapshots__', '.cache',
36
+ ]);
37
+ const SCHEMA_FILE_RE = /\.(sql|py|prisma|ts|js|rb)$/i;
38
+ const MAX_FILES = 1200;
39
+ const MAX_BYTES_PER_FILE = 600_000;
40
+ const MAX_ENTITIES = 300;
41
+ const MAX_FIELDS_SHOWN = 24;
42
+ function dmId(name) {
43
+ return 'datamodel.' + name.replace(/[^a-z0-9_]/gi, '_').toLowerCase();
44
+ }
45
+ async function walkSchemaFiles(root) {
46
+ const out = [];
47
+ const walk = async (dir) => {
48
+ if (out.length >= MAX_FILES)
49
+ return;
50
+ let entries;
51
+ try {
52
+ entries = await fs.readdir(dir, { withFileTypes: true });
53
+ }
54
+ catch {
55
+ return;
56
+ }
57
+ for (const e of entries) {
58
+ if (out.length >= MAX_FILES)
59
+ return;
60
+ if (e.name.startsWith('.') || EXCLUDED_DIRS.has(e.name))
61
+ continue;
62
+ const full = path.join(dir, e.name);
63
+ if (e.isDirectory())
64
+ await walk(full);
65
+ else if (e.isFile() && SCHEMA_FILE_RE.test(e.name))
66
+ out.push(full);
67
+ }
68
+ };
69
+ await walk(root);
70
+ return out;
71
+ }
72
+ /** Extract CREATE TABLE statements (name + columns + references). */
73
+ export function extractSqlTables(src, rel) {
74
+ const out = [];
75
+ const re = /create\s+table\s+(?:if\s+not\s+exists\s+)?[`"\[]?(?:[\w]+\.)?([\w]+)[`"\]]?\s*\(([\s\S]*?)\)\s*;/gi;
76
+ let m;
77
+ while ((m = re.exec(src)) !== null) {
78
+ const name = m[1];
79
+ const body = m[2] || '';
80
+ const fields = [];
81
+ const refs = new Set();
82
+ for (const rawLine of body.split(',')) {
83
+ const line = rawLine.trim();
84
+ if (!line)
85
+ continue;
86
+ const colM = line.match(/^[`"\[]?([\w]+)[`"\]]?\s+/);
87
+ const upper = line.toUpperCase();
88
+ if (colM && colM[1] && !/^(PRIMARY|FOREIGN|UNIQUE|CONSTRAINT|CHECK|KEY|INDEX)\b/.test(upper)) {
89
+ fields.push(colM[1]);
90
+ }
91
+ const refM = line.match(/references\s+[`"\[]?(?:[\w]+\.)?([\w]+)/i);
92
+ if (refM && refM[1])
93
+ refs.add(refM[1]);
94
+ }
95
+ out.push({ name, rel, flavor: 'sql', fields, refs: [...refs] });
96
+ }
97
+ return out;
98
+ }
99
+ /** Python: SQLAlchemy / Django / Pydantic / dataclass entities. */
100
+ export function extractPythonEntities(src, rel) {
101
+ const out = [];
102
+ /* class X(Base|models.Model|BaseModel|...) or @dataclass\nclass X: */
103
+ const classRe = /(^|\n)[ \t]*(@dataclass[\s\S]{0,40}?)?class\s+([A-Za-z_]\w*)\s*(?:\(([^)]*)\))?\s*:/g;
104
+ let m;
105
+ while ((m = classRe.exec(src)) !== null) {
106
+ const decorator = m[2] || '';
107
+ const name = m[3];
108
+ const bases = (m[4] || '').toLowerCase();
109
+ const isOrm = /\bbase\b|models\.model|declarative|sqlmodel/.test(bases);
110
+ const isSchema = /basemodel|pydantic|sqlmodel/.test(bases);
111
+ const isDataclass = /@dataclass/.test(decorator);
112
+ if (!isOrm && !isSchema && !isDataclass)
113
+ continue;
114
+ /* body = until the next top-level `class ` / EOF */
115
+ const start = classRe.lastIndex;
116
+ const restMatch = src.slice(start).match(/\n(?=class\s|\S)/);
117
+ const bodyEnd = restMatch && restMatch.index != null ? start + restMatch.index : Math.min(src.length, start + 4000);
118
+ const body = src.slice(start, bodyEnd);
119
+ const fields = [];
120
+ const refs = new Set();
121
+ /* SQLAlchemy column / Django field / annotated attr */
122
+ for (const fm of body.matchAll(/(^|\n)[ \t]+([a-z_]\w*)\s*[:=]/gi)) {
123
+ const f = fm[2];
124
+ if (f.startsWith('__') || ['class', 'def', 'return', 'self'].includes(f))
125
+ continue;
126
+ if (!fields.includes(f))
127
+ fields.push(f);
128
+ }
129
+ /* relationships: ForeignKey('other.id'), relationship('Other'),
130
+ * models.ForeignKey(Other), ForeignKey(Other, ...) */
131
+ for (const rm of body.matchAll(/(?:ForeignKey|OneToOneField|ManyToManyField|relationship)\s*\(\s*['"]?([A-Za-z_][\w.]*)/g)) {
132
+ const target = rm[1].split('.')[0]; /* 'other.id' -> 'other' ; 'Other' -> 'Other' */
133
+ if (target && target.toLowerCase() !== name.toLowerCase())
134
+ refs.add(target);
135
+ }
136
+ out.push({ name, rel, flavor: isOrm ? 'sqlalchemy/django' : (isDataclass ? 'dataclass' : 'pydantic'), fields, refs: [...refs] });
137
+ }
138
+ return out;
139
+ }
140
+ /** Prisma schema: model X { field Type ... }. */
141
+ export function extractPrismaModels(src, rel) {
142
+ const out = [];
143
+ const re = /model\s+([A-Za-z_]\w*)\s*\{([\s\S]*?)\}/g;
144
+ let m;
145
+ while ((m = re.exec(src)) !== null) {
146
+ const name = m[1];
147
+ const body = m[2] || '';
148
+ const fields = [];
149
+ const refs = new Set();
150
+ for (const line of body.split('\n')) {
151
+ const fm = line.trim().match(/^([A-Za-z_]\w*)\s+([A-Za-z_]\w*)(\[\])?/);
152
+ if (!fm)
153
+ continue;
154
+ const field = fm[1];
155
+ const type = fm[2];
156
+ if (field.startsWith('@'))
157
+ continue;
158
+ fields.push(field);
159
+ /* a field whose type is another model name (capitalised, not a scalar) */
160
+ if (/^[A-Z]/.test(type) && !['String', 'Int', 'Boolean', 'DateTime', 'Float', 'Json', 'Bytes', 'Decimal', 'BigInt'].includes(type)) {
161
+ if (type.toLowerCase() !== name.toLowerCase())
162
+ refs.add(type);
163
+ }
164
+ }
165
+ out.push({ name, rel, flavor: 'prisma', fields, refs: [...refs] });
166
+ }
167
+ return out;
168
+ }
169
+ /** TypeORM: @Entity() class X + @Column + @ManyToOne(() => Y). */
170
+ export function extractTypeOrmEntities(src, rel) {
171
+ if (!/@Entity\s*\(/.test(src))
172
+ return [];
173
+ const out = [];
174
+ const re = /@Entity\s*\([^)]*\)\s*(?:export\s+)?class\s+([A-Za-z_]\w*)([\s\S]*?)(?=\n@Entity|\nexport\s+class|\nclass\s|$)/g;
175
+ let m;
176
+ while ((m = re.exec(src)) !== null) {
177
+ const name = m[1];
178
+ const body = m[2] || '';
179
+ const fields = [];
180
+ const refs = new Set();
181
+ for (const cm of body.matchAll(/@Column\s*\([^)]*\)\s*([A-Za-z_]\w*)/g))
182
+ fields.push(cm[1]);
183
+ for (const rm of body.matchAll(/@(?:ManyToOne|OneToMany|ManyToMany|OneToOne)\s*\(\s*\(\)\s*=>\s*([A-Za-z_]\w*)/g)) {
184
+ if (rm[1] && rm[1].toLowerCase() !== name.toLowerCase())
185
+ refs.add(rm[1]);
186
+ }
187
+ out.push({ name, rel, flavor: 'typeorm', fields, refs: [...refs] });
188
+ }
189
+ return out;
190
+ }
191
+ function entitiesForFile(src, rel) {
192
+ const ext = rel.slice(rel.lastIndexOf('.') + 1).toLowerCase();
193
+ if (ext === 'sql')
194
+ return extractSqlTables(src, rel);
195
+ if (ext === 'prisma')
196
+ return extractPrismaModels(src, rel);
197
+ /* Code files: the language-specific extractor PLUS embedded SQL --
198
+ * Python/JS frequently ship the schema as CREATE TABLE strings inside
199
+ * the code (e.g. sqlite3 cursor.execute), which the ORM extractor would
200
+ * miss. Both run; extractDataModel dedupes by entity name. */
201
+ const out = [];
202
+ if (ext === 'py')
203
+ out.push(...extractPythonEntities(src, rel));
204
+ if (ext === 'ts' || ext === 'js')
205
+ out.push(...extractTypeOrmEntities(src, rel));
206
+ if (ext === 'py' || ext === 'ts' || ext === 'js') {
207
+ if (/create\s+table/i.test(src))
208
+ out.push(...extractSqlTables(src, rel));
209
+ }
210
+ return out;
211
+ }
212
+ /** Discover the project's data model -> datamodel nodes + reference edges. */
213
+ export async function extractDataModel(projectRoot) {
214
+ const now = new Date().toISOString();
215
+ const files = await walkSchemaFiles(projectRoot);
216
+ const capped = files.length >= MAX_FILES;
217
+ /* name (lowercased) -> entity id, so refs resolve even across flavors. */
218
+ const byName = new Map();
219
+ for (const abs of files) {
220
+ let src;
221
+ try {
222
+ const st = await fs.stat(abs);
223
+ if (st.size > MAX_BYTES_PER_FILE)
224
+ continue;
225
+ src = await fs.readFile(abs, 'utf-8');
226
+ }
227
+ catch {
228
+ continue;
229
+ }
230
+ if (byName.size >= MAX_ENTITIES)
231
+ break;
232
+ const rel = path.relative(projectRoot, abs).replace(/\\/g, '/');
233
+ for (const ent of entitiesForFile(src, rel)) {
234
+ if (byName.size >= MAX_ENTITIES)
235
+ break;
236
+ const key = ent.name.toLowerCase();
237
+ const prev = byName.get(key);
238
+ /* merge duplicate declarations (e.g. SQL + ORM of the same table) */
239
+ if (prev) {
240
+ for (const f of ent.fields)
241
+ if (!prev.fields.includes(f))
242
+ prev.fields.push(f);
243
+ for (const r of ent.refs)
244
+ if (!prev.refs.includes(r))
245
+ prev.refs.push(r);
246
+ }
247
+ else {
248
+ byName.set(key, { ...ent });
249
+ }
250
+ }
251
+ }
252
+ const nodes = [];
253
+ for (const ent of byName.values()) {
254
+ const shown = ent.fields.slice(0, MAX_FIELDS_SHOWN);
255
+ const more = ent.fields.length > shown.length ? ' +' + (ent.fields.length - shown.length) + ' mas' : '';
256
+ const purpose = 'Entidad/tabla "' + ent.name + '" (' + ent.flavor + ', '
257
+ + ent.fields.length + ' campos'
258
+ + (shown.length ? ': ' + shown.join(', ') + more : '') + ') [' + ent.rel + ']';
259
+ nodes.push({
260
+ id: dmId(ent.name), kind: 'datamodel', name: ent.name,
261
+ source: 'data-model',
262
+ purpose: purpose.length > 200 ? purpose.slice(0, 197) + '...' : purpose,
263
+ solves: [], do_not_invent: [], api_surface: shown,
264
+ confidence: 'auto', weight: 0.75, added_at: now, updated_at: now,
265
+ });
266
+ }
267
+ const ids = new Set(nodes.map((n) => n.id));
268
+ const edgeMap = new Map();
269
+ let relations = 0;
270
+ for (const ent of byName.values()) {
271
+ const from = dmId(ent.name);
272
+ for (const ref of ent.refs) {
273
+ const to = dmId(ref);
274
+ if (to === from || !ids.has(to))
275
+ continue; /* no dangling */
276
+ const id = 'references:' + from + '->' + to;
277
+ if (!edgeMap.has(id)) {
278
+ edgeMap.set(id, { id, kind: 'references', from_id: from, to_id: to, weight: 0.7, source: 'data-model', created_at: now });
279
+ relations++;
280
+ }
281
+ }
282
+ }
283
+ return {
284
+ nodes, edges: [...edgeMap.values()],
285
+ stats: { entity_nodes: nodes.length, relation_edges: relations, capped },
286
+ };
287
+ }
288
+ //# sourceMappingURL=data_model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data_model.js","sourceRoot":"","sources":["../../src/semantic_graph/data_model.ts"],"names":[],"mappings":"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"}
@@ -10,9 +10,12 @@ export interface DerivedGraph {
10
10
  module_nodes: number;
11
11
  lib_nodes: number;
12
12
  tooling_nodes: number;
13
+ datamodel_nodes: number;
13
14
  module_edges: number;
14
15
  lib_edges: number;
16
+ relation_edges: number;
15
17
  skipped_dangling: number;
18
+ capped: boolean;
16
19
  };
17
20
  }
18
21
  /** Tooling we discover GENERICALLY across any repo (this ships to
@@ -1 +1 @@
1
- {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../../src/semantic_graph/derive.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAoGvD;;wDAEwD;AACxD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAchE;AA+CD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9I;AAuDD;;;;;;;;;sCASsC;AACtC,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CA2F/E;AAoBD;sDACsD;AACtD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,OAAO,CAAC,YAAY,CAAC,CAqIxB;AAED;;qDAEqD;AACrD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAYtD"}
1
+ {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../../src/semantic_graph/derive.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAqJvD;;wDAEwD;AACxD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAchE;AA+CD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;CAChN;AAuDD;;;;;;;;;sCASsC;AACtC,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CA2F/E;AAoBD;sDACsD;AACtD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,OAAO,CAAC,YAAY,CAAC,CA6KxB;AAED;;qDAEqD;AACrD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAYtD"}
@@ -21,6 +21,8 @@ import path from 'node:path';
21
21
  import { ingestFromPackageJson } from '../chat/semantic_graph_ingest.js';
22
22
  import { upsertNodes, upsertEdges, pruneDanglingEdges, pruneStaleDerivedNodes } from './store.js';
23
23
  import { extractImportSpecifiersAst } from './ast_extract.js';
24
+ import { extractPythonImports } from './py_imports.js';
25
+ import { extractDataModel } from './data_model.js';
24
26
  /* PND-078 -- prefer the TS AST import walk (robust to comments/strings);
25
27
  * fall back to the regex for non-TS/JS files or on a parse failure. */
26
28
  const TS_JS_FILE = /\.(tsx?|jsx?|mjs|cjs)$/i;
@@ -120,6 +122,66 @@ async function collectLooseFiles(root, parent, add) {
120
122
  add(parent ? parent + '/' + e : e, e);
121
123
  }
122
124
  }
125
+ /** Top-level dirs already handled by discoverModuleDirs/discoverLooseFiles
126
+ * (their subtrees are scanned there). Everything ELSE in the tree is found
127
+ * by discoverExtraSourceFiles so a project whose code lives outside these
128
+ * (e.g. OSDE's demo_hitl/) is no longer invisible. */
129
+ const FIXED_ROOTS = new Set(['src', 'packages', 'apps', 'app', 'components']);
130
+ const MAX_EXTRA_FILE_NODES = 600;
131
+ /** Recursively discover source FILES anywhere in the tree EXCEPT under the
132
+ * fixed roots (handled elsewhere) and excluded dirs. One node per file so a
133
+ * flat code dir (demo_hitl/ has 73 .py in a single folder) exposes its
134
+ * files + their import edges instead of collapsing to one dot. Bounded by
135
+ * MAX_EXTRA_FILE_NODES; `capped` tells the caller to surface the cut. */
136
+ async function discoverExtraSourceFiles(projectRoot) {
137
+ const seen = new Set();
138
+ const files = [];
139
+ let capped = false;
140
+ const add = (rel, base) => {
141
+ if (files.length >= MAX_EXTRA_FILE_NODES) {
142
+ capped = true;
143
+ return;
144
+ }
145
+ const noExt = base.replace(/\.[^.]+$/, '');
146
+ const id = 'module.' + sanitiseId(noExt);
147
+ if (seen.has(id))
148
+ return;
149
+ seen.add(id);
150
+ files.push({ id, name: base, rel: rel.replace(/\\/g, '/') });
151
+ };
152
+ const walk = async (relDir) => {
153
+ if (files.length >= MAX_EXTRA_FILE_NODES) {
154
+ capped = true;
155
+ return;
156
+ }
157
+ const abs = relDir ? path.join(projectRoot, relDir) : projectRoot;
158
+ let entries;
159
+ try {
160
+ entries = await fs.readdir(abs, { withFileTypes: true });
161
+ }
162
+ catch {
163
+ return;
164
+ }
165
+ for (const e of entries) {
166
+ if (isExcludedDir(e.name))
167
+ continue;
168
+ const childRel = relDir ? relDir + '/' + e.name : e.name;
169
+ if (e.isDirectory()) {
170
+ /* skip the fixed roots at top level (handled elsewhere) */
171
+ if (!relDir && FIXED_ROOTS.has(e.name))
172
+ continue;
173
+ await walk(childRel);
174
+ }
175
+ else if (e.isFile() && isSourceFile(e.name)) {
176
+ /* root-level loose files are already covered by discoverLooseFiles */
177
+ if (relDir)
178
+ add(childRel, e.name);
179
+ }
180
+ }
181
+ };
182
+ await walk('');
183
+ return { files, capped };
184
+ }
123
185
  /** npm/dir name -> id token (lowercase, alfanumerico + dashes). */
124
186
  function sanitiseId(name) {
125
187
  return name.replace(/^@/, '').replace(/[/]/g, '-').replace(/[^a-z0-9\-_]/gi, '-').toLowerCase();
@@ -175,7 +237,7 @@ async function walkSourceFiles(absDir) {
175
237
  }
176
238
  if (st.isDirectory())
177
239
  out.push(...await walkSourceFiles(full));
178
- else if (/\.(ts|tsx|js|jsx|mjs)$/.test(e) && !e.endsWith('.d.ts'))
240
+ else if (isSourceFile(e))
179
241
  out.push(full);
180
242
  }
181
243
  return out;
@@ -462,11 +524,26 @@ export async function deriveProjectGraph(opts) {
462
524
  }
463
525
  /* Loose source files (incl. .py) as their own nodes -- the dir-based
464
526
  * scan above misses a project whose code is a flat file (e.g.
465
- * src/robot.py). rel -> id so import edges can resolve to them. */
527
+ * src/robot.py). rel -> id so import edges can resolve to them.
528
+ * PLUS extra files anywhere outside the fixed roots (e.g. demo_hitl/),
529
+ * so a project whose code lives in a non-standard dir is no longer a
530
+ * handful of dots. */
466
531
  const looseFiles = await discoverLooseFiles(opts.project_root);
532
+ const extra = await discoverExtraSourceFiles(opts.project_root);
533
+ const looseSeen = new Set(looseFiles.map((f) => f.id));
534
+ for (const f of extra.files) {
535
+ if (!looseSeen.has(f.id)) {
536
+ looseSeen.add(f.id);
537
+ looseFiles.push(f);
538
+ }
539
+ }
467
540
  const looseByRel = new Map();
541
+ const byBasename = new Map(); /* file stem -> node id (Python resolution) */
468
542
  for (const lf of looseFiles) {
469
543
  looseByRel.set(lf.rel, lf.id);
544
+ const stem = (lf.name.replace(/\.[^.]+$/, '') || lf.name).toLowerCase();
545
+ if (!byBasename.has(stem))
546
+ byBasename.set(stem, lf.id);
470
547
  moduleNodes.push({
471
548
  id: lf.id, kind: 'module', name: lf.name,
472
549
  source: 'ast', purpose: 'Archivo ' + lf.name + ' (' + lf.rel + ').',
@@ -478,9 +555,14 @@ export async function deriveProjectGraph(opts) {
478
555
  * any repo so a customer's pipeline + helpers are DISCOVERED and land
479
556
  * in the prompt -- the model stops inventing build/deploy commands. */
480
557
  const toolingNodes = await discoverTooling(opts.project_root);
558
+ /* Data model (Pablo 2026-06-23): tables/entities + their relationships,
559
+ * extracted from SQL / ORMs / Prisma. The schema is the project's domain
560
+ * backbone; the graph was blind to it before. Self-contained nodes+edges
561
+ * (both ends are datamodel nodes). */
562
+ const dataModel = await extractDataModel(opts.project_root);
481
563
  const allNodeIds = new Set([
482
564
  ...mods.map((m) => m.id), ...libIds, ...looseFiles.map((f) => f.id),
483
- ...toolingNodes.map((t) => t.id),
565
+ ...toolingNodes.map((t) => t.id), ...dataModel.nodes.map((n) => n.id),
484
566
  ]);
485
567
  function moduleForFile(absFile) {
486
568
  const rel = path.relative(opts.project_root, absFile).replace(/\\/g, '/');
@@ -498,6 +580,32 @@ export async function deriveProjectGraph(opts) {
498
580
  const pkg = spec.startsWith('@') ? spec.split('/').slice(0, 2).join('/') : (spec.split('/')[0] || spec);
499
581
  return 'lib.' + sanitiseId(pkg);
500
582
  }
583
+ /* Resolve a Python import to a project node id (or null). Handles
584
+ * relative (from .mod / from .. import x) by walking up from the file's
585
+ * dir, and absolute dotted (import a.b / from a.b import c) by trying the
586
+ * dotted path as a file/package, then falling back to a basename match.
587
+ * byBasename holds only real node ids, so a fallback can never dangle. */
588
+ function resolvePyImport(imp, fileAbs) {
589
+ const parts = imp.module.split('.').filter(Boolean);
590
+ if (parts.length === 0)
591
+ return null;
592
+ const tryPath = (baseAbs) => {
593
+ const asFile = moduleForFile(path.join(baseAbs, ...parts) + '.py');
594
+ if (asFile)
595
+ return asFile;
596
+ return moduleForFile(path.join(baseAbs, ...parts, '__init__.py'));
597
+ };
598
+ if (imp.dots > 0) {
599
+ let baseDir = path.dirname(fileAbs);
600
+ for (let i = 1; i < imp.dots; i++)
601
+ baseDir = path.dirname(baseDir);
602
+ return tryPath(baseDir) || byBasename.get(parts[parts.length - 1].toLowerCase()) || null;
603
+ }
604
+ return tryPath(opts.project_root)
605
+ || byBasename.get(parts[parts.length - 1].toLowerCase())
606
+ || byBasename.get(parts[0].toLowerCase())
607
+ || null;
608
+ }
501
609
  /* Aristas. */
502
610
  const edgeMap = new Map();
503
611
  let skipped = 0;
@@ -512,56 +620,47 @@ export async function deriveProjectGraph(opts) {
512
620
  if (!edgeMap.has(id))
513
621
  edgeMap.set(id, { id, kind, from_id: from, to_id: to, weight: 0.6, source: 'ast', created_at: now });
514
622
  }
515
- for (const m of mods) {
516
- const files = await walkSourceFiles(path.join(opts.project_root, m.dir));
517
- for (const f of files) {
518
- const owner = moduleForFile(f) || m.id;
519
- let src;
520
- try {
521
- src = await fs.readFile(f, 'utf-8');
522
- }
523
- catch {
524
- continue;
525
- }
526
- for (const spec of importSpecifiersFor(src, f)) {
527
- if (spec.startsWith('node:') || spec.startsWith('bun:'))
528
- continue;
529
- if (spec.startsWith('.') || spec.startsWith('/')) {
530
- const target = moduleForFile(path.resolve(path.dirname(f), spec));
531
- if (target)
532
- addEdge(owner, target, 'uses');
533
- }
534
- else {
535
- addEdge(owner, libIdFor(spec), 'depends_on');
536
- }
537
- }
538
- }
539
- }
540
- /* Loose-file imports (JS/TS specifiers only; .py yields none -- the
541
- * Python-import grammar is not parsed here, so loose .py files appear
542
- * as nodes without edges, which is correct for now). */
543
- for (const lf of looseFiles) {
544
- const abs = path.join(opts.project_root, lf.rel);
623
+ /* Parse a file's imports (language-aware) and add resolved edges from
624
+ * ownerId. JS/TS uses the AST/regex specifier walk; .py uses the Python
625
+ * import grammar. Non-(JS|TS|PY) source files yield no specifiers. */
626
+ async function addEdgesForFile(absFile, ownerId) {
545
627
  let src;
546
628
  try {
547
- src = await fs.readFile(abs, 'utf-8');
629
+ src = await fs.readFile(absFile, 'utf-8');
548
630
  }
549
631
  catch {
550
- continue;
632
+ return;
633
+ }
634
+ if (/\.py$/i.test(absFile)) {
635
+ for (const imp of extractPythonImports(src)) {
636
+ const target = resolvePyImport(imp, absFile);
637
+ if (target)
638
+ addEdge(ownerId, target, 'uses');
639
+ }
640
+ return;
551
641
  }
552
- for (const spec of importSpecifiersFor(src, abs)) {
642
+ for (const spec of importSpecifiersFor(src, absFile)) {
553
643
  if (spec.startsWith('node:') || spec.startsWith('bun:'))
554
644
  continue;
555
645
  if (spec.startsWith('.') || spec.startsWith('/')) {
556
- const target = moduleForFile(path.resolve(path.dirname(abs), spec));
646
+ const target = moduleForFile(path.resolve(path.dirname(absFile), spec));
557
647
  if (target)
558
- addEdge(lf.id, target, 'uses');
648
+ addEdge(ownerId, target, 'uses');
559
649
  }
560
650
  else {
561
- addEdge(lf.id, libIdFor(spec), 'depends_on');
651
+ addEdge(ownerId, libIdFor(spec), 'depends_on');
562
652
  }
563
653
  }
564
654
  }
655
+ for (const m of mods) {
656
+ const files = await walkSourceFiles(path.join(opts.project_root, m.dir));
657
+ for (const f of files)
658
+ await addEdgesForFile(f, moduleForFile(f) || m.id);
659
+ }
660
+ /* Loose + extra files (incl. .py with real Python-import edges now). */
661
+ for (const lf of looseFiles) {
662
+ await addEdgesForFile(path.join(opts.project_root, lf.rel), lf.id);
663
+ }
565
664
  /* Los nodos de libreria del ingest no traen added_at/updated_at
566
665
  * (su shape es IngestedNode); normalizarlos para el store. */
567
666
  const libNodes = libIngest.map((l) => ({
@@ -569,17 +668,20 @@ export async function deriveProjectGraph(opts) {
569
668
  added_at: now,
570
669
  updated_at: now,
571
670
  }));
572
- const edges = [...edgeMap.values()];
671
+ const edges = [...edgeMap.values(), ...dataModel.edges];
573
672
  return {
574
- nodes: [...moduleNodes, ...libNodes, ...toolingNodes],
673
+ nodes: [...moduleNodes, ...libNodes, ...toolingNodes, ...dataModel.nodes],
575
674
  edges,
576
675
  stats: {
577
676
  module_nodes: moduleNodes.length,
578
677
  lib_nodes: libIngest.length,
579
678
  tooling_nodes: toolingNodes.length,
679
+ datamodel_nodes: dataModel.nodes.length,
580
680
  module_edges: edges.filter((e) => e.to_id.startsWith('module.')).length,
581
681
  lib_edges: edges.filter((e) => e.to_id.startsWith('lib.')).length,
682
+ relation_edges: dataModel.edges.length,
582
683
  skipped_dangling: skipped,
684
+ capped: extra.capped || dataModel.stats.capped,
583
685
  },
584
686
  };
585
687
  }