@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,149 @@
1
+ // Symbol Extractor - Extract symbols from TypeScript/JavaScript files
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import type { SymbolInfo, WorkspaceIndex } from './types.js';
5
+
6
+ /**
7
+ * Simple symbol extractor using regex patterns
8
+ * For production use, consider ts-morph or @typescript/vfs
9
+ */
10
+ export class SymbolExtractor {
11
+ private rootPath: string;
12
+
13
+ constructor(rootPath: string) {
14
+ this.rootPath = rootPath;
15
+ }
16
+
17
+ /**
18
+ * Extract symbols from indexed files
19
+ */
20
+ async extractSymbols(index: WorkspaceIndex): Promise<SymbolInfo[]> {
21
+ const symbols: SymbolInfo[] = [];
22
+
23
+ // Filter to TS/JS files
24
+ const codeFiles = index.files.filter(f =>
25
+ (f.language === 'typescript' || f.language === 'javascript') &&
26
+ !f.isBinary &&
27
+ f.size < 500_000
28
+ );
29
+
30
+ for (const file of codeFiles) {
31
+ try {
32
+ const fullPath = path.join(this.rootPath, file.path);
33
+ const content = await fs.readFile(fullPath, 'utf-8');
34
+ const fileSymbols = this.parseSymbols(content, file.path);
35
+ symbols.push(...fileSymbols);
36
+ } catch {
37
+ // Skip files we can't read
38
+ }
39
+ }
40
+
41
+ return symbols;
42
+ }
43
+
44
+ /**
45
+ * Parse symbols from file content using regex
46
+ */
47
+ private parseSymbols(content: string, filePath: string): SymbolInfo[] {
48
+ const symbols: SymbolInfo[] = [];
49
+ const lines = content.split('\n');
50
+
51
+ // Patterns for different symbol types
52
+ const patterns = {
53
+ function: /(?:export\s+)?(?:async\s+)?function\s+(\w+)/g,
54
+ class: /(?:export\s+)?class\s+(\w+)/g,
55
+ interface: /(?:export\s+)?interface\s+(\w+)/g,
56
+ type: /(?:export\s+)?type\s+(\w+)/g,
57
+ const: /(?:export\s+)?const\s+(\w+)/g,
58
+ let: /(?:export\s+)?let\s+(\w+)/g,
59
+ var: /(?:export\s+)?var\s+(\w+)/g,
60
+ };
61
+
62
+ lines.forEach((line, idx) => {
63
+ const lineNum = idx + 1;
64
+
65
+ // Extract functions
66
+ const functionMatches = line.matchAll(patterns.function);
67
+ for (const match of functionMatches) {
68
+ symbols.push({
69
+ name: match[1],
70
+ kind: 'function',
71
+ path: filePath,
72
+ line: lineNum,
73
+ signature: line.trim(),
74
+ });
75
+ }
76
+
77
+ // Extract classes
78
+ const classMatches = line.matchAll(patterns.class);
79
+ for (const match of classMatches) {
80
+ symbols.push({
81
+ name: match[1],
82
+ kind: 'class',
83
+ path: filePath,
84
+ line: lineNum,
85
+ });
86
+ }
87
+
88
+ // Extract interfaces
89
+ const interfaceMatches = line.matchAll(patterns.interface);
90
+ for (const match of interfaceMatches) {
91
+ symbols.push({
92
+ name: match[1],
93
+ kind: 'interface',
94
+ path: filePath,
95
+ line: lineNum,
96
+ });
97
+ }
98
+
99
+ // Extract types
100
+ const typeMatches = line.matchAll(patterns.type);
101
+ for (const match of typeMatches) {
102
+ symbols.push({
103
+ name: match[1],
104
+ kind: 'type',
105
+ path: filePath,
106
+ line: lineNum,
107
+ });
108
+ }
109
+
110
+ // Extract exports (const/let/var)
111
+ if (line.includes('export')) {
112
+ for (const [pattern, kind] of [
113
+ [patterns.const, 'export'],
114
+ [patterns.let, 'export'],
115
+ [patterns.var, 'export'],
116
+ ] as const) {
117
+ const matches = line.matchAll(pattern);
118
+ for (const match of matches) {
119
+ symbols.push({
120
+ name: match[1],
121
+ kind,
122
+ path: filePath,
123
+ line: lineNum,
124
+ });
125
+ }
126
+ }
127
+ }
128
+ });
129
+
130
+ return symbols;
131
+ }
132
+
133
+ /**
134
+ * Find symbol definition by name
135
+ */
136
+ static findSymbol(symbols: SymbolInfo[], name: string): SymbolInfo[] {
137
+ return symbols.filter(s =>
138
+ s.name === name || s.name.toLowerCase() === name.toLowerCase()
139
+ );
140
+ }
141
+
142
+ /**
143
+ * Get symbols by kind
144
+ */
145
+ static filterByKind(symbols: SymbolInfo[], kinds: SymbolInfo['kind'][]): SymbolInfo[] {
146
+ const kindSet = new Set(kinds);
147
+ return symbols.filter(s => kindSet.has(s.kind));
148
+ }
149
+ }
@@ -0,0 +1,77 @@
1
+ // Test Mapper - Identify test files and their targets
2
+ import * as path from 'path';
3
+ import type { TestMapping, WorkspaceIndex } from './types.js';
4
+
5
+ const TEST_PATTERNS = [
6
+ /\.test\.(ts|tsx|js|jsx)$/,
7
+ /\.spec\.(ts|tsx|js|jsx)$/,
8
+ /__tests__\//,
9
+ /\.test\..*$/,
10
+ ];
11
+
12
+ export class TestMapper {
13
+ /**
14
+ * Map test files to their likely source targets
15
+ */
16
+ static mapTests(index: WorkspaceIndex): TestMapping[] {
17
+ const mappings: TestMapping[] = [];
18
+
19
+ // Find test files
20
+ const testFiles = index.files.filter(f =>
21
+ TEST_PATTERNS.some(pattern => pattern.test(f.path))
22
+ );
23
+
24
+ for (const testFile of testFiles) {
25
+ // Try to infer source file
26
+ const targetFiles = this.inferTargetFiles(testFile.path, index);
27
+
28
+ // Detect test framework
29
+ const framework = this.detectFramework(testFile.path);
30
+
31
+ mappings.push({
32
+ testFile: testFile.path,
33
+ targetFiles,
34
+ testFramework: framework,
35
+ });
36
+ }
37
+
38
+ return mappings;
39
+ }
40
+
41
+ /**
42
+ * Infer target source files from test file path
43
+ */
44
+ private static inferTargetFiles(testPath: string, index: WorkspaceIndex): string[] {
45
+ const targets: string[] = [];
46
+
47
+ // Remove test suffix/prefix patterns
48
+ let basePath = testPath
49
+ .replace(/\.test\.(ts|tsx|js|jsx)$/, '.$1')
50
+ .replace(/\.spec\.(ts|tsx|js|jsx)$/, '.$1')
51
+ .replace(/__tests__\//, '');
52
+
53
+ // Check if this file exists
54
+ if (index.files.some(f => f.path === basePath)) {
55
+ targets.push(basePath);
56
+ }
57
+
58
+ // Try src/ variant
59
+ const srcPath = path.join('src', path.basename(basePath));
60
+ if (index.files.some(f => f.path === srcPath)) {
61
+ targets.push(srcPath);
62
+ }
63
+
64
+ return targets;
65
+ }
66
+
67
+ /**
68
+ * Detect test framework from file path
69
+ */
70
+ private static detectFramework(testPath: string): string | undefined {
71
+ if (testPath.includes('jest')) return 'jest';
72
+ if (testPath.includes('mocha')) return 'mocha';
73
+ if (testPath.includes('vitest')) return 'vitest';
74
+ if (testPath.includes('spec')) return 'jasmine';
75
+ return undefined;
76
+ }
77
+ }
@@ -0,0 +1,69 @@
1
+ // Context Engine Types
2
+
3
+ export interface FileMetadata {
4
+ path: string;
5
+ size: number;
6
+ mtime: number;
7
+ hash: string;
8
+ language: string;
9
+ isBinary: boolean;
10
+ }
11
+
12
+ export interface WorkspaceIndex {
13
+ files: FileMetadata[];
14
+ totalFiles: number;
15
+ totalSize: number;
16
+ indexedAt: string;
17
+ rootPath: string;
18
+ }
19
+
20
+ export interface SymbolInfo {
21
+ name: string;
22
+ kind: 'function' | 'class' | 'variable' | 'interface' | 'type' | 'export' | 'import';
23
+ path: string;
24
+ line: number;
25
+ column?: number;
26
+ signature?: string;
27
+ }
28
+
29
+ export interface DependencyInfo {
30
+ name: string;
31
+ version: string;
32
+ type: 'dependency' | 'devDependency' | 'peerDependency';
33
+ packageJsonPath: string;
34
+ }
35
+
36
+ export interface ImportGraph {
37
+ [filePath: string]: {
38
+ imports: string[];
39
+ exportedSymbols: string[];
40
+ };
41
+ }
42
+
43
+ export interface TestMapping {
44
+ testFile: string;
45
+ targetFiles: string[];
46
+ testFramework?: string;
47
+ }
48
+
49
+ export interface SearchResult {
50
+ path: string;
51
+ score: number;
52
+ snippet: string;
53
+ line: number;
54
+ column?: number;
55
+ }
56
+
57
+ export interface ContextPack {
58
+ goal: string;
59
+ files: Array<{
60
+ path: string;
61
+ snippet: string;
62
+ relevance: number;
63
+ }>;
64
+ symbols: SymbolInfo[];
65
+ dependencies: DependencyInfo[];
66
+ testFiles: string[];
67
+ summary: string;
68
+ generatedAt: string;
69
+ }
@@ -0,0 +1,249 @@
1
+ // Workspace Indexer - Fast file scanning and caching
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import { createHash } from 'crypto';
5
+ import type { FileMetadata, WorkspaceIndex } from './types.js';
6
+
7
+ const DEFAULT_IGNORES = [
8
+ 'node_modules',
9
+ '.git',
10
+ 'dist',
11
+ 'build',
12
+ '.next',
13
+ '.cache',
14
+ 'coverage',
15
+ '.aicoder',
16
+ '*.lock',
17
+ 'package-lock.json',
18
+ 'pnpm-lock.yaml',
19
+ 'yarn.lock',
20
+ ];
21
+
22
+ const BINARY_EXTENSIONS = new Set([
23
+ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico',
24
+ '.pdf', '.zip', '.tar', '.gz', '.bz2',
25
+ '.exe', '.dll', '.so', '.dylib',
26
+ '.woff', '.woff2', '.ttf', '.eot',
27
+ '.mp4', '.mp3', '.wav', '.avi',
28
+ ]);
29
+
30
+ const LANGUAGE_MAP: Record<string, string> = {
31
+ '.ts': 'typescript',
32
+ '.tsx': 'typescript',
33
+ '.js': 'javascript',
34
+ '.jsx': 'javascript',
35
+ '.mjs': 'javascript',
36
+ '.cjs': 'javascript',
37
+ '.py': 'python',
38
+ '.go': 'go',
39
+ '.rs': 'rust',
40
+ '.java': 'java',
41
+ '.c': 'c',
42
+ '.cpp': 'cpp',
43
+ '.h': 'c',
44
+ '.hpp': 'cpp',
45
+ '.md': 'markdown',
46
+ '.json': 'json',
47
+ '.yaml': 'yaml',
48
+ '.yml': 'yaml',
49
+ '.toml': 'toml',
50
+ '.xml': 'xml',
51
+ '.html': 'html',
52
+ '.css': 'css',
53
+ '.scss': 'scss',
54
+ '.sql': 'sql',
55
+ '.sh': 'shell',
56
+ };
57
+
58
+ export class WorkspaceIndexer {
59
+ private rootPath: string;
60
+ private ignorePatterns: Set<string>;
61
+ private cacheDir: string;
62
+
63
+ constructor(rootPath: string, additionalIgnores: string[] = []) {
64
+ this.rootPath = path.resolve(rootPath);
65
+ this.ignorePatterns = new Set([...DEFAULT_IGNORES, ...additionalIgnores]);
66
+ this.cacheDir = path.join(this.rootPath, '.aicoder');
67
+ }
68
+
69
+ /**
70
+ * Build or refresh the workspace index
71
+ */
72
+ async buildIndex(options: { useCache?: boolean; maxFileSize?: number } = {}): Promise<WorkspaceIndex> {
73
+ const useCache = options.useCache ?? true;
74
+ const maxFileSize = options.maxFileSize ?? 1_000_000; // 1MB default
75
+
76
+ // Try to load from cache
77
+ if (useCache) {
78
+ const cached = await this.loadCache();
79
+ if (cached) {
80
+ return cached;
81
+ }
82
+ }
83
+
84
+ // Scan workspace
85
+ const files: FileMetadata[] = [];
86
+ await this.scanDirectory(this.rootPath, files, maxFileSize);
87
+
88
+ const index: WorkspaceIndex = {
89
+ files,
90
+ totalFiles: files.length,
91
+ totalSize: files.reduce((sum, f) => sum + f.size, 0),
92
+ indexedAt: new Date().toISOString(),
93
+ rootPath: this.rootPath,
94
+ };
95
+
96
+ // Save to cache
97
+ if (useCache) {
98
+ await this.saveCache(index);
99
+ }
100
+
101
+ return index;
102
+ }
103
+
104
+ /**
105
+ * Recursively scan directory for files
106
+ */
107
+ private async scanDirectory(
108
+ dirPath: string,
109
+ files: FileMetadata[],
110
+ maxFileSize: number
111
+ ): Promise<void> {
112
+ try {
113
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
114
+
115
+ for (const entry of entries) {
116
+ const fullPath = path.join(dirPath, entry.name);
117
+ const relativePath = path.relative(this.rootPath, fullPath);
118
+
119
+ // Skip ignored patterns
120
+ if (this.shouldIgnore(relativePath, entry.name)) {
121
+ continue;
122
+ }
123
+
124
+ if (entry.isDirectory()) {
125
+ await this.scanDirectory(fullPath, files, maxFileSize);
126
+ } else if (entry.isFile()) {
127
+ try {
128
+ const stats = await fs.stat(fullPath);
129
+
130
+ // Skip files that are too large
131
+ if (stats.size > maxFileSize) {
132
+ continue;
133
+ }
134
+
135
+ const ext = path.extname(entry.name);
136
+ const isBinary = BINARY_EXTENSIONS.has(ext);
137
+
138
+ // Compute hash for non-binary files
139
+ let hash = '';
140
+ if (!isBinary && stats.size < 100_000) { // Hash only small text files
141
+ try {
142
+ const content = await fs.readFile(fullPath, 'utf-8');
143
+ hash = createHash('sha256').update(content).digest('hex').substring(0, 16);
144
+ } catch {
145
+ // Skip files that can't be read
146
+ continue;
147
+ }
148
+ }
149
+
150
+ files.push({
151
+ path: relativePath,
152
+ size: stats.size,
153
+ mtime: stats.mtimeMs,
154
+ hash,
155
+ language: LANGUAGE_MAP[ext] || 'unknown',
156
+ isBinary,
157
+ });
158
+ } catch (error) {
159
+ // Skip files we can't stat
160
+ continue;
161
+ }
162
+ }
163
+ }
164
+ } catch (error) {
165
+ // Skip directories we can't read
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Check if path should be ignored
171
+ */
172
+ private shouldIgnore(relativePath: string, name: string): boolean {
173
+ // Check exact matches
174
+ if (this.ignorePatterns.has(name)) {
175
+ return true;
176
+ }
177
+
178
+ // Check if any part of the path matches ignore patterns
179
+ const parts = relativePath.split(path.sep);
180
+ for (const part of parts) {
181
+ if (this.ignorePatterns.has(part)) {
182
+ return true;
183
+ }
184
+ }
185
+
186
+ // Check wildcard patterns
187
+ for (const pattern of this.ignorePatterns) {
188
+ if (pattern.includes('*')) {
189
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
190
+ if (regex.test(name)) {
191
+ return true;
192
+ }
193
+ }
194
+ }
195
+
196
+ return false;
197
+ }
198
+
199
+ /**
200
+ * Load index from cache
201
+ */
202
+ private async loadCache(): Promise<WorkspaceIndex | null> {
203
+ try {
204
+ const cachePath = path.join(this.cacheDir, 'index.json');
205
+ const content = await fs.readFile(cachePath, 'utf-8');
206
+ const index: WorkspaceIndex = JSON.parse(content);
207
+
208
+ // Validate cache is recent (within 1 hour)
209
+ const cacheAge = Date.now() - new Date(index.indexedAt).getTime();
210
+ if (cacheAge > 3600_000) {
211
+ return null;
212
+ }
213
+
214
+ return index;
215
+ } catch {
216
+ return null;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Save index to cache
222
+ */
223
+ private async saveCache(index: WorkspaceIndex): Promise<void> {
224
+ try {
225
+ await fs.mkdir(this.cacheDir, { recursive: true });
226
+ const cachePath = path.join(this.cacheDir, 'index.json');
227
+ await fs.writeFile(cachePath, JSON.stringify(index, null, 2), 'utf-8');
228
+ } catch {
229
+ // Cache save is best-effort
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Get files by language
235
+ */
236
+ static filterByLanguage(index: WorkspaceIndex, languages: string[]): FileMetadata[] {
237
+ const langSet = new Set(languages);
238
+ return index.files.filter(f => langSet.has(f.language));
239
+ }
240
+
241
+ /**
242
+ * Get recently modified files
243
+ */
244
+ static getRecentFiles(index: WorkspaceIndex, count: number = 10): FileMetadata[] {
245
+ return [...index.files]
246
+ .sort((a, b) => b.mtime - a.mtime)
247
+ .slice(0, count);
248
+ }
249
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ // ACM AI Coder - Phase 2 exports
2
+ export * from './tools-v2/index.js';
3
+ export * from './tasks-v2/index.js';
4
+ export * from './registries.js';
5
+ export * from './context/index.js';
@@ -0,0 +1,118 @@
1
+ // Registries and Policy for AI Coder
2
+ import {
3
+ CapabilityRegistry,
4
+ ToolRegistry,
5
+ PolicyEngine,
6
+ type Capability,
7
+ type Tool,
8
+ type Task,
9
+ type PolicyDecision,
10
+ } from '@ddse/acm-sdk';
11
+
12
+ /**
13
+ * Simple Capability Registry
14
+ */
15
+ export class SimpleCapabilityRegistry extends CapabilityRegistry {
16
+ private tasks = new Map<string, Task>();
17
+ private capabilities = new Map<string, Capability>();
18
+
19
+ register(capability: Capability, task: Task): void {
20
+ this.capabilities.set(capability.name, capability);
21
+ this.tasks.set(capability.name, task);
22
+ }
23
+
24
+ list(): Capability[] {
25
+ return Array.from(this.capabilities.values());
26
+ }
27
+
28
+ has(name: string): boolean {
29
+ return this.capabilities.has(name);
30
+ }
31
+
32
+ resolve(name: string): Task | undefined {
33
+ return this.tasks.get(name);
34
+ }
35
+
36
+ inputSchema(name: string): unknown | undefined {
37
+ return this.capabilities.get(name)?.inputSchema;
38
+ }
39
+
40
+ outputSchema(name: string): unknown | undefined {
41
+ return this.capabilities.get(name)?.outputSchema;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Simple Tool Registry
47
+ */
48
+ export class SimpleToolRegistry extends ToolRegistry {
49
+ private tools = new Map<string, Tool>();
50
+
51
+ register(tool: Tool): void {
52
+ this.tools.set(tool.name(), tool);
53
+ }
54
+
55
+ get(name: string): Tool | undefined {
56
+ return this.tools.get(name);
57
+ }
58
+
59
+ list(): string[] {
60
+ return Array.from(this.tools.keys());
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Simple Policy Engine with safety rules for code modifications
66
+ */
67
+ export class SimplePolicyEngine implements PolicyEngine {
68
+ private allowedPaths: string[] = [];
69
+ private blockedActions: string[] = [];
70
+
71
+ setAllowedPaths(paths: string[]): void {
72
+ this.allowedPaths = paths;
73
+ }
74
+
75
+ setBlockedActions(actions: string[]): void {
76
+ this.blockedActions = actions;
77
+ }
78
+
79
+ async evaluate(
80
+ action: 'plan.admit' | 'task.pre' | 'task.post',
81
+ payload: any
82
+ ): Promise<PolicyDecision> {
83
+ // Check if action is blocked
84
+ if (action === 'task.pre' && this.blockedActions.includes(payload?.action)) {
85
+ return {
86
+ allow: false,
87
+ reason: `Action ${payload?.action} is blocked by policy`,
88
+ };
89
+ }
90
+
91
+ // For code editing operations, check path is allowed
92
+ if (action === 'task.pre' && (payload?.action === 'fix_bug' || payload?.action === 'implement_feature')) {
93
+ const targetPath = payload?.path || payload?.targetPath;
94
+
95
+ if (this.allowedPaths.length > 0 && targetPath && !this.allowedPaths.includes('*')) {
96
+ const isAllowed = this.allowedPaths.some(allowed =>
97
+ targetPath.includes(allowed)
98
+ );
99
+
100
+ if (!isAllowed) {
101
+ return {
102
+ allow: false,
103
+ reason: `Path ${targetPath} is not in allowed paths`,
104
+ };
105
+ }
106
+ }
107
+ }
108
+
109
+ // Default: allow
110
+ return {
111
+ allow: true,
112
+ limits: {
113
+ timeoutMs: 30000,
114
+ retries: 3,
115
+ },
116
+ };
117
+ }
118
+ }