codeep 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +576 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.js +421 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.js +1406 -0
- package/dist/components/AgentProgress.d.ts +33 -0
- package/dist/components/AgentProgress.js +97 -0
- package/dist/components/Export.d.ts +8 -0
- package/dist/components/Export.js +27 -0
- package/dist/components/Help.d.ts +2 -0
- package/dist/components/Help.js +3 -0
- package/dist/components/Input.d.ts +9 -0
- package/dist/components/Input.js +89 -0
- package/dist/components/Loading.d.ts +9 -0
- package/dist/components/Loading.js +31 -0
- package/dist/components/Login.d.ts +7 -0
- package/dist/components/Login.js +77 -0
- package/dist/components/Logo.d.ts +8 -0
- package/dist/components/Logo.js +89 -0
- package/dist/components/LogoutPicker.d.ts +8 -0
- package/dist/components/LogoutPicker.js +61 -0
- package/dist/components/Message.d.ts +10 -0
- package/dist/components/Message.js +234 -0
- package/dist/components/MessageList.d.ts +10 -0
- package/dist/components/MessageList.js +8 -0
- package/dist/components/ProjectPermission.d.ts +7 -0
- package/dist/components/ProjectPermission.js +52 -0
- package/dist/components/Search.d.ts +10 -0
- package/dist/components/Search.js +30 -0
- package/dist/components/SessionPicker.d.ts +9 -0
- package/dist/components/SessionPicker.js +88 -0
- package/dist/components/Sessions.d.ts +12 -0
- package/dist/components/Sessions.js +102 -0
- package/dist/components/Settings.d.ts +7 -0
- package/dist/components/Settings.js +162 -0
- package/dist/components/Status.d.ts +2 -0
- package/dist/components/Status.js +12 -0
- package/dist/config/config.test.d.ts +1 -0
- package/dist/config/config.test.js +157 -0
- package/dist/config/index.d.ts +121 -0
- package/dist/config/index.js +555 -0
- package/dist/config/providers.d.ts +43 -0
- package/dist/config/providers.js +82 -0
- package/dist/config/providers.test.d.ts +1 -0
- package/dist/config/providers.test.js +132 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +38 -0
- package/dist/utils/agent.d.ts +37 -0
- package/dist/utils/agent.js +627 -0
- package/dist/utils/codeReview.d.ts +36 -0
- package/dist/utils/codeReview.js +390 -0
- package/dist/utils/context.d.ts +49 -0
- package/dist/utils/context.js +216 -0
- package/dist/utils/diffPreview.d.ts +57 -0
- package/dist/utils/diffPreview.js +335 -0
- package/dist/utils/export.d.ts +19 -0
- package/dist/utils/export.js +94 -0
- package/dist/utils/git.d.ts +85 -0
- package/dist/utils/git.js +399 -0
- package/dist/utils/git.test.d.ts +1 -0
- package/dist/utils/git.test.js +193 -0
- package/dist/utils/history.d.ts +93 -0
- package/dist/utils/history.js +348 -0
- package/dist/utils/interactive.d.ts +34 -0
- package/dist/utils/interactive.js +206 -0
- package/dist/utils/keychain.d.ts +17 -0
- package/dist/utils/keychain.js +160 -0
- package/dist/utils/learning.d.ts +89 -0
- package/dist/utils/learning.js +330 -0
- package/dist/utils/logger.d.ts +33 -0
- package/dist/utils/logger.js +130 -0
- package/dist/utils/project.d.ts +86 -0
- package/dist/utils/project.js +415 -0
- package/dist/utils/project.test.d.ts +1 -0
- package/dist/utils/project.test.js +212 -0
- package/dist/utils/ratelimit.d.ts +26 -0
- package/dist/utils/ratelimit.js +132 -0
- package/dist/utils/ratelimit.test.d.ts +1 -0
- package/dist/utils/ratelimit.test.js +131 -0
- package/dist/utils/retry.d.ts +28 -0
- package/dist/utils/retry.js +109 -0
- package/dist/utils/retry.test.d.ts +1 -0
- package/dist/utils/retry.test.js +163 -0
- package/dist/utils/search.d.ts +11 -0
- package/dist/utils/search.js +29 -0
- package/dist/utils/shell.d.ts +45 -0
- package/dist/utils/shell.js +242 -0
- package/dist/utils/skills.d.ts +144 -0
- package/dist/utils/skills.js +1137 -0
- package/dist/utils/smartContext.d.ts +29 -0
- package/dist/utils/smartContext.js +441 -0
- package/dist/utils/tools.d.ts +224 -0
- package/dist/utils/tools.js +731 -0
- package/dist/utils/update.d.ts +22 -0
- package/dist/utils/update.js +128 -0
- package/dist/utils/validation.d.ts +28 -0
- package/dist/utils/validation.js +141 -0
- package/dist/utils/validation.test.d.ts +1 -0
- package/dist/utils/validation.test.js +164 -0
- package/dist/utils/verify.d.ts +78 -0
- package/dist/utils/verify.js +464 -0
- package/package.json +68 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import keytar from 'keytar';
|
|
2
|
+
import { logger } from './logger';
|
|
3
|
+
const SERVICE_NAME = 'codeep';
|
|
4
|
+
class KeychainStorage {
|
|
5
|
+
getAccountName(providerId) {
|
|
6
|
+
return `api-key-${providerId}`;
|
|
7
|
+
}
|
|
8
|
+
async getApiKey(providerId) {
|
|
9
|
+
try {
|
|
10
|
+
const account = this.getAccountName(providerId);
|
|
11
|
+
const password = await keytar.getPassword(SERVICE_NAME, account);
|
|
12
|
+
return password;
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
logger.debug(`Failed to get API key from keychain: ${error}`);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async setApiKey(providerId, apiKey) {
|
|
20
|
+
try {
|
|
21
|
+
const account = this.getAccountName(providerId);
|
|
22
|
+
await keytar.setPassword(SERVICE_NAME, account, apiKey);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw new Error(`Failed to store API key in keychain: ${error}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async deleteApiKey(providerId) {
|
|
29
|
+
try {
|
|
30
|
+
const account = this.getAccountName(providerId);
|
|
31
|
+
await keytar.deletePassword(SERVICE_NAME, account);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
logger.debug(`Failed to delete API key from keychain: ${error}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async hasApiKey(providerId) {
|
|
38
|
+
const key = await this.getApiKey(providerId);
|
|
39
|
+
return key !== null && key.length > 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Fallback to plain text storage in config
|
|
44
|
+
* Used when keychain is not available or fails
|
|
45
|
+
*/
|
|
46
|
+
class FallbackStorage {
|
|
47
|
+
config;
|
|
48
|
+
constructor(config) {
|
|
49
|
+
this.config = config;
|
|
50
|
+
}
|
|
51
|
+
async getApiKey(providerId) {
|
|
52
|
+
const keys = this.config.get('apiKeys') || {};
|
|
53
|
+
return keys[providerId] || null;
|
|
54
|
+
}
|
|
55
|
+
async setApiKey(providerId, apiKey) {
|
|
56
|
+
const keys = this.config.get('apiKeys') || {};
|
|
57
|
+
keys[providerId] = apiKey;
|
|
58
|
+
this.config.set('apiKeys', keys);
|
|
59
|
+
}
|
|
60
|
+
async deleteApiKey(providerId) {
|
|
61
|
+
const keys = this.config.get('apiKeys') || {};
|
|
62
|
+
delete keys[providerId];
|
|
63
|
+
this.config.set('apiKeys', keys);
|
|
64
|
+
}
|
|
65
|
+
async hasApiKey(providerId) {
|
|
66
|
+
const key = await this.getApiKey(providerId);
|
|
67
|
+
return key !== null && key.length > 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Smart storage that tries keychain first, falls back to config
|
|
72
|
+
*/
|
|
73
|
+
class SmartStorage {
|
|
74
|
+
keychain;
|
|
75
|
+
fallback;
|
|
76
|
+
useKeychain = true;
|
|
77
|
+
keychainTested = false;
|
|
78
|
+
constructor(config) {
|
|
79
|
+
this.keychain = new KeychainStorage();
|
|
80
|
+
this.fallback = new FallbackStorage(config);
|
|
81
|
+
}
|
|
82
|
+
async ensureKeychainTested() {
|
|
83
|
+
if (this.keychainTested)
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
const testKey = '__codeep_test__';
|
|
87
|
+
await keytar.setPassword(SERVICE_NAME, testKey, 'test');
|
|
88
|
+
await keytar.deletePassword(SERVICE_NAME, testKey);
|
|
89
|
+
this.useKeychain = true;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
this.useKeychain = false;
|
|
93
|
+
// Silently fallback - don't warn user
|
|
94
|
+
}
|
|
95
|
+
this.keychainTested = true;
|
|
96
|
+
}
|
|
97
|
+
async getApiKey(providerId) {
|
|
98
|
+
await this.ensureKeychainTested();
|
|
99
|
+
if (this.useKeychain) {
|
|
100
|
+
const key = await this.keychain.getApiKey(providerId);
|
|
101
|
+
if (key)
|
|
102
|
+
return key;
|
|
103
|
+
}
|
|
104
|
+
return this.fallback.getApiKey(providerId);
|
|
105
|
+
}
|
|
106
|
+
async setApiKey(providerId, apiKey) {
|
|
107
|
+
await this.ensureKeychainTested();
|
|
108
|
+
if (this.useKeychain) {
|
|
109
|
+
try {
|
|
110
|
+
await this.keychain.setApiKey(providerId, apiKey);
|
|
111
|
+
// Remove from fallback if it was there
|
|
112
|
+
await this.fallback.deleteApiKey(providerId);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
// Keychain failed, fall back to config
|
|
117
|
+
this.useKeychain = false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
await this.fallback.setApiKey(providerId, apiKey);
|
|
121
|
+
}
|
|
122
|
+
async deleteApiKey(providerId) {
|
|
123
|
+
await this.ensureKeychainTested();
|
|
124
|
+
if (this.useKeychain) {
|
|
125
|
+
await this.keychain.deleteApiKey(providerId);
|
|
126
|
+
}
|
|
127
|
+
await this.fallback.deleteApiKey(providerId);
|
|
128
|
+
}
|
|
129
|
+
async hasApiKey(providerId) {
|
|
130
|
+
await this.ensureKeychainTested();
|
|
131
|
+
if (this.useKeychain) {
|
|
132
|
+
const hasKeychain = await this.keychain.hasApiKey(providerId);
|
|
133
|
+
if (hasKeychain)
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
return this.fallback.hasApiKey(providerId);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Migrate existing plain-text API keys to keychain
|
|
141
|
+
*/
|
|
142
|
+
export async function migrateApiKeysToKeychain(config) {
|
|
143
|
+
const storage = new SmartStorage(config);
|
|
144
|
+
const apiKeys = config.get('apiKeys') || {};
|
|
145
|
+
for (const [providerId, apiKey] of Object.entries(apiKeys)) {
|
|
146
|
+
if (typeof apiKey === 'string' && apiKey.length > 0) {
|
|
147
|
+
try {
|
|
148
|
+
await storage.setApiKey(providerId, apiKey);
|
|
149
|
+
logger.info(`Migrated API key for ${providerId} to secure storage`);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
logger.error(`Failed to migrate API key for ${providerId}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Export singleton
|
|
158
|
+
export function createSecureStorage(config) {
|
|
159
|
+
return new SmartStorage(config);
|
|
160
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning Mode - remember user preferences and coding style
|
|
3
|
+
*/
|
|
4
|
+
export interface UserPreferences {
|
|
5
|
+
codeStyle: {
|
|
6
|
+
indentation: 'tabs' | 'spaces';
|
|
7
|
+
indentSize: number;
|
|
8
|
+
quotes: 'single' | 'double';
|
|
9
|
+
semicolons: boolean;
|
|
10
|
+
trailingComma: 'none' | 'es5' | 'all';
|
|
11
|
+
lineWidth: number;
|
|
12
|
+
};
|
|
13
|
+
naming: {
|
|
14
|
+
variables: 'camelCase' | 'snake_case' | 'PascalCase';
|
|
15
|
+
functions: 'camelCase' | 'snake_case' | 'PascalCase';
|
|
16
|
+
classes: 'PascalCase' | 'camelCase';
|
|
17
|
+
constants: 'UPPER_CASE' | 'camelCase';
|
|
18
|
+
files: 'kebab-case' | 'camelCase' | 'PascalCase' | 'snake_case';
|
|
19
|
+
};
|
|
20
|
+
frameworks: {
|
|
21
|
+
frontend?: string;
|
|
22
|
+
backend?: string;
|
|
23
|
+
testing?: string;
|
|
24
|
+
styling?: string;
|
|
25
|
+
stateManagement?: string;
|
|
26
|
+
};
|
|
27
|
+
languages: {
|
|
28
|
+
primary?: string;
|
|
29
|
+
preferTypeScript: boolean;
|
|
30
|
+
preferAsyncAwait: boolean;
|
|
31
|
+
};
|
|
32
|
+
patterns: {
|
|
33
|
+
importStyle?: 'named' | 'default' | 'mixed';
|
|
34
|
+
exportStyle?: 'named' | 'default' | 'mixed';
|
|
35
|
+
componentStyle?: 'functional' | 'class';
|
|
36
|
+
errorHandling?: 'try-catch' | 'then-catch' | 'result-type';
|
|
37
|
+
};
|
|
38
|
+
preferredLibraries: string[];
|
|
39
|
+
customRules: string[];
|
|
40
|
+
lastUpdated: number;
|
|
41
|
+
sampleCount: number;
|
|
42
|
+
}
|
|
43
|
+
export interface ProjectPreferences extends UserPreferences {
|
|
44
|
+
projectPath: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Load global preferences
|
|
48
|
+
*/
|
|
49
|
+
export declare function loadGlobalPreferences(): UserPreferences;
|
|
50
|
+
/**
|
|
51
|
+
* Save global preferences
|
|
52
|
+
*/
|
|
53
|
+
export declare function saveGlobalPreferences(prefs: UserPreferences): void;
|
|
54
|
+
/**
|
|
55
|
+
* Load project-specific preferences
|
|
56
|
+
*/
|
|
57
|
+
export declare function loadProjectPreferences(projectRoot: string): UserPreferences;
|
|
58
|
+
/**
|
|
59
|
+
* Save project-specific preferences
|
|
60
|
+
*/
|
|
61
|
+
export declare function saveProjectPreferences(projectRoot: string, prefs: Partial<UserPreferences>): void;
|
|
62
|
+
/**
|
|
63
|
+
* Analyze code to learn preferences
|
|
64
|
+
*/
|
|
65
|
+
export declare function learnFromCode(code: string, filename: string, currentPrefs: UserPreferences): Partial<UserPreferences>;
|
|
66
|
+
/**
|
|
67
|
+
* Learn from multiple files in a project
|
|
68
|
+
*/
|
|
69
|
+
export declare function learnFromProject(projectRoot: string, files: string[]): UserPreferences;
|
|
70
|
+
/**
|
|
71
|
+
* Format preferences for system prompt
|
|
72
|
+
*/
|
|
73
|
+
export declare function formatPreferencesForPrompt(prefs: UserPreferences): string;
|
|
74
|
+
/**
|
|
75
|
+
* Add a custom rule
|
|
76
|
+
*/
|
|
77
|
+
export declare function addCustomRule(rule: string, projectRoot?: string): void;
|
|
78
|
+
/**
|
|
79
|
+
* Remove a custom rule
|
|
80
|
+
*/
|
|
81
|
+
export declare function removeCustomRule(rule: string, projectRoot?: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Reset preferences
|
|
84
|
+
*/
|
|
85
|
+
export declare function resetPreferences(projectRoot?: string): void;
|
|
86
|
+
/**
|
|
87
|
+
* Get learning status
|
|
88
|
+
*/
|
|
89
|
+
export declare function getLearningStatus(projectRoot?: string): string;
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning Mode - remember user preferences and coding style
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
// Learning data storage
|
|
8
|
+
const LEARNING_DIR = join(homedir(), '.codeep', 'learning');
|
|
9
|
+
const GLOBAL_PREFS_FILE = join(LEARNING_DIR, 'preferences.json');
|
|
10
|
+
const DEFAULT_PREFERENCES = {
|
|
11
|
+
codeStyle: {
|
|
12
|
+
indentation: 'spaces',
|
|
13
|
+
indentSize: 2,
|
|
14
|
+
quotes: 'single',
|
|
15
|
+
semicolons: true,
|
|
16
|
+
trailingComma: 'es5',
|
|
17
|
+
lineWidth: 100,
|
|
18
|
+
},
|
|
19
|
+
naming: {
|
|
20
|
+
variables: 'camelCase',
|
|
21
|
+
functions: 'camelCase',
|
|
22
|
+
classes: 'PascalCase',
|
|
23
|
+
constants: 'UPPER_CASE',
|
|
24
|
+
files: 'kebab-case',
|
|
25
|
+
},
|
|
26
|
+
frameworks: {},
|
|
27
|
+
languages: {
|
|
28
|
+
preferTypeScript: true,
|
|
29
|
+
preferAsyncAwait: true,
|
|
30
|
+
},
|
|
31
|
+
patterns: {},
|
|
32
|
+
preferredLibraries: [],
|
|
33
|
+
customRules: [],
|
|
34
|
+
lastUpdated: Date.now(),
|
|
35
|
+
sampleCount: 0,
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Ensure learning directory exists
|
|
39
|
+
*/
|
|
40
|
+
function ensureLearningDir() {
|
|
41
|
+
if (!existsSync(LEARNING_DIR)) {
|
|
42
|
+
mkdirSync(LEARNING_DIR, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get project-specific preferences file path
|
|
47
|
+
*/
|
|
48
|
+
function getProjectPrefsPath(projectRoot) {
|
|
49
|
+
const projectId = Buffer.from(projectRoot).toString('base64url').substring(0, 32);
|
|
50
|
+
return join(LEARNING_DIR, `project-${projectId}.json`);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Load global preferences
|
|
54
|
+
*/
|
|
55
|
+
export function loadGlobalPreferences() {
|
|
56
|
+
ensureLearningDir();
|
|
57
|
+
if (existsSync(GLOBAL_PREFS_FILE)) {
|
|
58
|
+
try {
|
|
59
|
+
const content = readFileSync(GLOBAL_PREFS_FILE, 'utf-8');
|
|
60
|
+
return { ...DEFAULT_PREFERENCES, ...JSON.parse(content) };
|
|
61
|
+
}
|
|
62
|
+
catch { }
|
|
63
|
+
}
|
|
64
|
+
return { ...DEFAULT_PREFERENCES };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Save global preferences
|
|
68
|
+
*/
|
|
69
|
+
export function saveGlobalPreferences(prefs) {
|
|
70
|
+
ensureLearningDir();
|
|
71
|
+
prefs.lastUpdated = Date.now();
|
|
72
|
+
writeFileSync(GLOBAL_PREFS_FILE, JSON.stringify(prefs, null, 2));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Load project-specific preferences
|
|
76
|
+
*/
|
|
77
|
+
export function loadProjectPreferences(projectRoot) {
|
|
78
|
+
const globalPrefs = loadGlobalPreferences();
|
|
79
|
+
const projectPrefsPath = getProjectPrefsPath(projectRoot);
|
|
80
|
+
if (existsSync(projectPrefsPath)) {
|
|
81
|
+
try {
|
|
82
|
+
const content = readFileSync(projectPrefsPath, 'utf-8');
|
|
83
|
+
const projectPrefs = JSON.parse(content);
|
|
84
|
+
// Merge with global, project takes precedence
|
|
85
|
+
return { ...globalPrefs, ...projectPrefs };
|
|
86
|
+
}
|
|
87
|
+
catch { }
|
|
88
|
+
}
|
|
89
|
+
return globalPrefs;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Save project-specific preferences
|
|
93
|
+
*/
|
|
94
|
+
export function saveProjectPreferences(projectRoot, prefs) {
|
|
95
|
+
ensureLearningDir();
|
|
96
|
+
const projectPrefsPath = getProjectPrefsPath(projectRoot);
|
|
97
|
+
const existing = existsSync(projectPrefsPath)
|
|
98
|
+
? JSON.parse(readFileSync(projectPrefsPath, 'utf-8'))
|
|
99
|
+
: {};
|
|
100
|
+
const merged = { ...existing, ...prefs, lastUpdated: Date.now() };
|
|
101
|
+
writeFileSync(projectPrefsPath, JSON.stringify(merged, null, 2));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Analyze code to learn preferences
|
|
105
|
+
*/
|
|
106
|
+
export function learnFromCode(code, filename, currentPrefs) {
|
|
107
|
+
const learned = {};
|
|
108
|
+
const codeStyle = {};
|
|
109
|
+
const naming = {};
|
|
110
|
+
const patterns = {};
|
|
111
|
+
const lines = code.split('\n');
|
|
112
|
+
// Learn indentation
|
|
113
|
+
const indentedLines = lines.filter(l => l.match(/^(\s+)\S/));
|
|
114
|
+
if (indentedLines.length > 0) {
|
|
115
|
+
const firstIndent = indentedLines[0].match(/^(\s+)/)?.[1] || '';
|
|
116
|
+
if (firstIndent.includes('\t')) {
|
|
117
|
+
codeStyle.indentation = 'tabs';
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
codeStyle.indentation = 'spaces';
|
|
121
|
+
codeStyle.indentSize = firstIndent.length;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Learn quote style
|
|
125
|
+
const singleQuotes = (code.match(/'/g) || []).length;
|
|
126
|
+
const doubleQuotes = (code.match(/"/g) || []).length;
|
|
127
|
+
if (singleQuotes > doubleQuotes * 1.5) {
|
|
128
|
+
codeStyle.quotes = 'single';
|
|
129
|
+
}
|
|
130
|
+
else if (doubleQuotes > singleQuotes * 1.5) {
|
|
131
|
+
codeStyle.quotes = 'double';
|
|
132
|
+
}
|
|
133
|
+
// Learn semicolon usage
|
|
134
|
+
const withSemicolons = (code.match(/;\s*$/gm) || []).length;
|
|
135
|
+
const statements = lines.filter(l => l.trim() && !l.trim().startsWith('//')).length;
|
|
136
|
+
codeStyle.semicolons = withSemicolons > statements * 0.5;
|
|
137
|
+
// Learn trailing comma
|
|
138
|
+
if (code.includes(',\n]') || code.includes(',\n}')) {
|
|
139
|
+
codeStyle.trailingComma = 'all';
|
|
140
|
+
}
|
|
141
|
+
// Learn naming conventions from variable declarations
|
|
142
|
+
const varMatches = code.matchAll(/(?:const|let|var)\s+(\w+)/g);
|
|
143
|
+
let camelCount = 0;
|
|
144
|
+
let snakeCount = 0;
|
|
145
|
+
for (const match of varMatches) {
|
|
146
|
+
const name = match[1];
|
|
147
|
+
if (name.includes('_'))
|
|
148
|
+
snakeCount++;
|
|
149
|
+
else if (/^[a-z]/.test(name) && /[A-Z]/.test(name))
|
|
150
|
+
camelCount++;
|
|
151
|
+
}
|
|
152
|
+
if (camelCount > snakeCount * 2) {
|
|
153
|
+
naming.variables = 'camelCase';
|
|
154
|
+
}
|
|
155
|
+
else if (snakeCount > camelCount * 2) {
|
|
156
|
+
naming.variables = 'snake_case';
|
|
157
|
+
}
|
|
158
|
+
// Learn import style
|
|
159
|
+
const namedImports = (code.match(/import\s*{[^}]+}\s*from/g) || []).length;
|
|
160
|
+
const defaultImports = (code.match(/import\s+\w+\s+from/g) || []).length;
|
|
161
|
+
if (namedImports > defaultImports * 2) {
|
|
162
|
+
patterns.importStyle = 'named';
|
|
163
|
+
}
|
|
164
|
+
else if (defaultImports > namedImports * 2) {
|
|
165
|
+
patterns.importStyle = 'default';
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
patterns.importStyle = 'mixed';
|
|
169
|
+
}
|
|
170
|
+
// Learn component style (React)
|
|
171
|
+
if (code.includes('function') && code.includes('return') && code.includes('<')) {
|
|
172
|
+
patterns.componentStyle = 'functional';
|
|
173
|
+
}
|
|
174
|
+
else if (code.includes('class') && code.includes('extends') && code.includes('render')) {
|
|
175
|
+
patterns.componentStyle = 'class';
|
|
176
|
+
}
|
|
177
|
+
// Learn async/await vs then/catch
|
|
178
|
+
const asyncAwait = (code.match(/async|await/g) || []).length;
|
|
179
|
+
const thenCatch = (code.match(/\.then\(|\.catch\(/g) || []).length;
|
|
180
|
+
if (asyncAwait > thenCatch) {
|
|
181
|
+
learned.languages = { ...currentPrefs.languages, preferAsyncAwait: true };
|
|
182
|
+
}
|
|
183
|
+
// Learn preferred libraries
|
|
184
|
+
const libraries = [];
|
|
185
|
+
const importMatches = code.matchAll(/import\s+.*?\s+from\s+['"]([^'"./][^'"]*)['"]/g);
|
|
186
|
+
for (const match of importMatches) {
|
|
187
|
+
const lib = match[1].split('/')[0];
|
|
188
|
+
if (!libraries.includes(lib)) {
|
|
189
|
+
libraries.push(lib);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (libraries.length > 0) {
|
|
193
|
+
learned.preferredLibraries = [
|
|
194
|
+
...new Set([...currentPrefs.preferredLibraries, ...libraries]),
|
|
195
|
+
].slice(0, 20);
|
|
196
|
+
}
|
|
197
|
+
// Merge learned styles
|
|
198
|
+
if (Object.keys(codeStyle).length > 0) {
|
|
199
|
+
learned.codeStyle = { ...currentPrefs.codeStyle, ...codeStyle };
|
|
200
|
+
}
|
|
201
|
+
if (Object.keys(naming).length > 0) {
|
|
202
|
+
learned.naming = { ...currentPrefs.naming, ...naming };
|
|
203
|
+
}
|
|
204
|
+
if (Object.keys(patterns).length > 0) {
|
|
205
|
+
learned.patterns = { ...currentPrefs.patterns, ...patterns };
|
|
206
|
+
}
|
|
207
|
+
learned.sampleCount = (currentPrefs.sampleCount || 0) + 1;
|
|
208
|
+
return learned;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Learn from multiple files in a project
|
|
212
|
+
*/
|
|
213
|
+
export function learnFromProject(projectRoot, files) {
|
|
214
|
+
let prefs = loadProjectPreferences(projectRoot);
|
|
215
|
+
for (const file of files.slice(0, 20)) { // Limit to 20 files
|
|
216
|
+
try {
|
|
217
|
+
const content = readFileSync(join(projectRoot, file), 'utf-8');
|
|
218
|
+
const learned = learnFromCode(content, file, prefs);
|
|
219
|
+
prefs = { ...prefs, ...learned };
|
|
220
|
+
}
|
|
221
|
+
catch { }
|
|
222
|
+
}
|
|
223
|
+
saveProjectPreferences(projectRoot, prefs);
|
|
224
|
+
return prefs;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Format preferences for system prompt
|
|
228
|
+
*/
|
|
229
|
+
export function formatPreferencesForPrompt(prefs) {
|
|
230
|
+
const lines = ['## User Preferences (Learned)', ''];
|
|
231
|
+
// Code style
|
|
232
|
+
lines.push('### Code Style');
|
|
233
|
+
lines.push(`- Indentation: ${prefs.codeStyle.indentSize} ${prefs.codeStyle.indentation}`);
|
|
234
|
+
lines.push(`- Quotes: ${prefs.codeStyle.quotes}`);
|
|
235
|
+
lines.push(`- Semicolons: ${prefs.codeStyle.semicolons ? 'yes' : 'no'}`);
|
|
236
|
+
lines.push(`- Trailing comma: ${prefs.codeStyle.trailingComma}`);
|
|
237
|
+
lines.push('');
|
|
238
|
+
// Naming
|
|
239
|
+
lines.push('### Naming Conventions');
|
|
240
|
+
lines.push(`- Variables: ${prefs.naming.variables}`);
|
|
241
|
+
lines.push(`- Functions: ${prefs.naming.functions}`);
|
|
242
|
+
lines.push(`- Classes: ${prefs.naming.classes}`);
|
|
243
|
+
lines.push(`- Files: ${prefs.naming.files}`);
|
|
244
|
+
lines.push('');
|
|
245
|
+
// Patterns
|
|
246
|
+
if (Object.keys(prefs.patterns).length > 0) {
|
|
247
|
+
lines.push('### Patterns');
|
|
248
|
+
if (prefs.patterns.importStyle)
|
|
249
|
+
lines.push(`- Import style: ${prefs.patterns.importStyle}`);
|
|
250
|
+
if (prefs.patterns.componentStyle)
|
|
251
|
+
lines.push(`- Components: ${prefs.patterns.componentStyle}`);
|
|
252
|
+
if (prefs.patterns.errorHandling)
|
|
253
|
+
lines.push(`- Error handling: ${prefs.patterns.errorHandling}`);
|
|
254
|
+
lines.push('');
|
|
255
|
+
}
|
|
256
|
+
// Libraries
|
|
257
|
+
if (prefs.preferredLibraries.length > 0) {
|
|
258
|
+
lines.push('### Preferred Libraries');
|
|
259
|
+
lines.push(prefs.preferredLibraries.slice(0, 10).join(', '));
|
|
260
|
+
lines.push('');
|
|
261
|
+
}
|
|
262
|
+
// Custom rules
|
|
263
|
+
if (prefs.customRules.length > 0) {
|
|
264
|
+
lines.push('### Custom Rules');
|
|
265
|
+
for (const rule of prefs.customRules) {
|
|
266
|
+
lines.push(`- ${rule}`);
|
|
267
|
+
}
|
|
268
|
+
lines.push('');
|
|
269
|
+
}
|
|
270
|
+
lines.push('Follow these preferences when writing code.');
|
|
271
|
+
return lines.join('\n');
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Add a custom rule
|
|
275
|
+
*/
|
|
276
|
+
export function addCustomRule(rule, projectRoot) {
|
|
277
|
+
if (projectRoot) {
|
|
278
|
+
const prefs = loadProjectPreferences(projectRoot);
|
|
279
|
+
prefs.customRules = [...new Set([...prefs.customRules, rule])];
|
|
280
|
+
saveProjectPreferences(projectRoot, prefs);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
const prefs = loadGlobalPreferences();
|
|
284
|
+
prefs.customRules = [...new Set([...prefs.customRules, rule])];
|
|
285
|
+
saveGlobalPreferences(prefs);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Remove a custom rule
|
|
290
|
+
*/
|
|
291
|
+
export function removeCustomRule(rule, projectRoot) {
|
|
292
|
+
if (projectRoot) {
|
|
293
|
+
const prefs = loadProjectPreferences(projectRoot);
|
|
294
|
+
prefs.customRules = prefs.customRules.filter(r => r !== rule);
|
|
295
|
+
saveProjectPreferences(projectRoot, prefs);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const prefs = loadGlobalPreferences();
|
|
299
|
+
prefs.customRules = prefs.customRules.filter(r => r !== rule);
|
|
300
|
+
saveGlobalPreferences(prefs);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Reset preferences
|
|
305
|
+
*/
|
|
306
|
+
export function resetPreferences(projectRoot) {
|
|
307
|
+
if (projectRoot) {
|
|
308
|
+
const projectPrefsPath = getProjectPrefsPath(projectRoot);
|
|
309
|
+
if (existsSync(projectPrefsPath)) {
|
|
310
|
+
writeFileSync(projectPrefsPath, JSON.stringify({}, null, 2));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
saveGlobalPreferences({ ...DEFAULT_PREFERENCES });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Get learning status
|
|
319
|
+
*/
|
|
320
|
+
export function getLearningStatus(projectRoot) {
|
|
321
|
+
const prefs = projectRoot
|
|
322
|
+
? loadProjectPreferences(projectRoot)
|
|
323
|
+
: loadGlobalPreferences();
|
|
324
|
+
const lines = [];
|
|
325
|
+
lines.push(`Samples analyzed: ${prefs.sampleCount || 0}`);
|
|
326
|
+
lines.push(`Last updated: ${new Date(prefs.lastUpdated).toLocaleString()}`);
|
|
327
|
+
lines.push(`Libraries known: ${prefs.preferredLibraries.length}`);
|
|
328
|
+
lines.push(`Custom rules: ${prefs.customRules.length}`);
|
|
329
|
+
return lines.join('\n');
|
|
330
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Set current project path for local logging
|
|
3
|
+
*/
|
|
4
|
+
export declare function setLogProjectPath(projectPath: string | null): void;
|
|
5
|
+
/**
|
|
6
|
+
* Logger API
|
|
7
|
+
*/
|
|
8
|
+
export declare const logger: {
|
|
9
|
+
info: (message: string, data?: any) => void;
|
|
10
|
+
warn: (message: string, data?: any) => void;
|
|
11
|
+
error: (message: string, data?: any) => void;
|
|
12
|
+
debug: (message: string, data?: any) => void;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Log API request (both global and local)
|
|
16
|
+
*/
|
|
17
|
+
export declare function logApiRequest(provider: string, model: string, messageCount: number): void;
|
|
18
|
+
/**
|
|
19
|
+
* Log API response (both global and local)
|
|
20
|
+
*/
|
|
21
|
+
export declare function logApiResponse(provider: string, success: boolean, responseLength?: number, error?: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Log session operation (local only - project-specific)
|
|
24
|
+
*/
|
|
25
|
+
export declare function logSession(operation: 'save' | 'load' | 'delete' | 'rename', sessionName: string, success: boolean): void;
|
|
26
|
+
/**
|
|
27
|
+
* Log application startup
|
|
28
|
+
*/
|
|
29
|
+
export declare function logStartup(version: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* Log application error
|
|
32
|
+
*/
|
|
33
|
+
export declare function logAppError(error: Error, context?: string): void;
|