@monoes/monograph 1.2.4 → 1.2.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoes/monograph",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "type": "module",
5
5
  "description": "Native TypeScript code intelligence engine for monomind",
6
6
  "main": "dist/src/index.js",
@@ -46,7 +46,6 @@
46
46
  "tree-sitter-vue": "^0.2.1"
47
47
  },
48
48
  "optionalDependencies": {
49
- "@anthropic-ai/sdk": "^0.39.0",
50
49
  "@huggingface/transformers": "^3.1.0"
51
50
  },
52
51
  "devDependencies": {
@@ -85,6 +85,38 @@ export async function buildAsync(repoPath: string, options: BuildOptions = {}):
85
85
 
86
86
  const outputs = await runner.run(ctx);
87
87
 
88
+ // Every full build reuses the same DB in place (no truncate-on-open), and no
89
+ // phase ever deletes nodes for files that were renamed/deleted since the last
90
+ // build. Left unchecked, orphaned rows (and their FTS/edge fallout) accumulate
91
+ // forever across repeated rebuilds. Sweep them here, once, after scan knows the
92
+ // authoritative current file set.
93
+ const scanOut = outputs.get('scan') as { filePaths: string[] } | undefined;
94
+ if (scanOut) {
95
+ const liveFiles = new Set(scanOut.filePaths.map((f) => resolve(f)));
96
+ const staleFiles = (
97
+ db.prepare('SELECT DISTINCT file_path FROM nodes WHERE file_path IS NOT NULL').all() as { file_path: string }[]
98
+ )
99
+ .map((r) => r.file_path)
100
+ .filter((f) => !liveFiles.has(resolve(f)));
101
+ if (staleFiles.length > 0) {
102
+ const deleteStale = db.transaction((files: string[]) => {
103
+ // Edges FK-reference nodes with no ON DELETE CASCADE, and a stale node
104
+ // can be referenced from either side (another file's export pointing at
105
+ // it, or it importing another file) — clear both directions first.
106
+ const deleteEdges = db.prepare(`
107
+ DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file_path = ?)
108
+ OR target_id IN (SELECT id FROM nodes WHERE file_path = ?)
109
+ `);
110
+ const deleteNodesStmt = db.prepare('DELETE FROM nodes WHERE file_path = ?');
111
+ for (const f of files) {
112
+ deleteEdges.run(f, f);
113
+ deleteNodesStmt.run(f);
114
+ }
115
+ });
116
+ deleteStale(staleFiles);
117
+ }
118
+ }
119
+
88
120
  const hash = getCurrentCommitHash(resolve(repoPath));
89
121
  if (hash) {
90
122
  db.prepare("INSERT OR REPLACE INTO index_meta VALUES ('last_commit_hash', ?)").run(hash);
@@ -107,7 +107,7 @@ export function computeAllCohesionScores(
107
107
  export const communitiesPhase: PipelinePhase<CommunitiesOutput> = {
108
108
  name: 'communities',
109
109
  deps: ['parse', 'cross-file', 'mro'],
110
- async execute(_ctx, deps) {
110
+ async execute(ctx, deps) {
111
111
  const { resolvedEdges } = deps.get('cross-file') as CrossFileOutput;
112
112
  const { allEdges } = deps.get('parse') as ParseOutput;
113
113
  const allUsedEdges: MonographEdge[] = [...allEdges, ...resolvedEdges];
@@ -142,6 +142,26 @@ export const communitiesPhase: PipelinePhase<CommunitiesOutput> = {
142
142
  // Compute all cohesion scores in one O(N+E) pass instead of O(K*(N+E))
143
143
  const cohesionScores = computeAllCohesionScores(memberships, allUsedEdges);
144
144
 
145
+ // Persist community assignments to the DB
146
+ if (memberships.size > 0) {
147
+ const commSizes = new Map<number, number>();
148
+ for (const commId of memberships.values()) {
149
+ commSizes.set(commId, (commSizes.get(commId) ?? 0) + 1);
150
+ }
151
+ const updateNode = ctx.db.prepare('UPDATE nodes SET community_id = ? WHERE id = ?');
152
+ const upsertComm = ctx.db.prepare(
153
+ 'INSERT OR REPLACE INTO communities (id, label, size, cohesion_score) VALUES (?, ?, ?, ?)',
154
+ );
155
+ ctx.db.transaction(() => {
156
+ for (const [nodeId, commId] of memberships) {
157
+ updateNode.run(commId, nodeId);
158
+ }
159
+ for (const [commId, label] of communityLabels) {
160
+ upsertComm.run(commId, label, commSizes.get(commId) ?? 0, cohesionScores.get(commId) ?? 0);
161
+ }
162
+ })();
163
+ }
164
+
145
165
  return { memberships, communityLabels, cohesionScores };
146
166
  },
147
167
  };
@@ -124,17 +124,26 @@ export const toolsPhase: PipelinePhase<ToolsOutput> = {
124
124
  return n.includes(lowerTool) || (n.length > 0 && lowerTool.includes(n));
125
125
  });
126
126
 
127
- if (matches.length === 1) {
128
- const edgeId = makeId(toolNodeId, matches[0].id, 'handles_tool');
127
+ if (matches.length >= 1) {
128
+ // Pick best match: exact name > name contains tool > tool contains name
129
+ const scored = matches
130
+ .map(row => {
131
+ const n = row.name.toLowerCase().replace(/[_-]/g, '');
132
+ const score = n === lowerTool ? 3 : n.includes(lowerTool) ? 2 : 1;
133
+ return { row, score };
134
+ })
135
+ .sort((a, b) => b.score - a.score);
136
+ const { row: best, score } = scored[0];
137
+ const edgeId = makeId(toolNodeId, best.id, 'handles_tool');
129
138
  handlesEdges.push({
130
139
  id: edgeId,
131
140
  sourceId: toolNodeId,
132
- targetId: matches[0].id,
141
+ targetId: best.id,
133
142
  relation: 'HANDLES_TOOL',
134
143
  confidence: 'EXTRACTED',
135
- confidenceScore: 0.85,
144
+ confidenceScore: score === 3 ? 0.95 : 0.75,
136
145
  });
137
- def.handlerNodeId = matches[0].id;
146
+ def.handlerNodeId = best.id;
138
147
  }
139
148
  }
140
149