@nac3/forge-cli 1.0.44 → 1.0.47
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/dist/chat/panel.d.ts.map +1 -1
- package/dist/chat/panel.js +2 -0
- package/dist/chat/panel.js.map +1 -1
- package/dist/chat/server.d.ts +9 -1
- package/dist/chat/server.d.ts.map +1 -1
- package/dist/chat/server.js +65 -17
- package/dist/chat/server.js.map +1 -1
- package/dist/core/project_identity.d.ts +29 -0
- package/dist/core/project_identity.d.ts.map +1 -0
- package/dist/core/project_identity.js +118 -0
- package/dist/core/project_identity.js.map +1 -0
- package/dist/core/repo_state.d.ts +4 -0
- package/dist/core/repo_state.d.ts.map +1 -1
- package/dist/core/repo_state.js +19 -0
- package/dist/core/repo_state.js.map +1 -1
- package/dist/relay/board_snapshot.d.ts +1 -1
- package/dist/relay/board_snapshot.d.ts.map +1 -1
- package/dist/relay/board_snapshot.js +3 -1
- package/dist/relay/board_snapshot.js.map +1 -1
- package/dist/relay/command_consumer.d.ts +8 -2
- package/dist/relay/command_consumer.d.ts.map +1 -1
- package/dist/relay/command_consumer.js +4 -2
- package/dist/relay/command_consumer.js.map +1 -1
- package/dist/semantic_graph/data_model.d.ts +29 -0
- package/dist/semantic_graph/data_model.d.ts.map +1 -0
- package/dist/semantic_graph/data_model.js +288 -0
- package/dist/semantic_graph/data_model.js.map +1 -0
- package/dist/semantic_graph/derive.d.ts +3 -0
- package/dist/semantic_graph/derive.d.ts.map +1 -1
- package/dist/semantic_graph/derive.js +163 -39
- package/dist/semantic_graph/derive.js.map +1 -1
- package/dist/semantic_graph/lang_imports.d.ts +32 -0
- package/dist/semantic_graph/lang_imports.d.ts.map +1 -0
- package/dist/semantic_graph/lang_imports.js +111 -0
- package/dist/semantic_graph/lang_imports.js.map +1 -0
- package/dist/semantic_graph/py_imports.d.ts +30 -0
- package/dist/semantic_graph/py_imports.d.ts.map +1 -0
- package/dist/semantic_graph/py_imports.js +77 -0
- package/dist/semantic_graph/py_imports.js.map +1 -0
- package/dist/semantic_graph/render.d.ts.map +1 -1
- package/dist/semantic_graph/render.js +2 -1
- package/dist/semantic_graph/render.js.map +1 -1
- package/dist/semantic_graph/store.js +1 -1
- package/dist/semantic_graph/store.js.map +1 -1
- package/dist/semantic_graph/types.d.ts +2 -2
- package/dist/semantic_graph/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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;
|
|
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;AAsJvD;;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,CAiMxB;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,9 @@ 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 { extractLangImports } from './lang_imports.js';
|
|
26
|
+
import { extractDataModel } from './data_model.js';
|
|
24
27
|
/* PND-078 -- prefer the TS AST import walk (robust to comments/strings);
|
|
25
28
|
* fall back to the regex for non-TS/JS files or on a parse failure. */
|
|
26
29
|
const TS_JS_FILE = /\.(tsx?|jsx?|mjs|cjs)$/i;
|
|
@@ -120,6 +123,66 @@ async function collectLooseFiles(root, parent, add) {
|
|
|
120
123
|
add(parent ? parent + '/' + e : e, e);
|
|
121
124
|
}
|
|
122
125
|
}
|
|
126
|
+
/** Top-level dirs already handled by discoverModuleDirs/discoverLooseFiles
|
|
127
|
+
* (their subtrees are scanned there). Everything ELSE in the tree is found
|
|
128
|
+
* by discoverExtraSourceFiles so a project whose code lives outside these
|
|
129
|
+
* (e.g. OSDE's demo_hitl/) is no longer invisible. */
|
|
130
|
+
const FIXED_ROOTS = new Set(['src', 'packages', 'apps', 'app', 'components']);
|
|
131
|
+
const MAX_EXTRA_FILE_NODES = 600;
|
|
132
|
+
/** Recursively discover source FILES anywhere in the tree EXCEPT under the
|
|
133
|
+
* fixed roots (handled elsewhere) and excluded dirs. One node per file so a
|
|
134
|
+
* flat code dir (demo_hitl/ has 73 .py in a single folder) exposes its
|
|
135
|
+
* files + their import edges instead of collapsing to one dot. Bounded by
|
|
136
|
+
* MAX_EXTRA_FILE_NODES; `capped` tells the caller to surface the cut. */
|
|
137
|
+
async function discoverExtraSourceFiles(projectRoot) {
|
|
138
|
+
const seen = new Set();
|
|
139
|
+
const files = [];
|
|
140
|
+
let capped = false;
|
|
141
|
+
const add = (rel, base) => {
|
|
142
|
+
if (files.length >= MAX_EXTRA_FILE_NODES) {
|
|
143
|
+
capped = true;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const noExt = base.replace(/\.[^.]+$/, '');
|
|
147
|
+
const id = 'module.' + sanitiseId(noExt);
|
|
148
|
+
if (seen.has(id))
|
|
149
|
+
return;
|
|
150
|
+
seen.add(id);
|
|
151
|
+
files.push({ id, name: base, rel: rel.replace(/\\/g, '/') });
|
|
152
|
+
};
|
|
153
|
+
const walk = async (relDir) => {
|
|
154
|
+
if (files.length >= MAX_EXTRA_FILE_NODES) {
|
|
155
|
+
capped = true;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const abs = relDir ? path.join(projectRoot, relDir) : projectRoot;
|
|
159
|
+
let entries;
|
|
160
|
+
try {
|
|
161
|
+
entries = await fs.readdir(abs, { withFileTypes: true });
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
for (const e of entries) {
|
|
167
|
+
if (isExcludedDir(e.name))
|
|
168
|
+
continue;
|
|
169
|
+
const childRel = relDir ? relDir + '/' + e.name : e.name;
|
|
170
|
+
if (e.isDirectory()) {
|
|
171
|
+
/* skip the fixed roots at top level (handled elsewhere) */
|
|
172
|
+
if (!relDir && FIXED_ROOTS.has(e.name))
|
|
173
|
+
continue;
|
|
174
|
+
await walk(childRel);
|
|
175
|
+
}
|
|
176
|
+
else if (e.isFile() && isSourceFile(e.name)) {
|
|
177
|
+
/* root-level loose files are already covered by discoverLooseFiles */
|
|
178
|
+
if (relDir)
|
|
179
|
+
add(childRel, e.name);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
await walk('');
|
|
184
|
+
return { files, capped };
|
|
185
|
+
}
|
|
123
186
|
/** npm/dir name -> id token (lowercase, alfanumerico + dashes). */
|
|
124
187
|
function sanitiseId(name) {
|
|
125
188
|
return name.replace(/^@/, '').replace(/[/]/g, '-').replace(/[^a-z0-9\-_]/gi, '-').toLowerCase();
|
|
@@ -175,7 +238,7 @@ async function walkSourceFiles(absDir) {
|
|
|
175
238
|
}
|
|
176
239
|
if (st.isDirectory())
|
|
177
240
|
out.push(...await walkSourceFiles(full));
|
|
178
|
-
else if (
|
|
241
|
+
else if (isSourceFile(e))
|
|
179
242
|
out.push(full);
|
|
180
243
|
}
|
|
181
244
|
return out;
|
|
@@ -462,11 +525,26 @@ export async function deriveProjectGraph(opts) {
|
|
|
462
525
|
}
|
|
463
526
|
/* Loose source files (incl. .py) as their own nodes -- the dir-based
|
|
464
527
|
* 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.
|
|
528
|
+
* src/robot.py). rel -> id so import edges can resolve to them.
|
|
529
|
+
* PLUS extra files anywhere outside the fixed roots (e.g. demo_hitl/),
|
|
530
|
+
* so a project whose code lives in a non-standard dir is no longer a
|
|
531
|
+
* handful of dots. */
|
|
466
532
|
const looseFiles = await discoverLooseFiles(opts.project_root);
|
|
533
|
+
const extra = await discoverExtraSourceFiles(opts.project_root);
|
|
534
|
+
const looseSeen = new Set(looseFiles.map((f) => f.id));
|
|
535
|
+
for (const f of extra.files) {
|
|
536
|
+
if (!looseSeen.has(f.id)) {
|
|
537
|
+
looseSeen.add(f.id);
|
|
538
|
+
looseFiles.push(f);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
467
541
|
const looseByRel = new Map();
|
|
542
|
+
const byBasename = new Map(); /* file stem -> node id (Python resolution) */
|
|
468
543
|
for (const lf of looseFiles) {
|
|
469
544
|
looseByRel.set(lf.rel, lf.id);
|
|
545
|
+
const stem = (lf.name.replace(/\.[^.]+$/, '') || lf.name).toLowerCase();
|
|
546
|
+
if (!byBasename.has(stem))
|
|
547
|
+
byBasename.set(stem, lf.id);
|
|
470
548
|
moduleNodes.push({
|
|
471
549
|
id: lf.id, kind: 'module', name: lf.name,
|
|
472
550
|
source: 'ast', purpose: 'Archivo ' + lf.name + ' (' + lf.rel + ').',
|
|
@@ -478,9 +556,14 @@ export async function deriveProjectGraph(opts) {
|
|
|
478
556
|
* any repo so a customer's pipeline + helpers are DISCOVERED and land
|
|
479
557
|
* in the prompt -- the model stops inventing build/deploy commands. */
|
|
480
558
|
const toolingNodes = await discoverTooling(opts.project_root);
|
|
559
|
+
/* Data model (Pablo 2026-06-23): tables/entities + their relationships,
|
|
560
|
+
* extracted from SQL / ORMs / Prisma. The schema is the project's domain
|
|
561
|
+
* backbone; the graph was blind to it before. Self-contained nodes+edges
|
|
562
|
+
* (both ends are datamodel nodes). */
|
|
563
|
+
const dataModel = await extractDataModel(opts.project_root);
|
|
481
564
|
const allNodeIds = new Set([
|
|
482
565
|
...mods.map((m) => m.id), ...libIds, ...looseFiles.map((f) => f.id),
|
|
483
|
-
...toolingNodes.map((t) => t.id),
|
|
566
|
+
...toolingNodes.map((t) => t.id), ...dataModel.nodes.map((n) => n.id),
|
|
484
567
|
]);
|
|
485
568
|
function moduleForFile(absFile) {
|
|
486
569
|
const rel = path.relative(opts.project_root, absFile).replace(/\\/g, '/');
|
|
@@ -498,6 +581,32 @@ export async function deriveProjectGraph(opts) {
|
|
|
498
581
|
const pkg = spec.startsWith('@') ? spec.split('/').slice(0, 2).join('/') : (spec.split('/')[0] || spec);
|
|
499
582
|
return 'lib.' + sanitiseId(pkg);
|
|
500
583
|
}
|
|
584
|
+
/* Resolve a Python import to a project node id (or null). Handles
|
|
585
|
+
* relative (from .mod / from .. import x) by walking up from the file's
|
|
586
|
+
* dir, and absolute dotted (import a.b / from a.b import c) by trying the
|
|
587
|
+
* dotted path as a file/package, then falling back to a basename match.
|
|
588
|
+
* byBasename holds only real node ids, so a fallback can never dangle. */
|
|
589
|
+
function resolvePyImport(imp, fileAbs) {
|
|
590
|
+
const parts = imp.module.split('.').filter(Boolean);
|
|
591
|
+
if (parts.length === 0)
|
|
592
|
+
return null;
|
|
593
|
+
const tryPath = (baseAbs) => {
|
|
594
|
+
const asFile = moduleForFile(path.join(baseAbs, ...parts) + '.py');
|
|
595
|
+
if (asFile)
|
|
596
|
+
return asFile;
|
|
597
|
+
return moduleForFile(path.join(baseAbs, ...parts, '__init__.py'));
|
|
598
|
+
};
|
|
599
|
+
if (imp.dots > 0) {
|
|
600
|
+
let baseDir = path.dirname(fileAbs);
|
|
601
|
+
for (let i = 1; i < imp.dots; i++)
|
|
602
|
+
baseDir = path.dirname(baseDir);
|
|
603
|
+
return tryPath(baseDir) || byBasename.get(parts[parts.length - 1].toLowerCase()) || null;
|
|
604
|
+
}
|
|
605
|
+
return tryPath(opts.project_root)
|
|
606
|
+
|| byBasename.get(parts[parts.length - 1].toLowerCase())
|
|
607
|
+
|| byBasename.get(parts[0].toLowerCase())
|
|
608
|
+
|| null;
|
|
609
|
+
}
|
|
501
610
|
/* Aristas. */
|
|
502
611
|
const edgeMap = new Map();
|
|
503
612
|
let skipped = 0;
|
|
@@ -512,56 +621,68 @@ export async function deriveProjectGraph(opts) {
|
|
|
512
621
|
if (!edgeMap.has(id))
|
|
513
622
|
edgeMap.set(id, { id, kind, from_id: from, to_id: to, weight: 0.6, source: 'ast', created_at: now });
|
|
514
623
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
}
|
|
624
|
+
/* Parse a file's imports (language-aware) and add resolved edges from
|
|
625
|
+
* ownerId. JS/TS uses the AST/regex specifier walk; .py uses the Python
|
|
626
|
+
* import grammar. Non-(JS|TS|PY) source files yield no specifiers. */
|
|
627
|
+
/* Resolve a relative import (Ruby require_relative / PHP require path) to a
|
|
628
|
+
* project node, trying the bare ref and the common code extensions. */
|
|
629
|
+
function resolveRelByExt(dirAbs, ref) {
|
|
630
|
+
for (const c of [ref, ref + '.rb', ref + '.php', ref + '.go', ref + '/index.php', ref + '/index.rb']) {
|
|
631
|
+
const t = moduleForFile(path.resolve(dirAbs, c));
|
|
632
|
+
if (t)
|
|
633
|
+
return t;
|
|
538
634
|
}
|
|
635
|
+
return null;
|
|
539
636
|
}
|
|
540
|
-
|
|
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);
|
|
637
|
+
async function addEdgesForFile(absFile, ownerId) {
|
|
545
638
|
let src;
|
|
546
639
|
try {
|
|
547
|
-
src = await fs.readFile(
|
|
640
|
+
src = await fs.readFile(absFile, 'utf-8');
|
|
548
641
|
}
|
|
549
642
|
catch {
|
|
550
|
-
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (/\.py$/i.test(absFile)) {
|
|
646
|
+
for (const imp of extractPythonImports(src)) {
|
|
647
|
+
const target = resolvePyImport(imp, absFile);
|
|
648
|
+
if (target)
|
|
649
|
+
addEdge(ownerId, target, 'uses');
|
|
650
|
+
}
|
|
651
|
+
return;
|
|
551
652
|
}
|
|
552
|
-
|
|
653
|
+
const ext = absFile.slice(absFile.lastIndexOf('.') + 1).toLowerCase();
|
|
654
|
+
if (ext === 'go' || ext === 'rb' || ext === 'php') {
|
|
655
|
+
for (const r of extractLangImports(src, ext)) {
|
|
656
|
+
const target = r.relative
|
|
657
|
+
? resolveRelByExt(path.dirname(absFile), r.ref)
|
|
658
|
+
: (byBasename.get(r.ref.toLowerCase()) ?? null);
|
|
659
|
+
if (target)
|
|
660
|
+
addEdge(ownerId, target, 'uses');
|
|
661
|
+
}
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
for (const spec of importSpecifiersFor(src, absFile)) {
|
|
553
665
|
if (spec.startsWith('node:') || spec.startsWith('bun:'))
|
|
554
666
|
continue;
|
|
555
667
|
if (spec.startsWith('.') || spec.startsWith('/')) {
|
|
556
|
-
const target = moduleForFile(path.resolve(path.dirname(
|
|
668
|
+
const target = moduleForFile(path.resolve(path.dirname(absFile), spec));
|
|
557
669
|
if (target)
|
|
558
|
-
addEdge(
|
|
670
|
+
addEdge(ownerId, target, 'uses');
|
|
559
671
|
}
|
|
560
672
|
else {
|
|
561
|
-
addEdge(
|
|
673
|
+
addEdge(ownerId, libIdFor(spec), 'depends_on');
|
|
562
674
|
}
|
|
563
675
|
}
|
|
564
676
|
}
|
|
677
|
+
for (const m of mods) {
|
|
678
|
+
const files = await walkSourceFiles(path.join(opts.project_root, m.dir));
|
|
679
|
+
for (const f of files)
|
|
680
|
+
await addEdgesForFile(f, moduleForFile(f) || m.id);
|
|
681
|
+
}
|
|
682
|
+
/* Loose + extra files (incl. .py with real Python-import edges now). */
|
|
683
|
+
for (const lf of looseFiles) {
|
|
684
|
+
await addEdgesForFile(path.join(opts.project_root, lf.rel), lf.id);
|
|
685
|
+
}
|
|
565
686
|
/* Los nodos de libreria del ingest no traen added_at/updated_at
|
|
566
687
|
* (su shape es IngestedNode); normalizarlos para el store. */
|
|
567
688
|
const libNodes = libIngest.map((l) => ({
|
|
@@ -569,17 +690,20 @@ export async function deriveProjectGraph(opts) {
|
|
|
569
690
|
added_at: now,
|
|
570
691
|
updated_at: now,
|
|
571
692
|
}));
|
|
572
|
-
const edges = [...edgeMap.values()];
|
|
693
|
+
const edges = [...edgeMap.values(), ...dataModel.edges];
|
|
573
694
|
return {
|
|
574
|
-
nodes: [...moduleNodes, ...libNodes, ...toolingNodes],
|
|
695
|
+
nodes: [...moduleNodes, ...libNodes, ...toolingNodes, ...dataModel.nodes],
|
|
575
696
|
edges,
|
|
576
697
|
stats: {
|
|
577
698
|
module_nodes: moduleNodes.length,
|
|
578
699
|
lib_nodes: libIngest.length,
|
|
579
700
|
tooling_nodes: toolingNodes.length,
|
|
701
|
+
datamodel_nodes: dataModel.nodes.length,
|
|
580
702
|
module_edges: edges.filter((e) => e.to_id.startsWith('module.')).length,
|
|
581
703
|
lib_edges: edges.filter((e) => e.to_id.startsWith('lib.')).length,
|
|
704
|
+
relation_edges: dataModel.edges.length,
|
|
582
705
|
skipped_dangling: skipped,
|
|
706
|
+
capped: extra.capped || dataModel.stats.capped,
|
|
583
707
|
},
|
|
584
708
|
};
|
|
585
709
|
}
|