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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -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,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/docs/lint.ts"],
4
+ "sourcesContent": ["/**\n * Documentation Lint\n *\n * Health checks for the docs index:\n * 1. Broken code refs \u2014 qualified_names that no longer resolve to any node\n * 2. Stale sections \u2014 sections whose content_hash doesn't match the file on disk\n * 3. FTS desync \u2014 doc_sections_fts row count doesn't match doc_sections\n * 4. Orphan refs \u2014 doc_code_refs pointing to non-existent sections\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as crypto from 'crypto';\n\nexport interface LintResult {\n brokenRefs: Array<{ sectionId: string; qualifiedName: string }>;\n staleSections: Array<{ id: string; filePath: string }>;\n ftsDesync: { sections: number; ftsRows: number } | null;\n orphanRefs: number;\n totalIssues: number;\n}\n\nexport function docsLint(db: any, projectRoot: string): LintResult {\n const result: LintResult = {\n brokenRefs: [],\n staleSections: [],\n ftsDesync: null,\n orphanRefs: 0,\n totalIssues: 0,\n };\n\n // 1. Broken code refs\n const refs = db.all(`\n SELECT r.section_id, r.qualified_name\n FROM doc_code_refs r\n LEFT JOIN nodes n ON n.qualified_name = r.qualified_name\n WHERE n.id IS NULL\n `) as Array<{ section_id: string; qualified_name: string }>;\n result.brokenRefs = refs.map(r => ({ sectionId: r.section_id, qualifiedName: r.qualified_name }));\n\n // 2. Stale sections (content hash mismatch)\n const sections = db.all(`\n SELECT id, file_path, byte_start, byte_end, content_hash\n FROM doc_sections\n `) as Array<{ id: string; file_path: string; byte_start: number; byte_end: number; content_hash: string }>;\n\n for (const section of sections) {\n const absPath = path.join(projectRoot, section.file_path);\n try {\n if (!fs.existsSync(absPath)) {\n result.staleSections.push({ id: section.id, filePath: section.file_path });\n continue;\n }\n const buffer = fs.readFileSync(absPath);\n const content = buffer.slice(section.byte_start, section.byte_end).toString('utf8');\n const hash = crypto.createHash('sha256').update(content).digest('hex');\n if (hash !== section.content_hash) {\n result.staleSections.push({ id: section.id, filePath: section.file_path });\n }\n } catch {\n result.staleSections.push({ id: section.id, filePath: section.file_path });\n }\n }\n\n // 3. FTS desync\n const sectionCount = db.get('SELECT COUNT(*) as cnt FROM doc_sections')?.cnt ?? 0;\n const ftsCount = db.get('SELECT COUNT(*) as cnt FROM doc_sections_fts')?.cnt ?? 0;\n if (sectionCount !== ftsCount) {\n result.ftsDesync = { sections: sectionCount, ftsRows: ftsCount };\n }\n\n // 4. Orphan refs (pointing to non-existent sections)\n const orphans = db.get(`\n SELECT COUNT(*) as cnt\n FROM doc_code_refs r\n LEFT JOIN doc_sections s ON s.id = r.section_id\n WHERE s.id IS NULL\n `)?.cnt ?? 0;\n result.orphanRefs = orphans;\n\n result.totalIssues = result.brokenRefs.length + result.staleSections.length +\n (result.ftsDesync ? 1 : 0) + result.orphanRefs;\n\n return result;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,SAAoB;AACpB,WAAsB;AACtB,aAAwB;AAUjB,SAAS,SAAS,IAAS,aAAiC;AACjE,QAAM,SAAqB;AAAA,IACzB,YAAY,CAAC;AAAA,IACb,eAAe,CAAC;AAAA,IAChB,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,aAAa;AAAA,EACf;AAGA,QAAM,OAAO,GAAG,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,GAKnB;AACD,SAAO,aAAa,KAAK,IAAI,QAAM,EAAE,WAAW,EAAE,YAAY,eAAe,EAAE,eAAe,EAAE;AAGhG,QAAM,WAAW,GAAG,IAAI;AAAA;AAAA;AAAA,GAGvB;AAED,aAAW,WAAW,UAAU;AAC9B,UAAM,UAAU,KAAK,KAAK,aAAa,QAAQ,SAAS;AACxD,QAAI;AACF,UAAI,CAAC,GAAG,WAAW,OAAO,GAAG;AAC3B,eAAO,cAAc,KAAK,EAAE,IAAI,QAAQ,IAAI,UAAU,QAAQ,UAAU,CAAC;AACzE;AAAA,MACF;AACA,YAAM,SAAS,GAAG,aAAa,OAAO;AACtC,YAAM,UAAU,OAAO,MAAM,QAAQ,YAAY,QAAQ,QAAQ,EAAE,SAAS,MAAM;AAClF,YAAM,OAAO,OAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AACrE,UAAI,SAAS,QAAQ,cAAc;AACjC,eAAO,cAAc,KAAK,EAAE,IAAI,QAAQ,IAAI,UAAU,QAAQ,UAAU,CAAC;AAAA,MAC3E;AAAA,IACF,QAAQ;AACN,aAAO,cAAc,KAAK,EAAE,IAAI,QAAQ,IAAI,UAAU,QAAQ,UAAU,CAAC;AAAA,IAC3E;AAAA,EACF;AAGA,QAAM,eAAe,GAAG,IAAI,0CAA0C,GAAG,OAAO;AAChF,QAAM,WAAW,GAAG,IAAI,8CAA8C,GAAG,OAAO;AAChF,MAAI,iBAAiB,UAAU;AAC7B,WAAO,YAAY,EAAE,UAAU,cAAc,SAAS,SAAS;AAAA,EACjE;AAGA,QAAM,UAAU,GAAG,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,GAKtB,GAAG,OAAO;AACX,SAAO,aAAa;AAEpB,SAAO,cAAc,OAAO,WAAW,SAAS,OAAO,cAAc,UAClE,OAAO,YAAY,IAAI,KAAK,OAAO;AAEtC,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,295 @@
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 queries_exports = {};
30
+ __export(queries_exports, {
31
+ DocsQueries: () => DocsQueries
32
+ });
33
+ module.exports = __toCommonJS(queries_exports);
34
+ var fs = __toESM(require("fs"));
35
+ var path = __toESM(require("path"));
36
+ var import_vectors = require("./vectors");
37
+ class DocsQueries {
38
+ constructor(db, projectRoot, config) {
39
+ this.db = db;
40
+ this.projectRoot = projectRoot;
41
+ this.config = config ?? null;
42
+ }
43
+ // ── TOC ─────────────────────────────────────────────────────────────────────
44
+ /**
45
+ * Get table of contents for a file or the whole project.
46
+ */
47
+ getToc(opts) {
48
+ const where = opts?.file ? "WHERE file_path = ?" : "";
49
+ const params = opts?.file ? [opts.file] : [];
50
+ const rows = this.db.all(`
51
+ SELECT id, title, level, file_path, summary, parent_id
52
+ FROM doc_sections
53
+ ${where}
54
+ ORDER BY file_path, position
55
+ `, params);
56
+ if (!opts?.tree) {
57
+ return rows.map((r) => ({
58
+ id: r.id,
59
+ title: r.title,
60
+ level: r.level,
61
+ filePath: r.file_path,
62
+ summary: r.summary
63
+ }));
64
+ }
65
+ return this.buildTocTree(rows);
66
+ }
67
+ /**
68
+ * Get heading hierarchy for a single document.
69
+ */
70
+ getOutline(file) {
71
+ return this.getToc({ file, tree: true });
72
+ }
73
+ // ── Section Retrieval ───────────────────────────────────────────────────────
74
+ /**
75
+ * Get a section by ID, optionally with context (ancestor chain + child summaries).
76
+ */
77
+ getSection(id, opts) {
78
+ const row = this.db.get("SELECT * FROM doc_sections WHERE id = ?", [id]);
79
+ if (!row) return null;
80
+ const section = this.rowToSection(row);
81
+ const content = this.readSectionContent(section);
82
+ if (!opts?.context) {
83
+ return { section, content };
84
+ }
85
+ const ancestors = [];
86
+ let currentParent = section.parentId;
87
+ while (currentParent) {
88
+ const parent = this.db.get("SELECT id, title, level, parent_id FROM doc_sections WHERE id = ?", [currentParent]);
89
+ if (!parent) break;
90
+ ancestors.unshift({ id: parent.id, title: parent.title, level: parent.level });
91
+ currentParent = parent.parent_id;
92
+ }
93
+ const childRows = this.db.all(
94
+ "SELECT id, title, summary FROM doc_sections WHERE parent_id = ? ORDER BY position",
95
+ [id]
96
+ );
97
+ return { section, content, ancestors, children: childRows };
98
+ }
99
+ // ── Search ──────────────────────────────────────────────────────────────────
100
+ /**
101
+ * Search sections by query using FTS5 + optional vector search (hybrid).
102
+ */
103
+ async searchSections(query, opts) {
104
+ const limit = opts?.limit ?? 10;
105
+ const ftsResults = this.searchFTS(query, opts?.file, limit);
106
+ if (this.config?.enableEmbeddings) {
107
+ try {
108
+ const vectorMgr = new import_vectors.DocsVectorManager(this.config, this.db);
109
+ const vectorResults = await vectorMgr.search(query, limit);
110
+ if (vectorResults.length > 0) {
111
+ const alpha = 0.5;
112
+ const merged = /* @__PURE__ */ new Map();
113
+ for (const r of ftsResults) {
114
+ merged.set(r.section.id, { section: r.section, score: r.score * (1 - alpha), matchType: "fts" });
115
+ }
116
+ for (const vr of vectorResults) {
117
+ const existing = merged.get(vr.sectionId);
118
+ if (existing) {
119
+ existing.score += vr.score * alpha;
120
+ existing.matchType = "semantic";
121
+ } else {
122
+ const row = this.db.get("SELECT * FROM doc_sections WHERE id = ?", [vr.sectionId]);
123
+ if (row) {
124
+ if (opts?.file && row.file_path !== opts.file) continue;
125
+ merged.set(vr.sectionId, {
126
+ section: this.rowToSection(row),
127
+ score: vr.score * alpha,
128
+ matchType: "semantic"
129
+ });
130
+ }
131
+ }
132
+ }
133
+ const sorted = [...merged.values()].sort((a, b) => b.score - a.score).slice(0, limit);
134
+ return sorted;
135
+ }
136
+ } catch {
137
+ }
138
+ }
139
+ return ftsResults;
140
+ }
141
+ /**
142
+ * FTS-only search (used internally and as fallback).
143
+ */
144
+ searchFTS(query, file, limit) {
145
+ const sanitized = query.replace(/[",]/g, " ").replace(/\s+/g, " ").trim();
146
+ if (!sanitized) return [];
147
+ let sql;
148
+ let params;
149
+ if (file) {
150
+ sql = `
151
+ SELECT s.*, rank
152
+ FROM doc_sections_fts fts
153
+ JOIN doc_sections s ON s.id = fts.id
154
+ WHERE doc_sections_fts MATCH ? AND s.file_path = ?
155
+ ORDER BY rank
156
+ LIMIT ?
157
+ `;
158
+ params = [sanitized, file, limit];
159
+ } else {
160
+ sql = `
161
+ SELECT s.*, rank
162
+ FROM doc_sections_fts fts
163
+ JOIN doc_sections s ON s.id = fts.id
164
+ WHERE doc_sections_fts MATCH ?
165
+ ORDER BY rank
166
+ LIMIT ?
167
+ `;
168
+ params = [sanitized, limit];
169
+ }
170
+ try {
171
+ const rows = this.db.all(sql, params);
172
+ return rows.map((row) => ({
173
+ section: this.rowToSection(row),
174
+ score: Math.abs(row.rank ?? 0),
175
+ matchType: "fts"
176
+ }));
177
+ } catch {
178
+ return this.searchFallback(sanitized, file, limit);
179
+ }
180
+ }
181
+ // ── Code Refs ───────────────────────────────────────────────────────────────
182
+ /**
183
+ * Get code symbols referenced by a section, or doc sections that reference a symbol.
184
+ */
185
+ getRefs(opts) {
186
+ if (opts.sectionId) {
187
+ return this.db.all(`
188
+ SELECT r.*, s.title as section_title
189
+ FROM doc_code_refs r
190
+ JOIN doc_sections s ON s.id = r.section_id
191
+ WHERE r.section_id = ?
192
+ ORDER BY r.confidence DESC
193
+ `, [opts.sectionId]).map((r) => ({
194
+ sectionId: r.section_id,
195
+ qualifiedName: r.qualified_name,
196
+ refType: r.ref_type,
197
+ confidence: r.confidence,
198
+ sectionTitle: r.section_title
199
+ }));
200
+ }
201
+ if (opts.qualifiedName) {
202
+ return this.db.all(`
203
+ SELECT r.*, s.title as section_title
204
+ FROM doc_code_refs r
205
+ JOIN doc_sections s ON s.id = r.section_id
206
+ WHERE r.qualified_name = ?
207
+ ORDER BY r.confidence DESC
208
+ `, [opts.qualifiedName]).map((r) => ({
209
+ sectionId: r.section_id,
210
+ qualifiedName: r.qualified_name,
211
+ refType: r.ref_type,
212
+ confidence: r.confidence,
213
+ sectionTitle: r.section_title
214
+ }));
215
+ }
216
+ return [];
217
+ }
218
+ // ── Stats ───────────────────────────────────────────────────────────────────
219
+ getStats() {
220
+ const files = this.db.get("SELECT COUNT(DISTINCT file_path) as cnt FROM doc_sections")?.cnt ?? 0;
221
+ const sections = this.db.get("SELECT COUNT(*) as cnt FROM doc_sections")?.cnt ?? 0;
222
+ const codeRefs = this.db.get("SELECT COUNT(*) as cnt FROM doc_code_refs")?.cnt ?? 0;
223
+ return { files, sections, codeRefs };
224
+ }
225
+ // ── Private ─────────────────────────────────────────────────────────────────
226
+ rowToSection(row) {
227
+ return {
228
+ id: row.id,
229
+ filePath: row.file_path,
230
+ title: row.title,
231
+ level: row.level,
232
+ parentId: row.parent_id ?? null,
233
+ summary: row.summary ?? null,
234
+ byteStart: row.byte_start,
235
+ byteEnd: row.byte_end,
236
+ contentHash: row.content_hash,
237
+ tags: row.tags ? JSON.parse(row.tags) : [],
238
+ position: row.position,
239
+ updatedAt: row.updated_at
240
+ };
241
+ }
242
+ readSectionContent(section) {
243
+ const absPath = path.join(this.projectRoot, section.filePath);
244
+ try {
245
+ const buffer = fs.readFileSync(absPath);
246
+ return buffer.slice(section.byteStart, section.byteEnd).toString("utf8");
247
+ } catch {
248
+ return "(file not found on disk)";
249
+ }
250
+ }
251
+ buildTocTree(rows) {
252
+ const map = /* @__PURE__ */ new Map();
253
+ const roots = [];
254
+ for (const row of rows) {
255
+ const entry = {
256
+ id: row.id,
257
+ title: row.title,
258
+ level: row.level,
259
+ filePath: row.file_path,
260
+ summary: row.summary,
261
+ children: []
262
+ };
263
+ map.set(row.id, entry);
264
+ if (!row.parent_id || !map.has(row.parent_id)) {
265
+ roots.push(entry);
266
+ } else {
267
+ map.get(row.parent_id).children.push(entry);
268
+ }
269
+ }
270
+ return roots;
271
+ }
272
+ searchFallback(query, file, limit) {
273
+ const pattern = `%${query}%`;
274
+ let sql;
275
+ let params;
276
+ if (file) {
277
+ sql = "SELECT * FROM doc_sections WHERE file_path = ? AND (title LIKE ? OR summary LIKE ?) LIMIT ?";
278
+ params = [file, pattern, pattern, limit];
279
+ } else {
280
+ sql = "SELECT * FROM doc_sections WHERE title LIKE ? OR summary LIKE ? LIMIT ?";
281
+ params = [pattern, pattern, limit];
282
+ }
283
+ const rows = this.db.all(sql, params);
284
+ return rows.map((row) => ({
285
+ section: this.rowToSection(row),
286
+ score: 1,
287
+ matchType: "fts"
288
+ }));
289
+ }
290
+ }
291
+ // Annotate the CommonJS export names for ESM import in node:
292
+ 0 && (module.exports = {
293
+ DocsQueries
294
+ });
295
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/docs/queries.ts"],
4
+ "sourcesContent": ["/**\n * Documentation Queries\n *\n * Read-side helpers for MCP tools and CLI commands.\n * All methods operate on the doc_sections and doc_code_refs tables.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { DocSection, DocTocEntry, DocSearchResult } from './types';\nimport type { KiroGraphConfig } from '../config';\nimport { parseSectionId } from './section-id';\nimport { DocsVectorManager } from './vectors';\n\nexport class DocsQueries {\n private readonly db: any;\n private readonly projectRoot: string;\n private readonly config: KiroGraphConfig | null;\n\n constructor(db: any, projectRoot: string, config?: KiroGraphConfig) {\n this.db = db;\n this.projectRoot = projectRoot;\n this.config = config ?? null;\n }\n\n // \u2500\u2500 TOC \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\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Get table of contents for a file or the whole project.\n */\n getToc(opts?: { file?: string; tree?: boolean }): DocTocEntry[] {\n const where = opts?.file ? 'WHERE file_path = ?' : '';\n const params = opts?.file ? [opts.file] : [];\n\n const rows = this.db.all(`\n SELECT id, title, level, file_path, summary, parent_id\n FROM doc_sections\n ${where}\n ORDER BY file_path, position\n `, params) as Array<{\n id: string; title: string; level: number;\n file_path: string; summary: string | null; parent_id: string | null;\n }>;\n\n if (!opts?.tree) {\n // Flat list\n return rows.map(r => ({\n id: r.id,\n title: r.title,\n level: r.level,\n filePath: r.file_path,\n summary: r.summary,\n }));\n }\n\n // Build tree\n return this.buildTocTree(rows);\n }\n\n /**\n * Get heading hierarchy for a single document.\n */\n getOutline(file: string): DocTocEntry[] {\n return this.getToc({ file, tree: true });\n }\n\n // \u2500\u2500 Section Retrieval \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 /**\n * Get a section by ID, optionally with context (ancestor chain + child summaries).\n */\n getSection(id: string, opts?: { context?: boolean }): {\n section: DocSection;\n content: string;\n ancestors?: Array<{ id: string; title: string; level: number }>;\n children?: Array<{ id: string; title: string; summary: string | null }>;\n } | null {\n const row = this.db.get('SELECT * FROM doc_sections WHERE id = ?', [id]);\n if (!row) return null;\n\n const section = this.rowToSection(row);\n const content = this.readSectionContent(section);\n\n if (!opts?.context) {\n return { section, content };\n }\n\n // Build ancestor chain\n const ancestors: Array<{ id: string; title: string; level: number }> = [];\n let currentParent = section.parentId;\n while (currentParent) {\n const parent = this.db.get('SELECT id, title, level, parent_id FROM doc_sections WHERE id = ?', [currentParent]);\n if (!parent) break;\n ancestors.unshift({ id: parent.id, title: parent.title, level: parent.level });\n currentParent = parent.parent_id;\n }\n\n // Get child summaries\n const childRows = this.db.all(\n 'SELECT id, title, summary FROM doc_sections WHERE parent_id = ? ORDER BY position',\n [id]\n ) as Array<{ id: string; title: string; summary: string | null }>;\n\n return { section, content, ancestors, children: childRows };\n }\n\n // \u2500\u2500 Search \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\u2500\u2500\n\n /**\n * Search sections by query using FTS5 + optional vector search (hybrid).\n */\n async searchSections(query: string, opts?: { file?: string; limit?: number }): Promise<DocSearchResult[]> {\n const limit = opts?.limit ?? 10;\n\n // FTS search\n const ftsResults = this.searchFTS(query, opts?.file, limit);\n\n // Vector search (if embeddings enabled)\n if (this.config?.enableEmbeddings) {\n try {\n const vectorMgr = new DocsVectorManager(this.config, this.db);\n const vectorResults = await vectorMgr.search(query, limit);\n\n if (vectorResults.length > 0) {\n // Merge FTS + vector results with alpha blend\n const alpha = 0.5; // 0 = pure FTS, 1 = pure vector\n const merged = new Map<string, { section: DocSection; score: number; matchType: 'fts' | 'semantic' }>();\n\n // Add FTS results\n for (const r of ftsResults) {\n merged.set(r.section.id, { section: r.section, score: r.score * (1 - alpha), matchType: 'fts' });\n }\n\n // Add/merge vector results\n for (const vr of vectorResults) {\n const existing = merged.get(vr.sectionId);\n if (existing) {\n existing.score += vr.score * alpha;\n existing.matchType = 'semantic'; // promoted to semantic if both match\n } else {\n // Fetch section from DB\n const row = this.db.get('SELECT * FROM doc_sections WHERE id = ?', [vr.sectionId]);\n if (row) {\n // Filter by file if specified\n if (opts?.file && row.file_path !== opts.file) continue;\n merged.set(vr.sectionId, {\n section: this.rowToSection(row),\n score: vr.score * alpha,\n matchType: 'semantic',\n });\n }\n }\n }\n\n // Sort by merged score, return top N\n const sorted = [...merged.values()].sort((a, b) => b.score - a.score).slice(0, limit);\n return sorted;\n }\n } catch { /* vector search is non-critical, fall through to FTS-only */ }\n }\n\n return ftsResults;\n }\n\n /**\n * FTS-only search (used internally and as fallback).\n */\n private searchFTS(query: string, file: string | undefined, limit: number): DocSearchResult[] {\n\n // Sanitize query for FTS5\n const sanitized = query\n .replace(/[\",]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n if (!sanitized) return [];\n\n let sql: string;\n let params: any[];\n\n if (file) {\n sql = `\n SELECT s.*, rank\n FROM doc_sections_fts fts\n JOIN doc_sections s ON s.id = fts.id\n WHERE doc_sections_fts MATCH ? AND s.file_path = ?\n ORDER BY rank\n LIMIT ?\n `;\n params = [sanitized, file, limit];\n } else {\n sql = `\n SELECT s.*, rank\n FROM doc_sections_fts fts\n JOIN doc_sections s ON s.id = fts.id\n WHERE doc_sections_fts MATCH ?\n ORDER BY rank\n LIMIT ?\n `;\n params = [sanitized, limit];\n }\n\n try {\n const rows = this.db.all(sql, params) as any[];\n return rows.map(row => ({\n section: this.rowToSection(row),\n score: Math.abs(row.rank ?? 0),\n matchType: 'fts' as const,\n }));\n } catch {\n // FTS query syntax error \u2014 fall back to LIKE search\n return this.searchFallback(sanitized, file, limit);\n }\n }\n\n // \u2500\u2500 Code Refs \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 /**\n * Get code symbols referenced by a section, or doc sections that reference a symbol.\n */\n getRefs(opts: { sectionId?: string; qualifiedName?: string }): Array<{\n sectionId: string;\n qualifiedName: string;\n refType: string;\n confidence: number;\n sectionTitle?: string;\n }> {\n if (opts.sectionId) {\n return this.db.all(`\n SELECT r.*, s.title as section_title\n FROM doc_code_refs r\n JOIN doc_sections s ON s.id = r.section_id\n WHERE r.section_id = ?\n ORDER BY r.confidence DESC\n `, [opts.sectionId]).map((r: any) => ({\n sectionId: r.section_id,\n qualifiedName: r.qualified_name,\n refType: r.ref_type,\n confidence: r.confidence,\n sectionTitle: r.section_title,\n }));\n }\n\n if (opts.qualifiedName) {\n return this.db.all(`\n SELECT r.*, s.title as section_title\n FROM doc_code_refs r\n JOIN doc_sections s ON s.id = r.section_id\n WHERE r.qualified_name = ?\n ORDER BY r.confidence DESC\n `, [opts.qualifiedName]).map((r: any) => ({\n sectionId: r.section_id,\n qualifiedName: r.qualified_name,\n refType: r.ref_type,\n confidence: r.confidence,\n sectionTitle: r.section_title,\n }));\n }\n\n return [];\n }\n\n // \u2500\u2500 Stats \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\u2500\u2500\u2500\n\n getStats(): { files: number; sections: number; codeRefs: number } {\n const files = this.db.get('SELECT COUNT(DISTINCT file_path) as cnt FROM doc_sections')?.cnt ?? 0;\n const sections = this.db.get('SELECT COUNT(*) as cnt FROM doc_sections')?.cnt ?? 0;\n const codeRefs = this.db.get('SELECT COUNT(*) as cnt FROM doc_code_refs')?.cnt ?? 0;\n return { files, sections, codeRefs };\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\u2500\n\n private rowToSection(row: any): DocSection {\n return {\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\n private readSectionContent(section: DocSection): string {\n const absPath = path.join(this.projectRoot, section.filePath);\n try {\n const buffer = fs.readFileSync(absPath);\n return buffer.slice(section.byteStart, section.byteEnd).toString('utf8');\n } catch {\n return '(file not found on disk)';\n }\n }\n\n private buildTocTree(rows: Array<{\n id: string; title: string; level: number;\n file_path: string; summary: string | null; parent_id: string | null;\n }>): DocTocEntry[] {\n const map = new Map<string, DocTocEntry>();\n const roots: DocTocEntry[] = [];\n\n for (const row of rows) {\n const entry: DocTocEntry = {\n id: row.id,\n title: row.title,\n level: row.level,\n filePath: row.file_path,\n summary: row.summary,\n children: [],\n };\n map.set(row.id, entry);\n\n if (!row.parent_id || !map.has(row.parent_id)) {\n roots.push(entry);\n } else {\n map.get(row.parent_id)!.children!.push(entry);\n }\n }\n\n return roots;\n }\n\n private searchFallback(query: string, file: string | undefined, limit: number): DocSearchResult[] {\n const pattern = `%${query}%`;\n let sql: string;\n let params: any[];\n\n if (file) {\n sql = 'SELECT * FROM doc_sections WHERE file_path = ? AND (title LIKE ? OR summary LIKE ?) LIMIT ?';\n params = [file, pattern, pattern, limit];\n } else {\n sql = 'SELECT * FROM doc_sections WHERE title LIKE ? OR summary LIKE ? LIMIT ?';\n params = [pattern, pattern, limit];\n }\n\n const rows = this.db.all(sql, params) as any[];\n return rows.map(row => ({\n section: this.rowToSection(row),\n score: 1.0,\n matchType: 'fts' as const,\n }));\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var section_id_exports = {};
20
+ __export(section_id_exports, {
21
+ buildAncestorChain: () => buildAncestorChain,
22
+ generateSectionId: () => generateSectionId,
23
+ parseSectionId: () => parseSectionId,
24
+ slugify: () => slugify
25
+ });
26
+ module.exports = __toCommonJS(section_id_exports);
27
+ function slugify(title) {
28
+ return title.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "") || "untitled";
29
+ }
30
+ function buildAncestorChain(parentSlugs, currentSlug) {
31
+ return [...parentSlugs, currentSlug].join("/");
32
+ }
33
+ function generateSectionId(filePath, ancestorSlugs, title, level) {
34
+ const slug = slugify(title);
35
+ const chain = buildAncestorChain(ancestorSlugs, slug);
36
+ return `${filePath}::${chain}#${level}`;
37
+ }
38
+ function parseSectionId(id) {
39
+ const doubleColonIdx = id.indexOf("::");
40
+ if (doubleColonIdx === -1) return null;
41
+ const filePath = id.slice(0, doubleColonIdx);
42
+ const rest = id.slice(doubleColonIdx + 2);
43
+ const hashIdx = rest.lastIndexOf("#");
44
+ if (hashIdx === -1) return null;
45
+ const ancestorChain = rest.slice(0, hashIdx);
46
+ const level = parseInt(rest.slice(hashIdx + 1), 10);
47
+ if (isNaN(level) || level < 0) return null;
48
+ return { filePath, ancestorChain, level };
49
+ }
50
+ // Annotate the CommonJS export names for ESM import in node:
51
+ 0 && (module.exports = {
52
+ buildAncestorChain,
53
+ generateSectionId,
54
+ parseSectionId,
55
+ slugify
56
+ });
57
+ //# sourceMappingURL=section-id.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/docs/section-id.ts"],
4
+ "sourcesContent": ["/**\n * Stable Section ID Generation\n *\n * Format: {file_path}::{ancestor-chain/slug}#{level}\n *\n * IDs remain stable across re-indexing when:\n * - file path doesn't change\n * - heading text doesn't change\n * - heading level doesn't change\n * - parent heading chain doesn't change\n */\n\n/**\n * Convert a heading title to a URL-safe slug.\n * Matches common Markdown anchor generation (GitHub-style).\n */\nexport function slugify(title: string): string {\n return title\n .toLowerCase()\n .trim()\n // Remove special characters except spaces and hyphens\n .replace(/[^\\w\\s-]/g, '')\n // Replace whitespace with hyphens\n .replace(/\\s+/g, '-')\n // Collapse multiple hyphens\n .replace(/-+/g, '-')\n // Trim leading/trailing hyphens\n .replace(/^-+|-+$/g, '')\n // Fallback for empty result\n || 'untitled';\n}\n\n/**\n * Build the ancestor chain string from parent slugs.\n * Example: ['installation', 'prerequisites'] \u2192 'installation/prerequisites'\n */\nexport function buildAncestorChain(parentSlugs: string[], currentSlug: string): string {\n return [...parentSlugs, currentSlug].join('/');\n}\n\n/**\n * Generate a stable section ID.\n *\n * @param filePath - Relative path to the doc file\n * @param ancestorSlugs - Slugified titles of all ancestor headings (root \u2192 parent)\n * @param title - The heading title of this section\n * @param level - The heading level (1\u20136)\n * @returns Stable section ID string\n *\n * @example\n * generateSectionId('docs/install.md', [], 'Installation', 1)\n * // \u2192 'docs/install.md::installation#1'\n *\n * generateSectionId('docs/install.md', ['installation'], 'Prerequisites', 2)\n * // \u2192 'docs/install.md::installation/prerequisites#2'\n */\nexport function generateSectionId(\n filePath: string,\n ancestorSlugs: string[],\n title: string,\n level: number,\n): string {\n const slug = slugify(title);\n const chain = buildAncestorChain(ancestorSlugs, slug);\n return `${filePath}::${chain}#${level}`;\n}\n\n/**\n * Parse a section ID back into its components.\n * Returns null if the ID is malformed.\n */\nexport function parseSectionId(id: string): {\n filePath: string;\n ancestorChain: string;\n level: number;\n} | null {\n const doubleColonIdx = id.indexOf('::');\n if (doubleColonIdx === -1) return null;\n\n const filePath = id.slice(0, doubleColonIdx);\n const rest = id.slice(doubleColonIdx + 2);\n\n const hashIdx = rest.lastIndexOf('#');\n if (hashIdx === -1) return null;\n\n const ancestorChain = rest.slice(0, hashIdx);\n const level = parseInt(rest.slice(hashIdx + 1), 10);\n\n if (isNaN(level) || level < 0) return null;\n\n return { filePath, ancestorChain, level };\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBO,SAAS,QAAQ,OAAuB;AAC7C,SAAO,MACJ,YAAY,EACZ,KAAK,EAEL,QAAQ,aAAa,EAAE,EAEvB,QAAQ,QAAQ,GAAG,EAEnB,QAAQ,OAAO,GAAG,EAElB,QAAQ,YAAY,EAAE,KAEpB;AACP;AAMO,SAAS,mBAAmB,aAAuB,aAA6B;AACrF,SAAO,CAAC,GAAG,aAAa,WAAW,EAAE,KAAK,GAAG;AAC/C;AAkBO,SAAS,kBACd,UACA,eACA,OACA,OACQ;AACR,QAAM,OAAO,QAAQ,KAAK;AAC1B,QAAM,QAAQ,mBAAmB,eAAe,IAAI;AACpD,SAAO,GAAG,QAAQ,KAAK,KAAK,IAAI,KAAK;AACvC;AAMO,SAAS,eAAe,IAItB;AACP,QAAM,iBAAiB,GAAG,QAAQ,IAAI;AACtC,MAAI,mBAAmB,GAAI,QAAO;AAElC,QAAM,WAAW,GAAG,MAAM,GAAG,cAAc;AAC3C,QAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC;AAExC,QAAM,UAAU,KAAK,YAAY,GAAG;AACpC,MAAI,YAAY,GAAI,QAAO;AAE3B,QAAM,gBAAgB,KAAK,MAAM,GAAG,OAAO;AAC3C,QAAM,QAAQ,SAAS,KAAK,MAAM,UAAU,CAAC,GAAG,EAAE;AAElD,MAAI,MAAM,KAAK,KAAK,QAAQ,EAAG,QAAO;AAEtC,SAAO,EAAE,UAAU,eAAe,MAAM;AAC1C;",
6
+ "names": []
7
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var summarizer_exports = {};
20
+ __export(summarizer_exports, {
21
+ summarizeSection: () => summarizeSection
22
+ });
23
+ module.exports = __toCommonJS(summarizer_exports);
24
+ function summarizeSection(content, title, strategy) {
25
+ if (strategy === "off") return null;
26
+ return extractFirstSentence(content, title);
27
+ }
28
+ function extractFirstSentence(content, title) {
29
+ const lines = content.split("\n");
30
+ const bodyLines = lines.filter((l) => {
31
+ const trimmed = l.trim();
32
+ if (trimmed === "") return false;
33
+ if (trimmed.startsWith("#") || trimmed.startsWith("=") || trimmed.startsWith("*")) {
34
+ if (/^[#=*]+\s/.test(trimmed)) return false;
35
+ }
36
+ if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) return false;
37
+ if (/^[=-]+\s*$/.test(trimmed)) return false;
38
+ if (/^<\/?(?:h[1-6]|div|section|article|nav|header|footer)[^>]*>$/i.test(trimmed)) return false;
39
+ if (trimmed.toLowerCase() === title.toLowerCase()) return false;
40
+ return true;
41
+ });
42
+ if (bodyLines.length === 0) return null;
43
+ const firstLine = bodyLines[0].trim();
44
+ const cleaned = firstLine.replace(/^[-*•]\s+/, "").replace(/^\d+\.\s+/, "");
45
+ const sentenceEnd = cleaned.search(/[.!?]\s|[.!?]$/);
46
+ if (sentenceEnd > 0 && sentenceEnd < 200) {
47
+ return cleaned.slice(0, sentenceEnd + 1).trim();
48
+ }
49
+ return cleaned.length > 200 ? cleaned.slice(0, 200) + "\u2026" : cleaned;
50
+ }
51
+ // Annotate the CommonJS export names for ESM import in node:
52
+ 0 && (module.exports = {
53
+ summarizeSection
54
+ });
55
+ //# sourceMappingURL=summarizer.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/docs/summarizer.ts"],
4
+ "sourcesContent": ["/**\n * Documentation Section Summarizer\n *\n * Strategies:\n * - 'first-sentence': Extract the first meaningful sentence from section content\n * - 'embedding': Use the embedding model to score sentences by relevance to heading (future)\n * - 'off': No summary, return null\n */\n\n/**\n * Extract a summary from section content based on the configured strategy.\n */\nexport function summarizeSection(\n content: string,\n title: string,\n strategy: 'embedding' | 'first-sentence' | 'off',\n): string | null {\n if (strategy === 'off') return null;\n\n // For now, both 'embedding' and 'first-sentence' use first-sentence extraction.\n // The 'embedding' strategy will be enhanced when vector scoring is integrated.\n return extractFirstSentence(content, title);\n}\n\n/**\n * Extract the first meaningful sentence from section content.\n * Skips the heading line itself, code blocks, and empty lines.\n */\nfunction extractFirstSentence(content: string, title: string): string | null {\n const lines = content.split('\\n');\n const bodyLines = lines.filter(l => {\n const trimmed = l.trim();\n if (trimmed === '') return false;\n // Skip heading markers\n if (trimmed.startsWith('#') || trimmed.startsWith('=') || trimmed.startsWith('*')) {\n // But only if it looks like a heading (starts with marker + space)\n if (/^[#=*]+\\s/.test(trimmed)) return false;\n }\n // Skip code fence markers\n if (trimmed.startsWith('```') || trimmed.startsWith('~~~')) return false;\n // Skip setext underlines\n if (/^[=-]+\\s*$/.test(trimmed)) return false;\n // Skip HTML tags that are likely structural\n if (/^<\\/?(?:h[1-6]|div|section|article|nav|header|footer)[^>]*>$/i.test(trimmed)) return false;\n // Skip lines that are just the title repeated\n if (trimmed.toLowerCase() === title.toLowerCase()) return false;\n return true;\n });\n\n if (bodyLines.length === 0) return null;\n\n // Take first non-empty line, truncate at sentence boundary\n const firstLine = bodyLines[0].trim();\n\n // Strip leading list markers (-, *, 1.)\n const cleaned = firstLine.replace(/^[-*\u2022]\\s+/, '').replace(/^\\d+\\.\\s+/, '');\n\n const sentenceEnd = cleaned.search(/[.!?]\\s|[.!?]$/);\n if (sentenceEnd > 0 && sentenceEnd < 200) {\n return cleaned.slice(0, sentenceEnd + 1).trim();\n }\n\n // No sentence boundary \u2014 take the whole first line (truncated)\n return cleaned.length > 200 ? cleaned.slice(0, 200) + '\u2026' : cleaned;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAYO,SAAS,iBACd,SACA,OACA,UACe;AACf,MAAI,aAAa,MAAO,QAAO;AAI/B,SAAO,qBAAqB,SAAS,KAAK;AAC5C;AAMA,SAAS,qBAAqB,SAAiB,OAA8B;AAC3E,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,QAAM,YAAY,MAAM,OAAO,OAAK;AAClC,UAAM,UAAU,EAAE,KAAK;AACvB,QAAI,YAAY,GAAI,QAAO;AAE3B,QAAI,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,GAAG,GAAG;AAEjF,UAAI,YAAY,KAAK,OAAO,EAAG,QAAO;AAAA,IACxC;AAEA,QAAI,QAAQ,WAAW,KAAK,KAAK,QAAQ,WAAW,KAAK,EAAG,QAAO;AAEnE,QAAI,aAAa,KAAK,OAAO,EAAG,QAAO;AAEvC,QAAI,gEAAgE,KAAK,OAAO,EAAG,QAAO;AAE1F,QAAI,QAAQ,YAAY,MAAM,MAAM,YAAY,EAAG,QAAO;AAC1D,WAAO;AAAA,EACT,CAAC;AAED,MAAI,UAAU,WAAW,EAAG,QAAO;AAGnC,QAAM,YAAY,UAAU,CAAC,EAAE,KAAK;AAGpC,QAAM,UAAU,UAAU,QAAQ,aAAa,EAAE,EAAE,QAAQ,aAAa,EAAE;AAE1E,QAAM,cAAc,QAAQ,OAAO,gBAAgB;AACnD,MAAI,cAAc,KAAK,cAAc,KAAK;AACxC,WAAO,QAAQ,MAAM,GAAG,cAAc,CAAC,EAAE,KAAK;AAAA,EAChD;AAGA,SAAO,QAAQ,SAAS,MAAM,QAAQ,MAAM,GAAG,GAAG,IAAI,WAAM;AAC9D;",
6
+ "names": []
7
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+ var types_exports = {};
16
+ module.exports = __toCommonJS(types_exports);
17
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/docs/types.ts"],
4
+ "sourcesContent": ["/**\n * KiroGraph Documentation Module \u2014 Type Definitions\n *\n * These types model the documentation layer: sections extracted from\n * doc files, cross-references to code symbols, and search results.\n * All populated only when enableDocs=true in config.\n */\n\n// \u2500\u2500 Section \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\u2500\u2500\u2500\n\nexport interface DocSection {\n id: string; // stable section ID (file_path::ancestor-chain/slug#level)\n filePath: string; // relative path to doc file\n title: string; // heading text\n level: number; // heading depth (1\u20136, 0 for root/document-level)\n parentId: string | null; // parent section ID (null for top-level)\n summary: string | null; // one-line summary (extractive or first-sentence)\n byteStart: number; // byte offset in original file\n byteEnd: number; // byte offset end\n contentHash: string; // SHA-256 of section content (drift detection)\n tags: string[]; // extracted tags/refs (code refs, links, etc.)\n position: number; // ordering among siblings\n updatedAt: number; // timestamp\n}\n\n// \u2500\u2500 Code Reference \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\nexport type DocRefType = 'mentions' | 'documents' | 'example' | 'configures';\n\nexport interface DocCodeRef {\n sectionId: string;\n qualifiedName: string; // stable across reindex\n refType: DocRefType;\n confidence: number; // 0.0\u20131.0\n}\n\n// \u2500\u2500 Parser Output \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\nexport interface ParsedSection {\n title: string;\n level: number;\n byteStart: number;\n byteEnd: number;\n content: string; // raw content of the section (for hashing + summarization)\n children: ParsedSection[];\n}\n\nexport interface ParseResult {\n sections: ParsedSection[];\n format: string; // 'markdown' | 'rst' | 'asciidoc' | 'rdoc' | 'org' | 'html' | 'plaintext' | 'openapi'\n}\n\n// \u2500\u2500 Format Parser Interface \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 interface DocFormatParser {\n /** Unique name for this parser (e.g. 'markdown', 'rst') */\n name: string;\n /** File extensions this parser handles (e.g. ['.md', '.mdx']) */\n extensions: string[];\n /** Parse a file's content into a section hierarchy */\n parse(content: string, filePath: string): ParseResult;\n}\n\n// \u2500\u2500 Search \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\u2500\u2500\u2500\u2500\n\nexport interface DocSearchResult {\n section: DocSection;\n score: number;\n matchType: 'fts' | 'semantic' | 'exact';\n}\n\n// \u2500\u2500 Index Result \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\nexport interface DocIndexResult {\n filesIndexed: number;\n sectionsCreated: number;\n sectionsUpdated: number;\n sectionsRemoved: number;\n codeRefsCreated: number;\n errors: string[];\n duration: number;\n}\n\n// \u2500\u2500 TOC Entry \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\u2500\n\nexport interface DocTocEntry {\n id: string;\n title: string;\n level: number;\n filePath: string;\n summary: string | null;\n children?: DocTocEntry[];\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
6
+ "names": []
7
+ }