@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.
- package/dist/chat/claude_cli.d.ts.map +1 -1
- package/dist/chat/claude_cli.js +24 -21
- package/dist/chat/claude_cli.js.map +1 -1
- 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 +143 -41
- package/dist/semantic_graph/derive.js.map +1 -1
- 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;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 (
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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(
|
|
629
|
+
src = await fs.readFile(absFile, 'utf-8');
|
|
548
630
|
}
|
|
549
631
|
catch {
|
|
550
|
-
|
|
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,
|
|
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(
|
|
646
|
+
const target = moduleForFile(path.resolve(path.dirname(absFile), spec));
|
|
557
647
|
if (target)
|
|
558
|
-
addEdge(
|
|
648
|
+
addEdge(ownerId, target, 'uses');
|
|
559
649
|
}
|
|
560
650
|
else {
|
|
561
|
-
addEdge(
|
|
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
|
}
|