knowledge-mcp-server 1.0.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 (52) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +205 -0
  4. package/dist/analytics.d.ts +24 -0
  5. package/dist/analytics.js +102 -0
  6. package/dist/analytics.js.map +1 -0
  7. package/dist/cli.d.ts +2 -0
  8. package/dist/cli.js +98 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/config.d.ts +39 -0
  11. package/dist/config.js +80 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/embeddings.d.ts +26 -0
  14. package/dist/embeddings.js +534 -0
  15. package/dist/embeddings.js.map +1 -0
  16. package/dist/formatter.d.ts +51 -0
  17. package/dist/formatter.js +273 -0
  18. package/dist/formatter.js.map +1 -0
  19. package/dist/generate-embeddings.d.ts +8 -0
  20. package/dist/generate-embeddings.js +146 -0
  21. package/dist/generate-embeddings.js.map +1 -0
  22. package/dist/graph.d.ts +20 -0
  23. package/dist/graph.js +133 -0
  24. package/dist/graph.js.map +1 -0
  25. package/dist/index.d.ts +14 -0
  26. package/dist/index.js +481 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/init.d.ts +1 -0
  29. package/dist/init.js +65 -0
  30. package/dist/init.js.map +1 -0
  31. package/dist/loader.d.ts +26 -0
  32. package/dist/loader.js +151 -0
  33. package/dist/loader.js.map +1 -0
  34. package/dist/logger.d.ts +6 -0
  35. package/dist/logger.js +15 -0
  36. package/dist/logger.js.map +1 -0
  37. package/dist/query-classifier.d.ts +23 -0
  38. package/dist/query-classifier.js +111 -0
  39. package/dist/query-classifier.js.map +1 -0
  40. package/dist/reranker.d.ts +7 -0
  41. package/dist/reranker.js +38 -0
  42. package/dist/reranker.js.map +1 -0
  43. package/dist/search.d.ts +17 -0
  44. package/dist/search.js +299 -0
  45. package/dist/search.js.map +1 -0
  46. package/dist/validator.d.ts +23 -0
  47. package/dist/validator.js +97 -0
  48. package/dist/validator.js.map +1 -0
  49. package/dist/writer.d.ts +36 -0
  50. package/dist/writer.js +360 -0
  51. package/dist/writer.js.map +1 -0
  52. package/package.json +58 -0
