kirograph 0.15.0 → 0.16.1

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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -1
  3. package/dist/bin/commands/docs.js +357 -0
  4. package/dist/bin/commands/docs.js.map +7 -0
  5. package/dist/bin/installer/config-prompt.js +17 -1
  6. package/dist/bin/installer/config-prompt.js.map +2 -2
  7. package/dist/bin/installer/index.js +6 -1
  8. package/dist/bin/installer/index.js.map +2 -2
  9. package/dist/bin/installer/steering.js +21 -0
  10. package/dist/bin/installer/steering.js.map +2 -2
  11. package/dist/bin/installer/targets/index.js.map +1 -1
  12. package/dist/bin/installer/targets/kiro.js +2 -2
  13. package/dist/bin/installer/targets/kiro.js.map +2 -2
  14. package/dist/bin/kirograph.js +3 -1
  15. package/dist/bin/kirograph.js.map +3 -3
  16. package/dist/compression/naive-cost.js +19 -0
  17. package/dist/compression/naive-cost.js.map +2 -2
  18. package/dist/compression/tracker.js +11 -2
  19. package/dist/compression/tracker.js.map +2 -2
  20. package/dist/compression/types.js.map +1 -1
  21. package/dist/config.js +34 -1
  22. package/dist/config.js.map +2 -2
  23. package/dist/core/pipeline.js +23 -0
  24. package/dist/core/pipeline.js.map +2 -2
  25. package/dist/db/database.js +11 -0
  26. package/dist/db/database.js.map +2 -2
  27. package/dist/db/docs-schema.sql +50 -0
  28. package/dist/docs/formats/asciidoc.js +108 -0
  29. package/dist/docs/formats/asciidoc.js.map +7 -0
  30. package/dist/docs/formats/html.js +100 -0
  31. package/dist/docs/formats/html.js.map +7 -0
  32. package/dist/docs/formats/index.js +81 -0
  33. package/dist/docs/formats/index.js.map +7 -0
  34. package/dist/docs/formats/markdown.js +146 -0
  35. package/dist/docs/formats/markdown.js.map +7 -0
  36. package/dist/docs/formats/openapi.js +228 -0
  37. package/dist/docs/formats/openapi.js.map +7 -0
  38. package/dist/docs/formats/org.js +117 -0
  39. package/dist/docs/formats/org.js.map +7 -0
  40. package/dist/docs/formats/plaintext.js +119 -0
  41. package/dist/docs/formats/plaintext.js.map +7 -0
  42. package/dist/docs/formats/rdoc.js +105 -0
  43. package/dist/docs/formats/rdoc.js.map +7 -0
  44. package/dist/docs/formats/rst.js +121 -0
  45. package/dist/docs/formats/rst.js.map +7 -0
  46. package/dist/docs/indexer.js +340 -0
  47. package/dist/docs/indexer.js.map +7 -0
  48. package/dist/docs/linker.js +151 -0
  49. package/dist/docs/linker.js.map +7 -0
  50. package/dist/docs/lint.js +92 -0
  51. package/dist/docs/lint.js.map +7 -0
  52. package/dist/docs/queries.js +295 -0
  53. package/dist/docs/queries.js.map +7 -0
  54. package/dist/docs/section-id.js +57 -0
  55. package/dist/docs/section-id.js.map +7 -0
  56. package/dist/docs/summarizer.js +55 -0
  57. package/dist/docs/summarizer.js.map +7 -0
  58. package/dist/docs/types.js +17 -0
  59. package/dist/docs/types.js.map +7 -0
  60. package/dist/docs/vectors.js +217 -0
  61. package/dist/docs/vectors.js.map +7 -0
  62. package/dist/mcp/tool-names.js +6 -1
  63. package/dist/mcp/tool-names.js.map +2 -2
  64. package/dist/mcp/tools.js +245 -1
  65. package/dist/mcp/tools.js.map +2 -2
  66. 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": "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",
6
+ "names": []
7
+ }
@@ -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
  }