cursor-recursive-rag 0.2.0-alpha.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -203
- package/dist/adapters/llm/anthropic.d.ts +27 -0
- package/dist/adapters/llm/anthropic.d.ts.map +1 -0
- package/dist/adapters/llm/anthropic.js +287 -0
- package/dist/adapters/llm/anthropic.js.map +1 -0
- package/dist/adapters/llm/base.d.ts +62 -0
- package/dist/adapters/llm/base.d.ts.map +1 -0
- package/dist/adapters/llm/base.js +140 -0
- package/dist/adapters/llm/base.js.map +1 -0
- package/dist/adapters/llm/deepseek.d.ts +24 -0
- package/dist/adapters/llm/deepseek.d.ts.map +1 -0
- package/dist/adapters/llm/deepseek.js +228 -0
- package/dist/adapters/llm/deepseek.js.map +1 -0
- package/dist/adapters/llm/groq.d.ts +25 -0
- package/dist/adapters/llm/groq.d.ts.map +1 -0
- package/dist/adapters/llm/groq.js +265 -0
- package/dist/adapters/llm/groq.js.map +1 -0
- package/dist/adapters/llm/index.d.ts +62 -0
- package/dist/adapters/llm/index.d.ts.map +1 -0
- package/dist/adapters/llm/index.js +380 -0
- package/dist/adapters/llm/index.js.map +1 -0
- package/dist/adapters/llm/ollama.d.ts +23 -0
- package/dist/adapters/llm/ollama.d.ts.map +1 -0
- package/dist/adapters/llm/ollama.js +261 -0
- package/dist/adapters/llm/ollama.js.map +1 -0
- package/dist/adapters/llm/openai.d.ts +22 -0
- package/dist/adapters/llm/openai.d.ts.map +1 -0
- package/dist/adapters/llm/openai.js +232 -0
- package/dist/adapters/llm/openai.js.map +1 -0
- package/dist/adapters/llm/openrouter.d.ts +27 -0
- package/dist/adapters/llm/openrouter.d.ts.map +1 -0
- package/dist/adapters/llm/openrouter.js +305 -0
- package/dist/adapters/llm/openrouter.js.map +1 -0
- package/dist/adapters/vector/index.d.ts.map +1 -1
- package/dist/adapters/vector/index.js +8 -0
- package/dist/adapters/vector/index.js.map +1 -1
- package/dist/adapters/vector/redis-native.d.ts +35 -0
- package/dist/adapters/vector/redis-native.d.ts.map +1 -0
- package/dist/adapters/vector/redis-native.js +170 -0
- package/dist/adapters/vector/redis-native.js.map +1 -0
- package/dist/cli/commands/chat.d.ts +4 -0
- package/dist/cli/commands/chat.d.ts.map +1 -0
- package/dist/cli/commands/chat.js +374 -0
- package/dist/cli/commands/chat.js.map +1 -0
- package/dist/cli/commands/maintenance.d.ts +4 -0
- package/dist/cli/commands/maintenance.d.ts.map +1 -0
- package/dist/cli/commands/maintenance.js +237 -0
- package/dist/cli/commands/maintenance.js.map +1 -0
- package/dist/cli/commands/rules.d.ts +9 -0
- package/dist/cli/commands/rules.d.ts.map +1 -0
- package/dist/cli/commands/rules.js +639 -0
- package/dist/cli/commands/rules.js.map +1 -0
- package/dist/cli/commands/setup.js +5 -4
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/index.js +6 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/config/memoryConfig.d.ts +427 -0
- package/dist/config/memoryConfig.d.ts.map +1 -0
- package/dist/config/memoryConfig.js +258 -0
- package/dist/config/memoryConfig.js.map +1 -0
- package/dist/config/rulesConfig.d.ts +486 -0
- package/dist/config/rulesConfig.d.ts.map +1 -0
- package/dist/config/rulesConfig.js +345 -0
- package/dist/config/rulesConfig.js.map +1 -0
- package/dist/dashboard/coreTools.d.ts +14 -0
- package/dist/dashboard/coreTools.d.ts.map +1 -0
- package/dist/dashboard/coreTools.js +413 -0
- package/dist/dashboard/coreTools.js.map +1 -0
- package/dist/dashboard/public/index.html +1982 -13
- package/dist/dashboard/server.d.ts +1 -8
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +846 -13
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/toolRegistry.d.ts +192 -0
- package/dist/dashboard/toolRegistry.d.ts.map +1 -0
- package/dist/dashboard/toolRegistry.js +322 -0
- package/dist/dashboard/toolRegistry.js.map +1 -0
- package/dist/proxy/index.d.ts +1 -1
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +9 -6
- package/dist/proxy/index.js.map +1 -1
- package/dist/server/index.js +21 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/tools/crawl.d.ts.map +1 -1
- package/dist/server/tools/crawl.js +8 -0
- package/dist/server/tools/crawl.js.map +1 -1
- package/dist/server/tools/index.d.ts.map +1 -1
- package/dist/server/tools/index.js +19 -1
- package/dist/server/tools/index.js.map +1 -1
- package/dist/server/tools/ingest.d.ts.map +1 -1
- package/dist/server/tools/ingest.js +5 -0
- package/dist/server/tools/ingest.js.map +1 -1
- package/dist/server/tools/memory.d.ts +250 -0
- package/dist/server/tools/memory.d.ts.map +1 -0
- package/dist/server/tools/memory.js +472 -0
- package/dist/server/tools/memory.js.map +1 -0
- package/dist/server/tools/recursive-query.d.ts.map +1 -1
- package/dist/server/tools/recursive-query.js +6 -0
- package/dist/server/tools/recursive-query.js.map +1 -1
- package/dist/server/tools/search.d.ts.map +1 -1
- package/dist/server/tools/search.js +6 -0
- package/dist/server/tools/search.js.map +1 -1
- package/dist/services/activity-log.d.ts +10 -0
- package/dist/services/activity-log.d.ts.map +1 -0
- package/dist/services/activity-log.js +53 -0
- package/dist/services/activity-log.js.map +1 -0
- package/dist/services/categoryManager.d.ts +110 -0
- package/dist/services/categoryManager.d.ts.map +1 -0
- package/dist/services/categoryManager.js +549 -0
- package/dist/services/categoryManager.js.map +1 -0
- package/dist/services/contextEnvironment.d.ts +206 -0
- package/dist/services/contextEnvironment.d.ts.map +1 -0
- package/dist/services/contextEnvironment.js +481 -0
- package/dist/services/contextEnvironment.js.map +1 -0
- package/dist/services/conversationProcessor.d.ts +99 -0
- package/dist/services/conversationProcessor.d.ts.map +1 -0
- package/dist/services/conversationProcessor.js +311 -0
- package/dist/services/conversationProcessor.js.map +1 -0
- package/dist/services/cursorChatReader.d.ts +129 -0
- package/dist/services/cursorChatReader.d.ts.map +1 -0
- package/dist/services/cursorChatReader.js +419 -0
- package/dist/services/cursorChatReader.js.map +1 -0
- package/dist/services/decayCalculator.d.ts +85 -0
- package/dist/services/decayCalculator.d.ts.map +1 -0
- package/dist/services/decayCalculator.js +182 -0
- package/dist/services/decayCalculator.js.map +1 -0
- package/dist/services/enhancedVectorStore.d.ts +102 -0
- package/dist/services/enhancedVectorStore.d.ts.map +1 -0
- package/dist/services/enhancedVectorStore.js +245 -0
- package/dist/services/enhancedVectorStore.js.map +1 -0
- package/dist/services/hybridScorer.d.ts +120 -0
- package/dist/services/hybridScorer.d.ts.map +1 -0
- package/dist/services/hybridScorer.js +334 -0
- package/dist/services/hybridScorer.js.map +1 -0
- package/dist/services/knowledgeExtractor.d.ts +45 -0
- package/dist/services/knowledgeExtractor.d.ts.map +1 -0
- package/dist/services/knowledgeExtractor.js +436 -0
- package/dist/services/knowledgeExtractor.js.map +1 -0
- package/dist/services/knowledgeStorage.d.ts +102 -0
- package/dist/services/knowledgeStorage.d.ts.map +1 -0
- package/dist/services/knowledgeStorage.js +383 -0
- package/dist/services/knowledgeStorage.js.map +1 -0
- package/dist/services/maintenanceScheduler.d.ts +89 -0
- package/dist/services/maintenanceScheduler.d.ts.map +1 -0
- package/dist/services/maintenanceScheduler.js +479 -0
- package/dist/services/maintenanceScheduler.js.map +1 -0
- package/dist/services/memoryMetadataStore.d.ts +62 -0
- package/dist/services/memoryMetadataStore.d.ts.map +1 -0
- package/dist/services/memoryMetadataStore.js +570 -0
- package/dist/services/memoryMetadataStore.js.map +1 -0
- package/dist/services/recursiveRetrieval.d.ts +122 -0
- package/dist/services/recursiveRetrieval.d.ts.map +1 -0
- package/dist/services/recursiveRetrieval.js +443 -0
- package/dist/services/recursiveRetrieval.js.map +1 -0
- package/dist/services/relationshipGraph.d.ts +77 -0
- package/dist/services/relationshipGraph.d.ts.map +1 -0
- package/dist/services/relationshipGraph.js +411 -0
- package/dist/services/relationshipGraph.js.map +1 -0
- package/dist/services/rlmSafeguards.d.ts +273 -0
- package/dist/services/rlmSafeguards.d.ts.map +1 -0
- package/dist/services/rlmSafeguards.js +705 -0
- package/dist/services/rlmSafeguards.js.map +1 -0
- package/dist/services/rulesAnalyzer.d.ts +119 -0
- package/dist/services/rulesAnalyzer.d.ts.map +1 -0
- package/dist/services/rulesAnalyzer.js +768 -0
- package/dist/services/rulesAnalyzer.js.map +1 -0
- package/dist/services/rulesMerger.d.ts +75 -0
- package/dist/services/rulesMerger.d.ts.map +1 -0
- package/dist/services/rulesMerger.js +404 -0
- package/dist/services/rulesMerger.js.map +1 -0
- package/dist/services/rulesParser.d.ts +127 -0
- package/dist/services/rulesParser.d.ts.map +1 -0
- package/dist/services/rulesParser.js +594 -0
- package/dist/services/rulesParser.js.map +1 -0
- package/dist/services/smartChunker.d.ts +110 -0
- package/dist/services/smartChunker.d.ts.map +1 -0
- package/dist/services/smartChunker.js +520 -0
- package/dist/services/smartChunker.js.map +1 -0
- package/dist/types/categories.d.ts +105 -0
- package/dist/types/categories.d.ts.map +1 -0
- package/dist/types/categories.js +108 -0
- package/dist/types/categories.js.map +1 -0
- package/dist/types/extractedKnowledge.d.ts +233 -0
- package/dist/types/extractedKnowledge.d.ts.map +1 -0
- package/dist/types/extractedKnowledge.js +56 -0
- package/dist/types/extractedKnowledge.js.map +1 -0
- package/dist/types/index.d.ts +9 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +12 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/llmProvider.d.ts +282 -0
- package/dist/types/llmProvider.d.ts.map +1 -0
- package/dist/types/llmProvider.js +48 -0
- package/dist/types/llmProvider.js.map +1 -0
- package/dist/types/memory.d.ts +227 -0
- package/dist/types/memory.d.ts.map +1 -0
- package/dist/types/memory.js +76 -0
- package/dist/types/memory.js.map +1 -0
- package/dist/types/relationships.d.ts +167 -0
- package/dist/types/relationships.d.ts.map +1 -0
- package/dist/types/relationships.js +106 -0
- package/dist/types/relationships.js.map +1 -0
- package/dist/types/rulesOptimizer.d.ts +345 -0
- package/dist/types/rulesOptimizer.d.ts.map +1 -0
- package/dist/types/rulesOptimizer.js +22 -0
- package/dist/types/rulesOptimizer.js.map +1 -0
- package/docs/cursor-recursive-rag-memory-spec.md +4569 -0
- package/docs/cursor-recursive-rag-tasks.md +1355 -0
- package/package.json +6 -3
- package/restart-rag.sh +16 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type CategoryDefinition, type CategoryClassification, type CategoryItemsOptions, type CategoryWithStats, type CategorySelectionOptions, type SelectedCategory, type SummaryEvolutionResult } from '../types/categories.js';
|
|
2
|
+
import type { Category, CategoryItem, EnhancedChunk } from '../types/memory.js';
|
|
3
|
+
import { MemoryMetadataStore } from './memoryMetadataStore.js';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for the CategoryManager
|
|
6
|
+
*/
|
|
7
|
+
export interface CategoryManagerConfig {
|
|
8
|
+
minRelevanceScore: number;
|
|
9
|
+
maxCategoriesPerChunk: number;
|
|
10
|
+
summaryMaxItems: number;
|
|
11
|
+
useLLMForClassification: boolean;
|
|
12
|
+
useLLMForSummaries: boolean;
|
|
13
|
+
llmEndpoint?: string;
|
|
14
|
+
llmApiKey?: string;
|
|
15
|
+
llmModel?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Manages category organization and summary evolution
|
|
19
|
+
*
|
|
20
|
+
* The CategoryManager provides:
|
|
21
|
+
* - Automatic initialization of default categories
|
|
22
|
+
* - Classification of chunks into relevant categories
|
|
23
|
+
* - Evolving summaries that integrate new knowledge
|
|
24
|
+
* - Query-based category selection for retrieval
|
|
25
|
+
*/
|
|
26
|
+
export declare class CategoryManager {
|
|
27
|
+
private metadataStore;
|
|
28
|
+
private config;
|
|
29
|
+
private initialized;
|
|
30
|
+
constructor(metadataStore?: MemoryMetadataStore, config?: Partial<CategoryManagerConfig>);
|
|
31
|
+
/**
|
|
32
|
+
* Initialize default categories if they don't exist
|
|
33
|
+
*/
|
|
34
|
+
initialize(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Classify a chunk into relevant categories
|
|
37
|
+
*/
|
|
38
|
+
classifyChunk(chunk: EnhancedChunk): Promise<CategoryClassification[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Heuristic-based classification using tags and content analysis
|
|
41
|
+
*/
|
|
42
|
+
private classifyWithHeuristics;
|
|
43
|
+
/**
|
|
44
|
+
* LLM-based classification (when enabled)
|
|
45
|
+
*/
|
|
46
|
+
private classifyWithLLM;
|
|
47
|
+
/**
|
|
48
|
+
* Add a chunk to a category
|
|
49
|
+
*/
|
|
50
|
+
addToCategory(chunkId: string, categoryName: string, relevanceScore: number): void;
|
|
51
|
+
/**
|
|
52
|
+
* Classify and add a chunk to all relevant categories
|
|
53
|
+
*/
|
|
54
|
+
classifyAndAssign(chunk: EnhancedChunk): Promise<CategoryClassification[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Get items in a category
|
|
57
|
+
*/
|
|
58
|
+
getCategoryItems(categoryName: string, options?: CategoryItemsOptions): CategoryItem[];
|
|
59
|
+
/**
|
|
60
|
+
* Evolve a category's summary with new items
|
|
61
|
+
*/
|
|
62
|
+
evolveSummary(categoryName: string): Promise<SummaryEvolutionResult | null>;
|
|
63
|
+
/**
|
|
64
|
+
* Heuristic-based summary evolution
|
|
65
|
+
*/
|
|
66
|
+
private evolveSummaryWithHeuristics;
|
|
67
|
+
/**
|
|
68
|
+
* LLM-based summary evolution
|
|
69
|
+
*/
|
|
70
|
+
private evolveSummaryWithLLM;
|
|
71
|
+
/**
|
|
72
|
+
* Get category summary for retrieval
|
|
73
|
+
*/
|
|
74
|
+
getCategorySummary(categoryName: string): string | null;
|
|
75
|
+
/**
|
|
76
|
+
* Select categories most relevant to a query
|
|
77
|
+
*/
|
|
78
|
+
selectRelevantCategories(query: string, options?: CategorySelectionOptions): Promise<SelectedCategory[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Heuristic-based category selection
|
|
81
|
+
*/
|
|
82
|
+
private selectCategoriesWithHeuristics;
|
|
83
|
+
/**
|
|
84
|
+
* LLM-based category selection
|
|
85
|
+
*/
|
|
86
|
+
private selectCategoriesWithLLM;
|
|
87
|
+
/**
|
|
88
|
+
* Get all categories with statistics
|
|
89
|
+
*/
|
|
90
|
+
getAllCategoriesWithStats(): CategoryWithStats[];
|
|
91
|
+
/**
|
|
92
|
+
* Create a custom category
|
|
93
|
+
*/
|
|
94
|
+
createCategory(definition: CategoryDefinition): Category;
|
|
95
|
+
/**
|
|
96
|
+
* Extract tags from a chunk for classification
|
|
97
|
+
*/
|
|
98
|
+
private extractTagsFromChunk;
|
|
99
|
+
/**
|
|
100
|
+
* Match categories by keyword presence in content
|
|
101
|
+
*/
|
|
102
|
+
private matchCategoriesByKeywords;
|
|
103
|
+
/**
|
|
104
|
+
* Call LLM endpoint
|
|
105
|
+
*/
|
|
106
|
+
private callLLM;
|
|
107
|
+
}
|
|
108
|
+
export declare function getCategoryManager(metadataStore?: MemoryMetadataStore, config?: Partial<CategoryManagerConfig>): CategoryManager;
|
|
109
|
+
export declare function resetCategoryManager(): void;
|
|
110
|
+
//# sourceMappingURL=categoryManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"categoryManager.d.ts","sourceRoot":"","sources":["../../src/services/categoryManager.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAG5B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAwB,MAAM,oBAAoB,CAAC;AACtG,OAAO,EAAE,mBAAmB,EAA0B,MAAM,0BAA0B,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,uBAAuB,EAAE,OAAO,CAAC;IACjC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAUD;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,WAAW,CAAkB;gBAGnC,aAAa,CAAC,EAAE,mBAAmB,EACnC,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC;IAMzC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBjC;;OAEG;IACG,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAU5E;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAkC9B;;OAEG;YACW,eAAe;IAoC7B;;OAEG;IACH,aAAa,CACX,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,GACrB,IAAI;IAUP;;OAEG;IACG,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAUhF;;OAEG;IACH,gBAAgB,CACd,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,oBAAyB,GACjC,YAAY,EAAE;IA8BjB;;OAEG;IACG,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC;IAkCjF;;OAEG;IACH,OAAO,CAAC,2BAA2B;IA6CnC;;OAEG;YACW,oBAAoB;IAkDlC;;OAEG;IACH,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAKvD;;OAEG;IACG,wBAAwB,CAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAkB9B;;OAEG;IACH,OAAO,CAAC,8BAA8B;IAqCtC;;OAEG;YACW,uBAAuB;IAsCrC;;OAEG;IACH,yBAAyB,IAAI,iBAAiB,EAAE;IAmBhD;;OAEG;IACH,cAAc,CAAC,UAAU,EAAE,kBAAkB,GAAG,QAAQ;IAexD;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAkC5B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAuEjC;;OAEG;YACW,OAAO;CAyBtB;AAID,wBAAgB,kBAAkB,CAChC,aAAa,CAAC,EAAE,mBAAmB,EACnC,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,GACtC,eAAe,CAKjB;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import { DEFAULT_CATEGORIES, scoreCategoryMatch, } from '../types/categories.js';
|
|
2
|
+
import { getMemoryMetadataStore } from './memoryMetadataStore.js';
|
|
3
|
+
const DEFAULT_CONFIG = {
|
|
4
|
+
minRelevanceScore: 0.4,
|
|
5
|
+
maxCategoriesPerChunk: 3,
|
|
6
|
+
summaryMaxItems: 20,
|
|
7
|
+
useLLMForClassification: false,
|
|
8
|
+
useLLMForSummaries: false,
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Manages category organization and summary evolution
|
|
12
|
+
*
|
|
13
|
+
* The CategoryManager provides:
|
|
14
|
+
* - Automatic initialization of default categories
|
|
15
|
+
* - Classification of chunks into relevant categories
|
|
16
|
+
* - Evolving summaries that integrate new knowledge
|
|
17
|
+
* - Query-based category selection for retrieval
|
|
18
|
+
*/
|
|
19
|
+
export class CategoryManager {
|
|
20
|
+
metadataStore;
|
|
21
|
+
config;
|
|
22
|
+
initialized = false;
|
|
23
|
+
constructor(metadataStore, config) {
|
|
24
|
+
this.metadataStore = metadataStore || getMemoryMetadataStore();
|
|
25
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Initialize default categories if they don't exist
|
|
29
|
+
*/
|
|
30
|
+
async initialize() {
|
|
31
|
+
if (this.initialized)
|
|
32
|
+
return;
|
|
33
|
+
for (const catDef of DEFAULT_CATEGORIES) {
|
|
34
|
+
const existing = this.metadataStore.getCategoryByName(catDef.name);
|
|
35
|
+
if (!existing) {
|
|
36
|
+
const category = {
|
|
37
|
+
id: `cat:${catDef.name}`,
|
|
38
|
+
name: catDef.name,
|
|
39
|
+
description: catDef.description,
|
|
40
|
+
parentId: catDef.parentName ? `cat:${catDef.parentName}` : null,
|
|
41
|
+
summary: '',
|
|
42
|
+
chunkCount: 0,
|
|
43
|
+
};
|
|
44
|
+
this.metadataStore.upsertCategory(category);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
this.initialized = true;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Classify a chunk into relevant categories
|
|
51
|
+
*/
|
|
52
|
+
async classifyChunk(chunk) {
|
|
53
|
+
await this.initialize();
|
|
54
|
+
if (this.config.useLLMForClassification) {
|
|
55
|
+
return this.classifyWithLLM(chunk);
|
|
56
|
+
}
|
|
57
|
+
return this.classifyWithHeuristics(chunk);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Heuristic-based classification using tags and content analysis
|
|
61
|
+
*/
|
|
62
|
+
classifyWithHeuristics(chunk) {
|
|
63
|
+
const classifications = [];
|
|
64
|
+
const contentTags = this.extractTagsFromChunk(chunk);
|
|
65
|
+
for (const catDef of DEFAULT_CATEGORIES) {
|
|
66
|
+
const score = scoreCategoryMatch(contentTags, catDef);
|
|
67
|
+
if (score >= this.config.minRelevanceScore) {
|
|
68
|
+
classifications.push({
|
|
69
|
+
category: catDef.name,
|
|
70
|
+
relevanceScore: score,
|
|
71
|
+
reason: `Matched tags: ${catDef.tags.filter(t => contentTags.some(ct => ct.toLowerCase().includes(t.toLowerCase()))).join(', ')}`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const keywordMatches = this.matchCategoriesByKeywords(chunk.content);
|
|
76
|
+
for (const match of keywordMatches) {
|
|
77
|
+
const existing = classifications.find(c => c.category === match.category);
|
|
78
|
+
if (existing) {
|
|
79
|
+
existing.relevanceScore = Math.min(1.0, existing.relevanceScore + match.relevanceScore * 0.3);
|
|
80
|
+
}
|
|
81
|
+
else if (match.relevanceScore >= this.config.minRelevanceScore) {
|
|
82
|
+
classifications.push(match);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return classifications
|
|
86
|
+
.sort((a, b) => b.relevanceScore - a.relevanceScore)
|
|
87
|
+
.slice(0, this.config.maxCategoriesPerChunk);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* LLM-based classification (when enabled)
|
|
91
|
+
*/
|
|
92
|
+
async classifyWithLLM(chunk) {
|
|
93
|
+
if (!this.config.llmEndpoint) {
|
|
94
|
+
console.warn('LLM classification enabled but no endpoint configured, falling back to heuristics');
|
|
95
|
+
return this.classifyWithHeuristics(chunk);
|
|
96
|
+
}
|
|
97
|
+
const categories = this.metadataStore.listCategories();
|
|
98
|
+
const prompt = `Classify this knowledge item into one or more categories.
|
|
99
|
+
|
|
100
|
+
## Item
|
|
101
|
+
Type: ${chunk.chunkType}
|
|
102
|
+
Content: ${chunk.content.substring(0, 2000)}
|
|
103
|
+
|
|
104
|
+
## Available Categories
|
|
105
|
+
${categories.map(c => `- ${c.name}: ${c.description}`).join('\n')}
|
|
106
|
+
|
|
107
|
+
## Instructions
|
|
108
|
+
Return a JSON array of classifications:
|
|
109
|
+
[
|
|
110
|
+
{ "category": "category_name", "relevanceScore": 0.0-1.0, "reason": "brief reason" }
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
Only include categories with relevanceScore > ${this.config.minRelevanceScore}.
|
|
114
|
+
Maximum ${this.config.maxCategoriesPerChunk} categories.`;
|
|
115
|
+
try {
|
|
116
|
+
const response = await this.callLLM(prompt);
|
|
117
|
+
const parsed = JSON.parse(response);
|
|
118
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error('LLM classification failed, falling back to heuristics:', error);
|
|
122
|
+
return this.classifyWithHeuristics(chunk);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Add a chunk to a category
|
|
127
|
+
*/
|
|
128
|
+
addToCategory(chunkId, categoryName, relevanceScore) {
|
|
129
|
+
const category = this.metadataStore.getCategoryByName(categoryName);
|
|
130
|
+
if (!category) {
|
|
131
|
+
console.warn(`Category not found: ${categoryName}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
this.metadataStore.assignChunkToCategory(chunkId, category.id, relevanceScore);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Classify and add a chunk to all relevant categories
|
|
138
|
+
*/
|
|
139
|
+
async classifyAndAssign(chunk) {
|
|
140
|
+
const classifications = await this.classifyChunk(chunk);
|
|
141
|
+
for (const classification of classifications) {
|
|
142
|
+
this.addToCategory(chunk.id, classification.category, classification.relevanceScore);
|
|
143
|
+
}
|
|
144
|
+
return classifications;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get items in a category
|
|
148
|
+
*/
|
|
149
|
+
getCategoryItems(categoryName, options = {}) {
|
|
150
|
+
const category = this.metadataStore.getCategoryByName(categoryName);
|
|
151
|
+
if (!category)
|
|
152
|
+
return [];
|
|
153
|
+
const allItems = this.metadataStore.getCategoryChunks(category.id);
|
|
154
|
+
let items = allItems;
|
|
155
|
+
if (options.minRelevance) {
|
|
156
|
+
items = items.filter(item => item.relevanceScore >= options.minRelevance);
|
|
157
|
+
}
|
|
158
|
+
if (options.since) {
|
|
159
|
+
const sinceStr = options.since.toISOString();
|
|
160
|
+
items = items.filter(item => item.assignedAt >= sinceStr);
|
|
161
|
+
}
|
|
162
|
+
if (options.sortBy === 'relevance') {
|
|
163
|
+
items.sort((a, b) => b.relevanceScore - a.relevanceScore);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
items.sort((a, b) => b.assignedAt.localeCompare(a.assignedAt));
|
|
167
|
+
}
|
|
168
|
+
if (options.limit) {
|
|
169
|
+
items = items.slice(0, options.limit);
|
|
170
|
+
}
|
|
171
|
+
return items;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Evolve a category's summary with new items
|
|
175
|
+
*/
|
|
176
|
+
async evolveSummary(categoryName) {
|
|
177
|
+
const category = this.metadataStore.getCategoryByName(categoryName);
|
|
178
|
+
if (!category)
|
|
179
|
+
return null;
|
|
180
|
+
const recentItems = this.getCategoryItems(categoryName, {
|
|
181
|
+
limit: this.config.summaryMaxItems,
|
|
182
|
+
sortBy: 'date',
|
|
183
|
+
});
|
|
184
|
+
if (recentItems.length === 0) {
|
|
185
|
+
return {
|
|
186
|
+
categoryName,
|
|
187
|
+
previousSummary: category.summary,
|
|
188
|
+
newSummary: category.summary,
|
|
189
|
+
itemsIntegrated: 0,
|
|
190
|
+
hadContradictions: false,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const itemContents = [];
|
|
194
|
+
for (const item of recentItems) {
|
|
195
|
+
const metadata = this.metadataStore.getChunkMetadata(item.chunkId);
|
|
196
|
+
if (metadata) {
|
|
197
|
+
itemContents.push(`[${metadata.chunkType}] Relevance: ${item.relevanceScore.toFixed(2)}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (this.config.useLLMForSummaries && this.config.llmEndpoint) {
|
|
201
|
+
return this.evolveSummaryWithLLM(category, itemContents);
|
|
202
|
+
}
|
|
203
|
+
return this.evolveSummaryWithHeuristics(category, recentItems);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Heuristic-based summary evolution
|
|
207
|
+
*/
|
|
208
|
+
evolveSummaryWithHeuristics(category, items) {
|
|
209
|
+
const previousSummary = category.summary;
|
|
210
|
+
const chunkTypes = new Map();
|
|
211
|
+
for (const item of items) {
|
|
212
|
+
const metadata = this.metadataStore.getChunkMetadata(item.chunkId);
|
|
213
|
+
if (metadata) {
|
|
214
|
+
const count = chunkTypes.get(metadata.chunkType) || 0;
|
|
215
|
+
chunkTypes.set(metadata.chunkType, count + 1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const typeBreakdown = Array.from(chunkTypes.entries())
|
|
219
|
+
.map(([type, count]) => `${count} ${type}(s)`)
|
|
220
|
+
.join(', ');
|
|
221
|
+
const avgRelevance = items.reduce((sum, i) => sum + i.relevanceScore, 0) / items.length;
|
|
222
|
+
const newSummary = `## ${category.name}
|
|
223
|
+
|
|
224
|
+
**Items**: ${category.chunkCount}
|
|
225
|
+
**Recent**: ${items.length} items (${typeBreakdown})
|
|
226
|
+
**Avg Relevance**: ${avgRelevance.toFixed(2)}
|
|
227
|
+
|
|
228
|
+
${previousSummary ? `### Previous Summary\n${previousSummary}` : 'No previous summary.'}
|
|
229
|
+
|
|
230
|
+
*Last updated: ${new Date().toISOString()}*`;
|
|
231
|
+
this.metadataStore.upsertCategory({
|
|
232
|
+
...category,
|
|
233
|
+
summary: newSummary,
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
categoryName: category.name,
|
|
237
|
+
previousSummary,
|
|
238
|
+
newSummary,
|
|
239
|
+
itemsIntegrated: items.length,
|
|
240
|
+
hadContradictions: false,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* LLM-based summary evolution
|
|
245
|
+
*/
|
|
246
|
+
async evolveSummaryWithLLM(category, itemContents) {
|
|
247
|
+
const prompt = `You are a Memory Synchronisation Specialist.
|
|
248
|
+
|
|
249
|
+
## Category: ${category.name}
|
|
250
|
+
${category.description}
|
|
251
|
+
|
|
252
|
+
## Current Summary
|
|
253
|
+
${category.summary || 'No existing summary.'}
|
|
254
|
+
|
|
255
|
+
## New Items to Integrate (${itemContents.length} items)
|
|
256
|
+
${itemContents.slice(0, 10).join('\n')}
|
|
257
|
+
${itemContents.length > 10 ? `\n... and ${itemContents.length - 10} more items` : ''}
|
|
258
|
+
|
|
259
|
+
## Instructions
|
|
260
|
+
1. Update the summary to incorporate new information
|
|
261
|
+
2. If new items conflict with existing summary, update to reflect the latest state
|
|
262
|
+
3. Keep the summary concise but comprehensive (max 500 words)
|
|
263
|
+
4. Use markdown formatting
|
|
264
|
+
5. Focus on actionable knowledge, patterns, and decisions
|
|
265
|
+
|
|
266
|
+
Return ONLY the updated summary markdown.`;
|
|
267
|
+
try {
|
|
268
|
+
const newSummary = await this.callLLM(prompt);
|
|
269
|
+
const hadContradictions = newSummary.toLowerCase().includes('previously') ||
|
|
270
|
+
newSummary.toLowerCase().includes('updated') ||
|
|
271
|
+
newSummary.toLowerCase().includes('changed from');
|
|
272
|
+
this.metadataStore.upsertCategory({
|
|
273
|
+
...category,
|
|
274
|
+
summary: newSummary,
|
|
275
|
+
});
|
|
276
|
+
return {
|
|
277
|
+
categoryName: category.name,
|
|
278
|
+
previousSummary: category.summary,
|
|
279
|
+
newSummary,
|
|
280
|
+
itemsIntegrated: itemContents.length,
|
|
281
|
+
hadContradictions,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
console.error('LLM summary evolution failed:', error);
|
|
286
|
+
return this.evolveSummaryWithHeuristics(category, []);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Get category summary for retrieval
|
|
291
|
+
*/
|
|
292
|
+
getCategorySummary(categoryName) {
|
|
293
|
+
const category = this.metadataStore.getCategoryByName(categoryName);
|
|
294
|
+
return category?.summary || null;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Select categories most relevant to a query
|
|
298
|
+
*/
|
|
299
|
+
async selectRelevantCategories(query, options = {}) {
|
|
300
|
+
await this.initialize();
|
|
301
|
+
const maxCategories = options.maxCategories ?? 3;
|
|
302
|
+
const minItemCount = options.minItemCount ?? 1;
|
|
303
|
+
const categories = this.metadataStore.listCategories()
|
|
304
|
+
.filter(c => c.chunkCount >= minItemCount);
|
|
305
|
+
if (categories.length === 0)
|
|
306
|
+
return [];
|
|
307
|
+
if (this.config.useLLMForClassification && this.config.llmEndpoint) {
|
|
308
|
+
return this.selectCategoriesWithLLM(query, categories, options);
|
|
309
|
+
}
|
|
310
|
+
return this.selectCategoriesWithHeuristics(query, categories, options);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Heuristic-based category selection
|
|
314
|
+
*/
|
|
315
|
+
selectCategoriesWithHeuristics(query, categories, options) {
|
|
316
|
+
const queryLower = query.toLowerCase();
|
|
317
|
+
const queryWords = queryLower.split(/\s+/).filter(w => w.length > 2);
|
|
318
|
+
const scored = [];
|
|
319
|
+
for (const category of categories) {
|
|
320
|
+
const catDef = DEFAULT_CATEGORIES.find(c => c.name === category.name);
|
|
321
|
+
if (!catDef)
|
|
322
|
+
continue;
|
|
323
|
+
let relevance = 0;
|
|
324
|
+
for (const word of queryWords) {
|
|
325
|
+
if (catDef.name.includes(word))
|
|
326
|
+
relevance += 0.3;
|
|
327
|
+
if (catDef.description.toLowerCase().includes(word))
|
|
328
|
+
relevance += 0.2;
|
|
329
|
+
if (catDef.tags.some(t => t.includes(word) || word.includes(t)))
|
|
330
|
+
relevance += 0.25;
|
|
331
|
+
}
|
|
332
|
+
if (relevance > 0) {
|
|
333
|
+
scored.push({
|
|
334
|
+
name: category.name,
|
|
335
|
+
relevance: Math.min(1.0, relevance),
|
|
336
|
+
summary: options.includeSummaries ? category.summary : undefined,
|
|
337
|
+
itemCount: category.chunkCount,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return scored
|
|
342
|
+
.sort((a, b) => b.relevance - a.relevance)
|
|
343
|
+
.slice(0, options.maxCategories ?? 3);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* LLM-based category selection
|
|
347
|
+
*/
|
|
348
|
+
async selectCategoriesWithLLM(query, categories, options) {
|
|
349
|
+
const prompt = `Query: ${query}
|
|
350
|
+
|
|
351
|
+
Available Categories:
|
|
352
|
+
${categories.map(c => `- ${c.name}: ${c.description} (${c.chunkCount} items)`).join('\n')}
|
|
353
|
+
|
|
354
|
+
Return a JSON array of the ${options.maxCategories ?? 3} most relevant category names:
|
|
355
|
+
["category1", "category2", ...]
|
|
356
|
+
|
|
357
|
+
Only include categories clearly relevant to the query.`;
|
|
358
|
+
try {
|
|
359
|
+
const response = await this.callLLM(prompt);
|
|
360
|
+
const names = JSON.parse(response);
|
|
361
|
+
const results = [];
|
|
362
|
+
for (const name of names) {
|
|
363
|
+
const cat = categories.find(c => c.name === name);
|
|
364
|
+
if (cat) {
|
|
365
|
+
results.push({
|
|
366
|
+
name: cat.name,
|
|
367
|
+
relevance: 0.8,
|
|
368
|
+
summary: options.includeSummaries ? cat.summary : undefined,
|
|
369
|
+
itemCount: cat.chunkCount,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return results;
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
console.error('LLM category selection failed:', error);
|
|
377
|
+
return this.selectCategoriesWithHeuristics(query, categories, options);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Get all categories with statistics
|
|
382
|
+
*/
|
|
383
|
+
getAllCategoriesWithStats() {
|
|
384
|
+
const categories = this.metadataStore.listCategories();
|
|
385
|
+
return categories.map(cat => {
|
|
386
|
+
const catDef = DEFAULT_CATEGORIES.find(c => c.name === cat.name);
|
|
387
|
+
const items = this.getCategoryItems(cat.name, { limit: 100 });
|
|
388
|
+
const avgRelevance = items.length > 0
|
|
389
|
+
? items.reduce((sum, i) => sum + i.relevanceScore, 0) / items.length
|
|
390
|
+
: 0;
|
|
391
|
+
return {
|
|
392
|
+
...cat,
|
|
393
|
+
recentItemCount: items.length,
|
|
394
|
+
avgRelevanceScore: avgRelevance,
|
|
395
|
+
topTags: catDef?.tags.slice(0, 5) ?? [],
|
|
396
|
+
};
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Create a custom category
|
|
401
|
+
*/
|
|
402
|
+
createCategory(definition) {
|
|
403
|
+
const category = {
|
|
404
|
+
id: `cat:${definition.name}`,
|
|
405
|
+
name: definition.name,
|
|
406
|
+
description: definition.description,
|
|
407
|
+
parentId: definition.parentName ? `cat:${definition.parentName}` : null,
|
|
408
|
+
summary: '',
|
|
409
|
+
chunkCount: 0,
|
|
410
|
+
};
|
|
411
|
+
this.metadataStore.upsertCategory(category);
|
|
412
|
+
return this.metadataStore.getCategoryByName(definition.name);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Extract tags from a chunk for classification
|
|
416
|
+
*/
|
|
417
|
+
extractTagsFromChunk(chunk) {
|
|
418
|
+
const tags = [];
|
|
419
|
+
if (chunk.entities) {
|
|
420
|
+
for (const entity of chunk.entities) {
|
|
421
|
+
tags.push(entity.value.toLowerCase());
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const content = chunk.content.toLowerCase();
|
|
425
|
+
const techPatterns = [
|
|
426
|
+
/\b(typescript|javascript|python|php|java|go|rust|ruby)\b/gi,
|
|
427
|
+
/\b(react|vue|angular|svelte|next\.?js|nuxt)\b/gi,
|
|
428
|
+
/\b(postgresql|mysql|sqlite|mongodb|redis|elasticsearch)\b/gi,
|
|
429
|
+
/\b(docker|kubernetes|aws|gcp|azure|vercel|cloudflare)\b/gi,
|
|
430
|
+
/\b(api|rest|graphql|grpc|websocket)\b/gi,
|
|
431
|
+
/\b(test|spec|mock|fixture|coverage)\b/gi,
|
|
432
|
+
/\b(auth|login|session|jwt|oauth|token)\b/gi,
|
|
433
|
+
/\b(cache|performance|optimize|speed)\b/gi,
|
|
434
|
+
/\b(error|bug|fix|debug|issue|exception)\b/gi,
|
|
435
|
+
/\b(deploy|ci|cd|pipeline|build)\b/gi,
|
|
436
|
+
];
|
|
437
|
+
for (const pattern of techPatterns) {
|
|
438
|
+
const matches = content.match(pattern);
|
|
439
|
+
if (matches) {
|
|
440
|
+
tags.push(...matches.map(m => m.toLowerCase()));
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return [...new Set(tags)];
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Match categories by keyword presence in content
|
|
447
|
+
*/
|
|
448
|
+
matchCategoriesByKeywords(content) {
|
|
449
|
+
const contentLower = content.toLowerCase();
|
|
450
|
+
const classifications = [];
|
|
451
|
+
const keywordMap = {
|
|
452
|
+
'authentication': [{ category: 'authentication', weight: 0.8 }],
|
|
453
|
+
'login': [{ category: 'authentication', weight: 0.7 }],
|
|
454
|
+
'password': [{ category: 'authentication', weight: 0.6 }],
|
|
455
|
+
'jwt': [{ category: 'authentication', weight: 0.8 }],
|
|
456
|
+
'oauth': [{ category: 'authentication', weight: 0.8 }],
|
|
457
|
+
'database': [{ category: 'database', weight: 0.8 }],
|
|
458
|
+
'query': [{ category: 'database', weight: 0.5 }],
|
|
459
|
+
'migration': [{ category: 'database', weight: 0.7 }],
|
|
460
|
+
'sql': [{ category: 'database', weight: 0.7 }],
|
|
461
|
+
'api': [{ category: 'api', weight: 0.7 }],
|
|
462
|
+
'endpoint': [{ category: 'api', weight: 0.7 }],
|
|
463
|
+
'rest': [{ category: 'api', weight: 0.7 }],
|
|
464
|
+
'graphql': [{ category: 'api', weight: 0.8 }],
|
|
465
|
+
'test': [{ category: 'testing', weight: 0.6 }],
|
|
466
|
+
'spec': [{ category: 'testing', weight: 0.6 }],
|
|
467
|
+
'mock': [{ category: 'testing', weight: 0.7 }],
|
|
468
|
+
'assert': [{ category: 'testing', weight: 0.7 }],
|
|
469
|
+
'component': [{ category: 'frontend', weight: 0.6 }],
|
|
470
|
+
'css': [{ category: 'frontend', weight: 0.7 }],
|
|
471
|
+
'style': [{ category: 'frontend', weight: 0.5 }],
|
|
472
|
+
'docker': [{ category: 'devops', weight: 0.8 }],
|
|
473
|
+
'deploy': [{ category: 'devops', weight: 0.7 }],
|
|
474
|
+
'ci/cd': [{ category: 'devops', weight: 0.8 }],
|
|
475
|
+
'kubernetes': [{ category: 'devops', weight: 0.8 }],
|
|
476
|
+
'pattern': [{ category: 'architecture', weight: 0.6 }],
|
|
477
|
+
'architecture': [{ category: 'architecture', weight: 0.8 }],
|
|
478
|
+
'design': [{ category: 'architecture', weight: 0.5 }],
|
|
479
|
+
'performance': [{ category: 'performance', weight: 0.8 }],
|
|
480
|
+
'cache': [{ category: 'performance', weight: 0.7 }],
|
|
481
|
+
'optimize': [{ category: 'performance', weight: 0.7 }],
|
|
482
|
+
'error': [{ category: 'debugging', weight: 0.6 }],
|
|
483
|
+
'bug': [{ category: 'debugging', weight: 0.7 }],
|
|
484
|
+
'fix': [{ category: 'debugging', weight: 0.5 }],
|
|
485
|
+
'debug': [{ category: 'debugging', weight: 0.8 }],
|
|
486
|
+
'convention': [{ category: 'standards', weight: 0.7 }],
|
|
487
|
+
'standard': [{ category: 'standards', weight: 0.7 }],
|
|
488
|
+
'best practice': [{ category: 'standards', weight: 0.8 }],
|
|
489
|
+
};
|
|
490
|
+
const categoryScores = new Map();
|
|
491
|
+
for (const [keyword, mappings] of Object.entries(keywordMap)) {
|
|
492
|
+
if (contentLower.includes(keyword)) {
|
|
493
|
+
for (const mapping of mappings) {
|
|
494
|
+
const existing = categoryScores.get(mapping.category) || { score: 0, reasons: [] };
|
|
495
|
+
existing.score += mapping.weight;
|
|
496
|
+
existing.reasons.push(keyword);
|
|
497
|
+
categoryScores.set(mapping.category, existing);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
for (const [category, data] of categoryScores) {
|
|
502
|
+
const normalizedScore = Math.min(1.0, data.score / 2);
|
|
503
|
+
if (normalizedScore >= this.config.minRelevanceScore) {
|
|
504
|
+
classifications.push({
|
|
505
|
+
category,
|
|
506
|
+
relevanceScore: normalizedScore,
|
|
507
|
+
reason: `Keywords: ${data.reasons.slice(0, 3).join(', ')}`,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return classifications;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Call LLM endpoint
|
|
515
|
+
*/
|
|
516
|
+
async callLLM(prompt) {
|
|
517
|
+
if (!this.config.llmEndpoint) {
|
|
518
|
+
throw new Error('LLM endpoint not configured');
|
|
519
|
+
}
|
|
520
|
+
const response = await fetch(this.config.llmEndpoint, {
|
|
521
|
+
method: 'POST',
|
|
522
|
+
headers: {
|
|
523
|
+
'Content-Type': 'application/json',
|
|
524
|
+
...(this.config.llmApiKey && { 'Authorization': `Bearer ${this.config.llmApiKey}` }),
|
|
525
|
+
},
|
|
526
|
+
body: JSON.stringify({
|
|
527
|
+
model: this.config.llmModel || 'gpt-4o-mini',
|
|
528
|
+
messages: [{ role: 'user', content: prompt }],
|
|
529
|
+
temperature: 0.3,
|
|
530
|
+
}),
|
|
531
|
+
});
|
|
532
|
+
if (!response.ok) {
|
|
533
|
+
throw new Error(`LLM request failed: ${response.status}`);
|
|
534
|
+
}
|
|
535
|
+
const data = await response.json();
|
|
536
|
+
return data.choices?.[0]?.message?.content || '';
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
let instance = null;
|
|
540
|
+
export function getCategoryManager(metadataStore, config) {
|
|
541
|
+
if (!instance) {
|
|
542
|
+
instance = new CategoryManager(metadataStore, config);
|
|
543
|
+
}
|
|
544
|
+
return instance;
|
|
545
|
+
}
|
|
546
|
+
export function resetCategoryManager() {
|
|
547
|
+
instance = null;
|
|
548
|
+
}
|
|
549
|
+
//# sourceMappingURL=categoryManager.js.map
|