kirograph 0.15.0 → 0.16.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/LICENSE +21 -0
- package/README.md +112 -1
- package/dist/bin/commands/docs.js +357 -0
- package/dist/bin/commands/docs.js.map +7 -0
- package/dist/bin/installer/config-prompt.js +17 -1
- package/dist/bin/installer/config-prompt.js.map +2 -2
- package/dist/bin/installer/index.js +6 -1
- package/dist/bin/installer/index.js.map +2 -2
- package/dist/bin/installer/steering.js +21 -0
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/installer/targets/index.js.map +1 -1
- package/dist/bin/installer/targets/kiro.js +2 -2
- package/dist/bin/installer/targets/kiro.js.map +2 -2
- package/dist/bin/kirograph.js +3 -1
- package/dist/bin/kirograph.js.map +3 -3
- package/dist/compression/naive-cost.js +19 -0
- package/dist/compression/naive-cost.js.map +2 -2
- package/dist/compression/tracker.js +11 -2
- package/dist/compression/tracker.js.map +2 -2
- package/dist/compression/types.js.map +1 -1
- package/dist/config.js +34 -1
- package/dist/config.js.map +2 -2
- package/dist/core/pipeline.js +23 -0
- package/dist/core/pipeline.js.map +2 -2
- package/dist/db/database.js +11 -0
- package/dist/db/database.js.map +2 -2
- package/dist/db/docs-schema.sql +50 -0
- package/dist/docs/formats/asciidoc.js +108 -0
- package/dist/docs/formats/asciidoc.js.map +7 -0
- package/dist/docs/formats/html.js +100 -0
- package/dist/docs/formats/html.js.map +7 -0
- package/dist/docs/formats/index.js +81 -0
- package/dist/docs/formats/index.js.map +7 -0
- package/dist/docs/formats/markdown.js +146 -0
- package/dist/docs/formats/markdown.js.map +7 -0
- package/dist/docs/formats/openapi.js +228 -0
- package/dist/docs/formats/openapi.js.map +7 -0
- package/dist/docs/formats/org.js +117 -0
- package/dist/docs/formats/org.js.map +7 -0
- package/dist/docs/formats/plaintext.js +119 -0
- package/dist/docs/formats/plaintext.js.map +7 -0
- package/dist/docs/formats/rdoc.js +105 -0
- package/dist/docs/formats/rdoc.js.map +7 -0
- package/dist/docs/formats/rst.js +121 -0
- package/dist/docs/formats/rst.js.map +7 -0
- package/dist/docs/indexer.js +340 -0
- package/dist/docs/indexer.js.map +7 -0
- package/dist/docs/linker.js +151 -0
- package/dist/docs/linker.js.map +7 -0
- package/dist/docs/lint.js +92 -0
- package/dist/docs/lint.js.map +7 -0
- package/dist/docs/queries.js +295 -0
- package/dist/docs/queries.js.map +7 -0
- package/dist/docs/section-id.js +57 -0
- package/dist/docs/section-id.js.map +7 -0
- package/dist/docs/summarizer.js +55 -0
- package/dist/docs/summarizer.js.map +7 -0
- package/dist/docs/types.js +17 -0
- package/dist/docs/types.js.map +7 -0
- package/dist/docs/vectors.js +217 -0
- package/dist/docs/vectors.js.map +7 -0
- package/dist/mcp/tool-names.js +6 -1
- package/dist/mcp/tool-names.js.map +2 -2
- package/dist/mcp/tools.js +245 -1
- package/dist/mcp/tools.js.map +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var vectors_exports = {};
|
|
30
|
+
__export(vectors_exports, {
|
|
31
|
+
DocsVectorManager: () => DocsVectorManager
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(vectors_exports);
|
|
34
|
+
var import_errors = require("../errors");
|
|
35
|
+
const MAX_TOKEN_CHARS = 2e3;
|
|
36
|
+
let transformers = null;
|
|
37
|
+
let pipeline = null;
|
|
38
|
+
let pipelineModel = null;
|
|
39
|
+
async function getTransformers() {
|
|
40
|
+
if (!transformers) {
|
|
41
|
+
transformers = await import("@huggingface/transformers");
|
|
42
|
+
}
|
|
43
|
+
return transformers;
|
|
44
|
+
}
|
|
45
|
+
async function getPipeline(modelName, cacheDir) {
|
|
46
|
+
if (pipeline && pipelineModel === modelName) return pipeline;
|
|
47
|
+
const tf = await getTransformers();
|
|
48
|
+
pipeline = await tf.pipeline("feature-extraction", modelName, {
|
|
49
|
+
cache_dir: cacheDir,
|
|
50
|
+
dtype: "fp32"
|
|
51
|
+
});
|
|
52
|
+
pipelineModel = modelName;
|
|
53
|
+
return pipeline;
|
|
54
|
+
}
|
|
55
|
+
function sectionToText(section) {
|
|
56
|
+
const parts = [section.title];
|
|
57
|
+
if (section.summary) parts.push(section.summary);
|
|
58
|
+
const text = parts.join(": ");
|
|
59
|
+
return text.length > MAX_TOKEN_CHARS ? text.slice(0, MAX_TOKEN_CHARS) : text;
|
|
60
|
+
}
|
|
61
|
+
function cosine(a, b) {
|
|
62
|
+
let dot = 0, na = 0, nb = 0;
|
|
63
|
+
for (let i = 0; i < a.length; i++) {
|
|
64
|
+
dot += a[i] * b[i];
|
|
65
|
+
na += a[i] * a[i];
|
|
66
|
+
nb += b[i] * b[i];
|
|
67
|
+
}
|
|
68
|
+
const denom = Math.sqrt(na) * Math.sqrt(nb);
|
|
69
|
+
return denom === 0 ? 0 : dot / denom;
|
|
70
|
+
}
|
|
71
|
+
class DocsVectorManager {
|
|
72
|
+
constructor(config, db) {
|
|
73
|
+
this.config = config;
|
|
74
|
+
this.db = db;
|
|
75
|
+
this.modelName = config.embeddingModel ?? "nomic-ai/nomic-embed-text-v1.5";
|
|
76
|
+
const { homedir } = require("os");
|
|
77
|
+
const path = require("path");
|
|
78
|
+
this.cacheDir = path.join(homedir(), ".kirograph", "models");
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if embeddings are enabled in config.
|
|
82
|
+
*/
|
|
83
|
+
isEnabled() {
|
|
84
|
+
return !!this.config.enableEmbeddings;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Embed a single doc section and store in doc_vectors.
|
|
88
|
+
*/
|
|
89
|
+
async embedSection(section) {
|
|
90
|
+
if (!this.isEnabled()) return;
|
|
91
|
+
try {
|
|
92
|
+
const text = sectionToText(section);
|
|
93
|
+
const embedding = await this.embed(text);
|
|
94
|
+
if (embedding) {
|
|
95
|
+
const buffer = Buffer.from(embedding.buffer);
|
|
96
|
+
this.db.run(
|
|
97
|
+
"INSERT OR REPLACE INTO doc_vectors (section_id, embedding, model, created_at) VALUES (?, ?, ?, ?)",
|
|
98
|
+
[section.id, buffer, this.modelName, Date.now()]
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
(0, import_errors.logWarn)(`Failed to embed doc section ${section.id}: ${err}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Embed multiple sections in batch.
|
|
107
|
+
*/
|
|
108
|
+
async embedBatch(sections) {
|
|
109
|
+
if (!this.isEnabled()) return 0;
|
|
110
|
+
let embedded = 0;
|
|
111
|
+
for (const section of sections) {
|
|
112
|
+
try {
|
|
113
|
+
await this.embedSection(section);
|
|
114
|
+
embedded++;
|
|
115
|
+
} catch (err) {
|
|
116
|
+
(0, import_errors.logWarn)(`Failed to embed doc section ${section.id}: ${err}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return embedded;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Vector search over doc sections.
|
|
123
|
+
* Returns scored sections sorted by cosine similarity.
|
|
124
|
+
* Falls back to empty results if embeddings are disabled.
|
|
125
|
+
*/
|
|
126
|
+
async search(query, limit = 10) {
|
|
127
|
+
if (!this.isEnabled()) return [];
|
|
128
|
+
try {
|
|
129
|
+
const queryEmbedding = await this.embed(query);
|
|
130
|
+
if (!queryEmbedding) return [];
|
|
131
|
+
const allVectors = this.db.all(
|
|
132
|
+
"SELECT section_id, embedding FROM doc_vectors WHERE model = ?",
|
|
133
|
+
[this.modelName]
|
|
134
|
+
);
|
|
135
|
+
if (allVectors.length === 0) return [];
|
|
136
|
+
const scored = [];
|
|
137
|
+
for (const { section_id, embedding } of allVectors) {
|
|
138
|
+
const vec = new Float32Array(
|
|
139
|
+
embedding.buffer,
|
|
140
|
+
embedding.byteOffset,
|
|
141
|
+
embedding.byteLength / 4
|
|
142
|
+
);
|
|
143
|
+
const score = cosine(queryEmbedding, vec);
|
|
144
|
+
scored.push({ sectionId: section_id, score });
|
|
145
|
+
}
|
|
146
|
+
scored.sort((a, b) => b.score - a.score);
|
|
147
|
+
return scored.slice(0, limit);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
(0, import_errors.logError)("Docs vector search failed", { error: err });
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Check if there are vectors with a mismatched model.
|
|
155
|
+
*/
|
|
156
|
+
hasModelMismatch() {
|
|
157
|
+
const mismatch = this.db.get(
|
|
158
|
+
"SELECT COUNT(*) as cnt FROM doc_vectors WHERE model != ?",
|
|
159
|
+
[this.modelName]
|
|
160
|
+
)?.cnt ?? 0;
|
|
161
|
+
return mismatch > 0;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get vector stats.
|
|
165
|
+
*/
|
|
166
|
+
getStats() {
|
|
167
|
+
const total = this.db.get("SELECT COUNT(*) as cnt FROM doc_vectors")?.cnt ?? 0;
|
|
168
|
+
const currentModel = this.db.get(
|
|
169
|
+
"SELECT COUNT(*) as cnt FROM doc_vectors WHERE model = ?",
|
|
170
|
+
[this.modelName]
|
|
171
|
+
)?.cnt ?? 0;
|
|
172
|
+
return { total, currentModel, mismatch: total - currentModel };
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Re-embed all doc sections with the current model.
|
|
176
|
+
*/
|
|
177
|
+
async reembed(batchSize = 32) {
|
|
178
|
+
this.db.run("DELETE FROM doc_vectors");
|
|
179
|
+
const rows = this.db.all("SELECT * FROM doc_sections ORDER BY file_path, position");
|
|
180
|
+
const sections = rows.map((row) => ({
|
|
181
|
+
id: row.id,
|
|
182
|
+
filePath: row.file_path,
|
|
183
|
+
title: row.title,
|
|
184
|
+
level: row.level,
|
|
185
|
+
parentId: row.parent_id ?? null,
|
|
186
|
+
summary: row.summary ?? null,
|
|
187
|
+
byteStart: row.byte_start,
|
|
188
|
+
byteEnd: row.byte_end,
|
|
189
|
+
contentHash: row.content_hash,
|
|
190
|
+
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
191
|
+
position: row.position,
|
|
192
|
+
updatedAt: row.updated_at
|
|
193
|
+
}));
|
|
194
|
+
let embedded = 0;
|
|
195
|
+
for (let i = 0; i < sections.length; i += batchSize) {
|
|
196
|
+
const batch = sections.slice(i, i + batchSize);
|
|
197
|
+
embedded += await this.embedBatch(batch);
|
|
198
|
+
}
|
|
199
|
+
return embedded;
|
|
200
|
+
}
|
|
201
|
+
// ── Private ────────────────────────────────────────────────────────────────
|
|
202
|
+
async embed(text) {
|
|
203
|
+
try {
|
|
204
|
+
const pipe = await getPipeline(this.modelName, this.cacheDir);
|
|
205
|
+
const output = await pipe(text, { pooling: "mean", normalize: true });
|
|
206
|
+
return output.data;
|
|
207
|
+
} catch (err) {
|
|
208
|
+
(0, import_errors.logDebug)(`Doc embedding failed: ${err}`);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
214
|
+
0 && (module.exports = {
|
|
215
|
+
DocsVectorManager
|
|
216
|
+
});
|
|
217
|
+
//# sourceMappingURL=vectors.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/docs/vectors.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Docs \u2014 Vector embedding and search\n *\n * Reuses the same embedding model and pipeline as the memory module,\n * but operates on doc_vectors table independently.\n */\n\nimport type { KiroGraphConfig } from '../config';\nimport type { DocSection, DocSearchResult } from './types';\nimport { logDebug, logWarn, logError } from '../errors';\n\nconst MAX_TOKEN_CHARS = 2000;\n\n// \u2500\u2500 Embedder (reuses the same pipeline as memory/code vectors) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nlet transformers: typeof import('@huggingface/transformers') | null = null;\nlet pipeline: any = null;\nlet pipelineModel: string | null = null;\n\nasync function getTransformers() {\n if (!transformers) {\n transformers = await import('@huggingface/transformers');\n }\n return transformers;\n}\n\nasync function getPipeline(modelName: string, cacheDir: string) {\n if (pipeline && pipelineModel === modelName) return pipeline;\n\n const tf = await getTransformers();\n pipeline = await tf.pipeline('feature-extraction', modelName, {\n cache_dir: cacheDir,\n dtype: 'fp32',\n } as any);\n pipelineModel = modelName;\n return pipeline;\n}\n\n/**\n * Build searchable text from a doc section.\n * Uses title + summary for embedding (concise, semantically rich).\n */\nfunction sectionToText(section: DocSection): string {\n const parts = [section.title];\n if (section.summary) parts.push(section.summary);\n const text = parts.join(': ');\n return text.length > MAX_TOKEN_CHARS ? text.slice(0, MAX_TOKEN_CHARS) : text;\n}\n\n/** Cosine similarity between two Float32Arrays */\nfunction cosine(a: Float32Array, b: Float32Array): number {\n let dot = 0, na = 0, nb = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n na += a[i] * a[i];\n nb += b[i] * b[i];\n }\n const denom = Math.sqrt(na) * Math.sqrt(nb);\n return denom === 0 ? 0 : dot / denom;\n}\n\n// \u2500\u2500 DocsVectorManager \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class DocsVectorManager {\n private config: KiroGraphConfig;\n private db: any; // raw SQLite handle\n private modelName: string;\n private cacheDir: string;\n\n constructor(config: KiroGraphConfig, db: any) {\n this.config = config;\n this.db = db;\n this.modelName = config.embeddingModel ?? 'nomic-ai/nomic-embed-text-v1.5';\n\n const { homedir } = require('os');\n const path = require('path');\n this.cacheDir = path.join(homedir(), '.kirograph', 'models');\n }\n\n /**\n * Check if embeddings are enabled in config.\n */\n isEnabled(): boolean {\n return !!this.config.enableEmbeddings;\n }\n\n /**\n * Embed a single doc section and store in doc_vectors.\n */\n async embedSection(section: DocSection): Promise<void> {\n if (!this.isEnabled()) return;\n\n try {\n const text = sectionToText(section);\n const embedding = await this.embed(text);\n if (embedding) {\n const buffer = Buffer.from(embedding.buffer);\n this.db.run(\n 'INSERT OR REPLACE INTO doc_vectors (section_id, embedding, model, created_at) VALUES (?, ?, ?, ?)',\n [section.id, buffer, this.modelName, Date.now()],\n );\n }\n } catch (err) {\n logWarn(`Failed to embed doc section ${section.id}: ${err}`);\n }\n }\n\n /**\n * Embed multiple sections in batch.\n */\n async embedBatch(sections: DocSection[]): Promise<number> {\n if (!this.isEnabled()) return 0;\n\n let embedded = 0;\n for (const section of sections) {\n try {\n await this.embedSection(section);\n embedded++;\n } catch (err) {\n logWarn(`Failed to embed doc section ${section.id}: ${err}`);\n }\n }\n return embedded;\n }\n\n /**\n * Vector search over doc sections.\n * Returns scored sections sorted by cosine similarity.\n * Falls back to empty results if embeddings are disabled.\n */\n async search(query: string, limit = 10): Promise<Array<{ sectionId: string; score: number }>> {\n if (!this.isEnabled()) return [];\n\n try {\n const queryEmbedding = await this.embed(query);\n if (!queryEmbedding) return [];\n\n // Get all doc vectors with matching model\n const allVectors = this.db.all(\n 'SELECT section_id, embedding FROM doc_vectors WHERE model = ?',\n [this.modelName],\n ) as Array<{ section_id: string; embedding: Buffer }>;\n\n if (allVectors.length === 0) return [];\n\n // Compute cosine similarity for each\n const scored: Array<{ sectionId: string; score: number }> = [];\n for (const { section_id, embedding } of allVectors) {\n const vec = new Float32Array(\n embedding.buffer,\n embedding.byteOffset,\n embedding.byteLength / 4,\n );\n const score = cosine(queryEmbedding, vec);\n scored.push({ sectionId: section_id, score });\n }\n\n // Sort by score descending, take top N\n scored.sort((a, b) => b.score - a.score);\n return scored.slice(0, limit);\n } catch (err) {\n logError('Docs vector search failed', { error: err });\n return [];\n }\n }\n\n /**\n * Check if there are vectors with a mismatched model.\n */\n hasModelMismatch(): boolean {\n const mismatch = this.db.get(\n 'SELECT COUNT(*) as cnt FROM doc_vectors WHERE model != ?',\n [this.modelName],\n )?.cnt ?? 0;\n return mismatch > 0;\n }\n\n /**\n * Get vector stats.\n */\n getStats(): { total: number; currentModel: number; mismatch: number } {\n const total = this.db.get('SELECT COUNT(*) as cnt FROM doc_vectors')?.cnt ?? 0;\n const currentModel = this.db.get(\n 'SELECT COUNT(*) as cnt FROM doc_vectors WHERE model = ?',\n [this.modelName],\n )?.cnt ?? 0;\n return { total, currentModel, mismatch: total - currentModel };\n }\n\n /**\n * Re-embed all doc sections with the current model.\n */\n async reembed(batchSize = 32): Promise<number> {\n // Delete all existing doc vectors\n this.db.run('DELETE FROM doc_vectors');\n\n // Get all sections\n const rows = this.db.all('SELECT * FROM doc_sections ORDER BY file_path, position') as any[];\n const sections: DocSection[] = rows.map((row: any) => ({\n id: row.id,\n filePath: row.file_path,\n title: row.title,\n level: row.level,\n parentId: row.parent_id ?? null,\n summary: row.summary ?? null,\n byteStart: row.byte_start,\n byteEnd: row.byte_end,\n contentHash: row.content_hash,\n tags: row.tags ? JSON.parse(row.tags) : [],\n position: row.position,\n updatedAt: row.updated_at,\n }));\n\n let embedded = 0;\n for (let i = 0; i < sections.length; i += batchSize) {\n const batch = sections.slice(i, i + batchSize);\n embedded += await this.embedBatch(batch);\n }\n\n return embedded;\n }\n\n // \u2500\u2500 Private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async embed(text: string): Promise<Float32Array | null> {\n try {\n const pipe = await getPipeline(this.modelName, this.cacheDir);\n const output = await pipe(text, { pooling: 'mean', normalize: true });\n return output.data as Float32Array;\n } catch (err) {\n logDebug(`Doc embedding failed: ${err}`);\n return null;\n }\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,oBAA4C;AAE5C,MAAM,kBAAkB;AAIxB,IAAI,eAAkE;AACtE,IAAI,WAAgB;AACpB,IAAI,gBAA+B;AAEnC,eAAe,kBAAkB;AAC/B,MAAI,CAAC,cAAc;AACjB,mBAAe,MAAM,OAAO,2BAA2B;AAAA,EACzD;AACA,SAAO;AACT;AAEA,eAAe,YAAY,WAAmB,UAAkB;AAC9D,MAAI,YAAY,kBAAkB,UAAW,QAAO;AAEpD,QAAM,KAAK,MAAM,gBAAgB;AACjC,aAAW,MAAM,GAAG,SAAS,sBAAsB,WAAW;AAAA,IAC5D,WAAW;AAAA,IACX,OAAO;AAAA,EACT,CAAQ;AACR,kBAAgB;AAChB,SAAO;AACT;AAMA,SAAS,cAAc,SAA6B;AAClD,QAAM,QAAQ,CAAC,QAAQ,KAAK;AAC5B,MAAI,QAAQ,QAAS,OAAM,KAAK,QAAQ,OAAO;AAC/C,QAAM,OAAO,MAAM,KAAK,IAAI;AAC5B,SAAO,KAAK,SAAS,kBAAkB,KAAK,MAAM,GAAG,eAAe,IAAI;AAC1E;AAGA,SAAS,OAAO,GAAiB,GAAyB;AACxD,MAAI,MAAM,GAAG,KAAK,GAAG,KAAK;AAC1B,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,WAAO,EAAE,CAAC,IAAI,EAAE,CAAC;AACjB,UAAM,EAAE,CAAC,IAAI,EAAE,CAAC;AAChB,UAAM,EAAE,CAAC,IAAI,EAAE,CAAC;AAAA,EAClB;AACA,QAAM,QAAQ,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,EAAE;AAC1C,SAAO,UAAU,IAAI,IAAI,MAAM;AACjC;AAIO,MAAM,kBAAkB;AAAA,EAM7B,YAAY,QAAyB,IAAS;AAC5C,SAAK,SAAS;AACd,SAAK,KAAK;AACV,SAAK,YAAY,OAAO,kBAAkB;AAE1C,UAAM,EAAE,QAAQ,IAAI,QAAQ,IAAI;AAChC,UAAM,OAAO,QAAQ,MAAM;AAC3B,SAAK,WAAW,KAAK,KAAK,QAAQ,GAAG,cAAc,QAAQ;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACnB,WAAO,CAAC,CAAC,KAAK,OAAO;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,SAAoC;AACrD,QAAI,CAAC,KAAK,UAAU,EAAG;AAEvB,QAAI;AACF,YAAM,OAAO,cAAc,OAAO;AAClC,YAAM,YAAY,MAAM,KAAK,MAAM,IAAI;AACvC,UAAI,WAAW;AACb,cAAM,SAAS,OAAO,KAAK,UAAU,MAAM;AAC3C,aAAK,GAAG;AAAA,UACN;AAAA,UACA,CAAC,QAAQ,IAAI,QAAQ,KAAK,WAAW,KAAK,IAAI,CAAC;AAAA,QACjD;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,iCAAQ,+BAA+B,QAAQ,EAAE,KAAK,GAAG,EAAE;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,UAAyC;AACxD,QAAI,CAAC,KAAK,UAAU,EAAG,QAAO;AAE9B,QAAI,WAAW;AACf,eAAW,WAAW,UAAU;AAC9B,UAAI;AACF,cAAM,KAAK,aAAa,OAAO;AAC/B;AAAA,MACF,SAAS,KAAK;AACZ,mCAAQ,+BAA+B,QAAQ,EAAE,KAAK,GAAG,EAAE;AAAA,MAC7D;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAO,OAAe,QAAQ,IAA0D;AAC5F,QAAI,CAAC,KAAK,UAAU,EAAG,QAAO,CAAC;AAE/B,QAAI;AACF,YAAM,iBAAiB,MAAM,KAAK,MAAM,KAAK;AAC7C,UAAI,CAAC,eAAgB,QAAO,CAAC;AAG7B,YAAM,aAAa,KAAK,GAAG;AAAA,QACzB;AAAA,QACA,CAAC,KAAK,SAAS;AAAA,MACjB;AAEA,UAAI,WAAW,WAAW,EAAG,QAAO,CAAC;AAGrC,YAAM,SAAsD,CAAC;AAC7D,iBAAW,EAAE,YAAY,UAAU,KAAK,YAAY;AAClD,cAAM,MAAM,IAAI;AAAA,UACd,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU,aAAa;AAAA,QACzB;AACA,cAAM,QAAQ,OAAO,gBAAgB,GAAG;AACxC,eAAO,KAAK,EAAE,WAAW,YAAY,MAAM,CAAC;AAAA,MAC9C;AAGA,aAAO,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACvC,aAAO,OAAO,MAAM,GAAG,KAAK;AAAA,IAC9B,SAAS,KAAK;AACZ,kCAAS,6BAA6B,EAAE,OAAO,IAAI,CAAC;AACpD,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,mBAA4B;AAC1B,UAAM,WAAW,KAAK,GAAG;AAAA,MACvB;AAAA,MACA,CAAC,KAAK,SAAS;AAAA,IACjB,GAAG,OAAO;AACV,WAAO,WAAW;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAsE;AACpE,UAAM,QAAQ,KAAK,GAAG,IAAI,yCAAyC,GAAG,OAAO;AAC7E,UAAM,eAAe,KAAK,GAAG;AAAA,MAC3B;AAAA,MACA,CAAC,KAAK,SAAS;AAAA,IACjB,GAAG,OAAO;AACV,WAAO,EAAE,OAAO,cAAc,UAAU,QAAQ,aAAa;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,YAAY,IAAqB;AAE7C,SAAK,GAAG,IAAI,yBAAyB;AAGrC,UAAM,OAAO,KAAK,GAAG,IAAI,yDAAyD;AAClF,UAAM,WAAyB,KAAK,IAAI,CAAC,SAAc;AAAA,MACrD,IAAI,IAAI;AAAA,MACR,UAAU,IAAI;AAAA,MACd,OAAO,IAAI;AAAA,MACX,OAAO,IAAI;AAAA,MACX,UAAU,IAAI,aAAa;AAAA,MAC3B,SAAS,IAAI,WAAW;AAAA,MACxB,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,aAAa,IAAI;AAAA,MACjB,MAAM,IAAI,OAAO,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC;AAAA,MACzC,UAAU,IAAI;AAAA,MACd,WAAW,IAAI;AAAA,IACjB,EAAE;AAEF,QAAI,WAAW;AACf,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,WAAW;AACnD,YAAM,QAAQ,SAAS,MAAM,GAAG,IAAI,SAAS;AAC7C,kBAAY,MAAM,KAAK,WAAW,KAAK;AAAA,IACzC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,MAAc,MAAM,MAA4C;AAC9D,QAAI;AACF,YAAM,OAAO,MAAM,YAAY,KAAK,WAAW,KAAK,QAAQ;AAC5D,YAAM,SAAS,MAAM,KAAK,MAAM,EAAE,SAAS,QAAQ,WAAW,KAAK,CAAC;AACpE,aAAO,OAAO;AAAA,IAChB,SAAS,KAAK;AACZ,kCAAS,yBAAyB,GAAG,EAAE;AACvC,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/mcp/tool-names.js
CHANGED
|
@@ -45,7 +45,12 @@ const KIROGRAPH_TOOL_NAMES = [
|
|
|
45
45
|
"kirograph_mem_search",
|
|
46
46
|
"kirograph_mem_store",
|
|
47
47
|
"kirograph_mem_timeline",
|
|
48
|
-
"kirograph_mem_status"
|
|
48
|
+
"kirograph_mem_status",
|
|
49
|
+
"kirograph_docs_toc",
|
|
50
|
+
"kirograph_docs_search",
|
|
51
|
+
"kirograph_docs_section",
|
|
52
|
+
"kirograph_docs_outline",
|
|
53
|
+
"kirograph_docs_refs"
|
|
49
54
|
];
|
|
50
55
|
// Annotate the CommonJS export names for ESM import in node:
|
|
51
56
|
0 && (module.exports = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/mcp/tool-names.ts"],
|
|
4
|
-
"sourcesContent": ["export const KIROGRAPH_TOOL_NAMES = [\n 'kirograph_search',\n 'kirograph_context',\n 'kirograph_callers',\n 'kirograph_callees',\n 'kirograph_impact',\n 'kirograph_node',\n 'kirograph_status',\n 'kirograph_files',\n 'kirograph_dead_code',\n 'kirograph_circular_deps',\n 'kirograph_path',\n 'kirograph_architecture',\n 'kirograph_coupling',\n 'kirograph_package',\n 'kirograph_hotspots',\n 'kirograph_surprising',\n 'kirograph_diff',\n 'kirograph_type_hierarchy',\n 'kirograph_exec',\n 'kirograph_gain',\n 'kirograph_mem_search',\n 'kirograph_mem_store',\n 'kirograph_mem_timeline',\n 'kirograph_mem_status',\n];\n\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
|
|
4
|
+
"sourcesContent": ["export const KIROGRAPH_TOOL_NAMES = [\n 'kirograph_search',\n 'kirograph_context',\n 'kirograph_callers',\n 'kirograph_callees',\n 'kirograph_impact',\n 'kirograph_node',\n 'kirograph_status',\n 'kirograph_files',\n 'kirograph_dead_code',\n 'kirograph_circular_deps',\n 'kirograph_path',\n 'kirograph_architecture',\n 'kirograph_coupling',\n 'kirograph_package',\n 'kirograph_hotspots',\n 'kirograph_surprising',\n 'kirograph_diff',\n 'kirograph_type_hierarchy',\n 'kirograph_exec',\n 'kirograph_gain',\n 'kirograph_mem_search',\n 'kirograph_mem_store',\n 'kirograph_mem_timeline',\n 'kirograph_mem_status',\n 'kirograph_docs_toc',\n 'kirograph_docs_search',\n 'kirograph_docs_section',\n 'kirograph_docs_outline',\n 'kirograph_docs_refs',\n];\n\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/mcp/tools.js
CHANGED
|
@@ -416,6 +416,70 @@ const tools = [
|
|
|
416
416
|
projectPath: { type: "string", description: "Project root path (optional)" }
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
|
+
},
|
|
420
|
+
// ── Docs tools (require enableDocs=true) ────────────────────────────────────
|
|
421
|
+
{
|
|
422
|
+
name: "kirograph_docs_toc",
|
|
423
|
+
description: "Get table of contents for a documentation file or the whole project. Returns section IDs, titles, levels, and summaries.",
|
|
424
|
+
inputSchema: {
|
|
425
|
+
type: "object",
|
|
426
|
+
properties: {
|
|
427
|
+
file: { type: "string", description: "Filter to a specific doc file (relative path). Omit for project-wide TOC." },
|
|
428
|
+
tree: { type: "boolean", description: "Return nested tree structure (default: false, flat list)", default: false },
|
|
429
|
+
projectPath: { type: "string", description: "Project root path (optional)" }
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: "kirograph_docs_search",
|
|
435
|
+
description: "Search documentation sections by query. Returns matching sections ranked by relevance. Independent from kirograph_search (code-only).",
|
|
436
|
+
inputSchema: {
|
|
437
|
+
type: "object",
|
|
438
|
+
properties: {
|
|
439
|
+
query: { type: "string", description: "Search query (natural language or keywords)" },
|
|
440
|
+
file: { type: "string", description: "Narrow search to a specific doc file (relative path)" },
|
|
441
|
+
limit: { type: "number", description: "Max results (default: 10)", default: 10 },
|
|
442
|
+
projectPath: { type: "string", description: "Project root path (optional)" }
|
|
443
|
+
},
|
|
444
|
+
required: ["query"]
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: "kirograph_docs_section",
|
|
449
|
+
description: "Retrieve full content of a documentation section by its stable ID. Use context=true to also get ancestor headings and child summaries.",
|
|
450
|
+
inputSchema: {
|
|
451
|
+
type: "object",
|
|
452
|
+
properties: {
|
|
453
|
+
id: { type: "string", description: "Section ID (from kirograph_docs_toc or kirograph_docs_search results)" },
|
|
454
|
+
context: { type: "boolean", description: "Include ancestor heading chain and child summaries (default: false)", default: false },
|
|
455
|
+
projectPath: { type: "string", description: "Project root path (optional)" }
|
|
456
|
+
},
|
|
457
|
+
required: ["id"]
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: "kirograph_docs_outline",
|
|
462
|
+
description: "Get the heading hierarchy for a single documentation file. Lighter than full TOC when you know which file is relevant.",
|
|
463
|
+
inputSchema: {
|
|
464
|
+
type: "object",
|
|
465
|
+
properties: {
|
|
466
|
+
file: { type: "string", description: "Relative path to the doc file" },
|
|
467
|
+
projectPath: { type: "string", description: "Project root path (optional)" }
|
|
468
|
+
},
|
|
469
|
+
required: ["file"]
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "kirograph_docs_refs",
|
|
474
|
+
description: "Find code symbols referenced by a doc section, or doc sections that reference a code symbol. Bidirectional lookup.",
|
|
475
|
+
inputSchema: {
|
|
476
|
+
type: "object",
|
|
477
|
+
properties: {
|
|
478
|
+
sectionId: { type: "string", description: "Doc section ID (find code symbols it references)" },
|
|
479
|
+
nodeId: { type: "string", description: "Code symbol qualified name (find doc sections that reference it)" },
|
|
480
|
+
projectPath: { type: "string", description: "Project root path (optional)" }
|
|
481
|
+
}
|
|
482
|
+
}
|
|
419
483
|
}
|
|
420
484
|
];
|
|
421
485
|
class ToolHandler {
|
|
@@ -463,6 +527,8 @@ class ToolHandler {
|
|
|
463
527
|
const tracker = new import_tracker.TokenTracker(projectRoot);
|
|
464
528
|
if (toolName.startsWith("kirograph_mem_")) {
|
|
465
529
|
tracker.recordMemorySaving(toolName, outputTokens, naiveCost);
|
|
530
|
+
} else if (toolName.startsWith("kirograph_docs_")) {
|
|
531
|
+
tracker.recordDocsSaving(toolName, outputTokens, naiveCost);
|
|
466
532
|
} else {
|
|
467
533
|
tracker.recordGraphSaving(toolName, outputTokens, naiveCost);
|
|
468
534
|
}
|
|
@@ -532,11 +598,14 @@ class ToolHandler {
|
|
|
532
598
|
` Tokens with KiroGraph: ~${stats.totalCompressed.toLocaleString()}`,
|
|
533
599
|
` Saved: ${stats.totalSaved.toLocaleString()} tokens (${stats.savingsPercent}%)`
|
|
534
600
|
];
|
|
535
|
-
if (stats.bySource.exec.count > 0 || stats.bySource.graph.count > 0 || stats.bySource.memory.count > 0) {
|
|
601
|
+
if (stats.bySource.exec.count > 0 || stats.bySource.graph.count > 0 || stats.bySource.memory.count > 0 || stats.bySource.docs.count > 0) {
|
|
536
602
|
lines.push("", "By source:");
|
|
537
603
|
if (stats.bySource.graph.count > 0) {
|
|
538
604
|
lines.push(` Graph tools: ${stats.bySource.graph.count} calls, ~${stats.bySource.graph.saved.toLocaleString()} tokens saved (vs file reads/grep)`);
|
|
539
605
|
}
|
|
606
|
+
if (stats.bySource.docs.count > 0) {
|
|
607
|
+
lines.push(` Docs tools: ${stats.bySource.docs.count} calls, ~${stats.bySource.docs.saved.toLocaleString()} tokens saved (vs reading full doc files)`);
|
|
608
|
+
}
|
|
540
609
|
if (stats.bySource.exec.count > 0) {
|
|
541
610
|
lines.push(` Compression: ${stats.bySource.exec.count} calls, ~${stats.bySource.exec.saved.toLocaleString()} tokens saved (vs raw output)`);
|
|
542
611
|
}
|
|
@@ -630,6 +699,39 @@ class ToolHandler {
|
|
|
630
699
|
}
|
|
631
700
|
} catch {
|
|
632
701
|
}
|
|
702
|
+
try {
|
|
703
|
+
const projectRoot2 = cg.getProjectRoot();
|
|
704
|
+
const config2 = await (await Promise.resolve().then(() => require("../config.js"))).loadConfig(projectRoot2);
|
|
705
|
+
if (config2.enableDocs && config2.docsContextLimit > 0) {
|
|
706
|
+
const db2 = cg.getDatabase();
|
|
707
|
+
db2.applyDocsSchema();
|
|
708
|
+
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
709
|
+
const docsQueries = new DocsQueries(db2.getRawDb(), projectRoot2);
|
|
710
|
+
const qNames = ctx.entryPoints.map((n) => n.qualifiedName).filter(Boolean);
|
|
711
|
+
if (qNames.length > 0) {
|
|
712
|
+
const docRefs = docsQueries.getRefs({ qualifiedName: qNames[0] });
|
|
713
|
+
const additionalRefs = qNames.slice(1, 5).flatMap((qn) => docsQueries.getRefs({ qualifiedName: qn }));
|
|
714
|
+
const allDocRefs = [...docRefs, ...additionalRefs];
|
|
715
|
+
const seenSections = /* @__PURE__ */ new Set();
|
|
716
|
+
const uniqueRefs = allDocRefs.filter((r) => {
|
|
717
|
+
if (seenSections.has(r.sectionId)) return false;
|
|
718
|
+
seenSections.add(r.sectionId);
|
|
719
|
+
return r.confidence >= config2.docsContextThreshold;
|
|
720
|
+
}).slice(0, config2.docsContextLimit);
|
|
721
|
+
if (uniqueRefs.length > 0) {
|
|
722
|
+
lines.push("", "## Related Documentation");
|
|
723
|
+
for (const ref of uniqueRefs) {
|
|
724
|
+
const section = docsQueries.getSection(ref.sectionId);
|
|
725
|
+
if (section) {
|
|
726
|
+
const summary = section.section.summary ?? section.section.title;
|
|
727
|
+
lines.push(`- [${ref.refType}] ${summary} \u2014 ${section.section.filePath} (ID: ${ref.sectionId})`);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
} catch {
|
|
734
|
+
}
|
|
633
735
|
return lines.join("\n");
|
|
634
736
|
}
|
|
635
737
|
case "kirograph_callers": {
|
|
@@ -724,6 +826,21 @@ class ToolHandler {
|
|
|
724
826
|
] : [` Semantic search: disabled`];
|
|
725
827
|
const frameworkLine = stats.frameworks.length > 0 ? ` Frameworks: ${stats.frameworks.join(", ")}` : ` Frameworks: none detected`;
|
|
726
828
|
const archLine = stats.architectureEnabled ? stats.architectureStats ? ` Architecture: enabled \u2014 ${stats.architectureStats.packages} packages, ${stats.architectureStats.layers} layers, ${stats.architectureStats.packageDeps} deps` : ` Architecture: enabled (not yet analyzed \u2014 run kirograph index)` : ` Architecture: disabled`;
|
|
829
|
+
let docsLine = " Documentation: disabled";
|
|
830
|
+
try {
|
|
831
|
+
const { loadConfig: loadCfg } = await Promise.resolve().then(() => require("../config.js"));
|
|
832
|
+
const cfg = await loadCfg(cg.getProjectRoot());
|
|
833
|
+
if (cfg.enableDocs) {
|
|
834
|
+
const db = cg.getDatabase();
|
|
835
|
+
db.applyDocsSchema();
|
|
836
|
+
const rawDb = db.getRawDb();
|
|
837
|
+
const docFiles = rawDb.get("SELECT COUNT(DISTINCT file_path) as cnt FROM doc_sections")?.cnt ?? 0;
|
|
838
|
+
const docSections = rawDb.get("SELECT COUNT(*) as cnt FROM doc_sections")?.cnt ?? 0;
|
|
839
|
+
const docRefs = rawDb.get("SELECT COUNT(*) as cnt FROM doc_code_refs")?.cnt ?? 0;
|
|
840
|
+
docsLine = ` Documentation: enabled \u2014 ${docFiles} files, ${docSections} sections, ${docRefs} code refs`;
|
|
841
|
+
}
|
|
842
|
+
} catch {
|
|
843
|
+
}
|
|
727
844
|
const threshold = stats.syncWarningThreshold ?? 10;
|
|
728
845
|
const pendingFiles = stats.pendingFiles ?? 0;
|
|
729
846
|
const syncRunning = stats.syncRunning ?? false;
|
|
@@ -752,6 +869,7 @@ class ToolHandler {
|
|
|
752
869
|
langLine ? ` By language: ${langLine}` : "",
|
|
753
870
|
frameworkLine,
|
|
754
871
|
archLine,
|
|
872
|
+
docsLine,
|
|
755
873
|
` DB size: ${dbMb} MB`,
|
|
756
874
|
...semanticLines,
|
|
757
875
|
...syncLines,
|
|
@@ -1163,6 +1281,132 @@ class ToolHandler {
|
|
|
1163
1281
|
];
|
|
1164
1282
|
return lines.join("\n");
|
|
1165
1283
|
}
|
|
1284
|
+
// ── Docs tools ────────────────────────────────────────────────────────────
|
|
1285
|
+
case "kirograph_docs_toc": {
|
|
1286
|
+
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
1287
|
+
const projectRoot = cg.getProjectRoot();
|
|
1288
|
+
const config = await loadConfig(projectRoot);
|
|
1289
|
+
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
1290
|
+
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
1291
|
+
const db = cg.getDatabase();
|
|
1292
|
+
db.applyDocsSchema();
|
|
1293
|
+
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
1294
|
+
const toc = docs.getToc({ file: args.file, tree: args.tree });
|
|
1295
|
+
if (toc.length === 0) return args.file ? `No sections found in "${args.file}".` : "No documentation indexed. Run kirograph index.";
|
|
1296
|
+
const lines = [];
|
|
1297
|
+
const renderEntry = (entry, indent) => {
|
|
1298
|
+
const prefix = "#".repeat(entry.level || 1);
|
|
1299
|
+
const summary = entry.summary ? ` \u2014 ${entry.summary}` : "";
|
|
1300
|
+
lines.push(`${indent}${prefix} ${entry.title}${summary}`);
|
|
1301
|
+
lines.push(`${indent} ID: ${entry.id}`);
|
|
1302
|
+
if (entry.children?.length) {
|
|
1303
|
+
for (const child of entry.children) renderEntry(child, indent + " ");
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
if (args.tree) {
|
|
1307
|
+
for (const entry of toc) renderEntry(entry, "");
|
|
1308
|
+
} else {
|
|
1309
|
+
for (const entry of toc) {
|
|
1310
|
+
const prefix = "#".repeat(entry.level || 1);
|
|
1311
|
+
const summary = entry.summary ? ` \u2014 ${entry.summary}` : "";
|
|
1312
|
+
lines.push(`${prefix} ${entry.title} [${entry.filePath}]${summary}`);
|
|
1313
|
+
lines.push(` ID: ${entry.id}`);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
return lines.join("\n");
|
|
1317
|
+
}
|
|
1318
|
+
case "kirograph_docs_search": {
|
|
1319
|
+
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
1320
|
+
const projectRoot = cg.getProjectRoot();
|
|
1321
|
+
const config = await loadConfig(projectRoot);
|
|
1322
|
+
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
1323
|
+
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
1324
|
+
const db = cg.getDatabase();
|
|
1325
|
+
db.applyDocsSchema();
|
|
1326
|
+
const docs = new DocsQueries(db.getRawDb(), projectRoot, config);
|
|
1327
|
+
const results = await docs.searchSections(args.query, {
|
|
1328
|
+
file: args.file,
|
|
1329
|
+
limit: args.limit ?? 10
|
|
1330
|
+
});
|
|
1331
|
+
if (results.length === 0) return `No documentation sections found matching "${args.query}".`;
|
|
1332
|
+
return results.map((r, i) => {
|
|
1333
|
+
const summary = r.section.summary ? `
|
|
1334
|
+
${r.section.summary}` : "";
|
|
1335
|
+
return `${i + 1}. ${r.section.title} [${r.section.filePath}]${summary}
|
|
1336
|
+
ID: ${r.section.id}`;
|
|
1337
|
+
}).join("\n\n");
|
|
1338
|
+
}
|
|
1339
|
+
case "kirograph_docs_section": {
|
|
1340
|
+
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
1341
|
+
const projectRoot = cg.getProjectRoot();
|
|
1342
|
+
const config = await loadConfig(projectRoot);
|
|
1343
|
+
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
1344
|
+
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
1345
|
+
const db = cg.getDatabase();
|
|
1346
|
+
db.applyDocsSchema();
|
|
1347
|
+
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
1348
|
+
const result = docs.getSection(args.id, { context: args.context });
|
|
1349
|
+
if (!result) return `Section "${args.id}" not found.`;
|
|
1350
|
+
const lines = [];
|
|
1351
|
+
if (result.ancestors?.length) {
|
|
1352
|
+
lines.push("Breadcrumb: " + result.ancestors.map((a) => a.title).join(" > ") + " > " + result.section.title);
|
|
1353
|
+
lines.push("");
|
|
1354
|
+
}
|
|
1355
|
+
lines.push(result.content);
|
|
1356
|
+
if (result.children?.length) {
|
|
1357
|
+
lines.push("", "## Child sections:");
|
|
1358
|
+
for (const child of result.children) {
|
|
1359
|
+
const summary = child.summary ? ` \u2014 ${child.summary}` : "";
|
|
1360
|
+
lines.push(` - ${child.title}${summary} (ID: ${child.id})`);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
return lines.join("\n");
|
|
1364
|
+
}
|
|
1365
|
+
case "kirograph_docs_outline": {
|
|
1366
|
+
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
1367
|
+
const projectRoot = cg.getProjectRoot();
|
|
1368
|
+
const config = await loadConfig(projectRoot);
|
|
1369
|
+
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
1370
|
+
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
1371
|
+
const db = cg.getDatabase();
|
|
1372
|
+
db.applyDocsSchema();
|
|
1373
|
+
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
1374
|
+
const outline = docs.getOutline(args.file);
|
|
1375
|
+
if (outline.length === 0) return `No sections found in "${args.file}". Is the file indexed?`;
|
|
1376
|
+
const lines = [`Outline: ${args.file}`, ""];
|
|
1377
|
+
const renderOutline = (entries, indent) => {
|
|
1378
|
+
for (const entry of entries) {
|
|
1379
|
+
const summary = entry.summary ? ` \u2014 ${entry.summary}` : "";
|
|
1380
|
+
lines.push(`${indent}${"#".repeat(entry.level || 1)} ${entry.title}${summary}`);
|
|
1381
|
+
if (entry.children?.length) renderOutline(entry.children, indent + " ");
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
renderOutline(outline, "");
|
|
1385
|
+
return lines.join("\n");
|
|
1386
|
+
}
|
|
1387
|
+
case "kirograph_docs_refs": {
|
|
1388
|
+
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
1389
|
+
const projectRoot = cg.getProjectRoot();
|
|
1390
|
+
const config = await loadConfig(projectRoot);
|
|
1391
|
+
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
1392
|
+
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
1393
|
+
const db = cg.getDatabase();
|
|
1394
|
+
db.applyDocsSchema();
|
|
1395
|
+
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
1396
|
+
const refs = docs.getRefs({
|
|
1397
|
+
sectionId: args.sectionId,
|
|
1398
|
+
qualifiedName: args.nodeId
|
|
1399
|
+
});
|
|
1400
|
+
if (refs.length === 0) {
|
|
1401
|
+
if (args.sectionId) return `No code references found in section "${args.sectionId}".`;
|
|
1402
|
+
if (args.nodeId) return `No documentation sections reference "${args.nodeId}".`;
|
|
1403
|
+
return "Provide either sectionId or nodeId to look up cross-references.";
|
|
1404
|
+
}
|
|
1405
|
+
return refs.map((r) => {
|
|
1406
|
+
const direction = args.sectionId ? `\u2192 ${r.qualifiedName}` : `\u2190 ${r.sectionTitle ?? r.sectionId}`;
|
|
1407
|
+
return `[${r.refType}] ${direction} (confidence: ${r.confidence.toFixed(2)})`;
|
|
1408
|
+
}).join("\n");
|
|
1409
|
+
}
|
|
1166
1410
|
default:
|
|
1167
1411
|
return `Unknown tool: ${toolName}`;
|
|
1168
1412
|
}
|