@monoes/monograph 1.3.0 → 1.4.0
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/.monomind/registry.json +6 -0
- package/__tests__/staleness/git-staleness.test.ts +3 -3
- package/dist/src/analysis/cluster-quality.d.ts +4 -4
- package/dist/src/analysis/cluster-quality.d.ts.map +1 -1
- package/dist/src/analysis/cluster-quality.js +24 -27
- package/dist/src/analysis/cluster-quality.js.map +1 -1
- package/dist/src/analysis/combined.d.ts.map +1 -1
- package/dist/src/analysis/combined.js +2 -0
- package/dist/src/analysis/combined.js.map +1 -1
- package/dist/src/analysis/targets.d.ts.map +1 -1
- package/dist/src/analysis/targets.js +4 -5
- package/dist/src/analysis/targets.js.map +1 -1
- package/dist/src/export/analysis-json.js +1 -1
- package/dist/src/export/analysis-json.js.map +1 -1
- package/dist/src/export/sarif.d.ts.map +1 -1
- package/dist/src/export/sarif.js +13 -2
- package/dist/src/export/sarif.js.map +1 -1
- package/dist/src/graph/baseline.d.ts.map +1 -1
- package/dist/src/graph/baseline.js +29 -20
- package/dist/src/graph/baseline.js.map +1 -1
- package/dist/src/graph/health-score.d.ts.map +1 -1
- package/dist/src/graph/health-score.js +11 -14
- package/dist/src/graph/health-score.js.map +1 -1
- package/dist/src/graph/hotspots.d.ts.map +1 -1
- package/dist/src/graph/hotspots.js +8 -4
- package/dist/src/graph/hotspots.js.map +1 -1
- package/dist/src/graph/statistics.d.ts +3 -2
- package/dist/src/graph/statistics.d.ts.map +1 -1
- package/dist/src/graph/statistics.js +18 -7
- package/dist/src/graph/statistics.js.map +1 -1
- package/dist/src/graph/stats.d.ts.map +1 -1
- package/dist/src/graph/stats.js +2 -1
- package/dist/src/graph/stats.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/src/pipeline/orchestrator.js +37 -0
- package/dist/src/pipeline/orchestrator.js.map +1 -1
- package/dist/src/reporting/graph-report.d.ts.map +1 -1
- package/dist/src/reporting/graph-report.js +8 -9
- package/dist/src/reporting/graph-report.js.map +1 -1
- package/dist/src/staleness/git-staleness.d.ts.map +1 -1
- package/dist/src/staleness/git-staleness.js +5 -2
- package/dist/src/staleness/git-staleness.js.map +1 -1
- package/dist/src/storage/db.d.ts.map +1 -1
- package/dist/src/storage/db.js +6 -1
- package/dist/src/storage/db.js.map +1 -1
- package/dist/src/storage/schema.d.ts +5 -0
- package/dist/src/storage/schema.d.ts.map +1 -1
- package/dist/src/storage/schema.js +25 -0
- package/dist/src/storage/schema.js.map +1 -1
- package/dist/src/web/analyze-api.d.ts.map +1 -1
- package/dist/src/web/analyze-api.js +3 -1
- package/dist/src/web/analyze-api.js.map +1 -1
- package/dist/src/web/api.d.ts +3 -0
- package/dist/src/web/api.d.ts.map +1 -1
- package/dist/src/web/api.js +59 -20
- package/dist/src/web/api.js.map +1 -1
- package/dist/src/web/async-jobs.d.ts.map +1 -1
- package/dist/src/web/async-jobs.js +15 -0
- package/dist/src/web/async-jobs.js.map +1 -1
- package/dist/src/web/react-ui.js +2 -2
- package/dist/src/wiki/wiki-generator.d.ts.map +1 -1
- package/dist/src/wiki/wiki-generator.js +9 -6
- package/dist/src/wiki/wiki-generator.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/analysis/cluster-quality.ts +25 -26
- package/src/analysis/combined.ts +2 -0
- package/src/analysis/targets.ts +4 -5
- package/src/export/analysis-json.ts +1 -1
- package/src/export/sarif.ts +11 -2
- package/src/graph/baseline.ts +29 -20
- package/src/graph/health-score.ts +13 -16
- package/src/graph/hotspots.ts +8 -4
- package/src/graph/statistics.ts +19 -7
- package/src/graph/stats.ts +2 -1
- package/src/index.ts +3 -0
- package/src/pipeline/orchestrator.ts +37 -0
- package/src/reporting/graph-report.ts +8 -9
- package/src/staleness/git-staleness.ts +5 -2
- package/src/storage/db.ts +8 -0
- package/src/storage/schema.ts +30 -0
- package/src/web/analyze-api.ts +3 -1
- package/src/web/api.ts +62 -22
- package/src/web/async-jobs.ts +15 -0
- package/src/web/react-ui.ts +2 -2
- package/src/wiki/wiki-generator.ts +9 -6
package/src/web/api.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import type { Application } from 'express';
|
|
2
2
|
import type Database from 'better-sqlite3';
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
|
-
import { resolve, sep } from 'path';
|
|
4
|
+
import { resolve, sep, dirname, join } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
5
6
|
import { ftsSearch } from '../storage/fts-store.js';
|
|
6
7
|
import { globalJobRegistry } from './async-jobs.js';
|
|
7
8
|
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const PKG_VERSION = (() => {
|
|
11
|
+
try {
|
|
12
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf8'));
|
|
13
|
+
return pkg.version ?? '0.0.0';
|
|
14
|
+
} catch { return '0.0.0'; }
|
|
15
|
+
})();
|
|
16
|
+
|
|
8
17
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
9
18
|
|
|
10
19
|
export interface ApiNode {
|
|
@@ -28,6 +37,9 @@ export interface GraphData {
|
|
|
28
37
|
nodes: ApiNode[];
|
|
29
38
|
edges: ApiEdge[];
|
|
30
39
|
communities: Record<string, string[]>;
|
|
40
|
+
totalNodeCount: number;
|
|
41
|
+
totalEdgeCount: number;
|
|
42
|
+
truncated: boolean;
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
export interface NodeDetail {
|
|
@@ -58,9 +70,23 @@ export function rowToApiNode(row: Record<string, unknown>): ApiNode {
|
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
export function queryGraphData(db: Database.Database): GraphData {
|
|
73
|
+
const totalNodeCount = (db.prepare('SELECT COUNT(*) as c FROM nodes').get() as { c: number }).c;
|
|
74
|
+
const totalEdgeCount = (db.prepare('SELECT COUNT(*) as c FROM edges').get() as { c: number }).c;
|
|
75
|
+
|
|
76
|
+
// Select the most connected nodes for a representative visualization
|
|
61
77
|
const nodeRows = db
|
|
62
78
|
.prepare(
|
|
63
|
-
|
|
79
|
+
`SELECT n.id, n.name, n.label, n.file_path, n.start_line, n.end_line, n.community_id
|
|
80
|
+
FROM nodes n
|
|
81
|
+
LEFT JOIN (
|
|
82
|
+
SELECT node_id, SUM(cnt) AS deg FROM (
|
|
83
|
+
SELECT source_id AS node_id, COUNT(*) AS cnt FROM edges GROUP BY source_id
|
|
84
|
+
UNION ALL
|
|
85
|
+
SELECT target_id AS node_id, COUNT(*) AS cnt FROM edges GROUP BY target_id
|
|
86
|
+
) GROUP BY node_id
|
|
87
|
+
) d ON d.node_id = n.id
|
|
88
|
+
ORDER BY d.deg DESC NULLS LAST
|
|
89
|
+
LIMIT 2000`,
|
|
64
90
|
)
|
|
65
91
|
.all() as Record<string, unknown>[];
|
|
66
92
|
|
|
@@ -69,23 +95,23 @@ export function queryGraphData(db: Database.Database): GraphData {
|
|
|
69
95
|
|
|
70
96
|
const edges: ApiEdge[] = [];
|
|
71
97
|
if (nodeIds.size > 0) {
|
|
72
|
-
//
|
|
98
|
+
// Scan edges and keep those where at least one endpoint is a visible node
|
|
73
99
|
const edgeRows = db
|
|
74
|
-
.prepare(
|
|
75
|
-
`SELECT e.source_id, e.target_id, e.relation, e.confidence_score
|
|
76
|
-
FROM edges e
|
|
77
|
-
JOIN (SELECT id FROM nodes LIMIT 2000) n ON e.source_id = n.id
|
|
78
|
-
LIMIT 10000`,
|
|
79
|
-
)
|
|
100
|
+
.prepare('SELECT source_id, target_id, relation, confidence_score FROM edges')
|
|
80
101
|
.all() as Record<string, unknown>[];
|
|
81
102
|
|
|
82
103
|
for (const r of edgeRows) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
104
|
+
const src = r['source_id'] as string;
|
|
105
|
+
const tgt = r['target_id'] as string;
|
|
106
|
+
if (nodeIds.has(src) && nodeIds.has(tgt)) {
|
|
107
|
+
edges.push({
|
|
108
|
+
sourceId: src,
|
|
109
|
+
targetId: tgt,
|
|
110
|
+
relation: r['relation'] as string,
|
|
111
|
+
confidenceScore: r['confidence_score'] as number,
|
|
112
|
+
});
|
|
113
|
+
if (edges.length >= 10000) break;
|
|
114
|
+
}
|
|
89
115
|
}
|
|
90
116
|
}
|
|
91
117
|
|
|
@@ -98,7 +124,7 @@ export function queryGraphData(db: Database.Database): GraphData {
|
|
|
98
124
|
}
|
|
99
125
|
}
|
|
100
126
|
|
|
101
|
-
return { nodes, edges, communities };
|
|
127
|
+
return { nodes, edges, communities, totalNodeCount, totalEdgeCount, truncated: totalNodeCount > nodes.length };
|
|
102
128
|
}
|
|
103
129
|
|
|
104
130
|
export function queryNode(db: Database.Database, id: string): NodeDetail {
|
|
@@ -140,8 +166,8 @@ export function querySearch(db: Database.Database, q: string): ApiNode[] {
|
|
|
140
166
|
name: r.name,
|
|
141
167
|
label: r.label,
|
|
142
168
|
filePath: r.filePath,
|
|
143
|
-
startLine: null,
|
|
144
|
-
endLine: null,
|
|
169
|
+
startLine: r.startLine ?? null,
|
|
170
|
+
endLine: r.endLine ?? null,
|
|
145
171
|
communityId: null,
|
|
146
172
|
}));
|
|
147
173
|
}
|
|
@@ -294,7 +320,7 @@ export interface ServerInfo {
|
|
|
294
320
|
export function getServerInfo(): ServerInfo {
|
|
295
321
|
return {
|
|
296
322
|
name: 'monograph',
|
|
297
|
-
version:
|
|
323
|
+
version: PKG_VERSION,
|
|
298
324
|
nodeVersion: process.version,
|
|
299
325
|
uptimeSeconds: process.uptime(),
|
|
300
326
|
};
|
|
@@ -409,16 +435,30 @@ export function setupApiRoutes(app: Application, db: Database.Database): void {
|
|
|
409
435
|
|
|
410
436
|
// Async analyze job API
|
|
411
437
|
app.post('/api/analyze', (req, res) => {
|
|
412
|
-
const { repoPath } = req.body as { repoPath?: string };
|
|
438
|
+
const { repoPath, codeOnly, force } = req.body as { repoPath?: string; codeOnly?: boolean; force?: boolean };
|
|
413
439
|
if (!repoPath) {
|
|
414
440
|
res.status(400).json({ error: 'repoPath is required' });
|
|
415
441
|
return;
|
|
416
442
|
}
|
|
417
443
|
const job = globalJobRegistry.create('analyze', { repoPath });
|
|
418
444
|
globalJobRegistry.update(job.id, { status: 'running' });
|
|
419
|
-
|
|
420
|
-
|
|
445
|
+
|
|
446
|
+
import('../pipeline/orchestrator.js').then(({ buildAsync }) => {
|
|
447
|
+
buildAsync(repoPath, {
|
|
448
|
+
codeOnly,
|
|
449
|
+
force,
|
|
450
|
+
onProgress: (p) => {
|
|
451
|
+
globalJobRegistry.emitProgress(job.id, { phase: p.phase ?? '', message: p.message });
|
|
452
|
+
},
|
|
453
|
+
}).then(() => {
|
|
454
|
+
globalJobRegistry.update(job.id, { status: 'done', result: { message: 'Build complete' } });
|
|
455
|
+
}).catch((err) => {
|
|
456
|
+
globalJobRegistry.update(job.id, { status: 'failed', result: { error: String(err) } });
|
|
457
|
+
});
|
|
458
|
+
}).catch((err) => {
|
|
459
|
+
globalJobRegistry.update(job.id, { status: 'failed', result: { error: String(err) } });
|
|
421
460
|
});
|
|
461
|
+
|
|
422
462
|
res.status(202).json({ jobId: job.id });
|
|
423
463
|
});
|
|
424
464
|
|
package/src/web/async-jobs.ts
CHANGED
|
@@ -31,12 +31,26 @@ export interface JobRegistry {
|
|
|
31
31
|
getProgress(id: string): ProgressEvent[];
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
const MAX_COMPLETED_JOBS = 50;
|
|
35
|
+
|
|
34
36
|
export function createJobRegistry(): JobRegistry {
|
|
35
37
|
const jobs = new Map<string, Job>();
|
|
36
38
|
const progressMap = new Map<string, ProgressEvent[]>();
|
|
37
39
|
|
|
38
40
|
function now(): string { return new Date().toISOString(); }
|
|
39
41
|
|
|
42
|
+
function evictOldCompleted(): void {
|
|
43
|
+
const completed = [...jobs.entries()]
|
|
44
|
+
.filter(([, j]) => j.status === 'done' || j.status === 'failed' || j.status === 'cancelled');
|
|
45
|
+
if (completed.length <= MAX_COMPLETED_JOBS) return;
|
|
46
|
+
completed.sort((a, b) => a[1].updatedAt.localeCompare(b[1].updatedAt));
|
|
47
|
+
const toEvict = completed.slice(0, completed.length - MAX_COMPLETED_JOBS);
|
|
48
|
+
for (const [id] of toEvict) {
|
|
49
|
+
jobs.delete(id);
|
|
50
|
+
progressMap.delete(id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
40
54
|
return {
|
|
41
55
|
create(type, payload) {
|
|
42
56
|
const job: Job = {
|
|
@@ -60,6 +74,7 @@ export function createJobRegistry(): JobRegistry {
|
|
|
60
74
|
if (!j) return false;
|
|
61
75
|
if (j.status === 'done' || j.status === 'failed' || j.status === 'cancelled') return false;
|
|
62
76
|
Object.assign(j, patch, { updatedAt: now() });
|
|
77
|
+
if (patch.status === 'done' || patch.status === 'failed') evictOldCompleted();
|
|
63
78
|
return true;
|
|
64
79
|
},
|
|
65
80
|
cancel(id) {
|
package/src/web/react-ui.ts
CHANGED
|
@@ -29,9 +29,9 @@ export function getReactUiHtml(): string {
|
|
|
29
29
|
|
|
30
30
|
async function search() {
|
|
31
31
|
if (!query.trim()) return;
|
|
32
|
-
const res = await fetch('/api/
|
|
32
|
+
const res = await fetch('/api/search?q=' + encodeURIComponent(query));
|
|
33
33
|
const data = await res.json();
|
|
34
|
-
setResults(data
|
|
34
|
+
setResults(Array.isArray(data) ? data : data.results ?? []);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
async function analyze() {
|
|
@@ -104,14 +104,17 @@ export async function generateWikiPage(
|
|
|
104
104
|
|
|
105
105
|
// 2. Get top 5 symbols by degree (count of connected edges, since no centrality column)
|
|
106
106
|
const symbolRows = db.prepare(`
|
|
107
|
-
SELECT n.name, n.label, n.file_path
|
|
108
|
-
COUNT(DISTINCT e1.id) + COUNT(DISTINCT e2.id) AS degree
|
|
107
|
+
SELECT n.name, n.label, n.file_path
|
|
109
108
|
FROM nodes n
|
|
110
|
-
LEFT JOIN
|
|
111
|
-
|
|
109
|
+
LEFT JOIN (
|
|
110
|
+
SELECT node_id, SUM(cnt) AS deg FROM (
|
|
111
|
+
SELECT source_id AS node_id, COUNT(*) AS cnt FROM edges GROUP BY source_id
|
|
112
|
+
UNION ALL
|
|
113
|
+
SELECT target_id AS node_id, COUNT(*) AS cnt FROM edges GROUP BY target_id
|
|
114
|
+
) GROUP BY node_id
|
|
115
|
+
) d ON d.node_id = n.id
|
|
112
116
|
WHERE n.community_id = ?
|
|
113
|
-
|
|
114
|
-
ORDER BY degree DESC, n.name ASC
|
|
117
|
+
ORDER BY d.deg DESC NULLS LAST, n.name ASC
|
|
115
118
|
LIMIT 5
|
|
116
119
|
`).all(Number(communityIdStr)) as { name: string; label: string; file_path: string | null }[];
|
|
117
120
|
|