@ddse/acm-aicoder 0.5.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 (165) hide show
  1. package/.aicoder/index.json +304 -0
  2. package/AICODER_IMPLEMENTATION_PLAN_PHASE2.md +284 -0
  3. package/LICENSE +21 -0
  4. package/README.md +490 -0
  5. package/bin/interactive.tsx +232 -0
  6. package/dist/bin/interactive.d.ts +3 -0
  7. package/dist/bin/interactive.d.ts.map +1 -0
  8. package/dist/bin/interactive.js +155 -0
  9. package/dist/bin/interactive.js.map +1 -0
  10. package/dist/src/config/providers.d.ts +15 -0
  11. package/dist/src/config/providers.d.ts.map +1 -0
  12. package/dist/src/config/providers.js +142 -0
  13. package/dist/src/config/providers.js.map +1 -0
  14. package/dist/src/config/session.d.ts +25 -0
  15. package/dist/src/config/session.d.ts.map +1 -0
  16. package/dist/src/config/session.js +97 -0
  17. package/dist/src/config/session.js.map +1 -0
  18. package/dist/src/context/bm25.d.ts +68 -0
  19. package/dist/src/context/bm25.d.ts.map +1 -0
  20. package/dist/src/context/bm25.js +131 -0
  21. package/dist/src/context/bm25.js.map +1 -0
  22. package/dist/src/context/code-search.d.ts +30 -0
  23. package/dist/src/context/code-search.d.ts.map +1 -0
  24. package/dist/src/context/code-search.js +150 -0
  25. package/dist/src/context/code-search.js.map +1 -0
  26. package/dist/src/context/context-pack.d.ts +25 -0
  27. package/dist/src/context/context-pack.d.ts.map +1 -0
  28. package/dist/src/context/context-pack.js +92 -0
  29. package/dist/src/context/context-pack.js.map +1 -0
  30. package/dist/src/context/dependency-mapper.d.ts +10 -0
  31. package/dist/src/context/dependency-mapper.d.ts.map +1 -0
  32. package/dist/src/context/dependency-mapper.js +62 -0
  33. package/dist/src/context/dependency-mapper.js.map +1 -0
  34. package/dist/src/context/index.d.ts +8 -0
  35. package/dist/src/context/index.d.ts.map +1 -0
  36. package/dist/src/context/index.js +9 -0
  37. package/dist/src/context/index.js.map +1 -0
  38. package/dist/src/context/symbol-extractor.d.ts +26 -0
  39. package/dist/src/context/symbol-extractor.d.ts.map +1 -0
  40. package/dist/src/context/symbol-extractor.js +129 -0
  41. package/dist/src/context/symbol-extractor.js.map +1 -0
  42. package/dist/src/context/test-mapper.d.ts +16 -0
  43. package/dist/src/context/test-mapper.d.ts.map +1 -0
  44. package/dist/src/context/test-mapper.js +66 -0
  45. package/dist/src/context/test-mapper.js.map +1 -0
  46. package/dist/src/context/types.d.ts +61 -0
  47. package/dist/src/context/types.d.ts.map +1 -0
  48. package/dist/src/context/types.js +3 -0
  49. package/dist/src/context/types.js.map +1 -0
  50. package/dist/src/context/workspace-indexer.d.ts +39 -0
  51. package/dist/src/context/workspace-indexer.d.ts.map +1 -0
  52. package/dist/src/context/workspace-indexer.js +222 -0
  53. package/dist/src/context/workspace-indexer.js.map +1 -0
  54. package/dist/src/index.d.ts +5 -0
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/index.js +6 -0
  57. package/dist/src/index.js.map +1 -0
  58. package/dist/src/registries.d.ts +34 -0
  59. package/dist/src/registries.d.ts.map +1 -0
  60. package/dist/src/registries.js +87 -0
  61. package/dist/src/registries.js.map +1 -0
  62. package/dist/src/runtime/budget-manager.d.ts +42 -0
  63. package/dist/src/runtime/budget-manager.d.ts.map +1 -0
  64. package/dist/src/runtime/budget-manager.js +82 -0
  65. package/dist/src/runtime/budget-manager.js.map +1 -0
  66. package/dist/src/runtime/interactive-runtime.d.ts +39 -0
  67. package/dist/src/runtime/interactive-runtime.d.ts.map +1 -0
  68. package/dist/src/runtime/interactive-runtime.js +321 -0
  69. package/dist/src/runtime/interactive-runtime.js.map +1 -0
  70. package/dist/src/tasks-v2/analysis-tasks.d.ts +117 -0
  71. package/dist/src/tasks-v2/analysis-tasks.d.ts.map +1 -0
  72. package/dist/src/tasks-v2/analysis-tasks.js +209 -0
  73. package/dist/src/tasks-v2/analysis-tasks.js.map +1 -0
  74. package/dist/src/tasks-v2/developer-tasks.d.ts +226 -0
  75. package/dist/src/tasks-v2/developer-tasks.d.ts.map +1 -0
  76. package/dist/src/tasks-v2/developer-tasks.js +322 -0
  77. package/dist/src/tasks-v2/developer-tasks.js.map +1 -0
  78. package/dist/src/tasks-v2/index.d.ts +3 -0
  79. package/dist/src/tasks-v2/index.d.ts.map +1 -0
  80. package/dist/src/tasks-v2/index.js +4 -0
  81. package/dist/src/tasks-v2/index.js.map +1 -0
  82. package/dist/src/tools-v2/edit-tools.d.ts +67 -0
  83. package/dist/src/tools-v2/edit-tools.d.ts.map +1 -0
  84. package/dist/src/tools-v2/edit-tools.js +117 -0
  85. package/dist/src/tools-v2/edit-tools.js.map +1 -0
  86. package/dist/src/tools-v2/index.d.ts +6 -0
  87. package/dist/src/tools-v2/index.d.ts.map +1 -0
  88. package/dist/src/tools-v2/index.js +7 -0
  89. package/dist/src/tools-v2/index.js.map +1 -0
  90. package/dist/src/tools-v2/read-tools.d.ts +129 -0
  91. package/dist/src/tools-v2/read-tools.d.ts.map +1 -0
  92. package/dist/src/tools-v2/read-tools.js +216 -0
  93. package/dist/src/tools-v2/read-tools.js.map +1 -0
  94. package/dist/src/tools-v2/search-tools.d.ts +73 -0
  95. package/dist/src/tools-v2/search-tools.d.ts.map +1 -0
  96. package/dist/src/tools-v2/search-tools.js +132 -0
  97. package/dist/src/tools-v2/search-tools.js.map +1 -0
  98. package/dist/src/tools-v2/test-tools.d.ts +59 -0
  99. package/dist/src/tools-v2/test-tools.d.ts.map +1 -0
  100. package/dist/src/tools-v2/test-tools.js +111 -0
  101. package/dist/src/tools-v2/test-tools.js.map +1 -0
  102. package/dist/src/tools-v2/workspace-context.d.ts +65 -0
  103. package/dist/src/tools-v2/workspace-context.d.ts.map +1 -0
  104. package/dist/src/tools-v2/workspace-context.js +336 -0
  105. package/dist/src/tools-v2/workspace-context.js.map +1 -0
  106. package/dist/src/ui/App.d.ts +9 -0
  107. package/dist/src/ui/App.d.ts.map +1 -0
  108. package/dist/src/ui/App.js +257 -0
  109. package/dist/src/ui/App.js.map +1 -0
  110. package/dist/src/ui/components/ChatPane.d.ts +12 -0
  111. package/dist/src/ui/components/ChatPane.d.ts.map +1 -0
  112. package/dist/src/ui/components/ChatPane.js +41 -0
  113. package/dist/src/ui/components/ChatPane.js.map +1 -0
  114. package/dist/src/ui/components/EventsPane.d.ts +12 -0
  115. package/dist/src/ui/components/EventsPane.d.ts.map +1 -0
  116. package/dist/src/ui/components/EventsPane.js +48 -0
  117. package/dist/src/ui/components/EventsPane.js.map +1 -0
  118. package/dist/src/ui/components/GoalsTasksPane.d.ts +18 -0
  119. package/dist/src/ui/components/GoalsTasksPane.d.ts.map +1 -0
  120. package/dist/src/ui/components/GoalsTasksPane.js +83 -0
  121. package/dist/src/ui/components/GoalsTasksPane.js.map +1 -0
  122. package/dist/src/ui/store.d.ts +74 -0
  123. package/dist/src/ui/store.d.ts.map +1 -0
  124. package/dist/src/ui/store.js +260 -0
  125. package/dist/src/ui/store.js.map +1 -0
  126. package/dist/tests/integration.test.d.ts +2 -0
  127. package/dist/tests/integration.test.d.ts.map +1 -0
  128. package/dist/tests/integration.test.js +415 -0
  129. package/dist/tests/integration.test.js.map +1 -0
  130. package/dist/tsconfig.tsbuildinfo +1 -0
  131. package/docs/AICODER.png +0 -0
  132. package/docs/INTERACTIVE_CLI_GUIDE.md +201 -0
  133. package/docs/TUI_MOCKUP.md +180 -0
  134. package/package.json +52 -0
  135. package/src/config/providers.ts +174 -0
  136. package/src/config/session.ts +143 -0
  137. package/src/context/bm25.ts +173 -0
  138. package/src/context/code-search.ts +188 -0
  139. package/src/context/context-pack.ts +133 -0
  140. package/src/context/dependency-mapper.ts +72 -0
  141. package/src/context/index.ts +8 -0
  142. package/src/context/symbol-extractor.ts +149 -0
  143. package/src/context/test-mapper.ts +77 -0
  144. package/src/context/types.ts +69 -0
  145. package/src/context/workspace-indexer.ts +249 -0
  146. package/src/index.ts +5 -0
  147. package/src/registries.ts +118 -0
  148. package/src/runtime/budget-manager.ts +118 -0
  149. package/src/runtime/interactive-runtime.ts +423 -0
  150. package/src/tasks-v2/analysis-tasks.ts +311 -0
  151. package/src/tasks-v2/developer-tasks.ts +437 -0
  152. package/src/tasks-v2/index.ts +3 -0
  153. package/src/tools-v2/edit-tools.ts +153 -0
  154. package/src/tools-v2/index.ts +6 -0
  155. package/src/tools-v2/read-tools.ts +286 -0
  156. package/src/tools-v2/search-tools.ts +175 -0
  157. package/src/tools-v2/test-tools.ts +147 -0
  158. package/src/tools-v2/workspace-context.ts +428 -0
  159. package/src/ui/App.tsx +392 -0
  160. package/src/ui/components/ChatPane.tsx +84 -0
  161. package/src/ui/components/EventsPane.tsx +81 -0
  162. package/src/ui/components/GoalsTasksPane.tsx +149 -0
  163. package/src/ui/store.ts +362 -0
  164. package/tests/integration.test.ts +537 -0
  165. package/tsconfig.json +22 -0
