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,481 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+ const TYPESCRIPT_PATTERNS = [
5
+ /^(?<file>.+?)\((?<line>\d+),(?<column>\d+)\):\s*(?<severity>error|warning)\s+(?<code>TS\d+):\s*(?<message>.+)$/imu,
6
+ /^(?<file>.+?):(?<line>\d+):(?<column>\d+)\s*-\s*(?<severity>error|warning)\s+(?<code>TS\d+):\s*(?<message>.+)$/imu,
7
+ ];
8
+ const ESLINT_PATTERNS = [
9
+ /^(?<file>.+?):(?<line>\d+):(?<column>\d+):\s*(?<severity>error|warning)\s+(?<message>.+?)(?:\s+\((?<code>[^)]+)\))?$/imu,
10
+ /^(?<file>.+?)\((?<line>\d+),(?<column>\d+)\):\s*(?<severity>error|warning)\s+(?<message>.+?)(?:\s+\((?<code>[^)]+)\))?$/imu,
11
+ ];
12
+ const RUNTIME_IMPORT_PATTERNS = [
13
+ /Error \[ERR_MODULE_NOT_FOUND\]: Cannot find module ['"](?<missing>.+?)['"] imported from (?<file>.+)$/imu,
14
+ /Cannot find module ['"](?<missing>.+?)['"] imported from (?<file>.+)$/imu,
15
+ ];
16
+ const NODE_GLOBAL_NAMES = new Set(['process', 'buffer', '__dirname', '__filename', 'global']);
17
+ export class SelfHealingBuilder {
18
+ rootDir;
19
+ runner;
20
+ contexts = new Map();
21
+ constructor(rootDir = process.cwd(), options) {
22
+ this.rootDir = path.resolve(rootDir);
23
+ this.runner = options?.runner ?? runShellCommand;
24
+ }
25
+ async build(command = this.detectBuildCommand(this.rootDir)) {
26
+ this.contexts.clear();
27
+ const result = await this.runner(command, this.rootDir);
28
+ const errors = parseBuildErrors(`${result.stdout}\n${result.stderr}`, this.rootDir);
29
+ for (const parsed of errors) {
30
+ this.contexts.set(errorKey(parsed.error), parsed.context);
31
+ }
32
+ return {
33
+ success: result.exitCode === 0,
34
+ command,
35
+ exitCode: result.exitCode,
36
+ stdout: result.stdout,
37
+ stderr: result.stderr,
38
+ errors: errors.map((entry) => entry.error),
39
+ };
40
+ }
41
+ diagnose(errors) {
42
+ return errors.map((error) => {
43
+ const context = this.contexts.get(errorKey(error)) ?? inferContext(error);
44
+ return {
45
+ error,
46
+ diagnosis: describeDiagnosis(error, context),
47
+ fix: describeFix(error, context),
48
+ applied: false,
49
+ };
50
+ });
51
+ }
52
+ async applyFix(attempt) {
53
+ const context = this.contexts.get(errorKey(attempt.error)) ?? inferContext(attempt.error);
54
+ const applied = await this.applyFixForError(attempt.error, context);
55
+ attempt.applied = applied;
56
+ return applied;
57
+ }
58
+ async healAndRetry(maxAttempts = 3) {
59
+ const command = this.detectBuildCommand(this.rootDir);
60
+ const attempts = [];
61
+ let build = await this.build(command);
62
+ for (let iteration = 0; iteration < maxAttempts && !build.success; iteration += 1) {
63
+ const diagnoses = this.diagnose(build.errors);
64
+ const nextAttempt = diagnoses.find((attempt) => isFixable(attempt.fix));
65
+ if (!nextAttempt)
66
+ break;
67
+ const applied = await this.applyFix(nextAttempt);
68
+ attempts.push(nextAttempt);
69
+ if (!applied)
70
+ break;
71
+ build = await this.build(command);
72
+ }
73
+ return {
74
+ success: build.success,
75
+ command,
76
+ attempts,
77
+ build,
78
+ };
79
+ }
80
+ detectBuildCommand(rootDir) {
81
+ const packageJsonPath = path.join(rootDir, 'package.json');
82
+ const packageJson = readJsonFile(packageJsonPath);
83
+ const scripts = packageJson?.scripts ?? {};
84
+ if (typeof scripts.typecheck === 'string')
85
+ return 'npm run typecheck';
86
+ if (typeof scripts.build === 'string')
87
+ return 'npm run build';
88
+ if (typeof scripts.lint === 'string')
89
+ return 'npm run lint';
90
+ if (fs.existsSync(path.join(rootDir, 'tsconfig.json')))
91
+ return 'npx tsc --noEmit';
92
+ if (hasEslintConfig(rootDir))
93
+ return 'npx eslint .';
94
+ return 'npm run build';
95
+ }
96
+ async applyFixForError(error, context) {
97
+ switch (context.kind) {
98
+ case 'typescript':
99
+ if (context.details.suggestedImportPath) {
100
+ const currentSpecifier = context.details.importPath === context.details.suggestedImportPath
101
+ ? this.readImportSpecifier(error.file, error.line)
102
+ : context.details.importPath;
103
+ return this.rewriteImportSpecifier(error.file, error.line, currentSpecifier, context.details.suggestedImportPath);
104
+ }
105
+ if (context.details.importPath) {
106
+ const nextSpecifier = this.resolveImportCandidate(error.file, context.details.importPath);
107
+ if (nextSpecifier) {
108
+ return this.rewriteImportSpecifier(error.file, error.line, context.details.importPath, nextSpecifier);
109
+ }
110
+ }
111
+ if (error.code === 'TS2580' &&
112
+ context.details.missingName &&
113
+ NODE_GLOBAL_NAMES.has(context.details.missingName.toLowerCase())) {
114
+ return this.ensureNodeTypes();
115
+ }
116
+ return false;
117
+ case 'import':
118
+ if (!context.details.importPath)
119
+ return false;
120
+ return this.rewriteImportSpecifier(error.file, error.line, context.details.importPath, this.resolveImportCandidate(error.file, context.details.importPath) ??
121
+ context.details.importPath);
122
+ case 'eslint':
123
+ return this.applyEslintLineFix(error, context.details.rule ?? error.code);
124
+ default:
125
+ return false;
126
+ }
127
+ }
128
+ resolveImportCandidate(filePath, specifier) {
129
+ if (!filePath || !specifier.startsWith('.'))
130
+ return undefined;
131
+ const absoluteBase = path.resolve(path.dirname(filePath), specifier);
132
+ const candidates = [
133
+ { file: `${absoluteBase}.ts`, replacement: `${specifier}.js` },
134
+ { file: `${absoluteBase}.tsx`, replacement: `${specifier}.js` },
135
+ { file: path.join(absoluteBase, 'index.ts'), replacement: `${specifier}/index.js` },
136
+ { file: path.join(absoluteBase, 'index.tsx'), replacement: `${specifier}/index.js` },
137
+ ];
138
+ return candidates.find((candidate) => fs.existsSync(candidate.file))?.replacement;
139
+ }
140
+ rewriteImportSpecifier(filePath, lineNumber, previousSpecifier, nextSpecifier) {
141
+ if (!filePath || !previousSpecifier || !nextSpecifier || previousSpecifier === nextSpecifier) {
142
+ return false;
143
+ }
144
+ if (!fs.existsSync(filePath))
145
+ return false;
146
+ const source = fs.readFileSync(filePath, 'utf8');
147
+ const lines = source.split(/\r?\n/u);
148
+ const index = typeof lineNumber === 'number' && lineNumber > 0 ? lineNumber - 1 : -1;
149
+ if (index < 0 || index >= lines.length)
150
+ return false;
151
+ const line = lines[index];
152
+ if (!line.includes(previousSpecifier))
153
+ return false;
154
+ lines[index] = line.replace(previousSpecifier, nextSpecifier);
155
+ fs.writeFileSync(filePath, lines.join('\n'), 'utf8');
156
+ return true;
157
+ }
158
+ readImportSpecifier(filePath, lineNumber) {
159
+ if (!filePath || typeof lineNumber !== 'number' || !fs.existsSync(filePath))
160
+ return undefined;
161
+ const lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/u);
162
+ const line = lines[lineNumber - 1];
163
+ if (!line)
164
+ return undefined;
165
+ const match = /from\s+['"](?<value>[^'"]+)['"]|import\s+['"](?<value2>[^'"]+)['"]/u.exec(line);
166
+ return match?.groups?.value ?? match?.groups?.value2;
167
+ }
168
+ ensureNodeTypes() {
169
+ const tsconfigPath = path.join(this.rootDir, 'tsconfig.json');
170
+ const tsconfig = readJsonFile(tsconfigPath);
171
+ if (!tsconfig)
172
+ return false;
173
+ const currentTypes = new Set(tsconfig.compilerOptions?.types ?? []);
174
+ if (currentTypes.has('node'))
175
+ return false;
176
+ currentTypes.add('node');
177
+ tsconfig.compilerOptions = {
178
+ ...(tsconfig.compilerOptions ?? {}),
179
+ types: [...currentTypes],
180
+ };
181
+ fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + '\n', 'utf8');
182
+ return true;
183
+ }
184
+ applyEslintLineFix(error, rule) {
185
+ if (!error.file || typeof error.line !== 'number' || !fs.existsSync(error.file))
186
+ return false;
187
+ const source = fs.readFileSync(error.file, 'utf8');
188
+ const lines = source.split(/\r?\n/u);
189
+ const index = error.line - 1;
190
+ if (index < 0 || index >= lines.length)
191
+ return false;
192
+ const original = lines[index];
193
+ let updated = original;
194
+ const normalizedRule = (rule ?? '').trim();
195
+ if (normalizedRule === 'semi' || /missing semicolon/i.test(error.message)) {
196
+ updated = original.trimEnd().endsWith(';') ? original : `${original.trimEnd()};`;
197
+ }
198
+ else if (normalizedRule === 'no-trailing-spaces' || /trailing spaces?/i.test(error.message)) {
199
+ updated = original.replace(/\s+$/u, '');
200
+ }
201
+ else {
202
+ return false;
203
+ }
204
+ if (updated === original)
205
+ return false;
206
+ lines[index] = updated;
207
+ fs.writeFileSync(error.file, lines.join('\n'), 'utf8');
208
+ return true;
209
+ }
210
+ }
211
+ function parseBuildErrors(output, rootDir) {
212
+ const parsed = [];
213
+ const seen = new Set();
214
+ const lines = output.split(/\r?\n/u);
215
+ for (const line of lines) {
216
+ const entry = parseTypeScriptError(line, rootDir) ??
217
+ parseEslintError(line, rootDir) ??
218
+ parseImportError(line, rootDir);
219
+ if (!entry)
220
+ continue;
221
+ const key = errorKey(entry.error);
222
+ if (seen.has(key))
223
+ continue;
224
+ seen.add(key);
225
+ parsed.push(entry);
226
+ }
227
+ return parsed;
228
+ }
229
+ function parseTypeScriptError(line, rootDir) {
230
+ const trimmed = line.trim();
231
+ if (!trimmed)
232
+ return null;
233
+ for (const pattern of TYPESCRIPT_PATTERNS) {
234
+ const match = pattern.exec(trimmed);
235
+ if (!match?.groups?.message)
236
+ continue;
237
+ const rawFile = match.groups.file;
238
+ const file = rawFile ? resolveBuildPath(rootDir, rawFile.trim()) : undefined;
239
+ const message = match.groups.message.trim();
240
+ const importPath = extractQuotedValue(message);
241
+ const suggestedImportPath = extractDidYouMean(message);
242
+ const missingName = extractMissingName(message);
243
+ return {
244
+ error: {
245
+ file,
246
+ line: toNumber(match.groups.line),
247
+ code: match.groups.code?.trim(),
248
+ severity: normalizeSeverity(match.groups.severity),
249
+ message,
250
+ },
251
+ context: {
252
+ kind: 'typescript',
253
+ details: { importPath, suggestedImportPath, missingName },
254
+ },
255
+ };
256
+ }
257
+ return null;
258
+ }
259
+ function parseEslintError(line, rootDir) {
260
+ const trimmed = line.trim();
261
+ if (!trimmed)
262
+ return null;
263
+ for (const pattern of ESLINT_PATTERNS) {
264
+ const match = pattern.exec(trimmed);
265
+ if (!match?.groups?.message)
266
+ continue;
267
+ const rule = match.groups.code?.trim() || extractTrailingRule(match.groups.message);
268
+ return {
269
+ error: {
270
+ file: resolveBuildPath(rootDir, match.groups.file.trim()),
271
+ line: toNumber(match.groups.line),
272
+ code: rule,
273
+ severity: normalizeSeverity(match.groups.severity),
274
+ message: stripTrailingRule(match.groups.message),
275
+ },
276
+ context: {
277
+ kind: 'eslint',
278
+ details: { rule },
279
+ },
280
+ };
281
+ }
282
+ return null;
283
+ }
284
+ function parseImportError(line, rootDir) {
285
+ const trimmed = line.trim();
286
+ if (!trimmed)
287
+ return null;
288
+ for (const pattern of RUNTIME_IMPORT_PATTERNS) {
289
+ const match = pattern.exec(trimmed);
290
+ if (!match?.groups?.file || !match.groups.missing)
291
+ continue;
292
+ const importerFile = resolveBuildPath(rootDir, match.groups.file.trim());
293
+ const missingPath = match.groups.missing.trim();
294
+ const importPath = importerFile
295
+ ? deriveRelativeSpecifier(importerFile, missingPath)
296
+ : undefined;
297
+ return {
298
+ error: {
299
+ file: importerFile,
300
+ message: trimmed,
301
+ severity: 'error',
302
+ code: 'ERR_MODULE_NOT_FOUND',
303
+ },
304
+ context: {
305
+ kind: 'import',
306
+ details: { importPath },
307
+ },
308
+ };
309
+ }
310
+ return null;
311
+ }
312
+ function inferContext(error) {
313
+ if (error.code?.startsWith('TS')) {
314
+ return {
315
+ kind: 'typescript',
316
+ details: {
317
+ importPath: extractQuotedValue(error.message),
318
+ suggestedImportPath: extractDidYouMean(error.message),
319
+ missingName: extractMissingName(error.message),
320
+ },
321
+ };
322
+ }
323
+ if (error.code === 'ERR_MODULE_NOT_FOUND') {
324
+ return { kind: 'import', details: {} };
325
+ }
326
+ if (error.code) {
327
+ return { kind: 'eslint', details: { rule: error.code } };
328
+ }
329
+ return { kind: 'generic', details: {} };
330
+ }
331
+ function describeDiagnosis(error, context) {
332
+ switch (context.kind) {
333
+ case 'typescript':
334
+ if (context.details.importPath) {
335
+ return `TypeScript cannot resolve import ${context.details.importPath}.`;
336
+ }
337
+ if (context.details.missingName) {
338
+ return `TypeScript cannot find the global ${context.details.missingName}.`;
339
+ }
340
+ return `TypeScript reported ${error.code ?? 'a compiler error'}.`;
341
+ case 'eslint':
342
+ return `ESLint flagged rule ${context.details.rule ?? error.code ?? 'unknown'}.`;
343
+ case 'import':
344
+ return `A runtime import path cannot be resolved from ${error.file ?? 'the current file'}.`;
345
+ default:
346
+ return error.message;
347
+ }
348
+ }
349
+ function describeFix(error, context) {
350
+ switch (context.kind) {
351
+ case 'typescript':
352
+ if (context.details.suggestedImportPath) {
353
+ return `Update the import to ${context.details.suggestedImportPath}.`;
354
+ }
355
+ if (context.details.importPath) {
356
+ return 'Resolve the relative import to an existing .js entrypoint.';
357
+ }
358
+ if (error.code === 'TS2580' &&
359
+ context.details.missingName &&
360
+ NODE_GLOBAL_NAMES.has(context.details.missingName.toLowerCase())) {
361
+ return 'Add Node.js typings to tsconfig.json.';
362
+ }
363
+ return 'No safe automatic TypeScript fix available.';
364
+ case 'eslint':
365
+ if (context.details.rule === 'semi' ||
366
+ /missing semicolon/i.test(error.message) ||
367
+ context.details.rule === 'no-trailing-spaces') {
368
+ return 'Apply a single-line ESLint-safe edit.';
369
+ }
370
+ return 'No safe automatic ESLint fix available.';
371
+ case 'import':
372
+ if (context.details.importPath) {
373
+ return 'Rewrite the import to a resolvable relative .js path.';
374
+ }
375
+ return 'No safe automatic import fix available.';
376
+ default:
377
+ return 'No safe automatic fix available.';
378
+ }
379
+ }
380
+ function isFixable(fix) {
381
+ return !/^No safe automatic/i.test(fix);
382
+ }
383
+ function errorKey(error) {
384
+ return JSON.stringify({
385
+ file: error.file,
386
+ line: error.line,
387
+ message: error.message,
388
+ code: error.code,
389
+ severity: error.severity,
390
+ });
391
+ }
392
+ async function runShellCommand(command, cwd) {
393
+ return new Promise((resolve) => {
394
+ const isWin = process.platform === 'win32';
395
+ const shell = isWin ? 'powershell.exe' : 'bash';
396
+ const args = isWin ? ['-NoProfile', '-Command', command] : ['-lc', command];
397
+ const child = spawn(shell, args, {
398
+ cwd,
399
+ stdio: ['ignore', 'pipe', 'pipe'],
400
+ windowsHide: true,
401
+ });
402
+ let stdout = '';
403
+ let stderr = '';
404
+ child.stdout.on('data', (chunk) => {
405
+ stdout += chunk.toString();
406
+ });
407
+ child.stderr.on('data', (chunk) => {
408
+ stderr += chunk.toString();
409
+ });
410
+ child.on('close', (exitCode) => {
411
+ resolve({
412
+ exitCode: typeof exitCode === 'number' ? exitCode : -1,
413
+ stdout,
414
+ stderr,
415
+ });
416
+ });
417
+ child.on('error', (error) => {
418
+ resolve({
419
+ exitCode: -1,
420
+ stdout,
421
+ stderr: `${stderr}${error.message}`,
422
+ });
423
+ });
424
+ });
425
+ }
426
+ function resolveBuildPath(rootDir, filePath) {
427
+ if (path.isAbsolute(filePath))
428
+ return path.normalize(filePath);
429
+ return path.resolve(rootDir, filePath);
430
+ }
431
+ function normalizeSeverity(value) {
432
+ return value?.toLowerCase() === 'warning' ? 'warning' : 'error';
433
+ }
434
+ function toNumber(value) {
435
+ if (!value)
436
+ return undefined;
437
+ const parsed = Number.parseInt(value, 10);
438
+ return Number.isFinite(parsed) ? parsed : undefined;
439
+ }
440
+ function extractQuotedValue(message) {
441
+ const match = /['"](?<value>\.[^'"]+)['"]/u.exec(message);
442
+ return match?.groups?.value;
443
+ }
444
+ function extractDidYouMean(message) {
445
+ const match = /Did you mean ['"](?<value>[^'"]+)['"]\?/u.exec(message);
446
+ return match?.groups?.value;
447
+ }
448
+ function extractMissingName(message) {
449
+ const match = /Cannot find name ['"](?<value>[^'"]+)['"]/u.exec(message);
450
+ return match?.groups?.value;
451
+ }
452
+ function extractTrailingRule(message) {
453
+ const match = /\((?<rule>[^)]+)\)\s*$/u.exec(message.trim());
454
+ return match?.groups?.rule;
455
+ }
456
+ function stripTrailingRule(message) {
457
+ return message.replace(/\s+\([^)]+\)\s*$/u, '').trim();
458
+ }
459
+ function deriveRelativeSpecifier(importerFile, missingPath) {
460
+ const importerDir = path.dirname(importerFile);
461
+ const relative = path.relative(importerDir, missingPath).replace(/\\/gu, '/');
462
+ if (!relative)
463
+ return undefined;
464
+ return relative.startsWith('.') ? relative : `./${relative}`;
465
+ }
466
+ function readJsonFile(filePath) {
467
+ try {
468
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
469
+ }
470
+ catch {
471
+ return undefined;
472
+ }
473
+ }
474
+ function hasEslintConfig(rootDir) {
475
+ try {
476
+ return fs.readdirSync(rootDir).some((entry) => entry.startsWith('.eslintrc'));
477
+ }
478
+ catch {
479
+ return false;
480
+ }
481
+ }