package/dist/loader.js ADDED
@@ -0,0 +1,151 @@
1
+ import { readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { log } from "./logger.js";
5
+ function parseFrontmatter(raw) {
6
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
7
+ if (!match) {
8
+ return { frontmatter: {}, body: raw };
9
+ }
10
+ try {
11
+ const frontmatter = parseYaml(match[1]);
12
+ const body = match[2].trim();
13
+ return { frontmatter, body };
14
+ }
15
+ catch {
16
+ return { frontmatter: {}, body: raw };
17
+ }
18
+ }
19
+ export const VALID_TYPES = ["summary", "detail", "decision", "reference"];
20
+ export function deriveParentId(id) {
21
+ if (id === "root")
22
+ return null;
23
+ const segments = id.split("/");
24
+ if (segments.length <= 1)
25
+ return "root";
26
+ return segments.slice(0, -1).join("/");
27
+ }
28
+ export function collectMarkdownFiles(dir) {
29
+ const files = [];
30
+ const entries = readdirSync(dir);
31
+ for (const entry of entries) {
32
+ if (entry.startsWith("."))
33
+ continue;
34
+ const fullPath = join(dir, entry);
35
+ const stat = statSync(fullPath);
36
+ if (stat.isDirectory()) {
37
+ files.push(...collectMarkdownFiles(fullPath));
38
+ }
39
+ else if (entry.endsWith(".md")) {
40
+ files.push(fullPath);
41
+ }
42
+ }
43
+ return files;
44
+ }
45
+ const ID_PATTERN = /^[a-z][a-z0-9-]*(\/[a-z][a-z0-9-]*)*$/;
46
+ function validateFrontmatter(fm, filePath, validDomains) {
47
+ const warnings = [];
48
+ if (!fm.id) {
49
+ warnings.push(`${filePath}: missing required field 'id'`);
50
+ return warnings; // Can't validate further without ID
51
+ }
52
+ if (!ID_PATTERN.test(fm.id)) {
53
+ warnings.push(`${filePath}: invalid ID format "${fm.id}"`);
54
+ }
55
+ if (fm.domain && validDomains && !validDomains.includes(fm.domain)) {
56
+ warnings.push(`${filePath}: invalid domain "${fm.domain}"`);
57
+ }
58
+ if (fm.type && !VALID_TYPES.includes(fm.type)) {
59
+ warnings.push(`${filePath}: invalid type "${fm.type}"`);
60
+ }
61
+ const phases = fm.phase ? (Array.isArray(fm.phase) ? fm.phase : [fm.phase]) : [];
62
+ for (const p of phases) {
63
+ if (p < 1 || !Number.isInteger(p)) {
64
+ warnings.push(`${filePath}: invalid phase value ${p}`);
65
+ }
66
+ }
67
+ if (fm.type === "decision" && !fm.decision_status) {
68
+ warnings.push(`${filePath}: decision document missing 'decision_status' field`);
69
+ }
70
+ return warnings;
71
+ }
72
+ export function loadDocuments(knowledgeDir, validDomains) {
73
+ const docs = new Map();
74
+ const files = collectMarkdownFiles(knowledgeDir);
75
+ const childrenFromFrontmatter = new Map();
76
+ for (const filePath of files) {
77
+ const raw = readFileSync(filePath, "utf-8");
78
+ const { frontmatter, body } = parseFrontmatter(raw);
79
+ // Validate schema
80
+ const validationWarnings = validateFrontmatter(frontmatter, filePath, validDomains);
81
+ if (validationWarnings.length > 0) {
82
+ for (const w of validationWarnings)
83
+ log.warn("schema_validation", { warning: w });
84
+ }
85
+ if (!frontmatter.id)
86
+ continue;
87
+ const id = frontmatter.id;
88
+ // Check for duplicate IDs
89
+ if (docs.has(id)) {
90
+ log.warn("duplicate_id", { id, file: filePath, existingFile: docs.get(id).filePath });
91
+ }
92
+ const phase = frontmatter.phase
93
+ ? Array.isArray(frontmatter.phase)
94
+ ? frontmatter.phase
95
+ : [frontmatter.phase]
96
+ : [];
97
+ if (frontmatter.children) {
98
+ childrenFromFrontmatter.set(id, frontmatter.children);
99
+ }
100
+ // Parse status: support both "status" and legacy "decision_status" fields
101
+ const rawStatus = frontmatter.status ||
102
+ (frontmatter.decision_status === "deprecated" ? "deprecated" : undefined);
103
+ const status = rawStatus === "draft" ? "draft" : rawStatus === "deprecated" ? "deprecated" : "active";
104
+ // Parse decision_status into typed enum value
105
+ const rawDecisionStatus = frontmatter.decision_status;
106
+ const validDecisionStatuses = ["proposed", "accepted", "deprecated", "superseded", "finalized"];
107
+ const decisionStatus = rawDecisionStatus && validDecisionStatuses.includes(rawDecisionStatus)
108
+ ? rawDecisionStatus
109
+ : undefined;
110
+ const doc = {
111
+ id,
112
+ title: frontmatter.title || id,
113
+ type: frontmatter.type || "detail",
114
+ domain: frontmatter.domain || id.split("/")[0],
115
+ subdomain: frontmatter.subdomain,
116
+ tags: frontmatter.tags || [],
117
+ phase,
118
+ related: frontmatter.related || [],
119
+ parentId: deriveParentId(id),
120
+ childrenIds: [],
121
+ contentBody: body,
122
+ filePath: relative(join(knowledgeDir, ".."), filePath),
123
+ wordCount: frontmatter.word_count || body.split(/\s+/).filter(Boolean).length,
124
+ status,
125
+ supersededBy: frontmatter.superseded_by,
126
+ lastUpdated: frontmatter.last_updated,
127
+ decisionStatus,
128
+ alternativesConsidered: frontmatter.alternatives_considered,
129
+ decisionDate: frontmatter.decision_date,
130
+ };
131
+ docs.set(id, doc);
132
+ }
133
+ // Populate childrenIds from frontmatter `children` fields
134
+ for (const [parentId, children] of childrenFromFrontmatter) {
135
+ const parent = docs.get(parentId);
136
+ if (parent) {
137
+ parent.childrenIds = children.filter((c) => docs.has(c));
138
+ }
139
+ }
140
+ // For docs without explicit children, derive from parentId
141
+ for (const doc of docs.values()) {
142
+ if (doc.parentId && docs.has(doc.parentId)) {
143
+ const parent = docs.get(doc.parentId);
144
+ if (!parent.childrenIds.includes(doc.id)) {
145
+ parent.childrenIds.push(doc.id);
146
+ }
147
+ }
148
+ }
149
+ return docs;
150
+ }
151
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare const log: {
2
+ debug: (event: string, data?: Record<string, unknown>) => void;
3
+ info: (event: string, data?: Record<string, unknown>) => void;
4
+ warn: (event: string, data?: Record<string, unknown>) => void;
5
+ error: (event: string, data?: Record<string, unknown>) => void;
6
+ };
package/dist/logger.js ADDED
@@ -0,0 +1,15 @@
1
+ const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
2
+ const currentLevel = process.env.LOG_LEVEL in LEVELS ? process.env.LOG_LEVEL : "info";
3
+ function emit(level, event, data) {
4
+ if (LEVELS[level] < LEVELS[currentLevel])
5
+ return;
6
+ const entry = { ts: new Date().toISOString(), level, event, ...data };
7
+ process.stderr.write(JSON.stringify(entry) + "\n");
8
+ }
9
+ export const log = {
10
+ debug: (event, data) => emit("debug", event, data),
11
+ info: (event, data) => emit("info", event, data),
12
+ warn: (event, data) => emit("warn", event, data),
13
+ error: (event, data) => emit("error", event, data),
14
+ };
15
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAW,CAAC;AAGjE,MAAM,YAAY,GACf,OAAO,CAAC,GAAG,CAAC,SAAmB,IAAI,MAAM,CAAC,CAAC,CAAE,OAAO,CAAC,GAAG,CAAC,SAAmB,CAAC,CAAC,CAAC,MAAM,CAAC;AAEzF,SAAS,IAAI,CAAC,KAAY,EAAE,KAAa,EAAE,IAA8B;IACvE,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC;QAAE,OAAO;IACjD,MAAM,KAAK,GAAG,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,CAAC;IACtE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,KAAK,EAAE,CAAC,KAAa,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC;IACpF,IAAI,EAAE,CAAC,KAAa,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC;IAClF,IAAI,EAAE,CAAC,KAAa,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC;IAClF,KAAK,EAAE,CAAC,KAAa,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC;CACrF,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { KnowledgeConfig } from "./config.js";
2
+ export interface QueryClassification {
3
+ domains: string[];
4
+ phases: number[];
5
+ queryType: "broad" | "specific" | "decision";
6
+ }
7
+ export interface ClassifierConfig {
8
+ domainKeywords: Record<string, string[]>;
9
+ phasePatterns: Array<{
10
+ pattern: RegExp;
11
+ phase: number;
12
+ dynamic?: boolean;
13
+ }>;
14
+ synonymMap: Record<string, string[]>;
15
+ }
16
+ /**
17
+ * Build a ClassifierConfig from a KnowledgeConfig.
18
+ * When config is null (zero-config mode), returns empty config —
19
+ * search still works via BM25 + embeddings, just without domain pre-filtering.
20
+ */
21
+ export declare function buildClassifierConfig(config: KnowledgeConfig | null): ClassifierConfig;
22
+ export declare function expandSynonyms(query: string, synonymMap: Record<string, string[]>): string;
23
+ export declare function classifyQuery(query: string, config: ClassifierConfig): QueryClassification;
@@ -0,0 +1,111 @@
1
+ function escapeRegex(s) {
2
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3
+ }
4
+ /**
5
+ * Build a ClassifierConfig from a KnowledgeConfig.
6
+ * When config is null (zero-config mode), returns empty config —
7
+ * search still works via BM25 + embeddings, just without domain pre-filtering.
8
+ */
9
+ export function buildClassifierConfig(config) {
10
+ const domainKeywords = config?.query_hints ?? {};
11
+ // Always include generic "phase N" pattern (dynamic extraction)
12
+ const phasePatterns = [
13
+ { pattern: /\bphase\s*(\d+)\b/i, phase: 0, dynamic: true },
14
+ ];
15
+ // Add configured phase name + alias patterns
16
+ if (config?.phases) {
17
+ for (const p of config.phases) {
18
+ phasePatterns.push({
19
+ pattern: new RegExp(`\\b${escapeRegex(p.name)}\\b`, "i"),
20
+ phase: p.id,
21
+ });
22
+ if (p.aliases) {
23
+ for (const alias of p.aliases) {
24
+ phasePatterns.push({
25
+ pattern: new RegExp(`\\b${escapeRegex(alias)}\\b`, "i"),
26
+ phase: p.id,
27
+ });
28
+ }
29
+ }
30
+ }
31
+ }
32
+ const synonymMap = config?.synonyms ?? {};
33
+ return { domainKeywords, phasePatterns, synonymMap };
34
+ }
35
+ // These are linguistically universal — not project-specific
36
+ const DECISION_KEYWORDS = [
37
+ "why did we",
38
+ "why do we",
39
+ "decision",
40
+ "chose",
41
+ "choose",
42
+ "trade-off",
43
+ "tradeoff",
44
+ "vs",
45
+ "versus",
46
+ "compared to",
47
+ "alternative",
48
+ "rationale",
49
+ ];
50
+ export function expandSynonyms(query, synonymMap) {
51
+ const lower = query.toLowerCase();
52
+ const tokens = lower.split(/\s+/);
53
+ const expansions = [];
54
+ for (const token of tokens) {
55
+ const clean = token.replace(/[^a-z0-9-]/g, "");
56
+ const synonyms = synonymMap[clean];
57
+ if (synonyms) {
58
+ expansions.push(...synonyms);
59
+ }
60
+ }
61
+ if (expansions.length === 0)
62
+ return query;
63
+ return `${query} ${expansions.join(" ")}`;
64
+ }
65
+ export function classifyQuery(query, config) {
66
+ const lower = query.toLowerCase();
67
+ const domains = [];
68
+ const phases = [];
69
+ // Domain classification from config keywords
70
+ for (const [domain, keywords] of Object.entries(config.domainKeywords)) {
71
+ for (const kw of keywords) {
72
+ if (lower.includes(kw.toLowerCase())) {
73
+ if (!domains.includes(domain)) {
74
+ domains.push(domain);
75
+ }
76
+ break;
77
+ }
78
+ }
79
+ }
80
+ // Phase detection
81
+ for (const { pattern, phase, dynamic } of config.phasePatterns) {
82
+ if (dynamic) {
83
+ // Dynamic "phase N" pattern — extract the number
84
+ const match = pattern.exec(lower);
85
+ if (match) {
86
+ const n = parseInt(match[1], 10);
87
+ if (n > 0 && !phases.includes(n)) {
88
+ phases.push(n);
89
+ }
90
+ }
91
+ }
92
+ else if (pattern.test(lower) && !phases.includes(phase)) {
93
+ phases.push(phase);
94
+ }
95
+ }
96
+ // Query type detection (universal heuristics, not project-specific)
97
+ let queryType = "specific";
98
+ if (DECISION_KEYWORDS.some((kw) => lower.includes(kw))) {
99
+ queryType = "decision";
100
+ }
101
+ else if (lower.startsWith("how does") ||
102
+ lower.startsWith("what is") ||
103
+ lower.startsWith("overview") ||
104
+ lower.startsWith("explain") ||
105
+ lower.includes("tell me about") ||
106
+ domains.length === 0) {
107
+ queryType = "broad";
108
+ }
109
+ return { domains, phases, queryType };
110
+ }
111
+ //# sourceMappingURL=query-classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-classifier.js","sourceRoot":"","sources":["../src/query-classifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { KnowledgeDocument } from "./loader.js";
2
+ interface ScoredDoc {
3
+ doc: KnowledgeDocument;
4
+ score: number;
5
+ }
6
+ export declare function rerank(docs: ScoredDoc[], query: string, queryType: "broad" | "specific" | "decision"): ScoredDoc[];
7
+ export {};
@@ -0,0 +1,38 @@
1
+ import { tokenize } from "./embeddings.js";
2
+ const SIX_MONTHS_MS = 180 * 24 * 60 * 60 * 1000;
3
+ export function rerank(docs, query, queryType) {
4
+ if (docs.length === 0)
5
+ return docs;
6
+ const queryLower = query.toLowerCase().trim();
7
+ const queryTokens = tokenize(query);
8
+ const queryTokenSet = new Set(queryTokens);
9
+ const now = Date.now();
10
+ return docs
11
+ .map(({ doc, score }) => {
12
+ let adjusted = score;
13
+ // Title match bonus: query tokens found in title
14
+ const titleTokens = tokenize(doc.title);
15
+ const titleOverlap = titleTokens.filter((t) => queryTokenSet.has(t)).length;
16
+ if (titleOverlap > 0) {
17
+ adjusted += 0.15 * (titleOverlap / Math.max(queryTokenSet.size, 1));
18
+ }
19
+ // Decision-type boost when query is decision-oriented
20
+ if (queryType === "decision" && doc.type === "decision") {
21
+ adjusted += 0.1;
22
+ }
23
+ // Staleness penalty: docs older than 6 months get a small penalty
24
+ if (doc.lastUpdated) {
25
+ const docDate = new Date(doc.lastUpdated).getTime();
26
+ if (!isNaN(docDate) && now - docDate > SIX_MONTHS_MS) {
27
+ adjusted -= 0.05;
28
+ }
29
+ }
30
+ // Exact phrase bonus: query substring appears in content body
31
+ if (queryLower.length > 3 && doc.contentBody.toLowerCase().includes(queryLower)) {
32
+ adjusted += 0.1;
33
+ }
34
+ return { doc, score: adjusted };
35
+ })
36
+ .sort((a, b) => b.score - a.score);
37
+ }
38
+ //# sourceMappingURL=reranker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reranker.js","sourceRoot":"","sources":["../src/reranker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAO3C,MAAM,aAAa,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEhD,MAAM,UAAU,MAAM,CACpB,IAAiB,EACjB,KAAa,EACb,SAA4C;IAE5C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnC,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAC9C,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,OAAO,IAAI;SACR,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE;QACtB,IAAI,QAAQ,GAAG,KAAK,CAAC;QAErB,iDAAiD;QACjD,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAC5E,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACrB,QAAQ,IAAI,IAAI,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACtE,CAAC;QAED,sDAAsD;QACtD,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACxD,QAAQ,IAAI,GAAG,CAAC;QAClB,CAAC;QAED,kEAAkE;QAClE,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YACpB,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC;YACpD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,GAAG,GAAG,OAAO,GAAG,aAAa,EAAE,CAAC;gBACrD,QAAQ,IAAI,IAAI,CAAC;YACnB,CAAC;QACH,CAAC;QAED,8DAA8D;QAC9D,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAChF,QAAQ,IAAI,GAAG,CAAC;QAClB,CAAC;QAED,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClC,CAAC,CAAC;SACD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { KnowledgeGraph } from "./graph.js";
2
+ import { type ClassifierConfig } from "./query-classifier.js";
3
+ import { type Bm25Index } from "./embeddings.js";
4
+ import { type DetailLevel } from "./formatter.js";
5
+ export type TfIdfIndex = Bm25Index;
6
+ interface SearchOptions {
7
+ query: string;
8
+ domains?: string[];
9
+ phases?: number[];
10
+ tags?: string[];
11
+ type?: string;
12
+ maxResults?: number;
13
+ detailLevel?: DetailLevel;
14
+ includeDrafts?: boolean;
15
+ }
16
+ export declare function knowledgeSearch(graph: KnowledgeGraph, bm25Index: Bm25Index, options: SearchOptions, classifierConfig: ClassifierConfig): Promise<string>;
17
+ export {};