icopilot 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +250 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/bin/icopilot.js +6 -0
- package/dist/acp/router.js +123 -0
- package/dist/acp/schema.js +53 -0
- package/dist/agents/aggregator.js +187 -0
- package/dist/agents/custom-agents.js +97 -0
- package/dist/agents/goal-driven.js +411 -0
- package/dist/agents/multi-repo.js +350 -0
- package/dist/agents/parallel-runner.js +181 -0
- package/dist/agents/router.js +144 -0
- package/dist/agents/self-heal.js +481 -0
- package/dist/agents/tdd-agent.js +278 -0
- package/dist/api/github-models.js +158 -0
- package/dist/bridge/ide-bridge.js +479 -0
- package/dist/cloud/routine-executor.js +34 -0
- package/dist/cloud/routine-scheduler.js +67 -0
- package/dist/cloud/routine-storage.js +297 -0
- package/dist/commands/acp-cmd.js +143 -0
- package/dist/commands/actions-cmd.js +624 -0
- package/dist/commands/agent-cmd.js +144 -0
- package/dist/commands/alias-cmd.js +132 -0
- package/dist/commands/bookmark-cmd.js +77 -0
- package/dist/commands/changelog-cmd.js +99 -0
- package/dist/commands/changes-cmd.js +120 -0
- package/dist/commands/clipboard-cmd.js +217 -0
- package/dist/commands/cloud-routine-cmd.js +265 -0
- package/dist/commands/codegen-cmd.js +544 -0
- package/dist/commands/compare-cmd.js +116 -0
- package/dist/commands/context-cmd.js +247 -0
- package/dist/commands/context-viz-cmd.js +43 -0
- package/dist/commands/conventions-cmd.js +116 -0
- package/dist/commands/cost-cmd.js +51 -0
- package/dist/commands/deps-cmd.js +294 -0
- package/dist/commands/diagram-cmd.js +658 -0
- package/dist/commands/diff-review-cmd.js +92 -0
- package/dist/commands/doc-cmd.js +412 -0
- package/dist/commands/doctor-cmd.js +152 -0
- package/dist/commands/editor-cmd.js +49 -0
- package/dist/commands/env-cmd.js +86 -0
- package/dist/commands/explain-cmd.js +78 -0
- package/dist/commands/explain-shell-cmd.js +22 -0
- package/dist/commands/explore-cmd.js +231 -0
- package/dist/commands/feedback-cmd.js +98 -0
- package/dist/commands/fix-cmd.js +17 -0
- package/dist/commands/generate-cmd.js +38 -0
- package/dist/commands/git-extra.js +197 -0
- package/dist/commands/git-log-cmd.js +98 -0
- package/dist/commands/git-undo-cmd.js +137 -0
- package/dist/commands/git.js +155 -0
- package/dist/commands/history-cmd.js +122 -0
- package/dist/commands/index-cmd.js +65 -0
- package/dist/commands/init-cmd.js +73 -0
- package/dist/commands/lint-cmd.js +133 -0
- package/dist/commands/memory-cmd.js +98 -0
- package/dist/commands/metrics-cmd.js +97 -0
- package/dist/commands/mode-prefix.js +30 -0
- package/dist/commands/multi-cmd.js +44 -0
- package/dist/commands/notify-cmd.js +204 -0
- package/dist/commands/profile-cmd.js +101 -0
- package/dist/commands/prompts.js +17 -0
- package/dist/commands/rag-cmd.js +60 -0
- package/dist/commands/readme-cmd.js +564 -0
- package/dist/commands/reasoning-cmd.js +34 -0
- package/dist/commands/refactor-cmd.js +96 -0
- package/dist/commands/release-cmd.js +450 -0
- package/dist/commands/repo-cmd.js +195 -0
- package/dist/commands/route-cmd.js +21 -0
- package/dist/commands/schedule-cmd.js +109 -0
- package/dist/commands/search-cmd.js +47 -0
- package/dist/commands/security-cmd.js +156 -0
- package/dist/commands/settings-cmd.js +238 -0
- package/dist/commands/skill-cmd.js +338 -0
- package/dist/commands/slash.js +2721 -0
- package/dist/commands/snippets-cmd.js +83 -0
- package/dist/commands/space-cmd.js +92 -0
- package/dist/commands/stash-cmd.js +156 -0
- package/dist/commands/stats-cmd.js +36 -0
- package/dist/commands/style-cmd.js +85 -0
- package/dist/commands/suggest-cmd.js +40 -0
- package/dist/commands/summary-cmd.js +138 -0
- package/dist/commands/task-cmd.js +58 -0
- package/dist/commands/team-memory-cmd.js +97 -0
- package/dist/commands/template-cmd.js +475 -0
- package/dist/commands/test-cmd.js +146 -0
- package/dist/commands/todo-cmd.js +172 -0
- package/dist/commands/tokens-cmd.js +277 -0
- package/dist/commands/trigger-cmd.js +147 -0
- package/dist/commands/undo-cmd.js +18 -0
- package/dist/commands/voice-cmd.js +89 -0
- package/dist/commands/watch-cmd.js +110 -0
- package/dist/commands/web-cmd.js +183 -0
- package/dist/commands/worktree-cmd.js +119 -0
- package/dist/config-profile.js +66 -0
- package/dist/config.js +288 -0
- package/dist/context/compactor.js +53 -0
- package/dist/context/dep-context.js +329 -0
- package/dist/context/file-refs.js +54 -0
- package/dist/context/git-context.js +229 -0
- package/dist/context/image-input.js +66 -0
- package/dist/context/memory.js +55 -0
- package/dist/context/persistent-memory.js +104 -0
- package/dist/context/pinned.js +96 -0
- package/dist/context/priority.js +150 -0
- package/dist/context/read-only.js +48 -0
- package/dist/context/smart-files.js +286 -0
- package/dist/context/team-memory.js +156 -0
- package/dist/extensions/loader.js +149 -0
- package/dist/extensions/marketplace.js +49 -0
- package/dist/extensions/slack-provider.js +181 -0
- package/dist/extensions/team.js +56 -0
- package/dist/extensions/teams-provider.js +222 -0
- package/dist/extensions/voice.js +18 -0
- package/dist/hooks/lifecycle.js +215 -0
- package/dist/hooks/precommit.js +463 -0
- package/dist/index/embeddings.js +23 -0
- package/dist/index/indexer.js +86 -0
- package/dist/index/retrieve.js +20 -0
- package/dist/index/store.js +95 -0
- package/dist/index.js +286 -0
- package/dist/intelligence/dead-code.js +457 -0
- package/dist/intelligence/error-watch.js +263 -0
- package/dist/intelligence/navigation.js +141 -0
- package/dist/intelligence/stack-trace.js +210 -0
- package/dist/intelligence/symbol-index.js +410 -0
- package/dist/knowledge/auto-memory.js +412 -0
- package/dist/knowledge/conventions.js +475 -0
- package/dist/knowledge/corrections.js +213 -0
- package/dist/knowledge/rag.js +450 -0
- package/dist/knowledge/style-learner.js +324 -0
- package/dist/logger.js +35 -0
- package/dist/mcp/client.js +144 -0
- package/dist/mcp/config.js +24 -0
- package/dist/mcp/index.js +89 -0
- package/dist/modes/auto-compact.js +20 -0
- package/dist/modes/autopilot.js +157 -0
- package/dist/modes/background.js +82 -0
- package/dist/modes/interactive.js +187 -0
- package/dist/modes/oneshot.js +36 -0
- package/dist/modes/tui.js +265 -0
- package/dist/modes/turn.js +342 -0
- package/dist/notifications/manager.js +107 -0
- package/dist/plugins/marketplace.js +244 -0
- package/dist/providers/custom-provider.js +298 -0
- package/dist/providers/local-model.js +121 -0
- package/dist/routing/profiles.js +44 -0
- package/dist/routing/router.js +18 -0
- package/dist/sandbox/container.js +151 -0
- package/dist/security/audit.js +237 -0
- package/dist/security/content-filter.js +449 -0
- package/dist/security/proxy.js +301 -0
- package/dist/security/retention.js +281 -0
- package/dist/security/roles.js +252 -0
- package/dist/server/api-server.js +679 -0
- package/dist/session/bookmarks.js +72 -0
- package/dist/session/cloud-session.js +291 -0
- package/dist/session/handoff.js +405 -0
- package/dist/session/manager.js +35 -0
- package/dist/session/session.js +296 -0
- package/dist/session/share.js +313 -0
- package/dist/session/undo-journal.js +91 -0
- package/dist/snippets/store.js +60 -0
- package/dist/spaces/space-config.js +156 -0
- package/dist/spaces/space.js +220 -0
- package/dist/stats/store.js +101 -0
- package/dist/tools/apply-patch.js +134 -0
- package/dist/tools/auto-check.js +218 -0
- package/dist/tools/diff-edit.js +150 -0
- package/dist/tools/diff-prompt.js +36 -0
- package/dist/tools/edit-file.js +66 -0
- package/dist/tools/file-ops.js +205 -0
- package/dist/tools/glob.js +17 -0
- package/dist/tools/grep.js +56 -0
- package/dist/tools/image.js +194 -0
- package/dist/tools/list-directory.js +228 -0
- package/dist/tools/memory.js +17 -0
- package/dist/tools/multi-edit.js +299 -0
- package/dist/tools/policy.js +95 -0
- package/dist/tools/registry.js +484 -0
- package/dist/tools/retry.js +74 -0
- package/dist/tools/run-in-terminal.js +162 -0
- package/dist/tools/safety.js +64 -0
- package/dist/tools/sandbox.js +15 -0
- package/dist/tools/search-symbols.js +212 -0
- package/dist/tools/shell.js +118 -0
- package/dist/tools/web.js +167 -0
- package/dist/ui/prompt.js +37 -0
- package/dist/ui/render.js +96 -0
- package/dist/ui/screen.js +13 -0
- package/dist/ui/theme.js +56 -0
- package/dist/util/browser.js +34 -0
- package/dist/util/completion.js +350 -0
- package/dist/util/cost.js +28 -0
- package/dist/util/keybindings.js +113 -0
- package/dist/util/lazy.js +26 -0
- package/dist/util/perf.js +25 -0
- package/dist/util/token-worker.js +11 -0
- package/dist/util/tokens.js +50 -0
- package/dist/workflows/builtins.js +128 -0
- package/dist/workflows/engine.js +496 -0
- package/dist/workflows/file-trigger.js +197 -0
- package/package.json +79 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const DEFAULT_PROFILE = {
|
|
4
|
+
indentation: '2 spaces',
|
|
5
|
+
quotes: 'single',
|
|
6
|
+
semicolons: 'always',
|
|
7
|
+
trailingComma: 'always',
|
|
8
|
+
namingConvention: 'camelCase',
|
|
9
|
+
importStyle: 'named imports, single block',
|
|
10
|
+
functionStyle: 'arrow',
|
|
11
|
+
commentStyle: 'line',
|
|
12
|
+
maxLineLength: 100,
|
|
13
|
+
};
|
|
14
|
+
const DEFAULT_STATS = () => ({
|
|
15
|
+
indentation: {},
|
|
16
|
+
quotes: {},
|
|
17
|
+
semicolons: {},
|
|
18
|
+
trailingComma: {},
|
|
19
|
+
namingConvention: {},
|
|
20
|
+
importForms: {},
|
|
21
|
+
importGroups: 0,
|
|
22
|
+
importFiles: 0,
|
|
23
|
+
functionStyle: {},
|
|
24
|
+
commentStyle: {},
|
|
25
|
+
lineLengths: [],
|
|
26
|
+
});
|
|
27
|
+
const IDENTIFIER_DECLARATION = /\b(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
28
|
+
export class StyleLearner {
|
|
29
|
+
profile;
|
|
30
|
+
stats;
|
|
31
|
+
constructor(profile = DEFAULT_PROFILE) {
|
|
32
|
+
this.profile = { ...profile };
|
|
33
|
+
this.stats = DEFAULT_STATS();
|
|
34
|
+
}
|
|
35
|
+
analyze(files) {
|
|
36
|
+
this.stats = DEFAULT_STATS();
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
this.learnFromFile(file);
|
|
39
|
+
}
|
|
40
|
+
this.profile = this.buildProfile();
|
|
41
|
+
return this.getProfile();
|
|
42
|
+
}
|
|
43
|
+
learnFromFile(filePath) {
|
|
44
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile())
|
|
45
|
+
return;
|
|
46
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
47
|
+
this.collectIndentation(source);
|
|
48
|
+
this.collectQuotes(source);
|
|
49
|
+
this.collectSemicolons(source);
|
|
50
|
+
this.collectTrailingCommas(source);
|
|
51
|
+
this.collectNaming(source);
|
|
52
|
+
this.collectFunctions(source);
|
|
53
|
+
this.collectImports(source);
|
|
54
|
+
this.collectComments(source);
|
|
55
|
+
this.collectLineLengths(source);
|
|
56
|
+
this.profile = this.buildProfile();
|
|
57
|
+
}
|
|
58
|
+
getProfile() {
|
|
59
|
+
return { ...this.profile };
|
|
60
|
+
}
|
|
61
|
+
toPromptContext() {
|
|
62
|
+
const profile = this.profile;
|
|
63
|
+
return [
|
|
64
|
+
'Follow the project style profile when generating code:',
|
|
65
|
+
`- Indentation: ${profile.indentation}`,
|
|
66
|
+
`- Quotes: ${profile.quotes}`,
|
|
67
|
+
`- Semicolons: ${profile.semicolons}`,
|
|
68
|
+
`- Trailing commas: ${profile.trailingComma}`,
|
|
69
|
+
`- Naming convention: ${profile.namingConvention}`,
|
|
70
|
+
`- Imports: ${profile.importStyle}`,
|
|
71
|
+
`- Functions: prefer ${profile.functionStyle}`,
|
|
72
|
+
`- Comments: prefer ${profile.commentStyle} comments`,
|
|
73
|
+
`- Keep lines around ${profile.maxLineLength} characters when practical`,
|
|
74
|
+
].join('\n');
|
|
75
|
+
}
|
|
76
|
+
save(filePath) {
|
|
77
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
78
|
+
const document = {
|
|
79
|
+
profile: this.profile,
|
|
80
|
+
stats: this.stats,
|
|
81
|
+
};
|
|
82
|
+
fs.writeFileSync(filePath, `${JSON.stringify(document, null, 2)}\n`, 'utf8');
|
|
83
|
+
}
|
|
84
|
+
load(filePath) {
|
|
85
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
86
|
+
if (isStyleProfileDocument(parsed)) {
|
|
87
|
+
this.profile = { ...DEFAULT_PROFILE, ...parsed.profile };
|
|
88
|
+
this.stats = parsed.stats ? normalizeStats(parsed.stats) : DEFAULT_STATS();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.profile = { ...DEFAULT_PROFILE, ...parsed };
|
|
92
|
+
this.stats = DEFAULT_STATS();
|
|
93
|
+
}
|
|
94
|
+
buildProfile() {
|
|
95
|
+
const importForm = dominant(this.stats.importForms, 'named');
|
|
96
|
+
const groupedImports = this.stats.importFiles > 0 && this.stats.importGroups / this.stats.importFiles > 1;
|
|
97
|
+
return {
|
|
98
|
+
indentation: dominant(this.stats.indentation, DEFAULT_PROFILE.indentation),
|
|
99
|
+
quotes: dominant(this.stats.quotes, DEFAULT_PROFILE.quotes),
|
|
100
|
+
semicolons: dominant(this.stats.semicolons, DEFAULT_PROFILE.semicolons),
|
|
101
|
+
trailingComma: dominant(this.stats.trailingComma, DEFAULT_PROFILE.trailingComma),
|
|
102
|
+
namingConvention: dominant(this.stats.namingConvention, DEFAULT_PROFILE.namingConvention),
|
|
103
|
+
importStyle: `${importForm} imports, ${groupedImports ? 'grouped' : 'single block'}`,
|
|
104
|
+
functionStyle: dominant(this.stats.functionStyle, DEFAULT_PROFILE.functionStyle),
|
|
105
|
+
commentStyle: dominant(this.stats.commentStyle, DEFAULT_PROFILE.commentStyle),
|
|
106
|
+
maxLineLength: inferLineLength(this.stats.lineLengths),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
collectIndentation(source) {
|
|
110
|
+
for (const line of source.split(/\r?\n/)) {
|
|
111
|
+
if (!line.trim())
|
|
112
|
+
continue;
|
|
113
|
+
const match = line.match(/^[\t ]+/);
|
|
114
|
+
if (!match)
|
|
115
|
+
continue;
|
|
116
|
+
const indent = match[0];
|
|
117
|
+
if (indent.includes('\t') && !indent.includes(' ')) {
|
|
118
|
+
bump(this.stats.indentation, 'tabs');
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (indent.includes(' ')) {
|
|
122
|
+
bump(this.stats.indentation, `${indent.length} spaces`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
collectQuotes(source) {
|
|
127
|
+
const single = source.match(/'([^'\\]|\\.)*'/g)?.length ?? 0;
|
|
128
|
+
const double = source.match(/"([^"\\]|\\.)*"/g)?.length ?? 0;
|
|
129
|
+
if (single > 0)
|
|
130
|
+
bump(this.stats.quotes, 'single', single);
|
|
131
|
+
if (double > 0)
|
|
132
|
+
bump(this.stats.quotes, 'double', double);
|
|
133
|
+
}
|
|
134
|
+
collectSemicolons(source) {
|
|
135
|
+
for (const line of source.split(/\r?\n/)) {
|
|
136
|
+
const trimmed = line.trim();
|
|
137
|
+
if (!looksLikeStatement(trimmed))
|
|
138
|
+
continue;
|
|
139
|
+
if (/;\s*(?:\/\/.*)?$/.test(trimmed)) {
|
|
140
|
+
bump(this.stats.semicolons, 'always');
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
bump(this.stats.semicolons, 'never');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
collectTrailingCommas(source) {
|
|
148
|
+
const lines = source.split(/\r?\n/);
|
|
149
|
+
for (let index = 0; index < lines.length - 1; index++) {
|
|
150
|
+
const current = lines[index].trimEnd();
|
|
151
|
+
const next = lines[index + 1].trimStart();
|
|
152
|
+
if (!current.trim() || !/^[}\]]/.test(next))
|
|
153
|
+
continue;
|
|
154
|
+
if (current.trim().endsWith(',')) {
|
|
155
|
+
bump(this.stats.trailingComma, 'always');
|
|
156
|
+
}
|
|
157
|
+
else if (/[A-Za-z0-9_'")\]}]$/.test(current.trim())) {
|
|
158
|
+
bump(this.stats.trailingComma, 'never');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
collectNaming(source) {
|
|
163
|
+
for (const match of source.matchAll(IDENTIFIER_DECLARATION)) {
|
|
164
|
+
const name = match[1];
|
|
165
|
+
const convention = classifyName(name);
|
|
166
|
+
if (convention)
|
|
167
|
+
bump(this.stats.namingConvention, convention);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
collectFunctions(source) {
|
|
171
|
+
const arrowCount = source.match(/=>/g)?.length ?? 0;
|
|
172
|
+
const functionCount = source.match(/\bfunction\b/g)?.length ?? 0;
|
|
173
|
+
if (arrowCount > 0)
|
|
174
|
+
bump(this.stats.functionStyle, 'arrow', arrowCount);
|
|
175
|
+
if (functionCount > 0)
|
|
176
|
+
bump(this.stats.functionStyle, 'function', functionCount);
|
|
177
|
+
}
|
|
178
|
+
collectImports(source) {
|
|
179
|
+
const importLines = source.match(/^import\s.+$/gm) ?? [];
|
|
180
|
+
if (importLines.length === 0)
|
|
181
|
+
return;
|
|
182
|
+
const blocks = source
|
|
183
|
+
.split(/\r?\n\r?\n+/)
|
|
184
|
+
.map((block) => block.trim())
|
|
185
|
+
.filter((block) => block.startsWith('import '));
|
|
186
|
+
this.stats.importGroups += blocks.length;
|
|
187
|
+
this.stats.importFiles += 1;
|
|
188
|
+
for (const line of importLines) {
|
|
189
|
+
const match = line.match(/^import\s+(.+?)\s+from\s+['"]/);
|
|
190
|
+
if (!match)
|
|
191
|
+
continue;
|
|
192
|
+
const specifier = match[1].trim();
|
|
193
|
+
if (specifier.startsWith('{')) {
|
|
194
|
+
bump(this.stats.importForms, 'named');
|
|
195
|
+
}
|
|
196
|
+
else if (specifier.includes('{')) {
|
|
197
|
+
bump(this.stats.importForms, 'mixed');
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
bump(this.stats.importForms, 'default');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
collectComments(source) {
|
|
205
|
+
const lineComments = source.match(/^\s*\/\/.+$/gm)?.length ?? 0;
|
|
206
|
+
const blockComments = source.match(/\/\*[\s\S]*?\*\//g)?.length ?? 0;
|
|
207
|
+
if (lineComments > 0)
|
|
208
|
+
bump(this.stats.commentStyle, 'line', lineComments);
|
|
209
|
+
if (blockComments > 0)
|
|
210
|
+
bump(this.stats.commentStyle, 'block', blockComments);
|
|
211
|
+
}
|
|
212
|
+
collectLineLengths(source) {
|
|
213
|
+
for (const line of source.split(/\r?\n/)) {
|
|
214
|
+
if (!line.trim())
|
|
215
|
+
continue;
|
|
216
|
+
this.stats.lineLengths.push(line.length);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export function resolveStyleProfilePath(cwd) {
|
|
221
|
+
return path.join(cwd, '.icopilot', 'style-profile.json');
|
|
222
|
+
}
|
|
223
|
+
export function loadStyleProfile(cwd) {
|
|
224
|
+
const profilePath = resolveStyleProfilePath(cwd);
|
|
225
|
+
if (!fs.existsSync(profilePath))
|
|
226
|
+
return null;
|
|
227
|
+
const learner = new StyleLearner();
|
|
228
|
+
learner.load(profilePath);
|
|
229
|
+
return learner.getProfile();
|
|
230
|
+
}
|
|
231
|
+
export function loadStylePromptContext(cwd) {
|
|
232
|
+
const profilePath = resolveStyleProfilePath(cwd);
|
|
233
|
+
if (!fs.existsSync(profilePath))
|
|
234
|
+
return null;
|
|
235
|
+
try {
|
|
236
|
+
const learner = new StyleLearner();
|
|
237
|
+
learner.load(profilePath);
|
|
238
|
+
return learner.toPromptContext();
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
export function resetStyleProfile(cwd) {
|
|
245
|
+
const profilePath = resolveStyleProfilePath(cwd);
|
|
246
|
+
if (!fs.existsSync(profilePath))
|
|
247
|
+
return false;
|
|
248
|
+
fs.rmSync(profilePath, { force: true });
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
function bump(counter, key, amount = 1) {
|
|
252
|
+
counter[key] = (counter[key] ?? 0) + amount;
|
|
253
|
+
}
|
|
254
|
+
function dominant(counter, fallback) {
|
|
255
|
+
let winner = fallback;
|
|
256
|
+
let score = -1;
|
|
257
|
+
for (const [key, value] of Object.entries(counter)) {
|
|
258
|
+
if (value > score) {
|
|
259
|
+
winner = key;
|
|
260
|
+
score = value;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return winner;
|
|
264
|
+
}
|
|
265
|
+
function inferLineLength(lengths) {
|
|
266
|
+
if (lengths.length === 0)
|
|
267
|
+
return DEFAULT_PROFILE.maxLineLength;
|
|
268
|
+
const sorted = [...lengths].sort((left, right) => left - right);
|
|
269
|
+
const percentile = sorted[Math.max(0, Math.floor(sorted.length * 0.9) - 1)];
|
|
270
|
+
const rounded = Math.ceil(percentile / 10) * 10;
|
|
271
|
+
const common = [80, 88, 90, 100, 120];
|
|
272
|
+
let closest = rounded;
|
|
273
|
+
let closestDistance = Number.POSITIVE_INFINITY;
|
|
274
|
+
for (const candidate of common) {
|
|
275
|
+
const distance = Math.abs(candidate - percentile);
|
|
276
|
+
if (distance < closestDistance) {
|
|
277
|
+
closest = candidate;
|
|
278
|
+
closestDistance = distance;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return closestDistance <= 12 ? closest : Math.max(80, rounded);
|
|
282
|
+
}
|
|
283
|
+
function classifyName(name) {
|
|
284
|
+
if (!name || /^[A-Z0-9_]+$/.test(name))
|
|
285
|
+
return null;
|
|
286
|
+
if (/^[a-z][A-Za-z0-9]*$/.test(name))
|
|
287
|
+
return 'camelCase';
|
|
288
|
+
if (/^[A-Z][A-Za-z0-9]*$/.test(name))
|
|
289
|
+
return 'PascalCase';
|
|
290
|
+
if (/^[a-z][a-z0-9_]*$/.test(name) && name.includes('_'))
|
|
291
|
+
return 'snake_case';
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
function looksLikeStatement(line) {
|
|
295
|
+
if (!line)
|
|
296
|
+
return false;
|
|
297
|
+
if (line.startsWith('//') || line.startsWith('/*') || line.startsWith('*'))
|
|
298
|
+
return false;
|
|
299
|
+
if (/^[{}[\],]+$/.test(line))
|
|
300
|
+
return false;
|
|
301
|
+
if (/[{:,]$/.test(line))
|
|
302
|
+
return false;
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
function isStyleProfileDocument(value) {
|
|
306
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
307
|
+
return false;
|
|
308
|
+
return 'profile' in value;
|
|
309
|
+
}
|
|
310
|
+
function normalizeStats(stats) {
|
|
311
|
+
return {
|
|
312
|
+
indentation: { ...stats.indentation },
|
|
313
|
+
quotes: { ...stats.quotes },
|
|
314
|
+
semicolons: { ...stats.semicolons },
|
|
315
|
+
trailingComma: { ...stats.trailingComma },
|
|
316
|
+
namingConvention: { ...stats.namingConvention },
|
|
317
|
+
importForms: { ...stats.importForms },
|
|
318
|
+
importGroups: Number(stats.importGroups) || 0,
|
|
319
|
+
importFiles: Number(stats.importFiles) || 0,
|
|
320
|
+
functionStyle: { ...stats.functionStyle },
|
|
321
|
+
commentStyle: { ...stats.commentStyle },
|
|
322
|
+
lineLengths: Array.isArray(stats.lineLengths) ? stats.lineLengths.filter(Number.isFinite) : [],
|
|
323
|
+
};
|
|
324
|
+
}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { config } from './config.js';
|
|
2
|
+
const LEVELS = {
|
|
3
|
+
debug: 10,
|
|
4
|
+
info: 20,
|
|
5
|
+
warn: 30,
|
|
6
|
+
error: 40,
|
|
7
|
+
};
|
|
8
|
+
export class Logger {
|
|
9
|
+
redact(text) {
|
|
10
|
+
return text
|
|
11
|
+
.replace(/ghp_[A-Za-z0-9_]{20,}/g, '***REDACTED***')
|
|
12
|
+
.replace(/gho_[A-Za-z0-9_]{20,}/g, '***REDACTED***')
|
|
13
|
+
.replace(/Bearer\s+[A-Za-z0-9._-]+/g, 'Bearer ***REDACTED***')
|
|
14
|
+
.replace(/(?<="token":")[^"]+/g, '***REDACTED***');
|
|
15
|
+
}
|
|
16
|
+
debug(message, fields) {
|
|
17
|
+
this.write('debug', message, fields);
|
|
18
|
+
}
|
|
19
|
+
info(message, fields) {
|
|
20
|
+
this.write('info', message, fields);
|
|
21
|
+
}
|
|
22
|
+
warn(message, fields) {
|
|
23
|
+
this.write('warn', message, fields);
|
|
24
|
+
}
|
|
25
|
+
error(message, fields) {
|
|
26
|
+
this.write('error', message, fields);
|
|
27
|
+
}
|
|
28
|
+
write(level, message, fields) {
|
|
29
|
+
if (LEVELS[level] < LEVELS[config.logLevel])
|
|
30
|
+
return;
|
|
31
|
+
const suffix = fields ? ` ${this.redact(JSON.stringify(fields))}` : '';
|
|
32
|
+
process.stderr.write(`[${new Date().toISOString()}] ${level.toUpperCase()} ${this.redact(message)}${suffix}\n`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export const logger = new Logger();
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { Buffer } from 'node:buffer';
|
|
3
|
+
export class McpClient {
|
|
4
|
+
name;
|
|
5
|
+
config;
|
|
6
|
+
proc = null;
|
|
7
|
+
nextId = 1;
|
|
8
|
+
pending = new Map();
|
|
9
|
+
buffer = Buffer.alloc(0);
|
|
10
|
+
constructor(name, config) {
|
|
11
|
+
this.name = name;
|
|
12
|
+
this.config = config;
|
|
13
|
+
}
|
|
14
|
+
async start() {
|
|
15
|
+
if (this.proc)
|
|
16
|
+
return;
|
|
17
|
+
this.proc = spawn(this.config.command, this.config.args || [], {
|
|
18
|
+
cwd: this.config.cwd,
|
|
19
|
+
env: { ...process.env, ...(this.config.env || {}) },
|
|
20
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
21
|
+
windowsHide: true,
|
|
22
|
+
});
|
|
23
|
+
this.proc.stdout.on('data', (chunk) => this.onData(chunk));
|
|
24
|
+
this.proc.stderr.on('data', (chunk) => {
|
|
25
|
+
process.stderr.write(`[MCP/${this.name}] ${chunk.toString()}`);
|
|
26
|
+
});
|
|
27
|
+
this.proc.on('error', (err) => this.rejectAll(err));
|
|
28
|
+
this.proc.on('exit', (code) => {
|
|
29
|
+
this.rejectAll(new Error(`MCP server ${this.name} exited (${code ?? 'signal'})`));
|
|
30
|
+
this.proc = null;
|
|
31
|
+
});
|
|
32
|
+
await this.request('initialize', {
|
|
33
|
+
protocolVersion: '2024-11-05',
|
|
34
|
+
capabilities: {},
|
|
35
|
+
clientInfo: { name: 'icopilot', version: '0.4.0' },
|
|
36
|
+
});
|
|
37
|
+
this.notify('notifications/initialized', {});
|
|
38
|
+
}
|
|
39
|
+
async listTools() {
|
|
40
|
+
const result = await this.request('tools/list', {});
|
|
41
|
+
return Array.isArray(result?.tools) ? result.tools : [];
|
|
42
|
+
}
|
|
43
|
+
async callTool(name, args) {
|
|
44
|
+
const result = await this.request('tools/call', {
|
|
45
|
+
name,
|
|
46
|
+
arguments: args && typeof args === 'object' ? args : {},
|
|
47
|
+
});
|
|
48
|
+
if (Array.isArray(result?.content)) {
|
|
49
|
+
return result.content
|
|
50
|
+
.map((item) => {
|
|
51
|
+
if (typeof item?.text === 'string')
|
|
52
|
+
return item.text;
|
|
53
|
+
return JSON.stringify(item);
|
|
54
|
+
})
|
|
55
|
+
.join('\n');
|
|
56
|
+
}
|
|
57
|
+
return typeof result === 'string' ? result : JSON.stringify(result);
|
|
58
|
+
}
|
|
59
|
+
stop() {
|
|
60
|
+
this.rejectAll(new Error(`MCP server ${this.name} stopped`));
|
|
61
|
+
this.proc?.kill();
|
|
62
|
+
this.proc = null;
|
|
63
|
+
}
|
|
64
|
+
request(method, params) {
|
|
65
|
+
if (!this.proc)
|
|
66
|
+
throw new Error(`MCP server ${this.name} is not running`);
|
|
67
|
+
const id = this.nextId++;
|
|
68
|
+
const payload = { jsonrpc: '2.0', id, method, params };
|
|
69
|
+
this.write(payload);
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
this.pending.delete(id);
|
|
73
|
+
reject(new Error(`MCP request timed out: ${method}`));
|
|
74
|
+
}, 10_000);
|
|
75
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
notify(method, params) {
|
|
79
|
+
if (!this.proc)
|
|
80
|
+
return;
|
|
81
|
+
this.write({ jsonrpc: '2.0', method, params });
|
|
82
|
+
}
|
|
83
|
+
write(payload) {
|
|
84
|
+
const json = JSON.stringify(payload);
|
|
85
|
+
this.proc?.stdin.write(json + '\n');
|
|
86
|
+
}
|
|
87
|
+
onData(chunk) {
|
|
88
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
89
|
+
while (this.buffer.length) {
|
|
90
|
+
const message = this.readMessage();
|
|
91
|
+
if (!message)
|
|
92
|
+
break;
|
|
93
|
+
this.handleMessage(message);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
readMessage() {
|
|
97
|
+
if (this.buffer[0] === 67) {
|
|
98
|
+
const headerEnd = this.buffer.indexOf('\r\n\r\n');
|
|
99
|
+
if (headerEnd === -1)
|
|
100
|
+
return null;
|
|
101
|
+
const header = this.buffer.subarray(0, headerEnd).toString('utf8');
|
|
102
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
103
|
+
if (!match) {
|
|
104
|
+
this.buffer = Buffer.alloc(0);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const length = Number(match[1]);
|
|
108
|
+
const start = headerEnd + 4;
|
|
109
|
+
if (this.buffer.length < start + length)
|
|
110
|
+
return null;
|
|
111
|
+
const body = this.buffer.subarray(start, start + length).toString('utf8');
|
|
112
|
+
this.buffer = this.buffer.subarray(start + length);
|
|
113
|
+
return JSON.parse(body);
|
|
114
|
+
}
|
|
115
|
+
const newline = this.buffer.indexOf('\n');
|
|
116
|
+
if (newline === -1)
|
|
117
|
+
return null;
|
|
118
|
+
const line = this.buffer.subarray(0, newline).toString('utf8').trim();
|
|
119
|
+
this.buffer = this.buffer.subarray(newline + 1);
|
|
120
|
+
if (!line)
|
|
121
|
+
return null;
|
|
122
|
+
return JSON.parse(line);
|
|
123
|
+
}
|
|
124
|
+
handleMessage(message) {
|
|
125
|
+
if (typeof message?.id !== 'number')
|
|
126
|
+
return;
|
|
127
|
+
const pending = this.pending.get(message.id);
|
|
128
|
+
if (!pending)
|
|
129
|
+
return;
|
|
130
|
+
this.pending.delete(message.id);
|
|
131
|
+
clearTimeout(pending.timer);
|
|
132
|
+
if (message.error)
|
|
133
|
+
pending.reject(new Error(message.error.message || 'MCP error'));
|
|
134
|
+
else
|
|
135
|
+
pending.resolve(message.result);
|
|
136
|
+
}
|
|
137
|
+
rejectAll(reason) {
|
|
138
|
+
for (const pending of this.pending.values()) {
|
|
139
|
+
clearTimeout(pending.timer);
|
|
140
|
+
pending.reject(reason);
|
|
141
|
+
}
|
|
142
|
+
this.pending.clear();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export function loadMcpConfigs(cwd) {
|
|
5
|
+
const user = readConfig(path.join(os.homedir(), '.icopilot', 'mcp.json'));
|
|
6
|
+
const project = readConfig(path.join(cwd, '.mcp.json'));
|
|
7
|
+
return {
|
|
8
|
+
servers: {
|
|
9
|
+
...user.servers,
|
|
10
|
+
...project.servers,
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function readConfig(file) {
|
|
15
|
+
try {
|
|
16
|
+
if (!fs.existsSync(file))
|
|
17
|
+
return { servers: {} };
|
|
18
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
19
|
+
return { servers: parsed.servers && typeof parsed.servers === 'object' ? parsed.servers : {} };
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return { servers: {} };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { config } from '../config.js';
|
|
2
|
+
import { McpClient } from './client.js';
|
|
3
|
+
import { loadMcpConfigs } from './config.js';
|
|
4
|
+
const clients = new Map();
|
|
5
|
+
const toolRoutes = new Map();
|
|
6
|
+
let loaded = false;
|
|
7
|
+
export async function loadMcpServers() {
|
|
8
|
+
if (loaded)
|
|
9
|
+
return;
|
|
10
|
+
loaded = true;
|
|
11
|
+
try {
|
|
12
|
+
const file = loadMcpConfigs(config.cwd);
|
|
13
|
+
await Promise.all(Object.entries(file.servers).map(async ([name, serverConfig]) => {
|
|
14
|
+
try {
|
|
15
|
+
const client = new McpClient(name, serverConfig);
|
|
16
|
+
await client.start();
|
|
17
|
+
clients.set(name, client);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
process.stderr.write(`[MCP/${name}] disabled: ${e?.message || e}\n`);
|
|
21
|
+
}
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
process.stderr.write(`[MCP] load failed: ${e?.message || e}\n`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function getMcpTools() {
|
|
29
|
+
try {
|
|
30
|
+
await loadMcpServers();
|
|
31
|
+
const schemas = [];
|
|
32
|
+
toolRoutes.clear();
|
|
33
|
+
await Promise.all([...clients.entries()].map(async ([server, client]) => {
|
|
34
|
+
try {
|
|
35
|
+
const tools = await client.listTools();
|
|
36
|
+
for (const tool of tools) {
|
|
37
|
+
const functionName = namespacedToolName(server, tool.name);
|
|
38
|
+
toolRoutes.set(functionName, { server, tool: tool.name });
|
|
39
|
+
schemas.push(toChatTool(functionName, server, tool));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
process.stderr.write(`[MCP/${server}] tools/list failed: ${e?.message || e}\n`);
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
return { schemas, dispatch };
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
process.stderr.write(`[MCP] tools unavailable: ${e?.message || e}\n`);
|
|
50
|
+
return { schemas: [], dispatch };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function shutdownMcp() {
|
|
54
|
+
for (const client of clients.values())
|
|
55
|
+
client.stop();
|
|
56
|
+
clients.clear();
|
|
57
|
+
toolRoutes.clear();
|
|
58
|
+
loaded = false;
|
|
59
|
+
}
|
|
60
|
+
async function dispatch(name, args) {
|
|
61
|
+
const route = toolRoutes.get(name);
|
|
62
|
+
if (!route)
|
|
63
|
+
return JSON.stringify({ error: `unknown MCP tool: ${name}` });
|
|
64
|
+
const client = clients.get(route.server);
|
|
65
|
+
if (!client)
|
|
66
|
+
return JSON.stringify({ error: `MCP server not loaded: ${route.server}` });
|
|
67
|
+
try {
|
|
68
|
+
return await client.callTool(route.tool, args);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
return JSON.stringify({ error: e?.message || String(e) });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function toChatTool(name, server, tool) {
|
|
75
|
+
return {
|
|
76
|
+
type: 'function',
|
|
77
|
+
function: {
|
|
78
|
+
name,
|
|
79
|
+
description: `[MCP/${server}] ${tool.description || tool.name}`,
|
|
80
|
+
parameters: tool.inputSchema || { type: 'object', properties: {} },
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function namespacedToolName(server, tool) {
|
|
85
|
+
return `mcp__${safeName(server)}__${safeName(tool)}`;
|
|
86
|
+
}
|
|
87
|
+
function safeName(name) {
|
|
88
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
89
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { compactSession } from '../context/compactor.js';
|
|
2
|
+
import { config } from '../config.js';
|
|
3
|
+
import { theme } from '../ui/theme.js';
|
|
4
|
+
export async function handlePostTurnContextBudget(session, signal) {
|
|
5
|
+
const used = session.tokenUsage();
|
|
6
|
+
const usageRatio = used / config.contextWindow;
|
|
7
|
+
if (config.autoCompact && usageRatio > config.autoCompactThreshold) {
|
|
8
|
+
const pct = (usageRatio * 100).toFixed(0);
|
|
9
|
+
process.stdout.write(theme.dim(`\n⚡ auto-compacting context (${pct}% full)...\n`));
|
|
10
|
+
const summary = await compactSession(session, signal);
|
|
11
|
+
session.compactInto(summary);
|
|
12
|
+
const freed = Math.max(0, used - session.tokenUsage());
|
|
13
|
+
process.stdout.write(theme.ok(`✔ auto-compacted. Freed ${freed} tokens.\n`));
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (usageRatio > config.contextWarn) {
|
|
17
|
+
process.stdout.write(theme.warn(`\n⚠ context ${(usageRatio * 100).toFixed(0)}% full — run /compact to free space.\n`));
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|