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.
Files changed (203) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/LICENSE +21 -0
  3. package/README.md +214 -0
  4. package/bin/icopilot.js +6 -0
  5. package/dist/acp/router.js +123 -0
  6. package/dist/acp/schema.js +53 -0
  7. package/dist/agents/aggregator.js +187 -0
  8. package/dist/agents/custom-agents.js +97 -0
  9. package/dist/agents/goal-driven.js +411 -0
  10. package/dist/agents/multi-repo.js +350 -0
  11. package/dist/agents/parallel-runner.js +181 -0
  12. package/dist/agents/router.js +144 -0
  13. package/dist/agents/self-heal.js +481 -0
  14. package/dist/agents/tdd-agent.js +278 -0
  15. package/dist/api/github-models.js +158 -0
  16. package/dist/bridge/ide-bridge.js +479 -0
  17. package/dist/cloud/routine-executor.js +34 -0
  18. package/dist/cloud/routine-scheduler.js +67 -0
  19. package/dist/cloud/routine-storage.js +297 -0
  20. package/dist/commands/acp-cmd.js +143 -0
  21. package/dist/commands/actions-cmd.js +624 -0
  22. package/dist/commands/agent-cmd.js +144 -0
  23. package/dist/commands/alias-cmd.js +132 -0
  24. package/dist/commands/bookmark-cmd.js +77 -0
  25. package/dist/commands/changelog-cmd.js +99 -0
  26. package/dist/commands/changes-cmd.js +120 -0
  27. package/dist/commands/clipboard-cmd.js +217 -0
  28. package/dist/commands/cloud-routine-cmd.js +265 -0
  29. package/dist/commands/codegen-cmd.js +544 -0
  30. package/dist/commands/compare-cmd.js +116 -0
  31. package/dist/commands/context-cmd.js +247 -0
  32. package/dist/commands/context-viz-cmd.js +43 -0
  33. package/dist/commands/conventions-cmd.js +116 -0
  34. package/dist/commands/cost-cmd.js +51 -0
  35. package/dist/commands/deps-cmd.js +294 -0
  36. package/dist/commands/diagram-cmd.js +658 -0
  37. package/dist/commands/diff-review-cmd.js +92 -0
  38. package/dist/commands/doc-cmd.js +412 -0
  39. package/dist/commands/doctor-cmd.js +152 -0
  40. package/dist/commands/editor-cmd.js +49 -0
  41. package/dist/commands/env-cmd.js +86 -0
  42. package/dist/commands/explain-cmd.js +78 -0
  43. package/dist/commands/explain-shell-cmd.js +22 -0
  44. package/dist/commands/explore-cmd.js +231 -0
  45. package/dist/commands/feedback-cmd.js +98 -0
  46. package/dist/commands/fix-cmd.js +17 -0
  47. package/dist/commands/generate-cmd.js +38 -0
  48. package/dist/commands/git-extra.js +197 -0
  49. package/dist/commands/git-log-cmd.js +98 -0
  50. package/dist/commands/git-undo-cmd.js +137 -0
  51. package/dist/commands/git.js +155 -0
  52. package/dist/commands/history-cmd.js +122 -0
  53. package/dist/commands/index-cmd.js +65 -0
  54. package/dist/commands/init-cmd.js +73 -0
  55. package/dist/commands/lint-cmd.js +133 -0
  56. package/dist/commands/memory-cmd.js +98 -0
  57. package/dist/commands/metrics-cmd.js +97 -0
  58. package/dist/commands/mode-prefix.js +30 -0
  59. package/dist/commands/multi-cmd.js +44 -0
  60. package/dist/commands/notify-cmd.js +204 -0
  61. package/dist/commands/profile-cmd.js +101 -0
  62. package/dist/commands/prompts.js +17 -0
  63. package/dist/commands/rag-cmd.js +60 -0
  64. package/dist/commands/readme-cmd.js +564 -0
  65. package/dist/commands/reasoning-cmd.js +34 -0
  66. package/dist/commands/refactor-cmd.js +96 -0
  67. package/dist/commands/release-cmd.js +450 -0
  68. package/dist/commands/repo-cmd.js +195 -0
  69. package/dist/commands/route-cmd.js +21 -0
  70. package/dist/commands/schedule-cmd.js +109 -0
  71. package/dist/commands/search-cmd.js +47 -0
  72. package/dist/commands/security-cmd.js +156 -0
  73. package/dist/commands/settings-cmd.js +238 -0
  74. package/dist/commands/skill-cmd.js +338 -0
  75. package/dist/commands/slash.js +2721 -0
  76. package/dist/commands/snippets-cmd.js +83 -0
  77. package/dist/commands/space-cmd.js +92 -0
  78. package/dist/commands/stash-cmd.js +156 -0
  79. package/dist/commands/stats-cmd.js +36 -0
  80. package/dist/commands/style-cmd.js +85 -0
  81. package/dist/commands/suggest-cmd.js +40 -0
  82. package/dist/commands/summary-cmd.js +138 -0
  83. package/dist/commands/task-cmd.js +58 -0
  84. package/dist/commands/team-memory-cmd.js +97 -0
  85. package/dist/commands/template-cmd.js +475 -0
  86. package/dist/commands/test-cmd.js +146 -0
  87. package/dist/commands/todo-cmd.js +172 -0
  88. package/dist/commands/tokens-cmd.js +277 -0
  89. package/dist/commands/trigger-cmd.js +147 -0
  90. package/dist/commands/undo-cmd.js +18 -0
  91. package/dist/commands/voice-cmd.js +89 -0
  92. package/dist/commands/watch-cmd.js +110 -0
  93. package/dist/commands/web-cmd.js +183 -0
  94. package/dist/commands/worktree-cmd.js +119 -0
  95. package/dist/config-profile.js +66 -0
  96. package/dist/config.js +288 -0
  97. package/dist/context/compactor.js +53 -0
  98. package/dist/context/dep-context.js +329 -0
  99. package/dist/context/file-refs.js +54 -0
  100. package/dist/context/git-context.js +229 -0
  101. package/dist/context/image-input.js +66 -0
  102. package/dist/context/memory.js +55 -0
  103. package/dist/context/persistent-memory.js +104 -0
  104. package/dist/context/pinned.js +96 -0
  105. package/dist/context/priority.js +150 -0
  106. package/dist/context/read-only.js +48 -0
  107. package/dist/context/smart-files.js +286 -0
  108. package/dist/context/team-memory.js +156 -0
  109. package/dist/extensions/loader.js +149 -0
  110. package/dist/extensions/marketplace.js +49 -0
  111. package/dist/extensions/slack-provider.js +181 -0
  112. package/dist/extensions/team.js +56 -0
  113. package/dist/extensions/teams-provider.js +222 -0
  114. package/dist/extensions/voice.js +18 -0
  115. package/dist/hooks/lifecycle.js +215 -0
  116. package/dist/hooks/precommit.js +463 -0
  117. package/dist/index/embeddings.js +23 -0
  118. package/dist/index/indexer.js +86 -0
  119. package/dist/index/retrieve.js +20 -0
  120. package/dist/index/store.js +95 -0
  121. package/dist/index.js +286 -0
  122. package/dist/intelligence/dead-code.js +457 -0
  123. package/dist/intelligence/error-watch.js +263 -0
  124. package/dist/intelligence/navigation.js +141 -0
  125. package/dist/intelligence/stack-trace.js +210 -0
  126. package/dist/intelligence/symbol-index.js +410 -0
  127. package/dist/knowledge/auto-memory.js +412 -0
  128. package/dist/knowledge/conventions.js +475 -0
  129. package/dist/knowledge/corrections.js +213 -0
  130. package/dist/knowledge/rag.js +450 -0
  131. package/dist/knowledge/style-learner.js +324 -0
  132. package/dist/logger.js +35 -0
  133. package/dist/mcp/client.js +144 -0
  134. package/dist/mcp/config.js +24 -0
  135. package/dist/mcp/index.js +89 -0
  136. package/dist/modes/auto-compact.js +20 -0
  137. package/dist/modes/autopilot.js +157 -0
  138. package/dist/modes/background.js +82 -0
  139. package/dist/modes/interactive.js +187 -0
  140. package/dist/modes/oneshot.js +36 -0
  141. package/dist/modes/tui.js +265 -0
  142. package/dist/modes/turn.js +342 -0
  143. package/dist/notifications/manager.js +107 -0
  144. package/dist/plugins/marketplace.js +244 -0
  145. package/dist/providers/custom-provider.js +298 -0
  146. package/dist/providers/local-model.js +121 -0
  147. package/dist/routing/profiles.js +44 -0
  148. package/dist/routing/router.js +18 -0
  149. package/dist/sandbox/container.js +151 -0
  150. package/dist/security/audit.js +237 -0
  151. package/dist/security/content-filter.js +449 -0
  152. package/dist/security/proxy.js +301 -0
  153. package/dist/security/retention.js +281 -0
  154. package/dist/security/roles.js +252 -0
  155. package/dist/server/api-server.js +679 -0
  156. package/dist/session/bookmarks.js +72 -0
  157. package/dist/session/cloud-session.js +291 -0
  158. package/dist/session/handoff.js +405 -0
  159. package/dist/session/manager.js +35 -0
  160. package/dist/session/session.js +296 -0
  161. package/dist/session/share.js +313 -0
  162. package/dist/session/undo-journal.js +91 -0
  163. package/dist/snippets/store.js +60 -0
  164. package/dist/spaces/space-config.js +156 -0
  165. package/dist/spaces/space.js +220 -0
  166. package/dist/stats/store.js +101 -0
  167. package/dist/tools/apply-patch.js +134 -0
  168. package/dist/tools/auto-check.js +218 -0
  169. package/dist/tools/diff-edit.js +150 -0
  170. package/dist/tools/diff-prompt.js +36 -0
  171. package/dist/tools/edit-file.js +66 -0
  172. package/dist/tools/file-ops.js +205 -0
  173. package/dist/tools/glob.js +17 -0
  174. package/dist/tools/grep.js +56 -0
  175. package/dist/tools/image.js +194 -0
  176. package/dist/tools/list-directory.js +228 -0
  177. package/dist/tools/memory.js +17 -0
  178. package/dist/tools/multi-edit.js +299 -0
  179. package/dist/tools/policy.js +95 -0
  180. package/dist/tools/registry.js +484 -0
  181. package/dist/tools/retry.js +74 -0
  182. package/dist/tools/run-in-terminal.js +162 -0
  183. package/dist/tools/safety.js +64 -0
  184. package/dist/tools/sandbox.js +15 -0
  185. package/dist/tools/search-symbols.js +212 -0
  186. package/dist/tools/shell.js +118 -0
  187. package/dist/tools/web.js +167 -0
  188. package/dist/ui/prompt.js +37 -0
  189. package/dist/ui/render.js +96 -0
  190. package/dist/ui/screen.js +13 -0
  191. package/dist/ui/theme.js +56 -0
  192. package/dist/util/browser.js +34 -0
  193. package/dist/util/completion.js +350 -0
  194. package/dist/util/cost.js +28 -0
  195. package/dist/util/keybindings.js +113 -0
  196. package/dist/util/lazy.js +26 -0
  197. package/dist/util/perf.js +25 -0
  198. package/dist/util/token-worker.js +11 -0
  199. package/dist/util/tokens.js +50 -0
  200. package/dist/workflows/builtins.js +128 -0
  201. package/dist/workflows/engine.js +496 -0
  202. package/dist/workflows/file-trigger.js +197 -0
  203. package/package.json +79 -0
