@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.
- package/.aicoder/index.json +304 -0
- package/AICODER_IMPLEMENTATION_PLAN_PHASE2.md +284 -0
- package/LICENSE +21 -0
- package/README.md +490 -0
- package/bin/interactive.tsx +232 -0
- package/dist/bin/interactive.d.ts +3 -0
- package/dist/bin/interactive.d.ts.map +1 -0
- package/dist/bin/interactive.js +155 -0
- package/dist/bin/interactive.js.map +1 -0
- package/dist/src/config/providers.d.ts +15 -0
- package/dist/src/config/providers.d.ts.map +1 -0
- package/dist/src/config/providers.js +142 -0
- package/dist/src/config/providers.js.map +1 -0
- package/dist/src/config/session.d.ts +25 -0
- package/dist/src/config/session.d.ts.map +1 -0
- package/dist/src/config/session.js +97 -0
- package/dist/src/config/session.js.map +1 -0
- package/dist/src/context/bm25.d.ts +68 -0
- package/dist/src/context/bm25.d.ts.map +1 -0
- package/dist/src/context/bm25.js +131 -0
- package/dist/src/context/bm25.js.map +1 -0
- package/dist/src/context/code-search.d.ts +30 -0
- package/dist/src/context/code-search.d.ts.map +1 -0
- package/dist/src/context/code-search.js +150 -0
- package/dist/src/context/code-search.js.map +1 -0
- package/dist/src/context/context-pack.d.ts +25 -0
- package/dist/src/context/context-pack.d.ts.map +1 -0
- package/dist/src/context/context-pack.js +92 -0
- package/dist/src/context/context-pack.js.map +1 -0
- package/dist/src/context/dependency-mapper.d.ts +10 -0
- package/dist/src/context/dependency-mapper.d.ts.map +1 -0
- package/dist/src/context/dependency-mapper.js +62 -0
- package/dist/src/context/dependency-mapper.js.map +1 -0
- package/dist/src/context/index.d.ts +8 -0
- package/dist/src/context/index.d.ts.map +1 -0
- package/dist/src/context/index.js +9 -0
- package/dist/src/context/index.js.map +1 -0
- package/dist/src/context/symbol-extractor.d.ts +26 -0
- package/dist/src/context/symbol-extractor.d.ts.map +1 -0
- package/dist/src/context/symbol-extractor.js +129 -0
- package/dist/src/context/symbol-extractor.js.map +1 -0
- package/dist/src/context/test-mapper.d.ts +16 -0
- package/dist/src/context/test-mapper.d.ts.map +1 -0
- package/dist/src/context/test-mapper.js +66 -0
- package/dist/src/context/test-mapper.js.map +1 -0
- package/dist/src/context/types.d.ts +61 -0
- package/dist/src/context/types.d.ts.map +1 -0
- package/dist/src/context/types.js +3 -0
- package/dist/src/context/types.js.map +1 -0
- package/dist/src/context/workspace-indexer.d.ts +39 -0
- package/dist/src/context/workspace-indexer.d.ts.map +1 -0
- package/dist/src/context/workspace-indexer.js +222 -0
- package/dist/src/context/workspace-indexer.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/registries.d.ts +34 -0
- package/dist/src/registries.d.ts.map +1 -0
- package/dist/src/registries.js +87 -0
- package/dist/src/registries.js.map +1 -0
- package/dist/src/runtime/budget-manager.d.ts +42 -0
- package/dist/src/runtime/budget-manager.d.ts.map +1 -0
- package/dist/src/runtime/budget-manager.js +82 -0
- package/dist/src/runtime/budget-manager.js.map +1 -0
- package/dist/src/runtime/interactive-runtime.d.ts +39 -0
- package/dist/src/runtime/interactive-runtime.d.ts.map +1 -0
- package/dist/src/runtime/interactive-runtime.js +321 -0
- package/dist/src/runtime/interactive-runtime.js.map +1 -0
- package/dist/src/tasks-v2/analysis-tasks.d.ts +117 -0
- package/dist/src/tasks-v2/analysis-tasks.d.ts.map +1 -0
- package/dist/src/tasks-v2/analysis-tasks.js +209 -0
- package/dist/src/tasks-v2/analysis-tasks.js.map +1 -0
- package/dist/src/tasks-v2/developer-tasks.d.ts +226 -0
- package/dist/src/tasks-v2/developer-tasks.d.ts.map +1 -0
- package/dist/src/tasks-v2/developer-tasks.js +322 -0
- package/dist/src/tasks-v2/developer-tasks.js.map +1 -0
- package/dist/src/tasks-v2/index.d.ts +3 -0
- package/dist/src/tasks-v2/index.d.ts.map +1 -0
- package/dist/src/tasks-v2/index.js +4 -0
- package/dist/src/tasks-v2/index.js.map +1 -0
- package/dist/src/tools-v2/edit-tools.d.ts +67 -0
- package/dist/src/tools-v2/edit-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/edit-tools.js +117 -0
- package/dist/src/tools-v2/edit-tools.js.map +1 -0
- package/dist/src/tools-v2/index.d.ts +6 -0
- package/dist/src/tools-v2/index.d.ts.map +1 -0
- package/dist/src/tools-v2/index.js +7 -0
- package/dist/src/tools-v2/index.js.map +1 -0
- package/dist/src/tools-v2/read-tools.d.ts +129 -0
- package/dist/src/tools-v2/read-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/read-tools.js +216 -0
- package/dist/src/tools-v2/read-tools.js.map +1 -0
- package/dist/src/tools-v2/search-tools.d.ts +73 -0
- package/dist/src/tools-v2/search-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/search-tools.js +132 -0
- package/dist/src/tools-v2/search-tools.js.map +1 -0
- package/dist/src/tools-v2/test-tools.d.ts +59 -0
- package/dist/src/tools-v2/test-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/test-tools.js +111 -0
- package/dist/src/tools-v2/test-tools.js.map +1 -0
- package/dist/src/tools-v2/workspace-context.d.ts +65 -0
- package/dist/src/tools-v2/workspace-context.d.ts.map +1 -0
- package/dist/src/tools-v2/workspace-context.js +336 -0
- package/dist/src/tools-v2/workspace-context.js.map +1 -0
- package/dist/src/ui/App.d.ts +9 -0
- package/dist/src/ui/App.d.ts.map +1 -0
- package/dist/src/ui/App.js +257 -0
- package/dist/src/ui/App.js.map +1 -0
- package/dist/src/ui/components/ChatPane.d.ts +12 -0
- package/dist/src/ui/components/ChatPane.d.ts.map +1 -0
- package/dist/src/ui/components/ChatPane.js +41 -0
- package/dist/src/ui/components/ChatPane.js.map +1 -0
- package/dist/src/ui/components/EventsPane.d.ts +12 -0
- package/dist/src/ui/components/EventsPane.d.ts.map +1 -0
- package/dist/src/ui/components/EventsPane.js +48 -0
- package/dist/src/ui/components/EventsPane.js.map +1 -0
- package/dist/src/ui/components/GoalsTasksPane.d.ts +18 -0
- package/dist/src/ui/components/GoalsTasksPane.d.ts.map +1 -0
- package/dist/src/ui/components/GoalsTasksPane.js +83 -0
- package/dist/src/ui/components/GoalsTasksPane.js.map +1 -0
- package/dist/src/ui/store.d.ts +74 -0
- package/dist/src/ui/store.d.ts.map +1 -0
- package/dist/src/ui/store.js +260 -0
- package/dist/src/ui/store.js.map +1 -0
- package/dist/tests/integration.test.d.ts +2 -0
- package/dist/tests/integration.test.d.ts.map +1 -0
- package/dist/tests/integration.test.js +415 -0
- package/dist/tests/integration.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/AICODER.png +0 -0
- package/docs/INTERACTIVE_CLI_GUIDE.md +201 -0
- package/docs/TUI_MOCKUP.md +180 -0
- package/package.json +52 -0
- package/src/config/providers.ts +174 -0
- package/src/config/session.ts +143 -0
- package/src/context/bm25.ts +173 -0
- package/src/context/code-search.ts +188 -0
- package/src/context/context-pack.ts +133 -0
- package/src/context/dependency-mapper.ts +72 -0
- package/src/context/index.ts +8 -0
- package/src/context/symbol-extractor.ts +149 -0
- package/src/context/test-mapper.ts +77 -0
- package/src/context/types.ts +69 -0
- package/src/context/workspace-indexer.ts +249 -0
- package/src/index.ts +5 -0
- package/src/registries.ts +118 -0
- package/src/runtime/budget-manager.ts +118 -0
- package/src/runtime/interactive-runtime.ts +423 -0
- package/src/tasks-v2/analysis-tasks.ts +311 -0
- package/src/tasks-v2/developer-tasks.ts +437 -0
- package/src/tasks-v2/index.ts +3 -0
- package/src/tools-v2/edit-tools.ts +153 -0
- package/src/tools-v2/index.ts +6 -0
- package/src/tools-v2/read-tools.ts +286 -0
- package/src/tools-v2/search-tools.ts +175 -0
- package/src/tools-v2/test-tools.ts +147 -0
- package/src/tools-v2/workspace-context.ts +428 -0
- package/src/ui/App.tsx +392 -0
- package/src/ui/components/ChatPane.tsx +84 -0
- package/src/ui/components/EventsPane.tsx +81 -0
- package/src/ui/components/GoalsTasksPane.tsx +149 -0
- package/src/ui/store.ts +362 -0
- package/tests/integration.test.ts +537 -0
- 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,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
|
+
}
|