@optave/codegraph 3.1.2 → 3.1.4
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/README.md +19 -21
- package/package.json +10 -7
- package/src/analysis/context.js +408 -0
- package/src/analysis/dependencies.js +341 -0
- package/src/analysis/exports.js +130 -0
- package/src/analysis/impact.js +463 -0
- package/src/analysis/module-map.js +322 -0
- package/src/analysis/roles.js +45 -0
- package/src/analysis/symbol-lookup.js +232 -0
- package/src/ast-analysis/shared.js +5 -4
- package/src/batch.js +2 -1
- package/src/builder/context.js +85 -0
- package/src/builder/helpers.js +218 -0
- package/src/builder/incremental.js +178 -0
- package/src/builder/pipeline.js +130 -0
- package/src/builder/stages/build-edges.js +297 -0
- package/src/builder/stages/build-structure.js +113 -0
- package/src/builder/stages/collect-files.js +44 -0
- package/src/builder/stages/detect-changes.js +413 -0
- package/src/builder/stages/finalize.js +139 -0
- package/src/builder/stages/insert-nodes.js +195 -0
- package/src/builder/stages/parse-files.js +28 -0
- package/src/builder/stages/resolve-imports.js +143 -0
- package/src/builder/stages/run-analyses.js +44 -0
- package/src/builder.js +10 -1472
- package/src/cfg.js +1 -2
- package/src/cli/commands/ast.js +26 -0
- package/src/cli/commands/audit.js +46 -0
- package/src/cli/commands/batch.js +68 -0
- package/src/cli/commands/branch-compare.js +21 -0
- package/src/cli/commands/build.js +26 -0
- package/src/cli/commands/cfg.js +30 -0
- package/src/cli/commands/check.js +79 -0
- package/src/cli/commands/children.js +31 -0
- package/src/cli/commands/co-change.js +65 -0
- package/src/cli/commands/communities.js +23 -0
- package/src/cli/commands/complexity.js +45 -0
- package/src/cli/commands/context.js +34 -0
- package/src/cli/commands/cycles.js +28 -0
- package/src/cli/commands/dataflow.js +32 -0
- package/src/cli/commands/deps.js +16 -0
- package/src/cli/commands/diff-impact.js +30 -0
- package/src/cli/commands/embed.js +30 -0
- package/src/cli/commands/export.js +75 -0
- package/src/cli/commands/exports.js +18 -0
- package/src/cli/commands/flow.js +36 -0
- package/src/cli/commands/fn-impact.js +30 -0
- package/src/cli/commands/impact.js +16 -0
- package/src/cli/commands/info.js +76 -0
- package/src/cli/commands/map.js +19 -0
- package/src/cli/commands/mcp.js +18 -0
- package/src/cli/commands/models.js +19 -0
- package/src/cli/commands/owners.js +25 -0
- package/src/cli/commands/path.js +36 -0
- package/src/cli/commands/plot.js +80 -0
- package/src/cli/commands/query.js +49 -0
- package/src/cli/commands/registry.js +100 -0
- package/src/cli/commands/roles.js +34 -0
- package/src/cli/commands/search.js +42 -0
- package/src/cli/commands/sequence.js +32 -0
- package/src/cli/commands/snapshot.js +61 -0
- package/src/cli/commands/stats.js +15 -0
- package/src/cli/commands/structure.js +32 -0
- package/src/cli/commands/triage.js +78 -0
- package/src/cli/commands/watch.js +12 -0
- package/src/cli/commands/where.js +24 -0
- package/src/cli/index.js +118 -0
- package/src/cli/shared/options.js +39 -0
- package/src/cli/shared/output.js +1 -0
- package/src/cli.js +11 -1514
- package/src/commands/check.js +5 -5
- package/src/commands/manifesto.js +3 -3
- package/src/commands/structure.js +1 -1
- package/src/communities.js +15 -87
- package/src/complexity.js +1 -1
- package/src/cycles.js +30 -85
- package/src/dataflow.js +1 -2
- package/src/db/connection.js +4 -4
- package/src/db/migrations.js +41 -0
- package/src/db/query-builder.js +6 -5
- package/src/db/repository/base.js +201 -0
- package/src/db/repository/cached-stmt.js +19 -0
- package/src/db/repository/cfg.js +27 -38
- package/src/db/repository/cochange.js +16 -3
- package/src/db/repository/complexity.js +11 -6
- package/src/db/repository/dataflow.js +6 -1
- package/src/db/repository/edges.js +120 -98
- package/src/db/repository/embeddings.js +14 -3
- package/src/db/repository/graph-read.js +32 -9
- package/src/db/repository/in-memory-repository.js +584 -0
- package/src/db/repository/index.js +6 -1
- package/src/db/repository/nodes.js +110 -40
- package/src/db/repository/sqlite-repository.js +219 -0
- package/src/db.js +5 -0
- package/src/embeddings/generator.js +163 -0
- package/src/embeddings/index.js +13 -0
- package/src/embeddings/models.js +218 -0
- package/src/embeddings/search/cli-formatter.js +151 -0
- package/src/embeddings/search/filters.js +46 -0
- package/src/embeddings/search/hybrid.js +121 -0
- package/src/embeddings/search/keyword.js +68 -0
- package/src/embeddings/search/prepare.js +66 -0
- package/src/embeddings/search/semantic.js +145 -0
- package/src/embeddings/stores/fts5.js +27 -0
- package/src/embeddings/stores/sqlite-blob.js +24 -0
- package/src/embeddings/strategies/source.js +14 -0
- package/src/embeddings/strategies/structured.js +43 -0
- package/src/embeddings/strategies/text-utils.js +43 -0
- package/src/errors.js +78 -0
- package/src/export.js +217 -520
- package/src/extractors/csharp.js +10 -2
- package/src/extractors/go.js +3 -1
- package/src/extractors/helpers.js +71 -0
- package/src/extractors/java.js +9 -2
- package/src/extractors/javascript.js +38 -1
- package/src/extractors/php.js +3 -1
- package/src/extractors/python.js +14 -3
- package/src/extractors/rust.js +3 -1
- package/src/graph/algorithms/bfs.js +49 -0
- package/src/graph/algorithms/centrality.js +16 -0
- package/src/graph/algorithms/index.js +5 -0
- package/src/graph/algorithms/louvain.js +26 -0
- package/src/graph/algorithms/shortest-path.js +41 -0
- package/src/graph/algorithms/tarjan.js +49 -0
- package/src/graph/builders/dependency.js +91 -0
- package/src/graph/builders/index.js +3 -0
- package/src/graph/builders/structure.js +40 -0
- package/src/graph/builders/temporal.js +33 -0
- package/src/graph/classifiers/index.js +2 -0
- package/src/graph/classifiers/risk.js +85 -0
- package/src/graph/classifiers/roles.js +64 -0
- package/src/graph/index.js +13 -0
- package/src/graph/model.js +230 -0
- package/src/index.js +33 -204
- package/src/infrastructure/result-formatter.js +2 -21
- package/src/mcp/index.js +2 -0
- package/src/mcp/middleware.js +26 -0
- package/src/mcp/server.js +128 -0
- package/src/mcp/tool-registry.js +801 -0
- package/src/mcp/tools/ast-query.js +14 -0
- package/src/mcp/tools/audit.js +21 -0
- package/src/mcp/tools/batch-query.js +11 -0
- package/src/mcp/tools/branch-compare.js +10 -0
- package/src/mcp/tools/cfg.js +21 -0
- package/src/mcp/tools/check.js +43 -0
- package/src/mcp/tools/co-changes.js +20 -0
- package/src/mcp/tools/code-owners.js +12 -0
- package/src/mcp/tools/communities.js +15 -0
- package/src/mcp/tools/complexity.js +18 -0
- package/src/mcp/tools/context.js +17 -0
- package/src/mcp/tools/dataflow.js +26 -0
- package/src/mcp/tools/diff-impact.js +24 -0
- package/src/mcp/tools/execution-flow.js +26 -0
- package/src/mcp/tools/export-graph.js +57 -0
- package/src/mcp/tools/file-deps.js +12 -0
- package/src/mcp/tools/file-exports.js +13 -0
- package/src/mcp/tools/find-cycles.js +15 -0
- package/src/mcp/tools/fn-impact.js +15 -0
- package/src/mcp/tools/impact-analysis.js +12 -0
- package/src/mcp/tools/index.js +71 -0
- package/src/mcp/tools/list-functions.js +14 -0
- package/src/mcp/tools/list-repos.js +11 -0
- package/src/mcp/tools/module-map.js +6 -0
- package/src/mcp/tools/node-roles.js +14 -0
- package/src/mcp/tools/path.js +12 -0
- package/src/mcp/tools/query.js +30 -0
- package/src/mcp/tools/semantic-search.js +65 -0
- package/src/mcp/tools/sequence.js +17 -0
- package/src/mcp/tools/structure.js +15 -0
- package/src/mcp/tools/symbol-children.js +14 -0
- package/src/mcp/tools/triage.js +35 -0
- package/src/mcp/tools/where.js +13 -0
- package/src/mcp.js +2 -1470
- package/src/native.js +34 -10
- package/src/parser.js +53 -2
- package/src/presentation/colors.js +44 -0
- package/src/presentation/export.js +444 -0
- package/src/presentation/result-formatter.js +21 -0
- package/src/presentation/sequence-renderer.js +43 -0
- package/src/presentation/table.js +47 -0
- package/src/presentation/viewer.js +634 -0
- package/src/queries.js +35 -2276
- package/src/resolve.js +1 -1
- package/src/sequence.js +2 -38
- package/src/shared/file-utils.js +153 -0
- package/src/shared/generators.js +125 -0
- package/src/shared/hierarchy.js +27 -0
- package/src/shared/normalize.js +59 -0
- package/src/snapshot.js +6 -5
- package/src/structure.js +15 -40
- package/src/triage.js +20 -72
- package/src/viewer.js +35 -656
- package/src/watcher.js +8 -148
- package/src/embedder.js +0 -1097
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { warn } from '../../logger.js';
|
|
2
|
+
import { normalizeSymbol } from '../../queries.js';
|
|
3
|
+
import { embed } from '../models.js';
|
|
4
|
+
import { cosineSim } from '../stores/sqlite-blob.js';
|
|
5
|
+
import { prepareSearch } from './prepare.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Single-query semantic search — returns data instead of printing.
|
|
9
|
+
* Returns { results: [{ name, kind, file, line, similarity }] } or null on failure.
|
|
10
|
+
*/
|
|
11
|
+
export async function searchData(query, customDbPath, opts = {}) {
|
|
12
|
+
const limit = opts.limit || 15;
|
|
13
|
+
const minScore = opts.minScore || 0.2;
|
|
14
|
+
|
|
15
|
+
const prepared = prepareSearch(customDbPath, opts);
|
|
16
|
+
if (!prepared) return null;
|
|
17
|
+
const { db, rows, modelKey, storedDim } = prepared;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const {
|
|
21
|
+
vectors: [queryVec],
|
|
22
|
+
dim,
|
|
23
|
+
} = await embed([query], modelKey);
|
|
24
|
+
|
|
25
|
+
if (storedDim && dim !== storedDim) {
|
|
26
|
+
console.log(
|
|
27
|
+
`Warning: query model dimension (${dim}) doesn't match stored embeddings (${storedDim}).`,
|
|
28
|
+
);
|
|
29
|
+
console.log(` Re-run \`codegraph embed\` with the same model, or use --model to match.`);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const hc = new Map();
|
|
34
|
+
const results = [];
|
|
35
|
+
for (const row of rows) {
|
|
36
|
+
const vec = new Float32Array(new Uint8Array(row.vector).buffer);
|
|
37
|
+
const sim = cosineSim(queryVec, vec);
|
|
38
|
+
|
|
39
|
+
if (sim >= minScore) {
|
|
40
|
+
results.push({
|
|
41
|
+
...normalizeSymbol(row, db, hc),
|
|
42
|
+
similarity: sim,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
results.sort((a, b) => b.similarity - a.similarity);
|
|
48
|
+
return { results: results.slice(0, limit) };
|
|
49
|
+
} finally {
|
|
50
|
+
db.close();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Multi-query semantic search with Reciprocal Rank Fusion (RRF).
|
|
56
|
+
* Returns { results: [{ name, kind, file, line, rrf, queryScores }] } or null on failure.
|
|
57
|
+
*/
|
|
58
|
+
export async function multiSearchData(queries, customDbPath, opts = {}) {
|
|
59
|
+
const limit = opts.limit || 15;
|
|
60
|
+
const minScore = opts.minScore || 0.2;
|
|
61
|
+
const k = opts.rrfK || 60;
|
|
62
|
+
|
|
63
|
+
const prepared = prepareSearch(customDbPath, opts);
|
|
64
|
+
if (!prepared) return null;
|
|
65
|
+
const { db, rows, modelKey, storedDim } = prepared;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const { vectors: queryVecs, dim } = await embed(queries, modelKey);
|
|
69
|
+
|
|
70
|
+
// Warn about similar queries that may bias RRF results
|
|
71
|
+
const SIMILARITY_WARN_THRESHOLD = 0.85;
|
|
72
|
+
for (let i = 0; i < queryVecs.length; i++) {
|
|
73
|
+
for (let j = i + 1; j < queryVecs.length; j++) {
|
|
74
|
+
const sim = cosineSim(queryVecs[i], queryVecs[j]);
|
|
75
|
+
if (sim >= SIMILARITY_WARN_THRESHOLD) {
|
|
76
|
+
warn(
|
|
77
|
+
`Queries "${queries[i]}" and "${queries[j]}" are very similar ` +
|
|
78
|
+
`(${(sim * 100).toFixed(0)}% cosine similarity). ` +
|
|
79
|
+
`This may bias RRF results toward their shared matches. ` +
|
|
80
|
+
`Consider using more distinct queries.`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (storedDim && dim !== storedDim) {
|
|
87
|
+
console.log(
|
|
88
|
+
`Warning: query model dimension (${dim}) doesn't match stored embeddings (${storedDim}).`,
|
|
89
|
+
);
|
|
90
|
+
console.log(` Re-run \`codegraph embed\` with the same model, or use --model to match.`);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Parse row vectors once
|
|
95
|
+
const rowVecs = rows.map((row) => new Float32Array(new Uint8Array(row.vector).buffer));
|
|
96
|
+
|
|
97
|
+
// For each query: compute similarities, filter by minScore, rank
|
|
98
|
+
const perQueryRanked = queries.map((_query, qi) => {
|
|
99
|
+
const scored = [];
|
|
100
|
+
for (let ri = 0; ri < rows.length; ri++) {
|
|
101
|
+
const sim = cosineSim(queryVecs[qi], rowVecs[ri]);
|
|
102
|
+
if (sim >= minScore) {
|
|
103
|
+
scored.push({ rowIndex: ri, similarity: sim });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
scored.sort((a, b) => b.similarity - a.similarity);
|
|
107
|
+
// Assign 1-indexed ranks
|
|
108
|
+
return scored.map((item, rank) => ({ ...item, rank: rank + 1 }));
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Fuse results using RRF: for each unique row, sum 1/(k + rank_i) across queries
|
|
112
|
+
const fusionMap = new Map(); // rowIndex -> { rrfScore, queryScores[] }
|
|
113
|
+
for (let qi = 0; qi < queries.length; qi++) {
|
|
114
|
+
for (const item of perQueryRanked[qi]) {
|
|
115
|
+
if (!fusionMap.has(item.rowIndex)) {
|
|
116
|
+
fusionMap.set(item.rowIndex, { rrfScore: 0, queryScores: [] });
|
|
117
|
+
}
|
|
118
|
+
const entry = fusionMap.get(item.rowIndex);
|
|
119
|
+
entry.rrfScore += 1 / (k + item.rank);
|
|
120
|
+
entry.queryScores.push({
|
|
121
|
+
query: queries[qi],
|
|
122
|
+
similarity: item.similarity,
|
|
123
|
+
rank: item.rank,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Build results sorted by RRF score
|
|
129
|
+
const hc = new Map();
|
|
130
|
+
const results = [];
|
|
131
|
+
for (const [rowIndex, entry] of fusionMap) {
|
|
132
|
+
const row = rows[rowIndex];
|
|
133
|
+
results.push({
|
|
134
|
+
...normalizeSymbol(row, db, hc),
|
|
135
|
+
rrf: entry.rrfScore,
|
|
136
|
+
queryScores: entry.queryScores,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
results.sort((a, b) => b.rrf - a.rrf);
|
|
141
|
+
return { results: results.slice(0, limit) };
|
|
142
|
+
} finally {
|
|
143
|
+
db.close();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize a user query for FTS5 MATCH syntax.
|
|
3
|
+
* Wraps each token as an implicit OR and escapes special FTS5 characters.
|
|
4
|
+
*/
|
|
5
|
+
export function sanitizeFtsQuery(query) {
|
|
6
|
+
// Remove FTS5 special chars that could cause syntax errors
|
|
7
|
+
const cleaned = query.replace(/[*"():^{}~<>]/g, ' ').trim();
|
|
8
|
+
if (!cleaned) return null;
|
|
9
|
+
// Split into tokens, wrap with OR for multi-token queries
|
|
10
|
+
const tokens = cleaned.split(/\s+/).filter((t) => t.length > 0);
|
|
11
|
+
if (tokens.length === 0) return null;
|
|
12
|
+
if (tokens.length === 1) return `"${tokens[0]}"`;
|
|
13
|
+
return tokens.map((t) => `"${t}"`).join(' OR ');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if the FTS5 index exists in the database.
|
|
18
|
+
* Returns true if fts_index table exists and has rows, false otherwise.
|
|
19
|
+
*/
|
|
20
|
+
export function hasFtsIndex(db) {
|
|
21
|
+
try {
|
|
22
|
+
const row = db.prepare('SELECT COUNT(*) as c FROM fts_index').get();
|
|
23
|
+
return row.c > 0;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} VectorStore
|
|
3
|
+
* @property {(queryVec: Float32Array, rows: Array<{vector: Buffer}>) => Array<{index: number, score: number}>} search
|
|
4
|
+
* Score every row against a query vector and return scored indices.
|
|
5
|
+
*
|
|
6
|
+
* Future implementations (e.g. HNSW via `hnsw.js`) implement this same shape
|
|
7
|
+
* for approximate nearest-neighbor search.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Cosine similarity between two Float32Arrays.
|
|
12
|
+
*/
|
|
13
|
+
export function cosineSim(a, b) {
|
|
14
|
+
let dot = 0,
|
|
15
|
+
normA = 0,
|
|
16
|
+
normB = 0;
|
|
17
|
+
for (let i = 0; i < a.length; i++) {
|
|
18
|
+
dot += a[i] * b[i];
|
|
19
|
+
normA += a[i] * a[i];
|
|
20
|
+
normB += b[i] * b[i];
|
|
21
|
+
}
|
|
22
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
23
|
+
return denom === 0 ? 0 : dot / denom;
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { splitIdentifier } from './text-utils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build raw source-code text for a symbol (original strategy).
|
|
5
|
+
*/
|
|
6
|
+
export function buildSourceText(node, file, lines) {
|
|
7
|
+
const startLine = Math.max(0, node.line - 1);
|
|
8
|
+
const endLine = node.end_line
|
|
9
|
+
? Math.min(lines.length, node.end_line)
|
|
10
|
+
: Math.min(lines.length, startLine + 15);
|
|
11
|
+
const context = lines.slice(startLine, endLine).join('\n');
|
|
12
|
+
const readable = splitIdentifier(node.name);
|
|
13
|
+
return `${node.kind} ${node.name} (${readable}) in ${file}\n${context}`;
|
|
14
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { findCalleeNames, findCallerNames } from '../../db.js';
|
|
2
|
+
import { extractLeadingComment, splitIdentifier } from './text-utils.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build graph-enriched text for a symbol using dependency context.
|
|
6
|
+
* Produces compact, semantic text (~100 tokens) instead of full source code.
|
|
7
|
+
*/
|
|
8
|
+
export function buildStructuredText(node, file, lines, db) {
|
|
9
|
+
const readable = splitIdentifier(node.name);
|
|
10
|
+
const parts = [`${node.kind} ${node.name} (${readable}) in ${file}`];
|
|
11
|
+
const startLine = Math.max(0, node.line - 1);
|
|
12
|
+
|
|
13
|
+
// Extract parameters from signature (best-effort, single-line)
|
|
14
|
+
const sigLine = lines[startLine] || '';
|
|
15
|
+
const paramMatch = sigLine.match(/\(([^)]*)\)/);
|
|
16
|
+
if (paramMatch?.[1]?.trim()) {
|
|
17
|
+
parts.push(`Parameters: ${paramMatch[1].trim()}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Graph context: callees (capped at 10)
|
|
21
|
+
const callees = findCalleeNames(db, node.id);
|
|
22
|
+
if (callees.length > 0) {
|
|
23
|
+
parts.push(`Calls: ${callees.slice(0, 10).join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Graph context: callers (capped at 10)
|
|
27
|
+
const callers = findCallerNames(db, node.id);
|
|
28
|
+
if (callers.length > 0) {
|
|
29
|
+
parts.push(`Called by: ${callers.slice(0, 10).join(', ')}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Leading comment (high semantic value) or first few lines of code
|
|
33
|
+
const comment = extractLeadingComment(lines, startLine);
|
|
34
|
+
if (comment) {
|
|
35
|
+
parts.push(comment);
|
|
36
|
+
} else {
|
|
37
|
+
const endLine = Math.min(lines.length, startLine + 4);
|
|
38
|
+
const snippet = lines.slice(startLine, endLine).join('\n').trim();
|
|
39
|
+
if (snippet) parts.push(snippet);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return parts.join('\n');
|
|
43
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split an identifier into readable words.
|
|
3
|
+
* camelCase/PascalCase -> "camel Case", snake_case -> "snake case", kebab-case -> "kebab case"
|
|
4
|
+
*/
|
|
5
|
+
export function splitIdentifier(name) {
|
|
6
|
+
return name
|
|
7
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
8
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
9
|
+
.replace(/[_-]+/g, ' ')
|
|
10
|
+
.trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract leading comment text (JSDoc, //, #, etc.) above a function line.
|
|
15
|
+
* Returns the cleaned comment text or null if none found.
|
|
16
|
+
*/
|
|
17
|
+
export function extractLeadingComment(lines, fnLineIndex) {
|
|
18
|
+
if (fnLineIndex > lines.length) return null;
|
|
19
|
+
const raw = [];
|
|
20
|
+
for (let i = fnLineIndex - 1; i >= Math.max(0, fnLineIndex - 15); i--) {
|
|
21
|
+
if (i >= lines.length) continue;
|
|
22
|
+
const trimmed = lines[i].trim();
|
|
23
|
+
if (/^(\/\/|\/\*|\*\/|\*|#|\/\/\/)/.test(trimmed)) {
|
|
24
|
+
raw.unshift(trimmed);
|
|
25
|
+
} else if (trimmed === '') {
|
|
26
|
+
if (raw.length > 0) break;
|
|
27
|
+
} else {
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (raw.length === 0) return null;
|
|
32
|
+
return raw
|
|
33
|
+
.map((line) =>
|
|
34
|
+
line
|
|
35
|
+
.replace(/^\/\*\*?\s?|\*\/$/g, '') // opening /** or /* and closing */
|
|
36
|
+
.replace(/^\*\s?/, '') // middle * lines
|
|
37
|
+
.replace(/^\/\/\/?\s?/, '') // // or ///
|
|
38
|
+
.replace(/^#\s?/, '') // # (Python/Ruby)
|
|
39
|
+
.trim(),
|
|
40
|
+
)
|
|
41
|
+
.filter((l) => l.length > 0)
|
|
42
|
+
.join(' ');
|
|
43
|
+
}
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain error hierarchy for codegraph.
|
|
3
|
+
*
|
|
4
|
+
* Library code throws these instead of calling process.exit() or throwing
|
|
5
|
+
* bare Error instances. The CLI top-level catch formats them for humans;
|
|
6
|
+
* MCP returns structured { isError, code } responses.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export class CodegraphError extends Error {
|
|
10
|
+
/** @type {string} */
|
|
11
|
+
code;
|
|
12
|
+
|
|
13
|
+
/** @type {string|undefined} */
|
|
14
|
+
file;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} message
|
|
18
|
+
* @param {object} [opts]
|
|
19
|
+
* @param {string} [opts.code]
|
|
20
|
+
* @param {string} [opts.file] - Related file path, if applicable
|
|
21
|
+
* @param {Error} [opts.cause] - Original error that triggered this one
|
|
22
|
+
*/
|
|
23
|
+
constructor(message, { code = 'CODEGRAPH_ERROR', file, cause } = {}) {
|
|
24
|
+
super(message, { cause });
|
|
25
|
+
this.name = 'CodegraphError';
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.file = file;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class ParseError extends CodegraphError {
|
|
32
|
+
constructor(message, opts = {}) {
|
|
33
|
+
super(message, { code: 'PARSE_FAILED', ...opts });
|
|
34
|
+
this.name = 'ParseError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class DbError extends CodegraphError {
|
|
39
|
+
constructor(message, opts = {}) {
|
|
40
|
+
super(message, { code: 'DB_ERROR', ...opts });
|
|
41
|
+
this.name = 'DbError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class ConfigError extends CodegraphError {
|
|
46
|
+
constructor(message, opts = {}) {
|
|
47
|
+
super(message, { code: 'CONFIG_INVALID', ...opts });
|
|
48
|
+
this.name = 'ConfigError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class ResolutionError extends CodegraphError {
|
|
53
|
+
constructor(message, opts = {}) {
|
|
54
|
+
super(message, { code: 'RESOLUTION_FAILED', ...opts });
|
|
55
|
+
this.name = 'ResolutionError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class EngineError extends CodegraphError {
|
|
60
|
+
constructor(message, opts = {}) {
|
|
61
|
+
super(message, { code: 'ENGINE_UNAVAILABLE', ...opts });
|
|
62
|
+
this.name = 'EngineError';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class AnalysisError extends CodegraphError {
|
|
67
|
+
constructor(message, opts = {}) {
|
|
68
|
+
super(message, { code: 'ANALYSIS_FAILED', ...opts });
|
|
69
|
+
this.name = 'AnalysisError';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class BoundaryError extends CodegraphError {
|
|
74
|
+
constructor(message, opts = {}) {
|
|
75
|
+
super(message, { code: 'BOUNDARY_VIOLATION', ...opts });
|
|
76
|
+
this.name = 'BoundaryError';
|
|
77
|
+
}
|
|
78
|
+
}
|