@@ -0,0 +1,263 @@
1
+ import { spawn } from 'node:child_process';
2
+ const TYPESCRIPT_PATTERNS = [
3
+ /^(?<file>.+?)\((?<line>\d+),(?<column>\d+)\):\s*(?<severity>error|warning)\s+(?<code>TS\d+):\s*(?<message>.+)$/i,
4
+ /^(?<file>.+?):(?<line>\d+):(?<column>\d+)\s*-\s*(?<severity>error|warning)\s+(?<code>TS\d+):\s*(?<message>.+)$/i,
5
+ /^(?<severity>error|warning)\s+(?<code>TS\d+):\s*(?<message>.+)$/i,
6
+ ];
7
+ const ESLINT_PATTERNS = [
8
+ /^(?<file>.+?):(?<line>\d+):(?<column>\d+):\s*(?<severity>error|warning)\s+(?<message>.+?)(?:\s+\((?<code>[^)]+)\))?$/i,
9
+ /^(?<file>.+?)\((?<line>\d+),(?<column>\d+)\):\s*(?<severity>error|warning)\s+(?<message>.+?)(?:\s+\((?<code>[^)]+)\))?$/i,
10
+ ];
11
+ const GENERIC_PATTERNS = [
12
+ /^(?<file>.+?):(?<line>\d+):(?<column>\d+)\s*[:|-]\s*(?<severity>error|warning)\s*:?\s*(?<message>.+)$/i,
13
+ /^(?<severity>error|warning)\s*[:|-]\s*(?<message>.+)$/i,
14
+ /^(?<severity>warning|error)\b.*?:\s*(?<message>.+)$/i,
15
+ /^(?<severity>error|warning)\b(?<message>.+)$/i,
16
+ ];
17
+ export class ErrorWatcher {
18
+ proc = null;
19
+ errors = [];
20
+ callbacks = new Set();
21
+ seen = new Set();
22
+ stdoutBuffer = '';
23
+ stderrBuffer = '';
24
+ command = null;
25
+ running = false;
26
+ start(command) {
27
+ const trimmed = command.trim();
28
+ if (!trimmed) {
29
+ throw new Error('command is required');
30
+ }
31
+ this.stop();
32
+ this.clear();
33
+ this.command = trimmed;
34
+ const { shell, args } = getShellInvocation(trimmed);
35
+ const child = spawn(shell, args, {
36
+ cwd: process.cwd(),
37
+ stdio: ['ignore', 'pipe', 'pipe'],
38
+ windowsHide: true,
39
+ });
40
+ this.proc = child;
41
+ this.running = true;
42
+ child.stdout?.setEncoding('utf8');
43
+ child.stderr?.setEncoding('utf8');
44
+ child.stdout?.on('data', (chunk) => this.consumeChunk('stdout', chunk));
45
+ child.stderr?.on('data', (chunk) => this.consumeChunk('stderr', chunk));
46
+ child.on('error', (error) => {
47
+ this.record({
48
+ severity: 'error',
49
+ message: `Failed to start watcher: ${error.message}`,
50
+ raw: error.message,
51
+ });
52
+ this.running = false;
53
+ this.proc = null;
54
+ });
55
+ child.on('close', () => {
56
+ this.flushBuffer('stdout');
57
+ this.flushBuffer('stderr');
58
+ this.running = false;
59
+ this.proc = null;
60
+ });
61
+ }
62
+ stop() {
63
+ const child = this.proc;
64
+ this.proc = null;
65
+ this.running = false;
66
+ this.command = null;
67
+ if (!child)
68
+ return;
69
+ child.kill('SIGTERM');
70
+ }
71
+ onError(callback) {
72
+ this.callbacks.add(callback);
73
+ }
74
+ getErrors() {
75
+ return this.errors.map((error) => ({ ...error }));
76
+ }
77
+ clear() {
78
+ this.errors = [];
79
+ this.seen.clear();
80
+ this.stdoutBuffer = '';
81
+ this.stderrBuffer = '';
82
+ }
83
+ isRunning() {
84
+ return this.running;
85
+ }
86
+ getCommand() {
87
+ return this.command;
88
+ }
89
+ consumeChunk(stream, chunk) {
90
+ const next = `${stream === 'stdout' ? this.stdoutBuffer : this.stderrBuffer}${chunk}`;
91
+ const lines = next.split(/\r?\n/);
92
+ const remainder = lines.pop() ?? '';
93
+ if (stream === 'stdout') {
94
+ this.stdoutBuffer = remainder;
95
+ }
96
+ else {
97
+ this.stderrBuffer = remainder;
98
+ }
99
+ for (const line of lines) {
100
+ this.processLine(line);
101
+ }
102
+ }
103
+ flushBuffer(stream) {
104
+ const buffer = stream === 'stdout' ? this.stdoutBuffer : this.stderrBuffer;
105
+ if (buffer.trim()) {
106
+ this.processLine(buffer);
107
+ }
108
+ if (stream === 'stdout') {
109
+ this.stdoutBuffer = '';
110
+ }
111
+ else {
112
+ this.stderrBuffer = '';
113
+ }
114
+ }
115
+ processLine(line) {
116
+ const parsed = parseErrorLine(line);
117
+ if (parsed) {
118
+ this.record(parsed);
119
+ }
120
+ }
121
+ record(error) {
122
+ const signature = JSON.stringify({
123
+ file: error.file,
124
+ line: error.line,
125
+ column: error.column,
126
+ severity: error.severity,
127
+ code: error.code,
128
+ message: error.message,
129
+ raw: error.raw,
130
+ });
131
+ if (this.seen.has(signature))
132
+ return;
133
+ this.seen.add(signature);
134
+ this.errors.push(error);
135
+ for (const callback of this.callbacks) {
136
+ callback({ ...error });
137
+ }
138
+ }
139
+ }
140
+ export function suggestFix(error) {
141
+ const location = error.file && error.line !== undefined
142
+ ? `${error.file}:${error.line}${error.column !== undefined ? `:${error.column}` : ''}`
143
+ : error.file || 'unknown location';
144
+ const hint = inferHint(error);
145
+ const lines = [
146
+ `Investigate and fix this ${error.severity}:`,
147
+ `Location: ${location}`,
148
+ error.code ? `Code: ${error.code}` : undefined,
149
+ `Message: ${error.message}`,
150
+ hint ? `Hint: ${hint}` : undefined,
151
+ 'Propose the smallest safe code change, explain why it fixes the issue, and avoid unrelated edits.',
152
+ ];
153
+ return lines.filter((line) => Boolean(line)).join('\n');
154
+ }
155
+ function parseErrorLine(rawLine) {
156
+ const raw = rawLine.trim();
157
+ if (!raw)
158
+ return null;
159
+ const parsed = matchPattern(raw, TYPESCRIPT_PATTERNS, normalizeTypeScriptError) ??
160
+ matchPattern(raw, ESLINT_PATTERNS, normalizeLintError) ??
161
+ matchPattern(raw, GENERIC_PATTERNS, normalizeGenericError);
162
+ return parsed ? { ...parsed, raw } : null;
163
+ }
164
+ function matchPattern(raw, patterns, normalize) {
165
+ for (const pattern of patterns) {
166
+ const match = pattern.exec(raw);
167
+ if (!match)
168
+ continue;
169
+ return normalize(match);
170
+ }
171
+ return null;
172
+ }
173
+ function normalizeTypeScriptError(match) {
174
+ const groups = match.groups;
175
+ if (!groups?.message || !groups.severity)
176
+ return null;
177
+ return {
178
+ file: groups.file,
179
+ line: toNumber(groups.line),
180
+ column: toNumber(groups.column),
181
+ severity: normalizeSeverity(groups.severity),
182
+ code: groups.code,
183
+ message: groups.message.trim(),
184
+ };
185
+ }
186
+ function normalizeLintError(match) {
187
+ const groups = match.groups;
188
+ if (!groups?.message || !groups.severity)
189
+ return null;
190
+ const lintCode = groups.code?.trim();
191
+ return {
192
+ file: groups.file,
193
+ line: toNumber(groups.line),
194
+ column: toNumber(groups.column),
195
+ severity: normalizeSeverity(groups.severity),
196
+ code: lintCode || extractTrailingRule(groups.message),
197
+ message: stripTrailingRule(groups.message),
198
+ };
199
+ }
200
+ function normalizeGenericError(match) {
201
+ const groups = match.groups;
202
+ if (!groups?.message || !groups.severity)
203
+ return null;
204
+ return {
205
+ file: groups.file,
206
+ line: toNumber(groups.line),
207
+ column: toNumber(groups.column),
208
+ severity: normalizeSeverity(groups.severity),
209
+ message: groups.message.trim(),
210
+ };
211
+ }
212
+ function normalizeSeverity(value) {
213
+ return value.toLowerCase() === 'warning' ? 'warning' : 'error';
214
+ }
215
+ function toNumber(value) {
216
+ if (!value)
217
+ return undefined;
218
+ const parsed = Number(value);
219
+ return Number.isFinite(parsed) ? parsed : undefined;
220
+ }
221
+ function stripTrailingRule(message) {
222
+ return message.replace(/\s+\(([^)]+)\)\s*$/, '').trim();
223
+ }
224
+ function extractTrailingRule(message) {
225
+ const explicitRule = /\s+\(([^)]+)\)\s*$/.exec(message)?.[1];
226
+ if (explicitRule)
227
+ return explicitRule;
228
+ const tokens = message.trim().split(/\s+/);
229
+ const candidate = tokens[tokens.length - 1];
230
+ return candidate && /^(?:@?[\w-]+\/)?[\w-]+$/.test(candidate) ? candidate : undefined;
231
+ }
232
+ function inferHint(error) {
233
+ const code = error.code?.toUpperCase();
234
+ const message = error.message.toLowerCase();
235
+ if (code === 'TS2304' || message.includes('cannot find name')) {
236
+ return 'Check for a missing import, a misspelled symbol, or a variable that is out of scope.';
237
+ }
238
+ if (code === 'TS2322' || code === 'TS2345' || message.includes('not assignable')) {
239
+ return 'Compare the expected and actual types, then narrow, convert, or update the declaration at the source.';
240
+ }
241
+ if (code?.startsWith('TS')) {
242
+ return 'Inspect the referenced TypeScript types, imports, and nearby declarations before changing behavior.';
243
+ }
244
+ if (error.code) {
245
+ return `Review the rule or diagnostic "${error.code}" and make the smallest change that satisfies it.`;
246
+ }
247
+ if (error.severity === 'warning') {
248
+ return 'Prefer a low-risk cleanup that addresses the warning without changing runtime behavior.';
249
+ }
250
+ return 'Trace the failing file and surrounding code path, then patch the root cause instead of silencing the symptom.';
251
+ }
252
+ function getShellInvocation(command) {
253
+ if (process.platform === 'win32') {
254
+ return {
255
+ shell: 'powershell.exe',
256
+ args: ['-NoProfile', '-Command', command],
257
+ };
258
+ }
259
+ return {
260
+ shell: 'bash',
261
+ args: ['-lc', command],
262
+ };
263
+ }
@@ -0,0 +1,141 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import fg from 'fast-glob';
4
+ const SOURCE_PATTERNS = ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'];
5
+ const DEFAULT_IGNORES = [
6
+ '**/node_modules/**',
7
+ '**/dist/**',
8
+ '**/.git/**',
9
+ '**/coverage/**',
10
+ '**/*.d.ts',
11
+ ];
12
+ export function goToDefinition(symbolName, rootDir) {
13
+ const escapedSymbol = escapeRegex(symbolName.trim());
14
+ if (!escapedSymbol)
15
+ return null;
16
+ const declarationRegex = new RegExp(String.raw `^\s*export\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|type|interface)\s+(${escapedSymbol})\b`);
17
+ const assignmentRegex = new RegExp(String.raw `\b(${escapedSymbol})\b\s*=(?!=)`);
18
+ for (const file of listSourceFiles(rootDir)) {
19
+ const content = safeRead(file);
20
+ if (content === undefined)
21
+ continue;
22
+ const lines = content.split(/\r?\n/u);
23
+ for (let index = 0; index < lines.length; index += 1) {
24
+ const line = lines[index] ?? '';
25
+ const declarationMatch = declarationRegex.exec(line);
26
+ if (declarationMatch) {
27
+ return createLocation(file, rootDir, index + 1, declarationMatch.index + declarationMatch[0].indexOf(declarationMatch[1]), line);
28
+ }
29
+ const assignmentMatch = assignmentRegex.exec(line);
30
+ if (assignmentMatch) {
31
+ return createLocation(file, rootDir, index + 1, assignmentMatch.index, line);
32
+ }
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ export function findReferences(symbolName, rootDir) {
38
+ const escapedSymbol = escapeRegex(symbolName.trim());
39
+ if (!escapedSymbol)
40
+ return [];
41
+ const referenceRegex = new RegExp(String.raw `\b${escapedSymbol}\b`, 'g');
42
+ const locations = [];
43
+ for (const file of listSourceFiles(rootDir)) {
44
+ const content = safeRead(file);
45
+ if (content === undefined)
46
+ continue;
47
+ const lines = content.split(/\r?\n/u);
48
+ for (let index = 0; index < lines.length; index += 1) {
49
+ const line = lines[index] ?? '';
50
+ if (isDefinitionLine(line, escapedSymbol))
51
+ continue;
52
+ referenceRegex.lastIndex = 0;
53
+ let match = referenceRegex.exec(line);
54
+ while (match) {
55
+ locations.push(createLocation(file, rootDir, index + 1, match.index, line));
56
+ match = referenceRegex.exec(line);
57
+ }
58
+ }
59
+ }
60
+ return sortLocations(locations);
61
+ }
62
+ export function findImplementations(interfaceName, rootDir) {
63
+ const escapedSymbol = escapeRegex(interfaceName.trim());
64
+ if (!escapedSymbol)
65
+ return [];
66
+ const implementsRegex = new RegExp(String.raw `\bclass\s+[A-Za-z_$][\w$]*\b[^{\n]*\bimplements\b[^{\n]*\b(${escapedSymbol})\b`);
67
+ const extendsRegex = new RegExp(String.raw `\bclass\s+[A-Za-z_$][\w$]*\b[^{\n]*\bextends\s+(${escapedSymbol})\b`);
68
+ const locations = [];
69
+ for (const file of listSourceFiles(rootDir)) {
70
+ const content = safeRead(file);
71
+ if (content === undefined)
72
+ continue;
73
+ const lines = content.split(/\r?\n/u);
74
+ for (let index = 0; index < lines.length; index += 1) {
75
+ const line = lines[index] ?? '';
76
+ const match = implementsRegex.exec(line) ?? extendsRegex.exec(line);
77
+ if (!match)
78
+ continue;
79
+ locations.push(createLocation(file, rootDir, index + 1, match.index + match[0].lastIndexOf(match[1]), line));
80
+ }
81
+ }
82
+ return sortLocations(locations);
83
+ }
84
+ function listSourceFiles(rootDir) {
85
+ const normalizedRoot = path.resolve(rootDir);
86
+ return fg
87
+ .sync(SOURCE_PATTERNS, {
88
+ cwd: normalizedRoot,
89
+ onlyFiles: true,
90
+ absolute: true,
91
+ unique: true,
92
+ dot: false,
93
+ ignore: [...DEFAULT_IGNORES, ...readGitignorePatterns(normalizedRoot)],
94
+ })
95
+ .map((file) => path.resolve(file))
96
+ .sort((left, right) => left.localeCompare(right));
97
+ }
98
+ function readGitignorePatterns(rootDir) {
99
+ const gitignorePath = path.join(rootDir, '.gitignore');
100
+ if (!fs.existsSync(gitignorePath))
101
+ return [];
102
+ return (safeRead(gitignorePath)
103
+ ?.split(/\r?\n/u)
104
+ .map((line) => line.trim())
105
+ .filter((line) => line && !line.startsWith('#') && !line.startsWith('!'))
106
+ .flatMap((line) => {
107
+ const normalized = line.replace(/^\.\//u, '').replace(/^\/+/u, '');
108
+ if (normalized.endsWith('/'))
109
+ return [normalized, `${normalized}**`];
110
+ return [normalized];
111
+ }) ?? []);
112
+ }
113
+ function isDefinitionLine(line, escapedSymbol) {
114
+ const declarationRegex = new RegExp(String.raw `^\s*export\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|type|interface)\s+${escapedSymbol}\b`);
115
+ const assignmentRegex = new RegExp(String.raw `\b${escapedSymbol}\b\s*=(?!=)`);
116
+ return declarationRegex.test(line) || assignmentRegex.test(line);
117
+ }
118
+ function createLocation(file, rootDir, line, zeroBasedColumn, context) {
119
+ return {
120
+ file: path.relative(rootDir, file),
121
+ line,
122
+ column: zeroBasedColumn + 1,
123
+ context: context.trim(),
124
+ };
125
+ }
126
+ function sortLocations(locations) {
127
+ return locations.sort((left, right) => left.file.localeCompare(right.file) ||
128
+ left.line - right.line ||
129
+ (left.column ?? 0) - (right.column ?? 0));
130
+ }
131
+ function safeRead(file) {
132
+ try {
133
+ return fs.readFileSync(file, 'utf8');
134
+ }
135
+ catch {
136
+ return undefined;
137
+ }
138
+ }
139
+ function escapeRegex(value) {
140
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
141
+ }
@@ -0,0 +1,210 @@
1
+ const UNKNOWN_FRAME = {
2
+ file: '<unknown>',
3
+ line: 0,
4
+ isNative: true,
5
+ isNodeModule: false,
6
+ };
7
+ export function parseStackTrace(text) {
8
+ const raw = normalizeStackTraceText(text);
9
+ const lines = raw.split('\n');
10
+ const frames = lines.map(parseFrame).filter((frame) => frame !== null);
11
+ const { type, error } = extractErrorMetadata(lines);
12
+ return {
13
+ error,
14
+ type,
15
+ frames,
16
+ raw,
17
+ };
18
+ }
19
+ export function analyzeStackTrace(trace) {
20
+ const isPython = trace.raw.includes('Traceback (most recent call last):');
21
+ const nonModuleFrames = trace.frames.filter((frame) => !frame.isNodeModule);
22
+ const userFrames = nonModuleFrames.filter((frame) => !frame.isNative);
23
+ const relevantFrames = orderRelevantFrames(userFrames.length ? userFrames : nonModuleFrames, isPython).slice(0, 6);
24
+ const rootCause = relevantFrames[0] ?? nonModuleFrames[0] ?? trace.frames[0] ?? UNKNOWN_FRAME;
25
+ const userFiles = uniqueFiles(userFrames);
26
+ return {
27
+ rootCause,
28
+ relevantFrames,
29
+ userFiles,
30
+ suggestion: buildSuggestion(trace, rootCause, userFiles),
31
+ };
32
+ }
33
+ export function formatForLLM(analysis) {
34
+ const lines = [
35
+ 'Stack trace analysis:',
36
+ `- Root cause frame: ${formatFrame(analysis.rootCause)}`,
37
+ `- Relevant frames (${analysis.relevantFrames.length}):`,
38
+ ...analysis.relevantFrames.map((frame) => ` - ${formatFrame(frame)}`),
39
+ `- User files (${analysis.userFiles.length}):`,
40
+ ...(analysis.userFiles.length
41
+ ? analysis.userFiles.map((file) => ` - ${file}`)
42
+ : [' - none detected']),
43
+ `- Suggested investigation: ${analysis.suggestion}`,
44
+ ];
45
+ return lines.join('\n');
46
+ }
47
+ function normalizeStackTraceText(text) {
48
+ return text
49
+ .trim()
50
+ .replace(/\r\n/g, '\n')
51
+ .replace(/\\r?\\n(?=\s*(?:at\s|File\s|Traceback\b|[A-Za-z_][\w.]*?(?:Error|Exception|Warning)\b|@))/g, '\n');
52
+ }
53
+ function parseFrame(line) {
54
+ return parseNodeOrV8Frame(line) ?? parseBrowserFrame(line) ?? parsePythonFrame(line);
55
+ }
56
+ function parseNodeOrV8Frame(line) {
57
+ const trimmed = line.trim();
58
+ if (!trimmed.startsWith('at '))
59
+ return null;
60
+ const body = trimmed.slice(3).trim();
61
+ const callsite = matchCallsite(body);
62
+ if (!callsite)
63
+ return null;
64
+ return createFrame(callsite.location, callsite.fn);
65
+ }
66
+ function parseBrowserFrame(line) {
67
+ const trimmed = line.trim();
68
+ if (!trimmed.includes('@') || trimmed.startsWith('at '))
69
+ return null;
70
+ const atIndex = trimmed.lastIndexOf('@');
71
+ const fn = trimmed.slice(0, atIndex).trim() || undefined;
72
+ const location = trimmed.slice(atIndex + 1).trim();
73
+ return createFrame(location, fn);
74
+ }
75
+ function parsePythonFrame(line) {
76
+ const match = line.match(/^\s*File\s+"(.+?)",\s+line\s+(\d+)(?:,\s+in\s+(.+))?\s*$/);
77
+ if (!match)
78
+ return null;
79
+ const [, file, lineNumber, fn] = match;
80
+ return {
81
+ file,
82
+ line: Number(lineNumber),
83
+ function: fn?.trim() || undefined,
84
+ isNative: isNativeLocation(file),
85
+ isNodeModule: isNodeModuleFile(file),
86
+ };
87
+ }
88
+ function matchCallsite(body) {
89
+ const wrappedMatch = body.match(/^(.*?) \((.+)\)$/);
90
+ if (wrappedMatch) {
91
+ const [, fn, location] = wrappedMatch;
92
+ return { fn: fn.trim() || undefined, location: location.trim() };
93
+ }
94
+ return { location: body };
95
+ }
96
+ function createFrame(location, fn) {
97
+ const parsed = parseLocation(location);
98
+ if (parsed) {
99
+ return {
100
+ ...parsed,
101
+ function: fn,
102
+ isNative: isNativeLocation(parsed.file),
103
+ isNodeModule: isNodeModuleFile(parsed.file),
104
+ };
105
+ }
106
+ if (!isNativeLocation(location))
107
+ return null;
108
+ return {
109
+ file: location,
110
+ line: 0,
111
+ function: fn,
112
+ isNative: true,
113
+ isNodeModule: false,
114
+ };
115
+ }
116
+ function parseLocation(location) {
117
+ const cleaned = location.trim().replace(/^\(|\)$/g, '');
118
+ const match = cleaned.match(/^(.*):(\d+)(?::(\d+))$/);
119
+ if (!match)
120
+ return null;
121
+ const [, file, line, column] = match;
122
+ return {
123
+ file,
124
+ line: Number(line),
125
+ column: Number(column),
126
+ };
127
+ }
128
+ function extractErrorMetadata(lines) {
129
+ const nonEmptyLines = lines.map((line) => line.trim()).filter(Boolean);
130
+ const errorLine = [...nonEmptyLines].reverse().find((line) => looksLikeErrorLine(line)) ??
131
+ nonEmptyLines[0] ??
132
+ 'Error';
133
+ const match = errorLine.match(/^([A-Za-z_][\w.]*)(?::\s*(.*))?$/);
134
+ if (!match) {
135
+ return { type: 'Error', error: errorLine };
136
+ }
137
+ const [, type, message] = match;
138
+ return {
139
+ type,
140
+ error: message?.trim() || type,
141
+ };
142
+ }
143
+ function looksLikeErrorLine(line) {
144
+ return (/^[A-Za-z_][\w.]*?(?:Error|Exception|Warning)?(?::|$)/.test(line) && !line.startsWith('at '));
145
+ }
146
+ function orderRelevantFrames(frames, isPython) {
147
+ return isPython ? [...frames].reverse() : [...frames];
148
+ }
149
+ function uniqueFiles(frames) {
150
+ const seen = new Set();
151
+ const files = [];
152
+ for (const frame of frames) {
153
+ if (seen.has(frame.file))
154
+ continue;
155
+ seen.add(frame.file);
156
+ files.push(frame.file);
157
+ }
158
+ return files;
159
+ }
160
+ function buildSuggestion(trace, rootCause, userFiles) {
161
+ const location = rootCause.line > 0 ? `${rootCause.file}:${rootCause.line}` : rootCause.file;
162
+ const prefix = userFiles.length
163
+ ? `Start with ${location}${rootCause.function ? ` in ${rootCause.function}` : ''}.`
164
+ : 'The trace is dominated by runtime or dependency frames.';
165
+ const type = trace.type.toLowerCase();
166
+ const errorText = `${trace.type} ${trace.error}`.toLowerCase();
167
+ if (type.includes('syntax')) {
168
+ return `${prefix} This looks like a syntax issue, so inspect the exact line and the tokens immediately around it.`;
169
+ }
170
+ if (type.includes('typeerror') ||
171
+ type.includes('attributeerror') ||
172
+ errorText.includes('cannot read properties') ||
173
+ errorText.includes('undefined')) {
174
+ return `${prefix} Check for null or undefined values, unexpected return shapes, and missing guards before the failing access.`;
175
+ }
176
+ if (type.includes('referenceerror') ||
177
+ type.includes('nameerror') ||
178
+ errorText.includes('is not defined')) {
179
+ return `${prefix} Verify that the symbol is defined in scope and imported from the expected module.`;
180
+ }
181
+ if (type.includes('modulenotfound') ||
182
+ type.includes('importerror') ||
183
+ errorText.includes('cannot find module')) {
184
+ return `${prefix} Re-check dependency installation, import paths, and the runtime working directory.`;
185
+ }
186
+ if (errorText.includes('enoent') || type.includes('filenotfounderror')) {
187
+ return `${prefix} Confirm the file path exists relative to the process working directory and that the caller passes the right path.`;
188
+ }
189
+ return `${prefix} Follow the relevant user frames outward from the failing call and verify the inputs reaching that code path.`;
190
+ }
191
+ function formatFrame(frame) {
192
+ const location = frame.line > 0
193
+ ? `${frame.file}:${frame.line}${frame.column ? `:${frame.column}` : ''}`
194
+ : frame.file;
195
+ const fn = frame.function ? `${frame.function} @ ` : '';
196
+ const flags = [frame.isNative ? 'native' : null, frame.isNodeModule ? 'node_modules' : null]
197
+ .filter(Boolean)
198
+ .join(', ');
199
+ return `${fn}${location}${flags ? ` [${flags}]` : ''}`;
200
+ }
201
+ function isNodeModuleFile(file) {
202
+ return /(?:^|[\\/])node_modules(?:[\\/]|$)/.test(file);
203
+ }
204
+ function isNativeLocation(file) {
205
+ return (file === 'native' ||
206
+ file.startsWith('node:') ||
207
+ file.startsWith('internal/') ||
208
+ file.startsWith('internal\\') ||
209
+ file.startsWith('<'));
210
+ }