@monoes/monograph 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.monodesign/hook.cache.json +1 -0
- package/__tests__/storage/stores.test.ts +66 -1
- package/dist/src/analysis/cycles.d.ts.map +1 -1
- package/dist/src/analysis/cycles.js +5 -3
- package/dist/src/analysis/cycles.js.map +1 -1
- package/dist/src/analysis/trace.d.ts.map +1 -1
- package/dist/src/analysis/trace.js +1 -19
- package/dist/src/analysis/trace.js.map +1 -1
- package/dist/src/analysis/worker-pool.d.ts.map +1 -1
- package/dist/src/analysis/worker-pool.js +9 -7
- package/dist/src/analysis/worker-pool.js.map +1 -1
- package/dist/src/cli/skill-gen.d.ts.map +1 -1
- package/dist/src/cli/skill-gen.js +16 -25
- package/dist/src/cli/skill-gen.js.map +1 -1
- package/dist/src/export/html.d.ts.map +1 -1
- package/dist/src/export/html.js +3 -2
- package/dist/src/export/html.js.map +1 -1
- package/dist/src/graph/regex-search.d.ts.map +1 -1
- package/dist/src/graph/regex-search.js +9 -3
- package/dist/src/graph/regex-search.js.map +1 -1
- package/dist/src/pipeline/phases/call-site-extractors.d.ts +17 -0
- package/dist/src/pipeline/phases/call-site-extractors.d.ts.map +1 -0
- package/dist/src/pipeline/phases/call-site-extractors.js +132 -0
- package/dist/src/pipeline/phases/call-site-extractors.js.map +1 -0
- package/dist/src/pipeline/phases/module-resolution.d.ts +10 -0
- package/dist/src/pipeline/phases/module-resolution.d.ts.map +1 -0
- package/dist/src/pipeline/phases/module-resolution.js +301 -0
- package/dist/src/pipeline/phases/module-resolution.js.map +1 -0
- package/dist/src/pipeline/phases/orm.js +1 -2
- package/dist/src/pipeline/phases/orm.js.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.d.ts +2 -25
- package/dist/src/pipeline/phases/scope-resolution.d.ts.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.js +18 -580
- package/dist/src/pipeline/phases/scope-resolution.js.map +1 -1
- package/dist/src/pipeline/phases/wildcard-phase.d.ts.map +1 -1
- package/dist/src/pipeline/phases/wildcard-phase.js +6 -1
- package/dist/src/pipeline/phases/wildcard-phase.js.map +1 -1
- package/dist/src/search/hybrid-query.js +1 -1
- package/dist/src/search/hybrid-query.js.map +1 -1
- package/dist/src/storage/fts-store.d.ts.map +1 -1
- package/dist/src/storage/fts-store.js +7 -4
- package/dist/src/storage/fts-store.js.map +1 -1
- package/dist/src/watch/watcher.d.ts +2 -0
- package/dist/src/watch/watcher.d.ts.map +1 -1
- package/dist/src/watch/watcher.js +23 -1
- package/dist/src/watch/watcher.js.map +1 -1
- package/dist/src/web/api.d.ts.map +1 -1
- package/dist/src/web/api.js +17 -14
- package/dist/src/web/api.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/__tests__/pipeline/phases/wildcard-phase.test.ts +39 -4
- package/src/analysis/cycles.ts +7 -3
- package/src/analysis/trace.ts +1 -21
- package/src/analysis/worker-pool.ts +8 -7
- package/src/cli/skill-gen.ts +6 -14
- package/src/export/html.ts +3 -2
- package/src/graph/regex-search.ts +9 -5
- package/src/pipeline/phases/call-site-extractors.ts +169 -0
- package/src/pipeline/phases/module-resolution.ts +294 -0
- package/src/pipeline/phases/orm.ts +1 -2
- package/src/pipeline/phases/scope-resolution.ts +25 -618
- package/src/pipeline/phases/wildcard-phase.ts +5 -1
- package/src/search/hybrid-query.ts +1 -1
- package/src/storage/fts-store.ts +7 -3
- package/src/watch/watcher.ts +23 -1
- package/src/web/api.ts +17 -13
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// ── Module resolution for import specifiers ──────────────────────────────────
|
|
2
|
+
|
|
3
|
+
import { readFileSync, existsSync } from 'fs';
|
|
4
|
+
import { join, extname, dirname, resolve as resolvePath } from 'path';
|
|
5
|
+
import type { MonographNode } from '../../types.js';
|
|
6
|
+
import { TS_JS_EXTS, CJS_MJS_EXTS } from './call-site-extractors.js';
|
|
7
|
+
|
|
8
|
+
// ── Import regexes ───────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export const IMPORT_RE = /import\s+(?:type\s+)?(?:\{([^}]+)\}|(\w+)|\*\s+as\s+(\w+))(?:\s*,\s*(?:\{([^}]+)\}|(\w+)|\*\s+as\s+(\w+)))?\s+from\s+['"]([^'"]+)['"]/g;
|
|
11
|
+
export const REQUIRE_RE = /(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
12
|
+
const PY_FROM_IMPORT_RE = /from\s+([\w.]+)\s+import\s+(.+)/g;
|
|
13
|
+
const PY_IMPORT_RE = /^import\s+([\w.]+)(?:\s+as\s+(\w+))?/gm;
|
|
14
|
+
const GO_IMPORT_RE = /import\s+(?:"([^"]+)"|(?:\w+\s+)?"([^"]+)"|\(\s*([\s\S]*?)\s*\))/g;
|
|
15
|
+
const JAVA_IMPORT_RE = /import\s+(?:static\s+)?([\w.]+)\s*;/g;
|
|
16
|
+
const RUST_USE_RE = /use\s+((?:crate|super|self)(?:::\w+)+)(?:::\{([^}]+)\})?;/g;
|
|
17
|
+
export const REEXPORT_RE = /export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g;
|
|
18
|
+
|
|
19
|
+
const RESOLVE_EXTS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
20
|
+
|
|
21
|
+
const PY_EXTS = new Set(['.py']);
|
|
22
|
+
const GO_EXTS = new Set(['.go']);
|
|
23
|
+
const JAVA_EXTS = new Set(['.java']);
|
|
24
|
+
const RUST_EXTS = new Set(['.rs']);
|
|
25
|
+
|
|
26
|
+
// ── Workspace package map ────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const workspacePackageMapCache = new Map<string, Map<string, string>>();
|
|
29
|
+
|
|
30
|
+
export function clearWorkspacePackageMapCache(repoPath: string): void {
|
|
31
|
+
workspacePackageMapCache.delete(repoPath);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function buildWorkspacePackageMap(repoPath: string): Map<string, string> {
|
|
35
|
+
const cached = workspacePackageMapCache.get(repoPath);
|
|
36
|
+
if (cached) return cached;
|
|
37
|
+
const result = new Map<string, string>();
|
|
38
|
+
const packagesDir = join(repoPath, 'packages');
|
|
39
|
+
try {
|
|
40
|
+
const scanDirs = (base: string, depth: number) => {
|
|
41
|
+
if (depth > 2) return;
|
|
42
|
+
let entries: string[];
|
|
43
|
+
try { entries = require('fs').readdirSync(base); } catch { return; }
|
|
44
|
+
for (const e of entries) {
|
|
45
|
+
const full = join(base, e);
|
|
46
|
+
const pkgJson = join(full, 'package.json');
|
|
47
|
+
try {
|
|
48
|
+
const pkg = JSON.parse(readFileSync(pkgJson, 'utf-8'));
|
|
49
|
+
if (pkg.name) {
|
|
50
|
+
const relDir = full.slice(repoPath.length + 1);
|
|
51
|
+
result.set(pkg.name, relDir);
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
if (e.startsWith('@')) scanDirs(full, depth + 1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
scanDirs(packagesDir, 0);
|
|
59
|
+
} catch { /* no packages dir */ }
|
|
60
|
+
workspacePackageMapCache.set(repoPath, result);
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Specifier resolvers ──────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
export function resolveModuleSpecifier(
|
|
67
|
+
importerPath: string,
|
|
68
|
+
specifier: string,
|
|
69
|
+
repoPath: string,
|
|
70
|
+
knownFiles: Set<string>,
|
|
71
|
+
workspaceMap: Map<string, string>,
|
|
72
|
+
): string | null {
|
|
73
|
+
if (specifier.startsWith('.')) {
|
|
74
|
+
const dir = dirname(importerPath);
|
|
75
|
+
const raw = resolvePath('/', dir, specifier).slice(1);
|
|
76
|
+
const base = raw.replace(/\.(js|jsx)$/, '');
|
|
77
|
+
|
|
78
|
+
for (const candidate of [
|
|
79
|
+
raw,
|
|
80
|
+
base,
|
|
81
|
+
...RESOLVE_EXTS.map(e => base + e),
|
|
82
|
+
...RESOLVE_EXTS.map(e => base + '/index' + e),
|
|
83
|
+
]) {
|
|
84
|
+
if (knownFiles.has(candidate)) return candidate;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const ext of RESOLVE_EXTS) {
|
|
88
|
+
if (existsSync(join(repoPath, base + ext))) return base + ext;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const [pkgName, pkgDir] of workspaceMap) {
|
|
94
|
+
if (specifier === pkgName) {
|
|
95
|
+
for (const entry of RESOLVE_EXTS.map(e => pkgDir + '/src/index' + e)) {
|
|
96
|
+
if (knownFiles.has(entry)) return entry;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
if (specifier.startsWith(pkgName + '/')) {
|
|
101
|
+
const subpath = specifier.slice(pkgName.length + 1);
|
|
102
|
+
const base = pkgDir + '/' + subpath;
|
|
103
|
+
for (const candidate of [
|
|
104
|
+
base,
|
|
105
|
+
...RESOLVE_EXTS.map(e => base + e),
|
|
106
|
+
...RESOLVE_EXTS.map(e => base + '/index' + e),
|
|
107
|
+
]) {
|
|
108
|
+
if (knownFiles.has(candidate)) return candidate;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolvePythonModule(importerPath: string, modulePath: string, knownFiles: Set<string>): string | null {
|
|
118
|
+
const dir = dirname(importerPath);
|
|
119
|
+
for (const base of [dir + '/' + modulePath, modulePath]) {
|
|
120
|
+
for (const candidate of [base + '.py', base + '/__init__.py']) {
|
|
121
|
+
if (knownFiles.has(candidate)) return candidate;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function resolveGoPackage(_importerPath: string, goPath: string, knownFiles: Set<string>): string | null {
|
|
128
|
+
for (const f of knownFiles) {
|
|
129
|
+
if (f.startsWith(goPath + '/') && f.endsWith('.go')) return f;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function resolveJavaImport(qualifiedName: string, knownFiles: Set<string>): string | null {
|
|
135
|
+
const pathPart = qualifiedName.replace(/\./g, '/');
|
|
136
|
+
for (const candidate of [pathPart + '.java', 'src/main/java/' + pathPart + '.java', 'src/' + pathPart + '.java']) {
|
|
137
|
+
if (knownFiles.has(candidate)) return candidate;
|
|
138
|
+
}
|
|
139
|
+
if (pathPart.endsWith('/*')) {
|
|
140
|
+
const dir = pathPart.slice(0, -2);
|
|
141
|
+
for (const f of knownFiles) {
|
|
142
|
+
if (f.endsWith('.java') && (f.startsWith(dir + '/') || f.includes('/' + dir + '/'))) return f;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function resolveRustUse(usePath: string, importerPath: string, knownFiles: Set<string>): string | null {
|
|
149
|
+
const parts = usePath.split('::');
|
|
150
|
+
if (parts[0] === 'crate') parts[0] = 'src';
|
|
151
|
+
else if (parts[0] === 'super') {
|
|
152
|
+
const dir = dirname(importerPath);
|
|
153
|
+
parts[0] = dirname(dir);
|
|
154
|
+
} else if (parts[0] === 'self') {
|
|
155
|
+
parts[0] = dirname(importerPath);
|
|
156
|
+
}
|
|
157
|
+
for (let len = parts.length; len >= 2; len--) {
|
|
158
|
+
const base = parts.slice(0, len).join('/');
|
|
159
|
+
for (const candidate of [base + '.rs', base + '/mod.rs']) {
|
|
160
|
+
if (knownFiles.has(candidate)) return candidate;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ── Import name extraction ───────────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
export function extractImportNames(clause: string): string[] {
|
|
169
|
+
return clause
|
|
170
|
+
.split(',')
|
|
171
|
+
.map(s => s.trim().split(/\s+as\s+/).pop()!.trim())
|
|
172
|
+
.filter(Boolean);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── Build import maps from source ────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
export function buildAllImportMapsFromSource(
|
|
178
|
+
repoPath: string,
|
|
179
|
+
fileNodesByPath: Map<string, MonographNode>,
|
|
180
|
+
fileContents?: Map<string, string>,
|
|
181
|
+
): Map<string, Map<string, string>> {
|
|
182
|
+
const result = new Map<string, Map<string, string>>();
|
|
183
|
+
const knownFiles = new Set(fileNodesByPath.keys());
|
|
184
|
+
const workspaceMap = buildWorkspacePackageMap(repoPath);
|
|
185
|
+
|
|
186
|
+
for (const [filePath, fileNode] of fileNodesByPath) {
|
|
187
|
+
const ext = extname(filePath).toLowerCase();
|
|
188
|
+
const importMap = new Map<string, string>();
|
|
189
|
+
|
|
190
|
+
let source: string | undefined = fileContents?.get(filePath);
|
|
191
|
+
if (!source) {
|
|
192
|
+
try {
|
|
193
|
+
source = readFileSync(join(repoPath, filePath), 'utf-8');
|
|
194
|
+
} catch {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (TS_JS_EXTS.has(ext) || ext === '.cjs' || ext === '.mjs') {
|
|
200
|
+
IMPORT_RE.lastIndex = 0;
|
|
201
|
+
let m: RegExpExecArray | null;
|
|
202
|
+
while ((m = IMPORT_RE.exec(source)) !== null) {
|
|
203
|
+
const specifier = m[7];
|
|
204
|
+
const resolved = resolveModuleSpecifier(filePath, specifier, repoPath, knownFiles, workspaceMap);
|
|
205
|
+
if (!resolved) continue;
|
|
206
|
+
if (m[1]) for (const n of extractImportNames(m[1])) importMap.set(n, resolved);
|
|
207
|
+
if (m[4]) for (const n of extractImportNames(m[4])) importMap.set(n, resolved);
|
|
208
|
+
if (m[2]) importMap.set(m[2], resolved);
|
|
209
|
+
if (m[5]) importMap.set(m[5], resolved);
|
|
210
|
+
if (m[3]) importMap.set(m[3], resolved);
|
|
211
|
+
if (m[6]) importMap.set(m[6], resolved);
|
|
212
|
+
const baseName = resolved.split('/').pop()!.replace(/\.\w+$/, '');
|
|
213
|
+
importMap.set(baseName, resolved);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
REQUIRE_RE.lastIndex = 0;
|
|
217
|
+
while ((m = REQUIRE_RE.exec(source)) !== null) {
|
|
218
|
+
const specifier = m[3];
|
|
219
|
+
const resolved = resolveModuleSpecifier(filePath, specifier, repoPath, knownFiles, workspaceMap);
|
|
220
|
+
if (!resolved) continue;
|
|
221
|
+
if (m[1]) for (const n of extractImportNames(m[1])) importMap.set(n, resolved);
|
|
222
|
+
if (m[2]) importMap.set(m[2], resolved);
|
|
223
|
+
const baseName = resolved.split('/').pop()!.replace(/\.\w+$/, '');
|
|
224
|
+
importMap.set(baseName, resolved);
|
|
225
|
+
}
|
|
226
|
+
} else if (PY_EXTS.has(ext)) {
|
|
227
|
+
PY_FROM_IMPORT_RE.lastIndex = 0;
|
|
228
|
+
let m: RegExpExecArray | null;
|
|
229
|
+
while ((m = PY_FROM_IMPORT_RE.exec(source)) !== null) {
|
|
230
|
+
const modulePath = m[1].replace(/\./g, '/');
|
|
231
|
+
const resolved = resolvePythonModule(filePath, modulePath, knownFiles);
|
|
232
|
+
if (!resolved) continue;
|
|
233
|
+
for (const name of m[2].split(',').map(s => s.trim().split(/\s+as\s+/).pop()!.trim()).filter(Boolean)) {
|
|
234
|
+
importMap.set(name, resolved);
|
|
235
|
+
}
|
|
236
|
+
const baseName = resolved.split('/').pop()!.replace(/\.\w+$/, '');
|
|
237
|
+
importMap.set(baseName, resolved);
|
|
238
|
+
}
|
|
239
|
+
PY_IMPORT_RE.lastIndex = 0;
|
|
240
|
+
while ((m = PY_IMPORT_RE.exec(source)) !== null) {
|
|
241
|
+
const modulePath = m[1].replace(/\./g, '/');
|
|
242
|
+
const resolved = resolvePythonModule(filePath, modulePath, knownFiles);
|
|
243
|
+
if (!resolved) continue;
|
|
244
|
+
const alias = m[2] ?? m[1].split('.').pop()!;
|
|
245
|
+
importMap.set(alias, resolved);
|
|
246
|
+
}
|
|
247
|
+
} else if (GO_EXTS.has(ext)) {
|
|
248
|
+
GO_IMPORT_RE.lastIndex = 0;
|
|
249
|
+
let m: RegExpExecArray | null;
|
|
250
|
+
while ((m = GO_IMPORT_RE.exec(source)) !== null) {
|
|
251
|
+
const paths = m[3]
|
|
252
|
+
? m[3].match(/"([^"]+)"/g)?.map(s => s.slice(1, -1)) ?? []
|
|
253
|
+
: [m[1] ?? m[2]].filter(Boolean);
|
|
254
|
+
for (const goPath of paths) {
|
|
255
|
+
const resolved = resolveGoPackage(filePath, goPath, knownFiles);
|
|
256
|
+
if (!resolved) continue;
|
|
257
|
+
const pkgName = goPath.split('/').pop()!;
|
|
258
|
+
importMap.set(pkgName, resolved);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
} else if (JAVA_EXTS.has(ext)) {
|
|
262
|
+
JAVA_IMPORT_RE.lastIndex = 0;
|
|
263
|
+
let m: RegExpExecArray | null;
|
|
264
|
+
while ((m = JAVA_IMPORT_RE.exec(source)) !== null) {
|
|
265
|
+
const resolved = resolveJavaImport(m[1], knownFiles);
|
|
266
|
+
if (!resolved) continue;
|
|
267
|
+
const className = m[1].split('.').pop()!;
|
|
268
|
+
importMap.set(className, resolved);
|
|
269
|
+
}
|
|
270
|
+
} else if (RUST_EXTS.has(ext)) {
|
|
271
|
+
RUST_USE_RE.lastIndex = 0;
|
|
272
|
+
let m: RegExpExecArray | null;
|
|
273
|
+
while ((m = RUST_USE_RE.exec(source)) !== null) {
|
|
274
|
+
if (m[2]) {
|
|
275
|
+
for (const name of m[2].split(',').map(s => s.trim()).filter(Boolean)) {
|
|
276
|
+
const resolved = resolveRustUse(m[1] + '::' + name, filePath, knownFiles);
|
|
277
|
+
if (resolved) importMap.set(name.split('::').pop()!, resolved);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
const resolved = resolveRustUse(m[1], filePath, knownFiles);
|
|
281
|
+
if (!resolved) continue;
|
|
282
|
+
const itemName = m[1].split('::').pop()!;
|
|
283
|
+
importMap.set(itemName, resolved);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (importMap.size > 0) {
|
|
289
|
+
result.set(fileNode.id, importMap);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
@@ -134,6 +134,7 @@ function detectTypeOrmEntities(
|
|
|
134
134
|
edges: MonographEdge[],
|
|
135
135
|
): void {
|
|
136
136
|
const entityRe = new RegExp(TYPEORM_ENTITY_RE.source, 'g');
|
|
137
|
+
const lines = source.split('\n');
|
|
137
138
|
let m: RegExpExecArray | null;
|
|
138
139
|
|
|
139
140
|
while ((m = entityRe.exec(source)) !== null) {
|
|
@@ -154,9 +155,7 @@ function detectTypeOrmEntities(
|
|
|
154
155
|
entityNodes.push(entityNode);
|
|
155
156
|
|
|
156
157
|
const fields: FieldDef[] = [];
|
|
157
|
-
// Use a per-call local regex to avoid shared lastIndex state on the module-level global
|
|
158
158
|
const colDecRe = new RegExp(TYPEORM_COLUMN_DECORATOR_RE.source, 'g');
|
|
159
|
-
const lines = source.split('\n');
|
|
160
159
|
for (let i = 0; i < lines.length; i++) {
|
|
161
160
|
const line = lines[i];
|
|
162
161
|
if (colDecRe.test(line)) {
|