@@ -0,0 +1,173 @@
1
+ // Simple BM25 search implementation for ACM examples
2
+
3
+ /**
4
+ * Document interface for BM25 search
5
+ */
6
+ export interface Document {
7
+ id: string;
8
+ title?: string;
9
+ content: string;
10
+ [key: string]: any;
11
+ }
12
+
13
+ /**
14
+ * Search result with score
15
+ */
16
+ export interface SearchResult {
17
+ document: Document;
18
+ score: number;
19
+ }
20
+
21
+ /**
22
+ * BM25 parameters
23
+ */
24
+ export interface BM25Params {
25
+ k1?: number; // Term frequency saturation (default: 1.5)
26
+ b?: number; // Length normalization (default: 0.75)
27
+ }
28
+
29
+ /**
30
+ * Simple tokenizer
31
+ */
32
+ function tokenize(text?: string | null): string[] {
33
+ if (!text) return [];
34
+
35
+ return text
36
+ .toLowerCase()
37
+ .replace(/[^\w\s]/g, ' ')
38
+ .split(/\s+/)
39
+ .filter((token) => token.length > 0);
40
+ }
41
+
42
+ /**
43
+ * BM25 Search Engine
44
+ *
45
+ * Implements the BM25 ranking function for full-text search.
46
+ * BM25 is a probabilistic ranking function used by search engines
47
+ * to estimate the relevance of documents to a query.
48
+ */
49
+ export class BM25Search {
50
+ private documents: Document[] = [];
51
+ private documentTokens: Map<string, string[]> = new Map();
52
+ private documentFrequency: Map<string, number> = new Map();
53
+ private averageDocumentLength: number = 0;
54
+ private k1: number;
55
+ private b: number;
56
+
57
+ constructor(params: BM25Params = {}) {
58
+ this.k1 = params.k1 ?? 1.5;
59
+ this.b = params.b ?? 0.75;
60
+ }
61
+
62
+ /**
63
+ * Index documents for search
64
+ */
65
+ index(documents: Document[]): void {
66
+ this.documents = documents;
67
+ this.documentTokens.clear();
68
+ this.documentFrequency.clear();
69
+
70
+ // Tokenize documents
71
+ let totalLength = 0;
72
+ for (const doc of documents) {
73
+ const text = this.extractText(doc);
74
+ const tokens = tokenize(text);
75
+ this.documentTokens.set(doc.id, tokens);
76
+ totalLength += tokens.length;
77
+
78
+ // Update document frequency
79
+ const uniqueTokens = new Set(tokens);
80
+ for (const token of uniqueTokens) {
81
+ this.documentFrequency.set(token, (this.documentFrequency.get(token) || 0) + 1);
82
+ }
83
+ }
84
+
85
+ this.averageDocumentLength = totalLength / documents.length;
86
+ }
87
+
88
+ /**
89
+ * Extract searchable text from document
90
+ */
91
+ private extractText(doc: Document): string {
92
+ const parts: string[] = [];
93
+ if (doc.title) parts.push(doc.title);
94
+ if (doc.content) parts.push(doc.content);
95
+ return parts.join(' ');
96
+ }
97
+
98
+ /**
99
+ * Calculate IDF (Inverse Document Frequency)
100
+ */
101
+ private idf(term: string): number {
102
+ const df = this.documentFrequency.get(term) || 0;
103
+ if (df === 0) return 0;
104
+
105
+ const n = this.documents.length;
106
+ return Math.log((n - df + 0.5) / (df + 0.5) + 1);
107
+ }
108
+
109
+ /**
110
+ * Calculate BM25 score for a document
111
+ */
112
+ private score(docId: string, queryTokens: string[]): number {
113
+ const tokens = this.documentTokens.get(docId);
114
+ if (!tokens) return 0;
115
+
116
+ const docLength = tokens.length;
117
+ let score = 0;
118
+
119
+ for (const queryToken of queryTokens) {
120
+ const termFreq = tokens.filter((t) => t === queryToken).length;
121
+ if (termFreq === 0) continue;
122
+
123
+ const idf = this.idf(queryToken);
124
+ const numerator = termFreq * (this.k1 + 1);
125
+ const denominator = termFreq + this.k1 * (1 - this.b + this.b * (docLength / this.averageDocumentLength));
126
+
127
+ score += idf * (numerator / denominator);
128
+ }
129
+
130
+ return score;
131
+ }
132
+
133
+ /**
134
+ * Search for documents matching the query
135
+ */
136
+ search(query: string, limit: number = 10): SearchResult[] {
137
+ if (this.documents.length === 0) {
138
+ return [];
139
+ }
140
+
141
+ const queryTokens = tokenize(query);
142
+ if (queryTokens.length === 0) {
143
+ return [];
144
+ }
145
+
146
+ // Score all documents
147
+ const results: SearchResult[] = [];
148
+ for (const doc of this.documents) {
149
+ const score = this.score(doc.id, queryTokens);
150
+ if (score > 0) {
151
+ results.push({ document: doc, score });
152
+ }
153
+ }
154
+
155
+ // Sort by score (descending) and limit
156
+ results.sort((a, b) => b.score - a.score);
157
+ return results.slice(0, limit);
158
+ }
159
+
160
+ /**
161
+ * Get all indexed documents
162
+ */
163
+ getDocuments(): Document[] {
164
+ return this.documents;
165
+ }
166
+
167
+ /**
168
+ * Get document count
169
+ */
170
+ getDocumentCount(): number {
171
+ return this.documents.length;
172
+ }
173
+ }
@@ -0,0 +1,188 @@
1
+ // Code Search - BM25 based search for code files
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import { BM25Search, type Document } from './bm25.js';
5
+ import type { WorkspaceIndex, FileMetadata, SearchResult } from './types.js';
6
+
7
+ export interface SearchOptions {
8
+ k?: number; // Number of results
9
+ preferTypes?: string[]; // Prefer certain file types
10
+ includeContext?: boolean; // Include surrounding lines
11
+ contextLines?: number; // Number of context lines
12
+ }
13
+
14
+ export class CodeSearch {
15
+ private bm25: BM25Search;
16
+ private rootPath: string;
17
+ private documents: Map<string, Document> = new Map();
18
+
19
+ constructor(rootPath: string) {
20
+ this.rootPath = rootPath;
21
+ this.bm25 = new BM25Search({ k1: 1.5, b: 0.75 });
22
+ }
23
+
24
+ /**
25
+ * Index files for search
26
+ */
27
+ async indexFiles(index: WorkspaceIndex): Promise<void> {
28
+ const documents: Document[] = [];
29
+ this.documents.clear();
30
+
31
+ // Filter to text files only
32
+ const textFiles = index.files.filter(f =>
33
+ !f.isBinary &&
34
+ f.size < 500_000 && // Only index files < 500KB
35
+ (f.language === 'typescript' ||
36
+ f.language === 'javascript' ||
37
+ f.language === 'markdown' ||
38
+ f.language === 'json')
39
+ );
40
+
41
+ // Read and index each file
42
+ for (const file of textFiles) {
43
+ try {
44
+ const fullPath = path.join(this.rootPath, file.path);
45
+ const content = await fs.readFile(fullPath, 'utf-8');
46
+
47
+ const doc: Document = {
48
+ id: file.path,
49
+ title: path.basename(file.path),
50
+ content,
51
+ language: file.language,
52
+ size: file.size,
53
+ path: file.path,
54
+ };
55
+
56
+ documents.push(doc);
57
+ this.documents.set(file.path, doc);
58
+ } catch {
59
+ // Skip files we can't read
60
+ }
61
+ }
62
+
63
+ // Build BM25 index
64
+ this.bm25.index(documents);
65
+ }
66
+
67
+ /**
68
+ * Search for code
69
+ */
70
+ async search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {
71
+ const k = options.k ?? 10;
72
+ const preferTypes = new Set(options.preferTypes || []);
73
+
74
+ // Search using BM25
75
+ const bm25Results = this.bm25.search(query, k * 2); // Get more initially
76
+
77
+ // Apply preferences and convert to SearchResult
78
+ const results: SearchResult[] = [];
79
+
80
+ for (const result of bm25Results) {
81
+ const doc = result.document;
82
+ let score = result.score;
83
+
84
+ // Boost score for preferred file types
85
+ if (preferTypes.size > 0) {
86
+ const ext = path.extname(doc.path);
87
+ const lang = doc.language as string;
88
+ if (preferTypes.has(ext) || preferTypes.has(lang)) {
89
+ score *= 1.5;
90
+ }
91
+ }
92
+
93
+ // Find best matching line
94
+ const snippet = this.findBestSnippet(doc.content, query, options);
95
+
96
+ results.push({
97
+ path: doc.path,
98
+ score,
99
+ snippet: snippet.text,
100
+ line: snippet.line,
101
+ column: snippet.column,
102
+ });
103
+ }
104
+
105
+ // Re-sort by adjusted scores and limit
106
+ return results
107
+ .sort((a, b) => b.score - a.score)
108
+ .slice(0, k);
109
+ }
110
+
111
+ /**
112
+ * Find the best snippet matching the query
113
+ */
114
+ private findBestSnippet(
115
+ content: string,
116
+ query: string,
117
+ options: SearchOptions
118
+ ): { text: string; line: number; column: number } {
119
+ const lines = content.split('\n');
120
+ const queryLower = query.toLowerCase();
121
+ const queryTokens = queryLower.split(/\s+/);
122
+
123
+ let bestLine = 0;
124
+ let bestScore = 0;
125
+
126
+ // Find line with most query tokens
127
+ for (let i = 0; i < lines.length; i++) {
128
+ const line = lines[i];
129
+ const lineLower = line.toLowerCase();
130
+
131
+ let score = 0;
132
+ for (const token of queryTokens) {
133
+ if (lineLower.includes(token)) {
134
+ score += 1;
135
+ }
136
+ }
137
+
138
+ if (score > bestScore) {
139
+ bestScore = score;
140
+ bestLine = i;
141
+ }
142
+ }
143
+
144
+ // Extract snippet with context
145
+ const contextLines = options.includeContext ? (options.contextLines ?? 2) : 0;
146
+ const startLine = Math.max(0, bestLine - contextLines);
147
+ const endLine = Math.min(lines.length - 1, bestLine + contextLines);
148
+
149
+ const snippetLines = lines.slice(startLine, endLine + 1);
150
+ const snippet = snippetLines
151
+ .map((line, idx) => {
152
+ const lineNum = startLine + idx + 1;
153
+ const marker = lineNum === bestLine + 1 ? '>' : ' ';
154
+ return `${marker} ${lineNum.toString().padStart(4, ' ')} ${line}`;
155
+ })
156
+ .join('\n');
157
+
158
+ // Find column position (first occurrence of any query token)
159
+ let column = 0;
160
+ const bestLineLower = lines[bestLine].toLowerCase();
161
+ for (const token of queryTokens) {
162
+ const pos = bestLineLower.indexOf(token);
163
+ if (pos >= 0) {
164
+ column = pos;
165
+ break;
166
+ }
167
+ }
168
+
169
+ return {
170
+ text: snippet,
171
+ line: bestLine + 1,
172
+ column,
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Search for symbol by name (exact or partial match)
178
+ */
179
+ async searchSymbol(symbolName: string): Promise<SearchResult[]> {
180
+ // Search for exact symbol name with high weight
181
+ return this.search(`${symbolName} function class interface`, {
182
+ k: 5,
183
+ preferTypes: ['.ts', '.tsx', '.js', '.jsx'],
184
+ includeContext: true,
185
+ contextLines: 3,
186
+ });
187
+ }
188
+ }
@@ -0,0 +1,133 @@
1
+ // Context Pack Generator - Create rich context for LLM planning
2
+ import type {
3
+ ContextPack,
4
+ WorkspaceIndex,
5
+ SymbolInfo,
6
+ DependencyInfo,
7
+ TestMapping
8
+ } from './types.js';
9
+ import { CodeSearch } from './code-search.js';
10
+
11
+ export interface ContextPackOptions {
12
+ maxFiles?: number;
13
+ maxSymbols?: number;
14
+ includeTests?: boolean;
15
+ includeDependencies?: boolean;
16
+ }
17
+
18
+ export class ContextPackGenerator {
19
+ private search: CodeSearch;
20
+
21
+ constructor(search: CodeSearch) {
22
+ this.search = search;
23
+ }
24
+
25
+ /**
26
+ * Generate context pack for a goal
27
+ */
28
+ async generate(
29
+ goal: string,
30
+ index: WorkspaceIndex,
31
+ symbols: SymbolInfo[],
32
+ dependencies: DependencyInfo[],
33
+ testMappings: TestMapping[],
34
+ options: ContextPackOptions = {}
35
+ ): Promise<ContextPack> {
36
+ const maxFiles = options.maxFiles ?? 8;
37
+ const maxSymbols = options.maxSymbols ?? 20;
38
+ const normalizedGoal = (goal || '').toString();
39
+ const searchQuery = normalizedGoal.trim().length > 0
40
+ ? normalizedGoal
41
+ : 'workspace overview';
42
+
43
+ // Search for relevant files
44
+ const searchResults = await this.search.search(searchQuery, {
45
+ k: maxFiles,
46
+ includeContext: true,
47
+ contextLines: 2,
48
+ });
49
+
50
+ // Extract relevant symbols (top matching symbols)
51
+ const relevantSymbols = this.findRelevantSymbols(normalizedGoal, symbols, maxSymbols);
52
+
53
+ // Filter dependencies if requested
54
+ const relevantDeps = options.includeDependencies
55
+ ? dependencies.slice(0, 20) // Top 20 deps
56
+ : [];
57
+
58
+ // Extract test files if requested
59
+ const testFiles = options.includeTests
60
+ ? testMappings.map(m => m.testFile)
61
+ : [];
62
+
63
+ // Build context pack
64
+ const pack: ContextPack = {
65
+ goal: normalizedGoal,
66
+ files: searchResults.map(r => ({
67
+ path: r.path,
68
+ snippet: r.snippet,
69
+ relevance: r.score,
70
+ })),
71
+ symbols: relevantSymbols,
72
+ dependencies: relevantDeps,
73
+ testFiles,
74
+ summary: this.generateSummary(index, searchResults.length, relevantSymbols.length),
75
+ generatedAt: new Date().toISOString(),
76
+ };
77
+
78
+ return pack;
79
+ }
80
+
81
+ /**
82
+ * Find symbols relevant to the goal
83
+ */
84
+ private findRelevantSymbols(goal: string, symbols: SymbolInfo[], limit: number): SymbolInfo[] {
85
+ const goalLower = (goal || '').toLowerCase();
86
+ const tokens = goalLower.split(/\s+/).filter(Boolean);
87
+
88
+ if (tokens.length === 0) {
89
+ return [];
90
+ }
91
+
92
+ // Score symbols by relevance
93
+ const scored = symbols.map(symbol => {
94
+ let score = 0;
95
+ const nameLower = symbol.name.toLowerCase();
96
+
97
+ // Exact match
98
+ if (tokens.some(t => nameLower === t)) {
99
+ score += 10;
100
+ }
101
+
102
+ // Partial match
103
+ if (tokens.some(t => nameLower.includes(t) || t.includes(nameLower))) {
104
+ score += 5;
105
+ }
106
+
107
+ // Boost exported symbols
108
+ if (symbol.kind === 'function' || symbol.kind === 'class') {
109
+ score += 2;
110
+ }
111
+
112
+ return { symbol, score };
113
+ });
114
+
115
+ // Sort by score and return top symbols
116
+ return scored
117
+ .filter(s => s.score > 0)
118
+ .sort((a, b) => b.score - a.score)
119
+ .slice(0, limit)
120
+ .map(s => s.symbol);
121
+ }
122
+
123
+ /**
124
+ * Generate a summary of the context
125
+ */
126
+ private generateSummary(index: WorkspaceIndex, fileCount: number, symbolCount: number): string {
127
+ const totalFiles = index.totalFiles;
128
+ const totalSize = (index.totalSize / 1024 / 1024).toFixed(2);
129
+
130
+ return `Workspace: ${totalFiles} files, ${totalSize}MB. ` +
131
+ `Context: ${fileCount} relevant files, ${symbolCount} symbols.`;
132
+ }
133
+ }
@@ -0,0 +1,72 @@
1
+ // Dependency Mapper - Parse and analyze dependencies
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import type { DependencyInfo, WorkspaceIndex } from './types.js';
5
+
6
+ export class DependencyMapper {
7
+ private rootPath: string;
8
+
9
+ constructor(rootPath: string) {
10
+ this.rootPath = rootPath;
11
+ }
12
+
13
+ /**
14
+ * Extract dependencies from package.json files
15
+ */
16
+ async extractDependencies(index: WorkspaceIndex): Promise<DependencyInfo[]> {
17
+ const dependencies: DependencyInfo[] = [];
18
+
19
+ // Find all package.json files
20
+ const packageFiles = index.files.filter(f =>
21
+ path.basename(f.path) === 'package.json'
22
+ );
23
+
24
+ for (const file of packageFiles) {
25
+ try {
26
+ const fullPath = path.join(this.rootPath, file.path);
27
+ const content = await fs.readFile(fullPath, 'utf-8');
28
+ const pkg = JSON.parse(content);
29
+
30
+ // Extract dependencies
31
+ if (pkg.dependencies) {
32
+ for (const [name, version] of Object.entries(pkg.dependencies)) {
33
+ dependencies.push({
34
+ name,
35
+ version: version as string,
36
+ type: 'dependency',
37
+ packageJsonPath: file.path,
38
+ });
39
+ }
40
+ }
41
+
42
+ // Extract devDependencies
43
+ if (pkg.devDependencies) {
44
+ for (const [name, version] of Object.entries(pkg.devDependencies)) {
45
+ dependencies.push({
46
+ name,
47
+ version: version as string,
48
+ type: 'devDependency',
49
+ packageJsonPath: file.path,
50
+ });
51
+ }
52
+ }
53
+
54
+ // Extract peerDependencies
55
+ if (pkg.peerDependencies) {
56
+ for (const [name, version] of Object.entries(pkg.peerDependencies)) {
57
+ dependencies.push({
58
+ name,
59
+ version: version as string,
60
+ type: 'peerDependency',
61
+ packageJsonPath: file.path,
62
+ });
63
+ }
64
+ }
65
+ } catch {
66
+ // Skip invalid package.json files
67
+ }
68
+ }
69
+
70
+ return dependencies;
71
+ }
72
+ }
@@ -0,0 +1,8 @@
1
+ // Context Engine - Intelligent repository understanding
2
+ export * from './workspace-indexer.js';
3
+ export * from './symbol-extractor.js';
4
+ export * from './dependency-mapper.js';
5
+ export * from './test-mapper.js';
6
+ export * from './code-search.js';
7
+ export * from './context-pack.js';
8
+ export * from './types.js';