gitnexus 1.2.6 → 1.2.8
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 +9 -10
- package/dist/cli/analyze.d.ts +1 -1
- package/dist/cli/analyze.js +59 -15
- package/dist/cli/eval-server.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/mcp.js +1 -1
- package/dist/core/augmentation/engine.js +20 -20
- package/dist/core/embeddings/embedder.js +7 -0
- package/dist/core/embeddings/embedding-pipeline.js +26 -26
- package/dist/core/ingestion/cluster-enricher.js +16 -16
- package/dist/core/ingestion/filesystem-walker.js +17 -3
- package/dist/core/ingestion/parsing-processor.js +4 -1
- package/dist/core/ingestion/workers/parse-worker.js +13 -4
- package/dist/core/ingestion/workers/worker-pool.js +43 -9
- package/dist/core/kuzu/kuzu-adapter.js +9 -9
- package/dist/core/search/hybrid-search.js +3 -3
- package/dist/core/wiki/graph-queries.js +52 -52
- package/dist/core/wiki/prompts.js +82 -82
- package/dist/mcp/local/local-backend.d.ts +18 -3
- package/dist/mcp/local/local-backend.js +57 -13
- package/dist/mcp/resources.js +4 -4
- package/hooks/claude/gitnexus-hook.cjs +135 -135
- package/hooks/claude/pre-tool-use.sh +78 -78
- package/hooks/claude/session-start.sh +42 -42
- package/package.json +1 -1
- package/vendor/leiden/index.cjs +355 -355
- package/vendor/leiden/utils.cjs +392 -392
|
@@ -98,9 +98,9 @@ export const formatHybridResults = (results) => {
|
|
|
98
98
|
const location = r.startLine ? ` (lines ${r.startLine}-${r.endLine})` : '';
|
|
99
99
|
const label = r.label ? `${r.label}: ` : 'File: ';
|
|
100
100
|
const name = r.name || r.filePath.split('/').pop() || r.filePath;
|
|
101
|
-
return `[${i + 1}] ${label}${name}
|
|
102
|
-
File: ${r.filePath}${location}
|
|
103
|
-
Found by: ${sources}
|
|
101
|
+
return `[${i + 1}] ${label}${name}
|
|
102
|
+
File: ${r.filePath}${location}
|
|
103
|
+
Found by: ${sources}
|
|
104
104
|
Relevance: ${r.score.toFixed(4)}`;
|
|
105
105
|
});
|
|
106
106
|
return `Found ${results.length} results:\n\n${formatted.join('\n\n')}`;
|
|
@@ -22,11 +22,11 @@ export async function closeWikiDb() {
|
|
|
22
22
|
* Get all source files with their exported symbol names and types.
|
|
23
23
|
*/
|
|
24
24
|
export async function getFilesWithExports() {
|
|
25
|
-
const rows = await executeQuery(REPO_ID, `
|
|
26
|
-
MATCH (f:File)-[:CodeRelation {type: 'DEFINES'}]->(n)
|
|
27
|
-
WHERE n.isExported = true
|
|
28
|
-
RETURN f.filePath AS filePath, n.name AS name, labels(n)[0] AS type
|
|
29
|
-
ORDER BY f.filePath
|
|
25
|
+
const rows = await executeQuery(REPO_ID, `
|
|
26
|
+
MATCH (f:File)-[:CodeRelation {type: 'DEFINES'}]->(n)
|
|
27
|
+
WHERE n.isExported = true
|
|
28
|
+
RETURN f.filePath AS filePath, n.name AS name, labels(n)[0] AS type
|
|
29
|
+
ORDER BY f.filePath
|
|
30
30
|
`);
|
|
31
31
|
const fileMap = new Map();
|
|
32
32
|
for (const row of rows) {
|
|
@@ -46,10 +46,10 @@ export async function getFilesWithExports() {
|
|
|
46
46
|
* Get all files tracked in the graph (including those with no exports).
|
|
47
47
|
*/
|
|
48
48
|
export async function getAllFiles() {
|
|
49
|
-
const rows = await executeQuery(REPO_ID, `
|
|
50
|
-
MATCH (f:File)
|
|
51
|
-
RETURN f.filePath AS filePath
|
|
52
|
-
ORDER BY f.filePath
|
|
49
|
+
const rows = await executeQuery(REPO_ID, `
|
|
50
|
+
MATCH (f:File)
|
|
51
|
+
RETURN f.filePath AS filePath
|
|
52
|
+
ORDER BY f.filePath
|
|
53
53
|
`);
|
|
54
54
|
return rows.map(r => r.filePath || r[0]);
|
|
55
55
|
}
|
|
@@ -57,11 +57,11 @@ export async function getAllFiles() {
|
|
|
57
57
|
* Get inter-file call edges (calls between different files).
|
|
58
58
|
*/
|
|
59
59
|
export async function getInterFileCallEdges() {
|
|
60
|
-
const rows = await executeQuery(REPO_ID, `
|
|
61
|
-
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
62
|
-
WHERE a.filePath <> b.filePath
|
|
63
|
-
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
64
|
-
b.filePath AS toFile, b.name AS toName
|
|
60
|
+
const rows = await executeQuery(REPO_ID, `
|
|
61
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
62
|
+
WHERE a.filePath <> b.filePath
|
|
63
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
64
|
+
b.filePath AS toFile, b.name AS toName
|
|
65
65
|
`);
|
|
66
66
|
return rows.map(r => ({
|
|
67
67
|
fromFile: r.fromFile || r[0],
|
|
@@ -77,11 +77,11 @@ export async function getIntraModuleCallEdges(filePaths) {
|
|
|
77
77
|
if (filePaths.length === 0)
|
|
78
78
|
return [];
|
|
79
79
|
const fileList = filePaths.map(f => `'${f.replace(/'/g, "''")}'`).join(', ');
|
|
80
|
-
const rows = await executeQuery(REPO_ID, `
|
|
81
|
-
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
82
|
-
WHERE a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
|
|
83
|
-
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
84
|
-
b.filePath AS toFile, b.name AS toName
|
|
80
|
+
const rows = await executeQuery(REPO_ID, `
|
|
81
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
82
|
+
WHERE a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
|
|
83
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
84
|
+
b.filePath AS toFile, b.name AS toName
|
|
85
85
|
`);
|
|
86
86
|
return rows.map(r => ({
|
|
87
87
|
fromFile: r.fromFile || r[0],
|
|
@@ -97,19 +97,19 @@ export async function getInterModuleCallEdges(filePaths) {
|
|
|
97
97
|
if (filePaths.length === 0)
|
|
98
98
|
return { outgoing: [], incoming: [] };
|
|
99
99
|
const fileList = filePaths.map(f => `'${f.replace(/'/g, "''")}'`).join(', ');
|
|
100
|
-
const outRows = await executeQuery(REPO_ID, `
|
|
101
|
-
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
102
|
-
WHERE a.filePath IN [${fileList}] AND NOT b.filePath IN [${fileList}]
|
|
103
|
-
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
104
|
-
b.filePath AS toFile, b.name AS toName
|
|
105
|
-
LIMIT 30
|
|
100
|
+
const outRows = await executeQuery(REPO_ID, `
|
|
101
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
102
|
+
WHERE a.filePath IN [${fileList}] AND NOT b.filePath IN [${fileList}]
|
|
103
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
104
|
+
b.filePath AS toFile, b.name AS toName
|
|
105
|
+
LIMIT 30
|
|
106
106
|
`);
|
|
107
|
-
const inRows = await executeQuery(REPO_ID, `
|
|
108
|
-
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
109
|
-
WHERE NOT a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
|
|
110
|
-
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
111
|
-
b.filePath AS toFile, b.name AS toName
|
|
112
|
-
LIMIT 30
|
|
107
|
+
const inRows = await executeQuery(REPO_ID, `
|
|
108
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
109
|
+
WHERE NOT a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
|
|
110
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
111
|
+
b.filePath AS toFile, b.name AS toName
|
|
112
|
+
LIMIT 30
|
|
113
113
|
`);
|
|
114
114
|
return {
|
|
115
115
|
outgoing: outRows.map(r => ({
|
|
@@ -135,13 +135,13 @@ export async function getProcessesForFiles(filePaths, limit = 5) {
|
|
|
135
135
|
return [];
|
|
136
136
|
const fileList = filePaths.map(f => `'${f.replace(/'/g, "''")}'`).join(', ');
|
|
137
137
|
// Find processes that have steps in the given files
|
|
138
|
-
const procRows = await executeQuery(REPO_ID, `
|
|
139
|
-
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
140
|
-
WHERE s.filePath IN [${fileList}]
|
|
141
|
-
RETURN DISTINCT p.id AS id, p.heuristicLabel AS label,
|
|
142
|
-
p.processType AS type, p.stepCount AS stepCount
|
|
143
|
-
ORDER BY stepCount DESC
|
|
144
|
-
LIMIT ${limit}
|
|
138
|
+
const procRows = await executeQuery(REPO_ID, `
|
|
139
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
140
|
+
WHERE s.filePath IN [${fileList}]
|
|
141
|
+
RETURN DISTINCT p.id AS id, p.heuristicLabel AS label,
|
|
142
|
+
p.processType AS type, p.stepCount AS stepCount
|
|
143
|
+
ORDER BY stepCount DESC
|
|
144
|
+
LIMIT ${limit}
|
|
145
145
|
`);
|
|
146
146
|
const processes = [];
|
|
147
147
|
for (const row of procRows) {
|
|
@@ -150,10 +150,10 @@ export async function getProcessesForFiles(filePaths, limit = 5) {
|
|
|
150
150
|
const type = row.type || row[2] || 'unknown';
|
|
151
151
|
const stepCount = row.stepCount || row[3] || 0;
|
|
152
152
|
// Get the full step trace for this process
|
|
153
|
-
const stepRows = await executeQuery(REPO_ID, `
|
|
154
|
-
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
|
|
155
|
-
RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
|
|
156
|
-
ORDER BY r.step
|
|
153
|
+
const stepRows = await executeQuery(REPO_ID, `
|
|
154
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
|
|
155
|
+
RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
|
|
156
|
+
ORDER BY r.step
|
|
157
157
|
`);
|
|
158
158
|
processes.push({
|
|
159
159
|
id: procId,
|
|
@@ -174,12 +174,12 @@ export async function getProcessesForFiles(filePaths, limit = 5) {
|
|
|
174
174
|
* Get all processes in the graph (for overview page).
|
|
175
175
|
*/
|
|
176
176
|
export async function getAllProcesses(limit = 20) {
|
|
177
|
-
const procRows = await executeQuery(REPO_ID, `
|
|
178
|
-
MATCH (p:Process)
|
|
179
|
-
RETURN p.id AS id, p.heuristicLabel AS label,
|
|
180
|
-
p.processType AS type, p.stepCount AS stepCount
|
|
181
|
-
ORDER BY stepCount DESC
|
|
182
|
-
LIMIT ${limit}
|
|
177
|
+
const procRows = await executeQuery(REPO_ID, `
|
|
178
|
+
MATCH (p:Process)
|
|
179
|
+
RETURN p.id AS id, p.heuristicLabel AS label,
|
|
180
|
+
p.processType AS type, p.stepCount AS stepCount
|
|
181
|
+
ORDER BY stepCount DESC
|
|
182
|
+
LIMIT ${limit}
|
|
183
183
|
`);
|
|
184
184
|
const processes = [];
|
|
185
185
|
for (const row of procRows) {
|
|
@@ -187,10 +187,10 @@ export async function getAllProcesses(limit = 20) {
|
|
|
187
187
|
const label = row.label || row[1] || procId;
|
|
188
188
|
const type = row.type || row[2] || 'unknown';
|
|
189
189
|
const stepCount = row.stepCount || row[3] || 0;
|
|
190
|
-
const stepRows = await executeQuery(REPO_ID, `
|
|
191
|
-
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
|
|
192
|
-
RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
|
|
193
|
-
ORDER BY r.step
|
|
190
|
+
const stepRows = await executeQuery(REPO_ID, `
|
|
191
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
|
|
192
|
+
RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
|
|
193
|
+
ORDER BY r.step
|
|
194
194
|
`);
|
|
195
195
|
processes.push({
|
|
196
196
|
id: procId,
|
|
@@ -5,98 +5,98 @@
|
|
|
5
5
|
* Templates use {{PLACEHOLDER}} substitution.
|
|
6
6
|
*/
|
|
7
7
|
// ─── Grouping Prompt ──────────────────────────────────────────────────
|
|
8
|
-
export const GROUPING_SYSTEM_PROMPT = `You are a documentation architect. Given a list of source files with their exported symbols, group them into logical documentation modules.
|
|
9
|
-
|
|
10
|
-
Rules:
|
|
11
|
-
- Each module should represent a cohesive feature, layer, or domain
|
|
12
|
-
- Every file must appear in exactly one module
|
|
13
|
-
- Module names should be human-readable (e.g. "Authentication", "Database Layer", "API Routes")
|
|
14
|
-
- Aim for 5-15 modules for a typical project. Fewer for small projects, more for large ones
|
|
15
|
-
- Group by functionality, not by file type or directory structure alone
|
|
8
|
+
export const GROUPING_SYSTEM_PROMPT = `You are a documentation architect. Given a list of source files with their exported symbols, group them into logical documentation modules.
|
|
9
|
+
|
|
10
|
+
Rules:
|
|
11
|
+
- Each module should represent a cohesive feature, layer, or domain
|
|
12
|
+
- Every file must appear in exactly one module
|
|
13
|
+
- Module names should be human-readable (e.g. "Authentication", "Database Layer", "API Routes")
|
|
14
|
+
- Aim for 5-15 modules for a typical project. Fewer for small projects, more for large ones
|
|
15
|
+
- Group by functionality, not by file type or directory structure alone
|
|
16
16
|
- Do NOT create modules for tests, configs, or non-source files`;
|
|
17
|
-
export const GROUPING_USER_PROMPT = `Group these source files into documentation modules.
|
|
18
|
-
|
|
19
|
-
**Files and their exports:**
|
|
20
|
-
{{FILE_LIST}}
|
|
21
|
-
|
|
22
|
-
**Directory structure:**
|
|
23
|
-
{{DIRECTORY_TREE}}
|
|
24
|
-
|
|
25
|
-
Respond with ONLY a JSON object mapping module names to file path arrays. No markdown, no explanation.
|
|
26
|
-
Example format:
|
|
27
|
-
{
|
|
28
|
-
"Authentication": ["src/auth/login.ts", "src/auth/session.ts"],
|
|
29
|
-
"Database": ["src/db/connection.ts", "src/db/models.ts"]
|
|
17
|
+
export const GROUPING_USER_PROMPT = `Group these source files into documentation modules.
|
|
18
|
+
|
|
19
|
+
**Files and their exports:**
|
|
20
|
+
{{FILE_LIST}}
|
|
21
|
+
|
|
22
|
+
**Directory structure:**
|
|
23
|
+
{{DIRECTORY_TREE}}
|
|
24
|
+
|
|
25
|
+
Respond with ONLY a JSON object mapping module names to file path arrays. No markdown, no explanation.
|
|
26
|
+
Example format:
|
|
27
|
+
{
|
|
28
|
+
"Authentication": ["src/auth/login.ts", "src/auth/session.ts"],
|
|
29
|
+
"Database": ["src/db/connection.ts", "src/db/models.ts"]
|
|
30
30
|
}`;
|
|
31
31
|
// ─── Leaf Module Prompt ───────────────────────────────────────────────
|
|
32
|
-
export const MODULE_SYSTEM_PROMPT = `You are a technical documentation writer. Write clear, developer-focused documentation for a code module.
|
|
33
|
-
|
|
34
|
-
Rules:
|
|
35
|
-
- Reference actual function names, class names, and code patterns — do NOT invent APIs
|
|
36
|
-
- Use the call graph and execution flow data for accuracy, but do NOT mechanically list every edge
|
|
37
|
-
- Include Mermaid diagrams only when they genuinely help understanding. Keep them small (5-10 nodes max)
|
|
38
|
-
- Structure the document however makes sense for this module — there is no mandatory format
|
|
32
|
+
export const MODULE_SYSTEM_PROMPT = `You are a technical documentation writer. Write clear, developer-focused documentation for a code module.
|
|
33
|
+
|
|
34
|
+
Rules:
|
|
35
|
+
- Reference actual function names, class names, and code patterns — do NOT invent APIs
|
|
36
|
+
- Use the call graph and execution flow data for accuracy, but do NOT mechanically list every edge
|
|
37
|
+
- Include Mermaid diagrams only when they genuinely help understanding. Keep them small (5-10 nodes max)
|
|
38
|
+
- Structure the document however makes sense for this module — there is no mandatory format
|
|
39
39
|
- Write for a developer who needs to understand and contribute to this code`;
|
|
40
|
-
export const MODULE_USER_PROMPT = `Write documentation for the **{{MODULE_NAME}}** module.
|
|
41
|
-
|
|
42
|
-
## Source Code
|
|
43
|
-
|
|
44
|
-
{{SOURCE_CODE}}
|
|
45
|
-
|
|
46
|
-
## Call Graph & Execution Flows (reference for accuracy)
|
|
47
|
-
|
|
48
|
-
Internal calls: {{INTRA_CALLS}}
|
|
49
|
-
Outgoing calls: {{OUTGOING_CALLS}}
|
|
50
|
-
Incoming calls: {{INCOMING_CALLS}}
|
|
51
|
-
Execution flows: {{PROCESSES}}
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
40
|
+
export const MODULE_USER_PROMPT = `Write documentation for the **{{MODULE_NAME}}** module.
|
|
41
|
+
|
|
42
|
+
## Source Code
|
|
43
|
+
|
|
44
|
+
{{SOURCE_CODE}}
|
|
45
|
+
|
|
46
|
+
## Call Graph & Execution Flows (reference for accuracy)
|
|
47
|
+
|
|
48
|
+
Internal calls: {{INTRA_CALLS}}
|
|
49
|
+
Outgoing calls: {{OUTGOING_CALLS}}
|
|
50
|
+
Incoming calls: {{INCOMING_CALLS}}
|
|
51
|
+
Execution flows: {{PROCESSES}}
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
55
|
Write comprehensive documentation for this module. Cover its purpose, how it works, its key components, and how it connects to the rest of the codebase. Use whatever structure best fits this module — you decide the sections and headings. Include a Mermaid diagram only if it genuinely clarifies the architecture.`;
|
|
56
56
|
// ─── Parent Module Prompt ─────────────────────────────────────────────
|
|
57
|
-
export const PARENT_SYSTEM_PROMPT = `You are a technical documentation writer. Write a summary page for a module that contains sub-modules. Synthesize the children's documentation — do not re-read source code.
|
|
58
|
-
|
|
59
|
-
Rules:
|
|
60
|
-
- Reference actual components from the child modules
|
|
61
|
-
- Focus on how the sub-modules work together, not repeating their individual docs
|
|
62
|
-
- Keep it concise — the reader can click through to child pages for detail
|
|
57
|
+
export const PARENT_SYSTEM_PROMPT = `You are a technical documentation writer. Write a summary page for a module that contains sub-modules. Synthesize the children's documentation — do not re-read source code.
|
|
58
|
+
|
|
59
|
+
Rules:
|
|
60
|
+
- Reference actual components from the child modules
|
|
61
|
+
- Focus on how the sub-modules work together, not repeating their individual docs
|
|
62
|
+
- Keep it concise — the reader can click through to child pages for detail
|
|
63
63
|
- Include a Mermaid diagram only if it genuinely clarifies how the sub-modules relate`;
|
|
64
|
-
export const PARENT_USER_PROMPT = `Write documentation for the **{{MODULE_NAME}}** module, which contains these sub-modules:
|
|
65
|
-
|
|
66
|
-
{{CHILDREN_DOCS}}
|
|
67
|
-
|
|
68
|
-
Cross-module calls: {{CROSS_MODULE_CALLS}}
|
|
69
|
-
Shared execution flows: {{CROSS_PROCESSES}}
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
64
|
+
export const PARENT_USER_PROMPT = `Write documentation for the **{{MODULE_NAME}}** module, which contains these sub-modules:
|
|
65
|
+
|
|
66
|
+
{{CHILDREN_DOCS}}
|
|
67
|
+
|
|
68
|
+
Cross-module calls: {{CROSS_MODULE_CALLS}}
|
|
69
|
+
Shared execution flows: {{CROSS_PROCESSES}}
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
73
|
Write a concise overview of this module group. Explain its purpose, how the sub-modules fit together, and the key workflows that span them. Link to sub-module pages (e.g. \`[Sub-module Name](sub-module-slug.md)\`) rather than repeating their content. Use whatever structure fits best.`;
|
|
74
74
|
// ─── Overview Prompt ──────────────────────────────────────────────────
|
|
75
|
-
export const OVERVIEW_SYSTEM_PROMPT = `You are a technical documentation writer. Write the top-level overview page for a repository wiki. This is the first page a new developer sees.
|
|
76
|
-
|
|
77
|
-
Rules:
|
|
78
|
-
- Be clear and welcoming — this is the entry point to the entire codebase
|
|
79
|
-
- Reference actual module names so readers can navigate to their docs
|
|
80
|
-
- Include a high-level Mermaid architecture diagram showing only the most important modules and their relationships (max 10 nodes). A new dev should grasp it in 10 seconds
|
|
81
|
-
- Do NOT create module index tables or list every module with descriptions — just link to module pages naturally within the text
|
|
75
|
+
export const OVERVIEW_SYSTEM_PROMPT = `You are a technical documentation writer. Write the top-level overview page for a repository wiki. This is the first page a new developer sees.
|
|
76
|
+
|
|
77
|
+
Rules:
|
|
78
|
+
- Be clear and welcoming — this is the entry point to the entire codebase
|
|
79
|
+
- Reference actual module names so readers can navigate to their docs
|
|
80
|
+
- Include a high-level Mermaid architecture diagram showing only the most important modules and their relationships (max 10 nodes). A new dev should grasp it in 10 seconds
|
|
81
|
+
- Do NOT create module index tables or list every module with descriptions — just link to module pages naturally within the text
|
|
82
82
|
- Use the inter-module edges and execution flow data for accuracy, but do NOT dump them raw`;
|
|
83
|
-
export const OVERVIEW_USER_PROMPT = `Write the overview page for this repository's wiki.
|
|
84
|
-
|
|
85
|
-
## Project Info
|
|
86
|
-
|
|
87
|
-
{{PROJECT_INFO}}
|
|
88
|
-
|
|
89
|
-
## Module Summaries
|
|
90
|
-
|
|
91
|
-
{{MODULE_SUMMARIES}}
|
|
92
|
-
|
|
93
|
-
## Reference Data (for accuracy — do not reproduce verbatim)
|
|
94
|
-
|
|
95
|
-
Inter-module call edges: {{MODULE_EDGES}}
|
|
96
|
-
Key system flows: {{TOP_PROCESSES}}
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
83
|
+
export const OVERVIEW_USER_PROMPT = `Write the overview page for this repository's wiki.
|
|
84
|
+
|
|
85
|
+
## Project Info
|
|
86
|
+
|
|
87
|
+
{{PROJECT_INFO}}
|
|
88
|
+
|
|
89
|
+
## Module Summaries
|
|
90
|
+
|
|
91
|
+
{{MODULE_SUMMARIES}}
|
|
92
|
+
|
|
93
|
+
## Reference Data (for accuracy — do not reproduce verbatim)
|
|
94
|
+
|
|
95
|
+
Inter-module call edges: {{MODULE_EDGES}}
|
|
96
|
+
Key system flows: {{TOP_PROCESSES}}
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
100
|
Write a clear overview of this project: what it does, how it's architected, and the key end-to-end flows. Include a simple Mermaid architecture diagram (max 10 nodes, big-picture only). Link to module pages (e.g. \`[Module Name](module-slug.md)\`) naturally in the text rather than listing them in a table. If project config was provided, include brief setup instructions. Structure the page however reads best.`;
|
|
101
101
|
// ─── Template Substitution Helper ─────────────────────────────────────
|
|
102
102
|
/**
|
|
@@ -34,6 +34,12 @@ export declare class LocalBackend {
|
|
|
34
34
|
* Returns true if at least one repo is available.
|
|
35
35
|
*/
|
|
36
36
|
init(): Promise<boolean>;
|
|
37
|
+
/**
|
|
38
|
+
* Re-read the global registry and update the in-memory repo map.
|
|
39
|
+
* New repos are added, existing repos are updated, removed repos are pruned.
|
|
40
|
+
* KuzuDB connections for removed repos are NOT closed (they idle-timeout naturally).
|
|
41
|
+
*/
|
|
42
|
+
private refreshRepos;
|
|
37
43
|
/**
|
|
38
44
|
* Generate a stable repo ID from name + path.
|
|
39
45
|
* If names collide, append a hash of the path.
|
|
@@ -44,8 +50,15 @@ export declare class LocalBackend {
|
|
|
44
50
|
* - If repoParam is given, match by name or path
|
|
45
51
|
* - If only 1 repo, use it
|
|
46
52
|
* - If 0 or multiple without param, throw with helpful message
|
|
53
|
+
*
|
|
54
|
+
* On a miss, re-reads the registry once in case a new repo was indexed
|
|
55
|
+
* while the MCP server was running.
|
|
47
56
|
*/
|
|
48
|
-
resolveRepo(repoParam?: string): RepoHandle
|
|
57
|
+
resolveRepo(repoParam?: string): Promise<RepoHandle>;
|
|
58
|
+
/**
|
|
59
|
+
* Try to resolve a repo from the in-memory cache. Returns null on miss.
|
|
60
|
+
*/
|
|
61
|
+
private resolveRepoFromCache;
|
|
49
62
|
private ensureInitialized;
|
|
50
63
|
/**
|
|
51
64
|
* Get context for a specific repo (or the single repo if only one).
|
|
@@ -53,14 +66,16 @@ export declare class LocalBackend {
|
|
|
53
66
|
getContext(repoId?: string): CodebaseContext | null;
|
|
54
67
|
/**
|
|
55
68
|
* List all registered repos with their metadata.
|
|
69
|
+
* Re-reads the global registry so newly indexed repos are discovered
|
|
70
|
+
* without restarting the MCP server.
|
|
56
71
|
*/
|
|
57
|
-
listRepos(): Array<{
|
|
72
|
+
listRepos(): Promise<Array<{
|
|
58
73
|
name: string;
|
|
59
74
|
path: string;
|
|
60
75
|
indexedAt: string;
|
|
61
76
|
lastCommit: string;
|
|
62
77
|
stats?: any;
|
|
63
|
-
}
|
|
78
|
+
}>>;
|
|
64
79
|
callTool(method: string, params: any): Promise<any>;
|
|
65
80
|
/**
|
|
66
81
|
* Query tool — process-grouped search.
|
|
@@ -44,9 +44,20 @@ export class LocalBackend {
|
|
|
44
44
|
* Returns true if at least one repo is available.
|
|
45
45
|
*/
|
|
46
46
|
async init() {
|
|
47
|
+
await this.refreshRepos();
|
|
48
|
+
return this.repos.size > 0;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Re-read the global registry and update the in-memory repo map.
|
|
52
|
+
* New repos are added, existing repos are updated, removed repos are pruned.
|
|
53
|
+
* KuzuDB connections for removed repos are NOT closed (they idle-timeout naturally).
|
|
54
|
+
*/
|
|
55
|
+
async refreshRepos() {
|
|
47
56
|
const entries = await listRegisteredRepos({ validate: true });
|
|
57
|
+
const freshIds = new Set();
|
|
48
58
|
for (const entry of entries) {
|
|
49
59
|
const id = this.repoId(entry.name, entry.path);
|
|
60
|
+
freshIds.add(id);
|
|
50
61
|
const storagePath = entry.storagePath;
|
|
51
62
|
const kuzuPath = path.join(storagePath, 'kuzu');
|
|
52
63
|
const handle = {
|
|
@@ -72,7 +83,14 @@ export class LocalBackend {
|
|
|
72
83
|
},
|
|
73
84
|
});
|
|
74
85
|
}
|
|
75
|
-
|
|
86
|
+
// Prune repos that no longer exist in the registry
|
|
87
|
+
for (const id of this.repos.keys()) {
|
|
88
|
+
if (!freshIds.has(id)) {
|
|
89
|
+
this.repos.delete(id);
|
|
90
|
+
this.contextCache.delete(id);
|
|
91
|
+
this.initializedRepos.delete(id);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
76
94
|
}
|
|
77
95
|
/**
|
|
78
96
|
* Generate a stable repo ID from name + path.
|
|
@@ -96,11 +114,36 @@ export class LocalBackend {
|
|
|
96
114
|
* - If repoParam is given, match by name or path
|
|
97
115
|
* - If only 1 repo, use it
|
|
98
116
|
* - If 0 or multiple without param, throw with helpful message
|
|
117
|
+
*
|
|
118
|
+
* On a miss, re-reads the registry once in case a new repo was indexed
|
|
119
|
+
* while the MCP server was running.
|
|
99
120
|
*/
|
|
100
|
-
resolveRepo(repoParam) {
|
|
121
|
+
async resolveRepo(repoParam) {
|
|
122
|
+
const result = this.resolveRepoFromCache(repoParam);
|
|
123
|
+
if (result)
|
|
124
|
+
return result;
|
|
125
|
+
// Miss — refresh registry and try once more
|
|
126
|
+
await this.refreshRepos();
|
|
127
|
+
const retried = this.resolveRepoFromCache(repoParam);
|
|
128
|
+
if (retried)
|
|
129
|
+
return retried;
|
|
130
|
+
// Still no match — throw with helpful message
|
|
101
131
|
if (this.repos.size === 0) {
|
|
102
132
|
throw new Error('No indexed repositories. Run: gitnexus analyze');
|
|
103
133
|
}
|
|
134
|
+
if (repoParam) {
|
|
135
|
+
const names = [...this.repos.values()].map(h => h.name);
|
|
136
|
+
throw new Error(`Repository "${repoParam}" not found. Available: ${names.join(', ')}`);
|
|
137
|
+
}
|
|
138
|
+
const names = [...this.repos.values()].map(h => h.name);
|
|
139
|
+
throw new Error(`Multiple repositories indexed. Specify which one with the "repo" parameter. Available: ${names.join(', ')}`);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Try to resolve a repo from the in-memory cache. Returns null on miss.
|
|
143
|
+
*/
|
|
144
|
+
resolveRepoFromCache(repoParam) {
|
|
145
|
+
if (this.repos.size === 0)
|
|
146
|
+
return null;
|
|
104
147
|
if (repoParam) {
|
|
105
148
|
const paramLower = repoParam.toLowerCase();
|
|
106
149
|
// Match by id
|
|
@@ -122,14 +165,12 @@ export class LocalBackend {
|
|
|
122
165
|
if (handle.name.toLowerCase().includes(paramLower))
|
|
123
166
|
return handle;
|
|
124
167
|
}
|
|
125
|
-
|
|
126
|
-
throw new Error(`Repository "${repoParam}" not found. Available: ${names.join(', ')}`);
|
|
168
|
+
return null;
|
|
127
169
|
}
|
|
128
170
|
if (this.repos.size === 1) {
|
|
129
171
|
return this.repos.values().next().value;
|
|
130
172
|
}
|
|
131
|
-
|
|
132
|
-
throw new Error(`Multiple repositories indexed. Specify which one with the "repo" parameter. Available: ${names.join(', ')}`);
|
|
173
|
+
return null; // Multiple repos, no param — ambiguous
|
|
133
174
|
}
|
|
134
175
|
// ─── Lazy KuzuDB Init ────────────────────────────────────────────
|
|
135
176
|
async ensureInitialized(repoId) {
|
|
@@ -164,8 +205,11 @@ export class LocalBackend {
|
|
|
164
205
|
}
|
|
165
206
|
/**
|
|
166
207
|
* List all registered repos with their metadata.
|
|
208
|
+
* Re-reads the global registry so newly indexed repos are discovered
|
|
209
|
+
* without restarting the MCP server.
|
|
167
210
|
*/
|
|
168
|
-
listRepos() {
|
|
211
|
+
async listRepos() {
|
|
212
|
+
await this.refreshRepos();
|
|
169
213
|
return [...this.repos.values()].map(h => ({
|
|
170
214
|
name: h.name,
|
|
171
215
|
path: h.repoPath,
|
|
@@ -179,8 +223,8 @@ export class LocalBackend {
|
|
|
179
223
|
if (method === 'list_repos') {
|
|
180
224
|
return this.listRepos();
|
|
181
225
|
}
|
|
182
|
-
// Resolve repo from optional param
|
|
183
|
-
const repo = this.resolveRepo(params?.repo);
|
|
226
|
+
// Resolve repo from optional param (re-reads registry on miss)
|
|
227
|
+
const repo = await this.resolveRepo(params?.repo);
|
|
184
228
|
switch (method) {
|
|
185
229
|
case 'query':
|
|
186
230
|
return this.query(repo, params);
|
|
@@ -1125,7 +1169,7 @@ export class LocalBackend {
|
|
|
1125
1169
|
* Used by getClustersResource — avoids legacy overview() dispatch.
|
|
1126
1170
|
*/
|
|
1127
1171
|
async queryClusters(repoName, limit = 100) {
|
|
1128
|
-
const repo = this.resolveRepo(repoName);
|
|
1172
|
+
const repo = await this.resolveRepo(repoName);
|
|
1129
1173
|
await this.ensureInitialized(repo.id);
|
|
1130
1174
|
try {
|
|
1131
1175
|
const rawLimit = Math.max(limit * 5, 200);
|
|
@@ -1153,7 +1197,7 @@ export class LocalBackend {
|
|
|
1153
1197
|
* Used by getProcessesResource — avoids legacy overview() dispatch.
|
|
1154
1198
|
*/
|
|
1155
1199
|
async queryProcesses(repoName, limit = 50) {
|
|
1156
|
-
const repo = this.resolveRepo(repoName);
|
|
1200
|
+
const repo = await this.resolveRepo(repoName);
|
|
1157
1201
|
await this.ensureInitialized(repo.id);
|
|
1158
1202
|
try {
|
|
1159
1203
|
const processes = await executeQuery(repo.id, `
|
|
@@ -1181,7 +1225,7 @@ export class LocalBackend {
|
|
|
1181
1225
|
* Used by getClusterDetailResource.
|
|
1182
1226
|
*/
|
|
1183
1227
|
async queryClusterDetail(name, repoName) {
|
|
1184
|
-
const repo = this.resolveRepo(repoName);
|
|
1228
|
+
const repo = await this.resolveRepo(repoName);
|
|
1185
1229
|
await this.ensureInitialized(repo.id);
|
|
1186
1230
|
const escaped = name.replace(/'/g, "''");
|
|
1187
1231
|
const clusterQuery = `
|
|
@@ -1227,7 +1271,7 @@ export class LocalBackend {
|
|
|
1227
1271
|
* Used by getProcessDetailResource.
|
|
1228
1272
|
*/
|
|
1229
1273
|
async queryProcessDetail(name, repoName) {
|
|
1230
|
-
const repo = this.resolveRepo(repoName);
|
|
1274
|
+
const repo = await this.resolveRepo(repoName);
|
|
1231
1275
|
await this.ensureInitialized(repo.id);
|
|
1232
1276
|
const escaped = name.replace(/'/g, "''");
|
|
1233
1277
|
const processes = await executeQuery(repo.id, `
|
package/dist/mcp/resources.js
CHANGED
|
@@ -125,8 +125,8 @@ export async function readResource(uri, backend) {
|
|
|
125
125
|
/**
|
|
126
126
|
* Repos resource — list all indexed repositories
|
|
127
127
|
*/
|
|
128
|
-
function getReposResource(backend) {
|
|
129
|
-
const repos = backend.listRepos();
|
|
128
|
+
async function getReposResource(backend) {
|
|
129
|
+
const repos = await backend.listRepos();
|
|
130
130
|
if (repos.length === 0) {
|
|
131
131
|
return 'repos: []\n# No repositories indexed. Run: gitnexus analyze';
|
|
132
132
|
}
|
|
@@ -154,7 +154,7 @@ function getReposResource(backend) {
|
|
|
154
154
|
*/
|
|
155
155
|
async function getContextResource(backend, repoName) {
|
|
156
156
|
// Resolve repo
|
|
157
|
-
const repo = backend.resolveRepo(repoName);
|
|
157
|
+
const repo = await backend.resolveRepo(repoName);
|
|
158
158
|
const repoId = repo.name.toLowerCase();
|
|
159
159
|
const context = backend.getContext(repoId) || backend.getContext();
|
|
160
160
|
if (!context) {
|
|
@@ -370,7 +370,7 @@ async function getProcessDetailResource(name, backend, repoName) {
|
|
|
370
370
|
* Useful for `gitnexus setup` onboarding or dynamic content injection.
|
|
371
371
|
*/
|
|
372
372
|
async function getSetupResource(backend) {
|
|
373
|
-
const repos = backend.listRepos();
|
|
373
|
+
const repos = await backend.listRepos();
|
|
374
374
|
if (repos.length === 0) {
|
|
375
375
|
return '# GitNexus\n\nNo repositories indexed. Run: `npx gitnexus analyze` in a repository.';
|
|
376
376
|
}
|