@defai.digital/automatosx 12.4.1 → 12.5.1
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/README.md +53 -1
- package/dist/index.js +1952 -272
- package/dist/mcp/index.js +282 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9431,7 +9431,7 @@ var PRECOMPILED_CONFIG = {
|
|
|
9431
9431
|
"enableFreeTierPrioritization": true,
|
|
9432
9432
|
"enableWorkloadAwareRouting": true
|
|
9433
9433
|
},
|
|
9434
|
-
"version": "12.
|
|
9434
|
+
"version": "12.5.1"
|
|
9435
9435
|
};
|
|
9436
9436
|
|
|
9437
9437
|
// src/core/config/schemas.ts
|
|
@@ -14272,9 +14272,9 @@ async function initializeGitRepository(projectDir) {
|
|
|
14272
14272
|
logger.info("Git repository already exists, skipping initialization");
|
|
14273
14273
|
return true;
|
|
14274
14274
|
}
|
|
14275
|
-
const { spawn:
|
|
14275
|
+
const { spawn: spawn12 } = await import('child_process');
|
|
14276
14276
|
await new Promise((resolve13, reject) => {
|
|
14277
|
-
const child =
|
|
14277
|
+
const child = spawn12("git", ["init"], {
|
|
14278
14278
|
cwd: projectDir,
|
|
14279
14279
|
stdio: "pipe",
|
|
14280
14280
|
shell: false
|
|
@@ -14342,8 +14342,8 @@ async function updateGitignore(projectDir) {
|
|
|
14342
14342
|
try {
|
|
14343
14343
|
const exists = await checkExists2(gitignorePath);
|
|
14344
14344
|
if (exists) {
|
|
14345
|
-
const { readFile:
|
|
14346
|
-
const content = await
|
|
14345
|
+
const { readFile: readFile24 } = await import('fs/promises');
|
|
14346
|
+
const content = await readFile24(gitignorePath, "utf-8");
|
|
14347
14347
|
if (content.includes("# AutomatosX")) {
|
|
14348
14348
|
return;
|
|
14349
14349
|
}
|
|
@@ -16506,12 +16506,12 @@ var listCommand = {
|
|
|
16506
16506
|
};
|
|
16507
16507
|
async function listAgents(pathResolver, format) {
|
|
16508
16508
|
const agentsDir = pathResolver.getAgentsDirectory();
|
|
16509
|
-
const { existsSync:
|
|
16509
|
+
const { existsSync: existsSync29 } = await import('fs');
|
|
16510
16510
|
const projectDir = await detectProjectRoot();
|
|
16511
16511
|
const examplesDir = join(projectDir, "examples", "agents");
|
|
16512
16512
|
try {
|
|
16513
16513
|
const agentFiles = [];
|
|
16514
|
-
if (
|
|
16514
|
+
if (existsSync29(agentsDir)) {
|
|
16515
16515
|
const files = await readdir(agentsDir);
|
|
16516
16516
|
for (const file of files) {
|
|
16517
16517
|
if (file.endsWith(".yaml") || file.endsWith(".yml")) {
|
|
@@ -16523,7 +16523,7 @@ async function listAgents(pathResolver, format) {
|
|
|
16523
16523
|
}
|
|
16524
16524
|
}
|
|
16525
16525
|
}
|
|
16526
|
-
if (
|
|
16526
|
+
if (existsSync29(examplesDir)) {
|
|
16527
16527
|
const files = await readdir(examplesDir);
|
|
16528
16528
|
for (const file of files) {
|
|
16529
16529
|
if (file.endsWith(".yaml") || file.endsWith(".yml")) {
|
|
@@ -16549,12 +16549,12 @@ async function listAgents(pathResolver, format) {
|
|
|
16549
16549
|
return;
|
|
16550
16550
|
}
|
|
16551
16551
|
const { load: load6 } = await import('js-yaml');
|
|
16552
|
-
const { readFile:
|
|
16552
|
+
const { readFile: readFile24 } = await import('fs/promises');
|
|
16553
16553
|
agentFiles.sort((a, b) => a.file.localeCompare(b.file));
|
|
16554
16554
|
const agents = [];
|
|
16555
16555
|
for (const { file, path: agentPath, source } of agentFiles) {
|
|
16556
16556
|
try {
|
|
16557
|
-
const content = await
|
|
16557
|
+
const content = await readFile24(agentPath, "utf-8");
|
|
16558
16558
|
const agent = load6(content);
|
|
16559
16559
|
const name = agent.displayName || agent.name || file.replace(/\.(yaml|yml)$/, "");
|
|
16560
16560
|
const description = agent.description || "No description";
|
|
@@ -16607,12 +16607,12 @@ async function listAbilities(pathResolver, format) {
|
|
|
16607
16607
|
}
|
|
16608
16608
|
return;
|
|
16609
16609
|
}
|
|
16610
|
-
const { readFile:
|
|
16610
|
+
const { readFile: readFile24 } = await import('fs/promises');
|
|
16611
16611
|
const abilities = [];
|
|
16612
16612
|
for (const file of abilityFiles.sort()) {
|
|
16613
16613
|
const abilityPath = join(abilitiesDir, file);
|
|
16614
16614
|
try {
|
|
16615
|
-
const content = await
|
|
16615
|
+
const content = await readFile24(abilityPath, "utf-8");
|
|
16616
16616
|
const lines = content.split("\n");
|
|
16617
16617
|
const titleLine = lines.find((l) => l.startsWith("# "));
|
|
16618
16618
|
const descLine = lines.find((l) => l.startsWith("## Description"));
|
|
@@ -20487,9 +20487,9 @@ var MemoryManager = class _MemoryManager {
|
|
|
20487
20487
|
throw new MemoryError("Memory manager not initialized", "DATABASE_ERROR");
|
|
20488
20488
|
}
|
|
20489
20489
|
try {
|
|
20490
|
-
const { mkdir:
|
|
20490
|
+
const { mkdir: mkdir13 } = await import('fs/promises');
|
|
20491
20491
|
const destDir = dirname4(destPath);
|
|
20492
|
-
await
|
|
20492
|
+
await mkdir13(destDir, { recursive: true });
|
|
20493
20493
|
await this.db.backup(destPath);
|
|
20494
20494
|
logger.info("Database backup created", { destPath: normalizePath(destPath) });
|
|
20495
20495
|
} catch (error) {
|
|
@@ -20524,8 +20524,8 @@ var MemoryManager = class _MemoryManager {
|
|
|
20524
20524
|
tempDb.prepare("SELECT COUNT(*) FROM memory_entries").get();
|
|
20525
20525
|
tempDb.close();
|
|
20526
20526
|
} catch (verifyError) {
|
|
20527
|
-
const { unlink:
|
|
20528
|
-
await
|
|
20527
|
+
const { unlink: unlink7 } = await import('fs/promises');
|
|
20528
|
+
await unlink7(tempPath).catch(() => {
|
|
20529
20529
|
});
|
|
20530
20530
|
throw new MemoryError(
|
|
20531
20531
|
`Backup file verification failed: ${verifyError.message}`,
|
|
@@ -20645,9 +20645,9 @@ var MemoryManager = class _MemoryManager {
|
|
|
20645
20645
|
},
|
|
20646
20646
|
entries
|
|
20647
20647
|
};
|
|
20648
|
-
const { writeFile:
|
|
20648
|
+
const { writeFile: writeFile16 } = await import('fs/promises');
|
|
20649
20649
|
const json = pretty ? JSON.stringify(exportData, null, 2) : JSON.stringify(exportData);
|
|
20650
|
-
await
|
|
20650
|
+
await writeFile16(filePath, json, "utf-8");
|
|
20651
20651
|
const sizeBytes = Buffer.byteLength(json, "utf-8");
|
|
20652
20652
|
logger.info("Memory exported to JSON", {
|
|
20653
20653
|
filePath: normalizePath(filePath),
|
|
@@ -20696,8 +20696,8 @@ var MemoryManager = class _MemoryManager {
|
|
|
20696
20696
|
{ filePath }
|
|
20697
20697
|
);
|
|
20698
20698
|
}
|
|
20699
|
-
const { readFile:
|
|
20700
|
-
const content = await
|
|
20699
|
+
const { readFile: readFile24 } = await import('fs/promises');
|
|
20700
|
+
const content = await readFile24(filePath, "utf-8");
|
|
20701
20701
|
const importData = JSON.parse(content);
|
|
20702
20702
|
const SUPPORTED_VERSIONS = ["1.0", "4.0.0", "4.11.0"];
|
|
20703
20703
|
if (!importData.version || !SUPPORTED_VERSIONS.includes(importData.version)) {
|
|
@@ -26006,6 +26006,232 @@ ${context.task}`;
|
|
|
26006
26006
|
}
|
|
26007
26007
|
};
|
|
26008
26008
|
|
|
26009
|
+
// src/agents/agent-selector.ts
|
|
26010
|
+
init_esm_shims();
|
|
26011
|
+
init_logger();
|
|
26012
|
+
function scoreAgent(task, profile) {
|
|
26013
|
+
let score = 0;
|
|
26014
|
+
const taskLower = task.toLowerCase();
|
|
26015
|
+
if (profile.selectionMetadata?.primaryIntents) {
|
|
26016
|
+
for (const intent of profile.selectionMetadata.primaryIntents) {
|
|
26017
|
+
const intentKeywords = intent.toLowerCase().split(/\s+/);
|
|
26018
|
+
const matchedKeywords = intentKeywords.filter(
|
|
26019
|
+
(keyword) => taskLower.includes(keyword) && keyword.length > 3
|
|
26020
|
+
);
|
|
26021
|
+
if (matchedKeywords.length >= 2) {
|
|
26022
|
+
score += 10;
|
|
26023
|
+
} else if (matchedKeywords.length === 1) {
|
|
26024
|
+
score += 5;
|
|
26025
|
+
}
|
|
26026
|
+
}
|
|
26027
|
+
}
|
|
26028
|
+
if (profile.selectionMetadata?.secondarySignals) {
|
|
26029
|
+
for (const signal of profile.selectionMetadata.secondarySignals) {
|
|
26030
|
+
if (taskLower.includes(signal.toLowerCase())) {
|
|
26031
|
+
score += 5;
|
|
26032
|
+
}
|
|
26033
|
+
}
|
|
26034
|
+
}
|
|
26035
|
+
if (profile.selectionMetadata?.negativeIntents) {
|
|
26036
|
+
for (const negative of profile.selectionMetadata.negativeIntents) {
|
|
26037
|
+
const keywords = negative.split("(")[0]?.toLowerCase() || "";
|
|
26038
|
+
const negativeKeywords = keywords.split(/\s+/).filter((k) => k.length > 3);
|
|
26039
|
+
const matchedNegative = negativeKeywords.filter(
|
|
26040
|
+
(keyword) => taskLower.includes(keyword)
|
|
26041
|
+
);
|
|
26042
|
+
if (matchedNegative.length >= 2) {
|
|
26043
|
+
score -= 20;
|
|
26044
|
+
}
|
|
26045
|
+
}
|
|
26046
|
+
}
|
|
26047
|
+
if (profile.selectionMetadata?.redirectWhen) {
|
|
26048
|
+
for (const rule of profile.selectionMetadata.redirectWhen) {
|
|
26049
|
+
try {
|
|
26050
|
+
const regex = new RegExp(rule.phrase, "i");
|
|
26051
|
+
if (regex.test(task)) {
|
|
26052
|
+
score -= 15;
|
|
26053
|
+
}
|
|
26054
|
+
} catch (error) {
|
|
26055
|
+
logger.debug("Invalid regex pattern in redirectWhen rule", {
|
|
26056
|
+
pattern: rule.phrase,
|
|
26057
|
+
error: error instanceof Error ? error.message : String(error)
|
|
26058
|
+
});
|
|
26059
|
+
}
|
|
26060
|
+
}
|
|
26061
|
+
}
|
|
26062
|
+
if (profile.abilitySelection?.taskBased) {
|
|
26063
|
+
for (const keyword of Object.keys(profile.abilitySelection.taskBased)) {
|
|
26064
|
+
if (taskLower.includes(keyword.toLowerCase())) {
|
|
26065
|
+
score += 3;
|
|
26066
|
+
}
|
|
26067
|
+
}
|
|
26068
|
+
}
|
|
26069
|
+
return Math.max(0, score);
|
|
26070
|
+
}
|
|
26071
|
+
function buildRationale(task, profile) {
|
|
26072
|
+
const rationale = [];
|
|
26073
|
+
const taskLower = task.toLowerCase();
|
|
26074
|
+
if (profile.selectionMetadata?.primaryIntents) {
|
|
26075
|
+
const matchedIntents = profile.selectionMetadata.primaryIntents.filter((intent) => {
|
|
26076
|
+
const keywords = intent.toLowerCase().split(/\s+/);
|
|
26077
|
+
return keywords.some((k) => taskLower.includes(k) && k.length > 3);
|
|
26078
|
+
});
|
|
26079
|
+
if (matchedIntents.length > 0) {
|
|
26080
|
+
rationale.push(`Matches: ${matchedIntents.slice(0, 2).join(", ")}`);
|
|
26081
|
+
}
|
|
26082
|
+
}
|
|
26083
|
+
if (profile.selectionMetadata?.secondarySignals) {
|
|
26084
|
+
const matchedSignals = profile.selectionMetadata.secondarySignals.filter(
|
|
26085
|
+
(signal) => taskLower.includes(signal.toLowerCase())
|
|
26086
|
+
);
|
|
26087
|
+
if (matchedSignals.length > 0) {
|
|
26088
|
+
rationale.push(`Keywords: ${matchedSignals.slice(0, 3).join(", ")}`);
|
|
26089
|
+
}
|
|
26090
|
+
}
|
|
26091
|
+
if (profile.abilitySelection?.taskBased) {
|
|
26092
|
+
const matchedAbilities = Object.keys(profile.abilitySelection.taskBased).filter(
|
|
26093
|
+
(keyword) => taskLower.includes(keyword.toLowerCase())
|
|
26094
|
+
);
|
|
26095
|
+
if (matchedAbilities.length > 0) {
|
|
26096
|
+
rationale.push(`Abilities: ${matchedAbilities.slice(0, 2).join(", ")}`);
|
|
26097
|
+
}
|
|
26098
|
+
}
|
|
26099
|
+
if (rationale.length === 0) {
|
|
26100
|
+
rationale.push("General capability match");
|
|
26101
|
+
}
|
|
26102
|
+
return rationale;
|
|
26103
|
+
}
|
|
26104
|
+
function getConfidence(score) {
|
|
26105
|
+
if (score >= 30) return "high";
|
|
26106
|
+
if (score >= 15) return "medium";
|
|
26107
|
+
return "low";
|
|
26108
|
+
}
|
|
26109
|
+
var MIN_SELECTION_SCORE = 5;
|
|
26110
|
+
var FALLBACK_AGENT = "standard";
|
|
26111
|
+
var AgentSelector = class {
|
|
26112
|
+
profileLoader;
|
|
26113
|
+
constructor(profileLoader) {
|
|
26114
|
+
this.profileLoader = profileLoader;
|
|
26115
|
+
}
|
|
26116
|
+
/**
|
|
26117
|
+
* Select the best agent for a task
|
|
26118
|
+
*
|
|
26119
|
+
* @param task - Task description
|
|
26120
|
+
* @returns Selection result with agent, score, confidence, and rationale
|
|
26121
|
+
*/
|
|
26122
|
+
async selectAgent(task) {
|
|
26123
|
+
logger.debug("[AgentSelector] Selecting agent for task", {
|
|
26124
|
+
taskPreview: task.substring(0, 100)
|
|
26125
|
+
});
|
|
26126
|
+
const agentNames = await this.profileLoader.listProfiles();
|
|
26127
|
+
if (agentNames.length === 0) {
|
|
26128
|
+
logger.warn("[AgentSelector] No agents found, using fallback");
|
|
26129
|
+
return this.createFallbackResult(task);
|
|
26130
|
+
}
|
|
26131
|
+
const profileResults = await Promise.all(
|
|
26132
|
+
agentNames.map(async (name) => {
|
|
26133
|
+
try {
|
|
26134
|
+
const profile = await this.profileLoader.loadProfile(name);
|
|
26135
|
+
return { name, profile, score: scoreAgent(task, profile) };
|
|
26136
|
+
} catch (error) {
|
|
26137
|
+
logger.debug(`[AgentSelector] Failed to load profile: ${name}`, { error });
|
|
26138
|
+
return null;
|
|
26139
|
+
}
|
|
26140
|
+
})
|
|
26141
|
+
);
|
|
26142
|
+
const scoredAgents = profileResults.filter(
|
|
26143
|
+
(result) => result !== null
|
|
26144
|
+
);
|
|
26145
|
+
if (scoredAgents.length === 0) {
|
|
26146
|
+
logger.warn("[AgentSelector] Failed to load any profiles, using fallback");
|
|
26147
|
+
return this.createFallbackResult(task);
|
|
26148
|
+
}
|
|
26149
|
+
scoredAgents.sort((a, b) => b.score - a.score);
|
|
26150
|
+
const best = scoredAgents[0];
|
|
26151
|
+
const alternatives = scoredAgents.slice(1, 4).map((a) => ({
|
|
26152
|
+
agent: a.name,
|
|
26153
|
+
score: a.score
|
|
26154
|
+
}));
|
|
26155
|
+
if (best.score < MIN_SELECTION_SCORE) {
|
|
26156
|
+
logger.info("[AgentSelector] Best score below threshold, using fallback", {
|
|
26157
|
+
bestAgent: best.name,
|
|
26158
|
+
bestScore: best.score,
|
|
26159
|
+
threshold: MIN_SELECTION_SCORE
|
|
26160
|
+
});
|
|
26161
|
+
const standardAgent = scoredAgents.find((a) => a.name === FALLBACK_AGENT);
|
|
26162
|
+
if (standardAgent) {
|
|
26163
|
+
return {
|
|
26164
|
+
agent: standardAgent.name,
|
|
26165
|
+
displayName: standardAgent.profile.displayName || standardAgent.name,
|
|
26166
|
+
role: standardAgent.profile.role,
|
|
26167
|
+
score: standardAgent.score,
|
|
26168
|
+
confidence: "low",
|
|
26169
|
+
rationale: ["No strong match found, using general-purpose agent"],
|
|
26170
|
+
alternatives: scoredAgents.filter((a) => a.name !== FALLBACK_AGENT).slice(0, 3).map((a) => ({ agent: a.name, score: a.score })),
|
|
26171
|
+
usedFallback: true
|
|
26172
|
+
};
|
|
26173
|
+
}
|
|
26174
|
+
return {
|
|
26175
|
+
agent: best.name,
|
|
26176
|
+
displayName: best.profile.displayName || best.name,
|
|
26177
|
+
role: best.profile.role,
|
|
26178
|
+
score: best.score,
|
|
26179
|
+
confidence: "low",
|
|
26180
|
+
rationale: ["Low confidence match - consider specifying agent explicitly"],
|
|
26181
|
+
alternatives,
|
|
26182
|
+
usedFallback: false
|
|
26183
|
+
};
|
|
26184
|
+
}
|
|
26185
|
+
const rationale = buildRationale(task, best.profile);
|
|
26186
|
+
const confidence = getConfidence(best.score);
|
|
26187
|
+
logger.info("[AgentSelector] Agent selected", {
|
|
26188
|
+
agent: best.name,
|
|
26189
|
+
score: best.score,
|
|
26190
|
+
confidence,
|
|
26191
|
+
rationale
|
|
26192
|
+
});
|
|
26193
|
+
return {
|
|
26194
|
+
agent: best.name,
|
|
26195
|
+
displayName: best.profile.displayName || best.name,
|
|
26196
|
+
role: best.profile.role,
|
|
26197
|
+
score: best.score,
|
|
26198
|
+
confidence,
|
|
26199
|
+
rationale,
|
|
26200
|
+
alternatives,
|
|
26201
|
+
usedFallback: false
|
|
26202
|
+
};
|
|
26203
|
+
}
|
|
26204
|
+
/**
|
|
26205
|
+
* Create a fallback result when no agents are available
|
|
26206
|
+
*/
|
|
26207
|
+
async createFallbackResult(_task) {
|
|
26208
|
+
try {
|
|
26209
|
+
const profile = await this.profileLoader.loadProfile(FALLBACK_AGENT);
|
|
26210
|
+
return {
|
|
26211
|
+
agent: FALLBACK_AGENT,
|
|
26212
|
+
displayName: profile.displayName || FALLBACK_AGENT,
|
|
26213
|
+
role: profile.role,
|
|
26214
|
+
score: 0,
|
|
26215
|
+
confidence: "low",
|
|
26216
|
+
rationale: ["No agents available, using fallback"],
|
|
26217
|
+
alternatives: [],
|
|
26218
|
+
usedFallback: true
|
|
26219
|
+
};
|
|
26220
|
+
} catch {
|
|
26221
|
+
return {
|
|
26222
|
+
agent: FALLBACK_AGENT,
|
|
26223
|
+
displayName: "Standard",
|
|
26224
|
+
role: "General-purpose agent",
|
|
26225
|
+
score: 0,
|
|
26226
|
+
confidence: "low",
|
|
26227
|
+
rationale: ["No agents available"],
|
|
26228
|
+
alternatives: [],
|
|
26229
|
+
usedFallback: true
|
|
26230
|
+
};
|
|
26231
|
+
}
|
|
26232
|
+
}
|
|
26233
|
+
};
|
|
26234
|
+
|
|
26009
26235
|
// src/mcp/tools/run-agent.ts
|
|
26010
26236
|
init_logger();
|
|
26011
26237
|
|
|
@@ -26305,22 +26531,41 @@ async function buildAgentContext(agentName, task, deps, callerProvider, bestProv
|
|
|
26305
26531
|
}
|
|
26306
26532
|
function createRunAgentHandler(deps) {
|
|
26307
26533
|
return async (input, context) => {
|
|
26308
|
-
const {
|
|
26534
|
+
const { task, provider, no_memory, mode = "auto" } = input;
|
|
26535
|
+
let { agent } = input;
|
|
26309
26536
|
if (context?.signal?.aborted) {
|
|
26310
26537
|
throw new Error("Request was cancelled");
|
|
26311
26538
|
}
|
|
26312
|
-
validateAgentName(agent);
|
|
26313
26539
|
validateStringParameter(task, "task", {
|
|
26314
26540
|
required: true,
|
|
26315
26541
|
minLength: 1,
|
|
26316
26542
|
maxLength: 1e4
|
|
26317
26543
|
});
|
|
26544
|
+
let autoSelected = false;
|
|
26545
|
+
if (!agent && deps.profileLoader) {
|
|
26546
|
+
const selector = new AgentSelector(deps.profileLoader);
|
|
26547
|
+
const selection = await selector.selectAgent(task);
|
|
26548
|
+
agent = selection.agent;
|
|
26549
|
+
autoSelected = true;
|
|
26550
|
+
logger.info("[MCP] run_agent auto-selected agent", {
|
|
26551
|
+
task: task.substring(0, 100),
|
|
26552
|
+
selectedAgent: agent,
|
|
26553
|
+
confidence: selection.confidence,
|
|
26554
|
+
score: selection.score,
|
|
26555
|
+
rationale: selection.rationale
|
|
26556
|
+
});
|
|
26557
|
+
}
|
|
26558
|
+
if (!agent) {
|
|
26559
|
+
throw new Error("Agent name is required when profileLoader is not available");
|
|
26560
|
+
}
|
|
26561
|
+
validateAgentName(agent);
|
|
26318
26562
|
const actualProvider = mapMcpProviderToActual(provider);
|
|
26319
26563
|
const session = deps.getSession?.() || null;
|
|
26320
26564
|
const callerProvider = session?.normalizedProvider || "unknown";
|
|
26321
26565
|
const callerActual = mapNormalizedCallerToActual(callerProvider);
|
|
26322
26566
|
logger.info("[MCP] run_agent called (Smart Routing v10.5.0)", {
|
|
26323
26567
|
agent,
|
|
26568
|
+
autoSelected,
|
|
26324
26569
|
task: task.substring(0, 100),
|
|
26325
26570
|
mcpProvider: provider,
|
|
26326
26571
|
actualProvider,
|
|
@@ -27133,20 +27378,35 @@ init_logger();
|
|
|
27133
27378
|
function createGetAgentContextHandler(deps) {
|
|
27134
27379
|
return async (input) => {
|
|
27135
27380
|
const {
|
|
27136
|
-
agent,
|
|
27137
27381
|
task,
|
|
27138
27382
|
includeMemory = true,
|
|
27139
27383
|
maxMemoryResults = 5
|
|
27140
27384
|
} = input;
|
|
27385
|
+
let { agent } = input;
|
|
27141
27386
|
const startTime = Date.now();
|
|
27142
|
-
validateAgentName(agent);
|
|
27143
27387
|
validateStringParameter(task, "task", {
|
|
27144
27388
|
required: true,
|
|
27145
27389
|
minLength: 1,
|
|
27146
27390
|
maxLength: 1e4
|
|
27147
27391
|
});
|
|
27392
|
+
let autoSelected = false;
|
|
27393
|
+
if (!agent) {
|
|
27394
|
+
const selector = new AgentSelector(deps.profileLoader);
|
|
27395
|
+
const selection = await selector.selectAgent(task);
|
|
27396
|
+
agent = selection.agent;
|
|
27397
|
+
autoSelected = true;
|
|
27398
|
+
logger.info("[MCP] get_agent_context auto-selected agent", {
|
|
27399
|
+
task: task.substring(0, 100),
|
|
27400
|
+
selectedAgent: agent,
|
|
27401
|
+
confidence: selection.confidence,
|
|
27402
|
+
score: selection.score,
|
|
27403
|
+
rationale: selection.rationale
|
|
27404
|
+
});
|
|
27405
|
+
}
|
|
27406
|
+
validateAgentName(agent);
|
|
27148
27407
|
logger.info("[MCP] get_agent_context called", {
|
|
27149
27408
|
agent,
|
|
27409
|
+
autoSelected,
|
|
27150
27410
|
task: task.substring(0, 100),
|
|
27151
27411
|
includeMemory,
|
|
27152
27412
|
maxMemoryResults
|
|
@@ -31697,17 +31957,24 @@ var McpServer = class _McpServer {
|
|
|
31697
31957
|
return [
|
|
31698
31958
|
{
|
|
31699
31959
|
name: "run_agent",
|
|
31700
|
-
description:
|
|
31960
|
+
description: `Execute an AutomatosX agent with a specific task.
|
|
31961
|
+
|
|
31962
|
+
v12.5.1: Agent auto-selection - if agent is omitted, system automatically selects the best agent based on task keywords.
|
|
31963
|
+
Uses Smart Routing: returns context for same-provider calls, spawns cross-provider execution.
|
|
31964
|
+
|
|
31965
|
+
Examples:
|
|
31966
|
+
- With agent: run_agent({ agent: "backend", task: "implement API" })
|
|
31967
|
+
- Auto-select: run_agent({ task: "fix bugs in the codebase" }) \u2192 selects "quality" agent`,
|
|
31701
31968
|
inputSchema: {
|
|
31702
31969
|
type: "object",
|
|
31703
31970
|
properties: {
|
|
31704
|
-
agent: { type: "string", description: "
|
|
31971
|
+
agent: { type: "string", description: "Optional: Agent name (e.g., backend, quality). If omitted, best agent is auto-selected based on task." },
|
|
31705
31972
|
task: { type: "string", description: "The task for the agent to perform" },
|
|
31706
31973
|
provider: { type: "string", description: "Optional: Override the AI provider", enum: ["claude", "gemini", "openai"] },
|
|
31707
31974
|
no_memory: { type: "boolean", description: "Optional: Skip memory injection", default: false },
|
|
31708
31975
|
mode: { type: "string", description: "Optional: Execution mode - auto (default), context (always return context), execute (always spawn)", enum: ["auto", "context", "execute"], default: "auto" }
|
|
31709
31976
|
},
|
|
31710
|
-
required: ["
|
|
31977
|
+
required: ["task"]
|
|
31711
31978
|
}
|
|
31712
31979
|
},
|
|
31713
31980
|
{
|
|
@@ -31819,16 +32086,18 @@ Use this tool first to understand what AutomatosX offers.`,
|
|
|
31819
32086
|
// v10.5.0: Smart Routing - Explicit context retrieval
|
|
31820
32087
|
{
|
|
31821
32088
|
name: "get_agent_context",
|
|
31822
|
-
description:
|
|
32089
|
+
description: `Get agent context without executing. Returns profile, relevant memory, and enhanced prompt for AI assistant to execute directly.
|
|
32090
|
+
|
|
32091
|
+
v12.5.1: Agent auto-selection - if agent is omitted, system automatically selects the best agent based on task keywords.`,
|
|
31823
32092
|
inputSchema: {
|
|
31824
32093
|
type: "object",
|
|
31825
32094
|
properties: {
|
|
31826
|
-
agent: { type: "string", description: "
|
|
32095
|
+
agent: { type: "string", description: "Optional: Agent name (e.g., backend, quality). If omitted, best agent is auto-selected based on task." },
|
|
31827
32096
|
task: { type: "string", description: "The task description for context building" },
|
|
31828
32097
|
includeMemory: { type: "boolean", description: "Include relevant memory entries (default: true)", default: true },
|
|
31829
32098
|
maxMemoryResults: { type: "number", description: "Maximum memory entries to return (default: 5)", default: 5 }
|
|
31830
32099
|
},
|
|
31831
|
-
required: ["
|
|
32100
|
+
required: ["task"]
|
|
31832
32101
|
}
|
|
31833
32102
|
},
|
|
31834
32103
|
// v11.3.5: Task Engine tools
|
|
@@ -31837,6 +32106,8 @@ Use this tool first to understand what AutomatosX offers.`,
|
|
|
31837
32106
|
getTaskResultSchema,
|
|
31838
32107
|
listTasksSchema,
|
|
31839
32108
|
deleteTaskSchema
|
|
32109
|
+
// v12.4.0: Bugfix tools intentionally NOT exposed via MCP
|
|
32110
|
+
// Access via: run_agent({ agent: "quality", task: "scan for bugs" })
|
|
31840
32111
|
];
|
|
31841
32112
|
}
|
|
31842
32113
|
/**
|
|
@@ -35187,232 +35458,6 @@ var memoryCommand = {
|
|
|
35187
35458
|
// src/cli/commands/run.ts
|
|
35188
35459
|
init_esm_shims();
|
|
35189
35460
|
|
|
35190
|
-
// src/agents/agent-selector.ts
|
|
35191
|
-
init_esm_shims();
|
|
35192
|
-
init_logger();
|
|
35193
|
-
function scoreAgent(task, profile) {
|
|
35194
|
-
let score = 0;
|
|
35195
|
-
const taskLower = task.toLowerCase();
|
|
35196
|
-
if (profile.selectionMetadata?.primaryIntents) {
|
|
35197
|
-
for (const intent of profile.selectionMetadata.primaryIntents) {
|
|
35198
|
-
const intentKeywords = intent.toLowerCase().split(/\s+/);
|
|
35199
|
-
const matchedKeywords = intentKeywords.filter(
|
|
35200
|
-
(keyword) => taskLower.includes(keyword) && keyword.length > 3
|
|
35201
|
-
);
|
|
35202
|
-
if (matchedKeywords.length >= 2) {
|
|
35203
|
-
score += 10;
|
|
35204
|
-
} else if (matchedKeywords.length === 1) {
|
|
35205
|
-
score += 5;
|
|
35206
|
-
}
|
|
35207
|
-
}
|
|
35208
|
-
}
|
|
35209
|
-
if (profile.selectionMetadata?.secondarySignals) {
|
|
35210
|
-
for (const signal of profile.selectionMetadata.secondarySignals) {
|
|
35211
|
-
if (taskLower.includes(signal.toLowerCase())) {
|
|
35212
|
-
score += 5;
|
|
35213
|
-
}
|
|
35214
|
-
}
|
|
35215
|
-
}
|
|
35216
|
-
if (profile.selectionMetadata?.negativeIntents) {
|
|
35217
|
-
for (const negative of profile.selectionMetadata.negativeIntents) {
|
|
35218
|
-
const keywords = negative.split("(")[0]?.toLowerCase() || "";
|
|
35219
|
-
const negativeKeywords = keywords.split(/\s+/).filter((k) => k.length > 3);
|
|
35220
|
-
const matchedNegative = negativeKeywords.filter(
|
|
35221
|
-
(keyword) => taskLower.includes(keyword)
|
|
35222
|
-
);
|
|
35223
|
-
if (matchedNegative.length >= 2) {
|
|
35224
|
-
score -= 20;
|
|
35225
|
-
}
|
|
35226
|
-
}
|
|
35227
|
-
}
|
|
35228
|
-
if (profile.selectionMetadata?.redirectWhen) {
|
|
35229
|
-
for (const rule of profile.selectionMetadata.redirectWhen) {
|
|
35230
|
-
try {
|
|
35231
|
-
const regex = new RegExp(rule.phrase, "i");
|
|
35232
|
-
if (regex.test(task)) {
|
|
35233
|
-
score -= 15;
|
|
35234
|
-
}
|
|
35235
|
-
} catch (error) {
|
|
35236
|
-
logger.debug("Invalid regex pattern in redirectWhen rule", {
|
|
35237
|
-
pattern: rule.phrase,
|
|
35238
|
-
error: error instanceof Error ? error.message : String(error)
|
|
35239
|
-
});
|
|
35240
|
-
}
|
|
35241
|
-
}
|
|
35242
|
-
}
|
|
35243
|
-
if (profile.abilitySelection?.taskBased) {
|
|
35244
|
-
for (const keyword of Object.keys(profile.abilitySelection.taskBased)) {
|
|
35245
|
-
if (taskLower.includes(keyword.toLowerCase())) {
|
|
35246
|
-
score += 3;
|
|
35247
|
-
}
|
|
35248
|
-
}
|
|
35249
|
-
}
|
|
35250
|
-
return Math.max(0, score);
|
|
35251
|
-
}
|
|
35252
|
-
function buildRationale(task, profile) {
|
|
35253
|
-
const rationale = [];
|
|
35254
|
-
const taskLower = task.toLowerCase();
|
|
35255
|
-
if (profile.selectionMetadata?.primaryIntents) {
|
|
35256
|
-
const matchedIntents = profile.selectionMetadata.primaryIntents.filter((intent) => {
|
|
35257
|
-
const keywords = intent.toLowerCase().split(/\s+/);
|
|
35258
|
-
return keywords.some((k) => taskLower.includes(k) && k.length > 3);
|
|
35259
|
-
});
|
|
35260
|
-
if (matchedIntents.length > 0) {
|
|
35261
|
-
rationale.push(`Matches: ${matchedIntents.slice(0, 2).join(", ")}`);
|
|
35262
|
-
}
|
|
35263
|
-
}
|
|
35264
|
-
if (profile.selectionMetadata?.secondarySignals) {
|
|
35265
|
-
const matchedSignals = profile.selectionMetadata.secondarySignals.filter(
|
|
35266
|
-
(signal) => taskLower.includes(signal.toLowerCase())
|
|
35267
|
-
);
|
|
35268
|
-
if (matchedSignals.length > 0) {
|
|
35269
|
-
rationale.push(`Keywords: ${matchedSignals.slice(0, 3).join(", ")}`);
|
|
35270
|
-
}
|
|
35271
|
-
}
|
|
35272
|
-
if (profile.abilitySelection?.taskBased) {
|
|
35273
|
-
const matchedAbilities = Object.keys(profile.abilitySelection.taskBased).filter(
|
|
35274
|
-
(keyword) => taskLower.includes(keyword.toLowerCase())
|
|
35275
|
-
);
|
|
35276
|
-
if (matchedAbilities.length > 0) {
|
|
35277
|
-
rationale.push(`Abilities: ${matchedAbilities.slice(0, 2).join(", ")}`);
|
|
35278
|
-
}
|
|
35279
|
-
}
|
|
35280
|
-
if (rationale.length === 0) {
|
|
35281
|
-
rationale.push("General capability match");
|
|
35282
|
-
}
|
|
35283
|
-
return rationale;
|
|
35284
|
-
}
|
|
35285
|
-
function getConfidence(score) {
|
|
35286
|
-
if (score >= 30) return "high";
|
|
35287
|
-
if (score >= 15) return "medium";
|
|
35288
|
-
return "low";
|
|
35289
|
-
}
|
|
35290
|
-
var MIN_SELECTION_SCORE = 5;
|
|
35291
|
-
var FALLBACK_AGENT = "standard";
|
|
35292
|
-
var AgentSelector = class {
|
|
35293
|
-
profileLoader;
|
|
35294
|
-
constructor(profileLoader) {
|
|
35295
|
-
this.profileLoader = profileLoader;
|
|
35296
|
-
}
|
|
35297
|
-
/**
|
|
35298
|
-
* Select the best agent for a task
|
|
35299
|
-
*
|
|
35300
|
-
* @param task - Task description
|
|
35301
|
-
* @returns Selection result with agent, score, confidence, and rationale
|
|
35302
|
-
*/
|
|
35303
|
-
async selectAgent(task) {
|
|
35304
|
-
logger.debug("[AgentSelector] Selecting agent for task", {
|
|
35305
|
-
taskPreview: task.substring(0, 100)
|
|
35306
|
-
});
|
|
35307
|
-
const agentNames = await this.profileLoader.listProfiles();
|
|
35308
|
-
if (agentNames.length === 0) {
|
|
35309
|
-
logger.warn("[AgentSelector] No agents found, using fallback");
|
|
35310
|
-
return this.createFallbackResult(task);
|
|
35311
|
-
}
|
|
35312
|
-
const profileResults = await Promise.all(
|
|
35313
|
-
agentNames.map(async (name) => {
|
|
35314
|
-
try {
|
|
35315
|
-
const profile = await this.profileLoader.loadProfile(name);
|
|
35316
|
-
return { name, profile, score: scoreAgent(task, profile) };
|
|
35317
|
-
} catch (error) {
|
|
35318
|
-
logger.debug(`[AgentSelector] Failed to load profile: ${name}`, { error });
|
|
35319
|
-
return null;
|
|
35320
|
-
}
|
|
35321
|
-
})
|
|
35322
|
-
);
|
|
35323
|
-
const scoredAgents = profileResults.filter(
|
|
35324
|
-
(result) => result !== null
|
|
35325
|
-
);
|
|
35326
|
-
if (scoredAgents.length === 0) {
|
|
35327
|
-
logger.warn("[AgentSelector] Failed to load any profiles, using fallback");
|
|
35328
|
-
return this.createFallbackResult(task);
|
|
35329
|
-
}
|
|
35330
|
-
scoredAgents.sort((a, b) => b.score - a.score);
|
|
35331
|
-
const best = scoredAgents[0];
|
|
35332
|
-
const alternatives = scoredAgents.slice(1, 4).map((a) => ({
|
|
35333
|
-
agent: a.name,
|
|
35334
|
-
score: a.score
|
|
35335
|
-
}));
|
|
35336
|
-
if (best.score < MIN_SELECTION_SCORE) {
|
|
35337
|
-
logger.info("[AgentSelector] Best score below threshold, using fallback", {
|
|
35338
|
-
bestAgent: best.name,
|
|
35339
|
-
bestScore: best.score,
|
|
35340
|
-
threshold: MIN_SELECTION_SCORE
|
|
35341
|
-
});
|
|
35342
|
-
const standardAgent = scoredAgents.find((a) => a.name === FALLBACK_AGENT);
|
|
35343
|
-
if (standardAgent) {
|
|
35344
|
-
return {
|
|
35345
|
-
agent: standardAgent.name,
|
|
35346
|
-
displayName: standardAgent.profile.displayName || standardAgent.name,
|
|
35347
|
-
role: standardAgent.profile.role,
|
|
35348
|
-
score: standardAgent.score,
|
|
35349
|
-
confidence: "low",
|
|
35350
|
-
rationale: ["No strong match found, using general-purpose agent"],
|
|
35351
|
-
alternatives: scoredAgents.filter((a) => a.name !== FALLBACK_AGENT).slice(0, 3).map((a) => ({ agent: a.name, score: a.score })),
|
|
35352
|
-
usedFallback: true
|
|
35353
|
-
};
|
|
35354
|
-
}
|
|
35355
|
-
return {
|
|
35356
|
-
agent: best.name,
|
|
35357
|
-
displayName: best.profile.displayName || best.name,
|
|
35358
|
-
role: best.profile.role,
|
|
35359
|
-
score: best.score,
|
|
35360
|
-
confidence: "low",
|
|
35361
|
-
rationale: ["Low confidence match - consider specifying agent explicitly"],
|
|
35362
|
-
alternatives,
|
|
35363
|
-
usedFallback: false
|
|
35364
|
-
};
|
|
35365
|
-
}
|
|
35366
|
-
const rationale = buildRationale(task, best.profile);
|
|
35367
|
-
const confidence = getConfidence(best.score);
|
|
35368
|
-
logger.info("[AgentSelector] Agent selected", {
|
|
35369
|
-
agent: best.name,
|
|
35370
|
-
score: best.score,
|
|
35371
|
-
confidence,
|
|
35372
|
-
rationale
|
|
35373
|
-
});
|
|
35374
|
-
return {
|
|
35375
|
-
agent: best.name,
|
|
35376
|
-
displayName: best.profile.displayName || best.name,
|
|
35377
|
-
role: best.profile.role,
|
|
35378
|
-
score: best.score,
|
|
35379
|
-
confidence,
|
|
35380
|
-
rationale,
|
|
35381
|
-
alternatives,
|
|
35382
|
-
usedFallback: false
|
|
35383
|
-
};
|
|
35384
|
-
}
|
|
35385
|
-
/**
|
|
35386
|
-
* Create a fallback result when no agents are available
|
|
35387
|
-
*/
|
|
35388
|
-
async createFallbackResult(_task) {
|
|
35389
|
-
try {
|
|
35390
|
-
const profile = await this.profileLoader.loadProfile(FALLBACK_AGENT);
|
|
35391
|
-
return {
|
|
35392
|
-
agent: FALLBACK_AGENT,
|
|
35393
|
-
displayName: profile.displayName || FALLBACK_AGENT,
|
|
35394
|
-
role: profile.role,
|
|
35395
|
-
score: 0,
|
|
35396
|
-
confidence: "low",
|
|
35397
|
-
rationale: ["No agents available, using fallback"],
|
|
35398
|
-
alternatives: [],
|
|
35399
|
-
usedFallback: true
|
|
35400
|
-
};
|
|
35401
|
-
} catch {
|
|
35402
|
-
return {
|
|
35403
|
-
agent: FALLBACK_AGENT,
|
|
35404
|
-
displayName: "Standard",
|
|
35405
|
-
role: "General-purpose agent",
|
|
35406
|
-
score: 0,
|
|
35407
|
-
confidence: "low",
|
|
35408
|
-
rationale: ["No agents available"],
|
|
35409
|
-
alternatives: [],
|
|
35410
|
-
usedFallback: true
|
|
35411
|
-
};
|
|
35412
|
-
}
|
|
35413
|
-
}
|
|
35414
|
-
};
|
|
35415
|
-
|
|
35416
35461
|
// src/core/stage-execution-controller.ts
|
|
35417
35462
|
init_esm_shims();
|
|
35418
35463
|
|
|
@@ -42317,8 +42362,8 @@ async function getProjectInfo(projectDir) {
|
|
|
42317
42362
|
return {};
|
|
42318
42363
|
}
|
|
42319
42364
|
try {
|
|
42320
|
-
const { readFile:
|
|
42321
|
-
const content = await
|
|
42365
|
+
const { readFile: readFile24 } = await import('fs/promises');
|
|
42366
|
+
const content = await readFile24(packageJsonPath, "utf-8");
|
|
42322
42367
|
const pkg = JSON.parse(content);
|
|
42323
42368
|
return {
|
|
42324
42369
|
name: pkg.name,
|
|
@@ -42431,13 +42476,13 @@ async function getCurrentVersion() {
|
|
|
42431
42476
|
const result = JSON.parse(stdout);
|
|
42432
42477
|
return result.dependencies["@defai.digital/automatosx"]?.version || "unknown";
|
|
42433
42478
|
} catch (error) {
|
|
42434
|
-
const { readFile:
|
|
42435
|
-
const { dirname:
|
|
42479
|
+
const { readFile: readFile24 } = await import('fs/promises');
|
|
42480
|
+
const { dirname: dirname17, join: join54 } = await import('path');
|
|
42436
42481
|
const { fileURLToPath: fileURLToPath5 } = await import('url');
|
|
42437
42482
|
const __filename3 = fileURLToPath5(import.meta.url);
|
|
42438
|
-
const __dirname4 =
|
|
42439
|
-
const pkgPath =
|
|
42440
|
-
const content = await
|
|
42483
|
+
const __dirname4 = dirname17(__filename3);
|
|
42484
|
+
const pkgPath = join54(__dirname4, "../../../package.json");
|
|
42485
|
+
const content = await readFile24(pkgPath, "utf-8");
|
|
42441
42486
|
const pkg = JSON.parse(content);
|
|
42442
42487
|
return pkg.version;
|
|
42443
42488
|
}
|
|
@@ -49488,10 +49533,10 @@ async function handleReset() {
|
|
|
49488
49533
|
`));
|
|
49489
49534
|
}
|
|
49490
49535
|
async function handleTrace(workspacePath, argv) {
|
|
49491
|
-
const { existsSync:
|
|
49492
|
-
const { join:
|
|
49493
|
-
const traceFile =
|
|
49494
|
-
if (!
|
|
49536
|
+
const { existsSync: existsSync29, readFileSync: readFileSync10, watchFile } = await import('fs');
|
|
49537
|
+
const { join: join54 } = await import('path');
|
|
49538
|
+
const traceFile = join54(workspacePath, ".automatosx/logs/router.trace.jsonl");
|
|
49539
|
+
if (!existsSync29(traceFile)) {
|
|
49495
49540
|
console.log(chalk5.yellow("\n\u26A0\uFE0F No trace log found\n"));
|
|
49496
49541
|
console.log(chalk5.gray(`Expected location: ${traceFile}
|
|
49497
49542
|
`));
|
|
@@ -50225,8 +50270,8 @@ async function runCodexDiagnostics(verbose) {
|
|
|
50225
50270
|
const configPath = join(workingDir, "ax.config.json");
|
|
50226
50271
|
if (existsSync(configPath)) {
|
|
50227
50272
|
try {
|
|
50228
|
-
const { readFile:
|
|
50229
|
-
const configContent = await
|
|
50273
|
+
const { readFile: readFile24 } = await import('fs/promises');
|
|
50274
|
+
const configContent = await readFile24(configPath, "utf-8");
|
|
50230
50275
|
const config = JSON.parse(configContent);
|
|
50231
50276
|
providerConfigured = config?.providers?.openai?.enabled === true;
|
|
50232
50277
|
} catch {
|
|
@@ -50910,8 +50955,8 @@ async function runMcpDiagnostics(verbose) {
|
|
|
50910
50955
|
let serverStarts = false;
|
|
50911
50956
|
if (cliAvailable) {
|
|
50912
50957
|
try {
|
|
50913
|
-
const { spawn:
|
|
50914
|
-
const proc =
|
|
50958
|
+
const { spawn: spawn12 } = await import('child_process');
|
|
50959
|
+
const proc = spawn12("automatosx", ["mcp", "server"], { stdio: ["pipe", "pipe", "pipe"] });
|
|
50915
50960
|
const initResult = await new Promise((resolve13) => {
|
|
50916
50961
|
let output = "";
|
|
50917
50962
|
proc.stderr?.on("data", (data) => {
|
|
@@ -55574,6 +55619,1641 @@ var reviewCommand = {
|
|
|
55574
55619
|
}
|
|
55575
55620
|
};
|
|
55576
55621
|
|
|
55622
|
+
// src/cli/commands/bugfix.ts
|
|
55623
|
+
init_esm_shims();
|
|
55624
|
+
|
|
55625
|
+
// src/core/bugfix/index.ts
|
|
55626
|
+
init_esm_shims();
|
|
55627
|
+
|
|
55628
|
+
// src/core/bugfix/types.ts
|
|
55629
|
+
init_esm_shims();
|
|
55630
|
+
|
|
55631
|
+
// src/core/bugfix/bug-detector.ts
|
|
55632
|
+
init_esm_shims();
|
|
55633
|
+
init_logger();
|
|
55634
|
+
var DEFAULT_DETECTION_RULES = [
|
|
55635
|
+
// Timer leak: setInterval without .unref()
|
|
55636
|
+
{
|
|
55637
|
+
id: "timer-leak-interval",
|
|
55638
|
+
type: "timer_leak",
|
|
55639
|
+
name: "setInterval without unref",
|
|
55640
|
+
description: "setInterval() without .unref() blocks process exit",
|
|
55641
|
+
pattern: "setInterval\\s*\\(",
|
|
55642
|
+
negativePattern: "\\.unref\\s*\\(\\)",
|
|
55643
|
+
withinLines: 5,
|
|
55644
|
+
confidence: 0.9,
|
|
55645
|
+
severity: "high",
|
|
55646
|
+
autoFixable: true,
|
|
55647
|
+
fixTemplate: "add_unref",
|
|
55648
|
+
fileExtensions: [".ts", ".js", ".mts", ".mjs"]
|
|
55649
|
+
},
|
|
55650
|
+
// Timer leak: setTimeout in promise without cleanup
|
|
55651
|
+
{
|
|
55652
|
+
id: "timer-leak-timeout-promise",
|
|
55653
|
+
type: "promise_timeout_leak",
|
|
55654
|
+
name: "setTimeout in Promise without cleanup",
|
|
55655
|
+
description: "setTimeout in Promise should be cleared in finally block",
|
|
55656
|
+
pattern: "new\\s+Promise[^}]*setTimeout\\s*\\(",
|
|
55657
|
+
negativePattern: "finally|clearTimeout",
|
|
55658
|
+
withinLines: 20,
|
|
55659
|
+
confidence: 0.7,
|
|
55660
|
+
severity: "medium",
|
|
55661
|
+
autoFixable: false,
|
|
55662
|
+
// Complex, needs manual review
|
|
55663
|
+
fileExtensions: [".ts", ".js", ".mts", ".mjs"]
|
|
55664
|
+
},
|
|
55665
|
+
// Missing destroy: EventEmitter without destroy method
|
|
55666
|
+
{
|
|
55667
|
+
id: "missing-destroy-eventemitter",
|
|
55668
|
+
type: "missing_destroy",
|
|
55669
|
+
name: "EventEmitter without destroy",
|
|
55670
|
+
description: "Classes extending EventEmitter should have destroy() method",
|
|
55671
|
+
pattern: "class\\s+\\w+\\s+extends\\s+(?:EventEmitter|DisposableEventEmitter)",
|
|
55672
|
+
negativePattern: "destroy\\s*\\(\\s*\\)",
|
|
55673
|
+
withinLines: 100,
|
|
55674
|
+
confidence: 0.85,
|
|
55675
|
+
severity: "high",
|
|
55676
|
+
autoFixable: true,
|
|
55677
|
+
fixTemplate: "add_destroy_method",
|
|
55678
|
+
fileExtensions: [".ts", ".js", ".mts", ".mjs"]
|
|
55679
|
+
},
|
|
55680
|
+
// Event leak: .on() without corresponding cleanup
|
|
55681
|
+
{
|
|
55682
|
+
id: "event-leak-on",
|
|
55683
|
+
type: "event_leak",
|
|
55684
|
+
name: "Event listener without cleanup",
|
|
55685
|
+
description: ".on() or .addListener() without corresponding .off() or .removeListener()",
|
|
55686
|
+
pattern: "\\.(on|addListener)\\s*\\(['\"`]\\w+['\"`]",
|
|
55687
|
+
negativePattern: "\\.(off|removeListener|removeAllListeners)\\s*\\(",
|
|
55688
|
+
withinLines: 50,
|
|
55689
|
+
confidence: 0.6,
|
|
55690
|
+
// Lower confidence - may have false positives
|
|
55691
|
+
severity: "medium",
|
|
55692
|
+
autoFixable: false,
|
|
55693
|
+
fileExtensions: [".ts", ".js", ".mts", ".mjs"]
|
|
55694
|
+
},
|
|
55695
|
+
// Uncaught promise: Promise without catch
|
|
55696
|
+
{
|
|
55697
|
+
id: "uncaught-promise",
|
|
55698
|
+
type: "uncaught_promise",
|
|
55699
|
+
name: "Promise without error handling",
|
|
55700
|
+
description: "Promise should have .catch() or be awaited in try/catch",
|
|
55701
|
+
pattern: "new\\s+Promise\\s*\\([^)]+\\)",
|
|
55702
|
+
negativePattern: "\\.catch\\s*\\(|try\\s*\\{",
|
|
55703
|
+
withinLines: 10,
|
|
55704
|
+
confidence: 0.5,
|
|
55705
|
+
// Low confidence - many false positives
|
|
55706
|
+
severity: "low",
|
|
55707
|
+
autoFixable: false,
|
|
55708
|
+
fileExtensions: [".ts", ".js", ".mts", ".mjs"]
|
|
55709
|
+
}
|
|
55710
|
+
];
|
|
55711
|
+
var BugDetector = class {
|
|
55712
|
+
rules;
|
|
55713
|
+
config;
|
|
55714
|
+
constructor(config, customRules) {
|
|
55715
|
+
this.config = config;
|
|
55716
|
+
this.rules = customRules || DEFAULT_DETECTION_RULES;
|
|
55717
|
+
if (config.bugTypes.length > 0) {
|
|
55718
|
+
this.rules = this.rules.filter(
|
|
55719
|
+
(rule) => config.bugTypes.includes(rule.type)
|
|
55720
|
+
);
|
|
55721
|
+
}
|
|
55722
|
+
logger.debug("BugDetector initialized", {
|
|
55723
|
+
ruleCount: this.rules.length,
|
|
55724
|
+
bugTypes: config.bugTypes,
|
|
55725
|
+
scope: config.scope
|
|
55726
|
+
});
|
|
55727
|
+
}
|
|
55728
|
+
/**
|
|
55729
|
+
* Scan codebase for bugs
|
|
55730
|
+
*
|
|
55731
|
+
* @param rootDir - Root directory to scan
|
|
55732
|
+
* @returns Array of bug findings
|
|
55733
|
+
*/
|
|
55734
|
+
async scan(rootDir) {
|
|
55735
|
+
const startTime = Date.now();
|
|
55736
|
+
const findings = [];
|
|
55737
|
+
logger.info("Starting bug scan", {
|
|
55738
|
+
rootDir,
|
|
55739
|
+
scope: this.config.scope,
|
|
55740
|
+
ruleCount: this.rules.length
|
|
55741
|
+
});
|
|
55742
|
+
const scanDir = this.config.scope ? join(rootDir, this.config.scope) : rootDir;
|
|
55743
|
+
const files = await this.getFilesToScan(scanDir, rootDir);
|
|
55744
|
+
logger.debug("Files to scan", { count: files.length });
|
|
55745
|
+
for (const file of files) {
|
|
55746
|
+
try {
|
|
55747
|
+
const fileFindings = await this.scanFile(file, rootDir);
|
|
55748
|
+
findings.push(...fileFindings);
|
|
55749
|
+
} catch (error) {
|
|
55750
|
+
logger.warn("Error scanning file", {
|
|
55751
|
+
file,
|
|
55752
|
+
error: error.message
|
|
55753
|
+
});
|
|
55754
|
+
}
|
|
55755
|
+
}
|
|
55756
|
+
const filteredFindings = this.filterBySeverity(findings);
|
|
55757
|
+
filteredFindings.sort((a, b) => {
|
|
55758
|
+
const severityOrder = {
|
|
55759
|
+
critical: 4,
|
|
55760
|
+
high: 3,
|
|
55761
|
+
medium: 2,
|
|
55762
|
+
low: 1
|
|
55763
|
+
};
|
|
55764
|
+
const severityDiff = severityOrder[b.severity] - severityOrder[a.severity];
|
|
55765
|
+
if (severityDiff !== 0) return severityDiff;
|
|
55766
|
+
return b.confidence - a.confidence;
|
|
55767
|
+
});
|
|
55768
|
+
const duration = Date.now() - startTime;
|
|
55769
|
+
logger.info("Bug scan complete", {
|
|
55770
|
+
totalFindings: filteredFindings.length,
|
|
55771
|
+
filesScanned: files.length,
|
|
55772
|
+
durationMs: duration
|
|
55773
|
+
});
|
|
55774
|
+
return filteredFindings;
|
|
55775
|
+
}
|
|
55776
|
+
/**
|
|
55777
|
+
* Scan a single file for bugs
|
|
55778
|
+
*/
|
|
55779
|
+
async scanFile(filePath, rootDir) {
|
|
55780
|
+
const findings = [];
|
|
55781
|
+
const content = await readFile(filePath, "utf-8");
|
|
55782
|
+
const lines = content.split("\n");
|
|
55783
|
+
const relativePath = relative(rootDir, filePath);
|
|
55784
|
+
for (const rule of this.rules) {
|
|
55785
|
+
if (rule.fileExtensions && rule.fileExtensions.length > 0) {
|
|
55786
|
+
const ext = extname$1(filePath);
|
|
55787
|
+
if (!rule.fileExtensions.includes(ext)) {
|
|
55788
|
+
continue;
|
|
55789
|
+
}
|
|
55790
|
+
}
|
|
55791
|
+
const ruleFindings = this.applyRule(rule, content, lines, relativePath);
|
|
55792
|
+
findings.push(...ruleFindings);
|
|
55793
|
+
}
|
|
55794
|
+
return findings;
|
|
55795
|
+
}
|
|
55796
|
+
/**
|
|
55797
|
+
* Apply a detection rule to file content
|
|
55798
|
+
*/
|
|
55799
|
+
applyRule(rule, content, lines, filePath) {
|
|
55800
|
+
const findings = [];
|
|
55801
|
+
if (!rule.pattern) {
|
|
55802
|
+
return findings;
|
|
55803
|
+
}
|
|
55804
|
+
try {
|
|
55805
|
+
const regex = new RegExp(rule.pattern, "g");
|
|
55806
|
+
let match;
|
|
55807
|
+
while ((match = regex.exec(content)) !== null) {
|
|
55808
|
+
const beforeMatch = content.substring(0, match.index);
|
|
55809
|
+
const lineNumber = beforeMatch.split("\n").length;
|
|
55810
|
+
if (rule.negativePattern) {
|
|
55811
|
+
const withinLines = rule.withinLines || 5;
|
|
55812
|
+
const startLine = Math.max(0, lineNumber - 1);
|
|
55813
|
+
const endLine = Math.min(lines.length, lineNumber + withinLines);
|
|
55814
|
+
const contextLines = lines.slice(startLine, endLine).join("\n");
|
|
55815
|
+
const negativeRegex = new RegExp(rule.negativePattern);
|
|
55816
|
+
if (negativeRegex.test(contextLines)) {
|
|
55817
|
+
continue;
|
|
55818
|
+
}
|
|
55819
|
+
}
|
|
55820
|
+
const contextStart = Math.max(0, lineNumber - 3);
|
|
55821
|
+
const contextEnd = Math.min(lines.length, lineNumber + 3);
|
|
55822
|
+
const context = lines.slice(contextStart, contextEnd).join("\n");
|
|
55823
|
+
const finding = {
|
|
55824
|
+
id: randomUUID(),
|
|
55825
|
+
file: filePath,
|
|
55826
|
+
lineStart: lineNumber,
|
|
55827
|
+
lineEnd: lineNumber + (rule.withinLines ? Math.min(rule.withinLines, 5) : 1),
|
|
55828
|
+
type: rule.type,
|
|
55829
|
+
severity: rule.severity,
|
|
55830
|
+
message: rule.description,
|
|
55831
|
+
context,
|
|
55832
|
+
fixStrategy: rule.autoFixable ? rule.fixTemplate : void 0,
|
|
55833
|
+
confidence: rule.confidence,
|
|
55834
|
+
detectionMethod: "regex",
|
|
55835
|
+
metadata: {
|
|
55836
|
+
ruleId: rule.id,
|
|
55837
|
+
ruleName: rule.name,
|
|
55838
|
+
matchedText: match[0].substring(0, 100)
|
|
55839
|
+
},
|
|
55840
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
55841
|
+
};
|
|
55842
|
+
findings.push(finding);
|
|
55843
|
+
logger.debug("Bug detected", {
|
|
55844
|
+
file: filePath,
|
|
55845
|
+
line: lineNumber,
|
|
55846
|
+
type: rule.type,
|
|
55847
|
+
rule: rule.id
|
|
55848
|
+
});
|
|
55849
|
+
}
|
|
55850
|
+
} catch (error) {
|
|
55851
|
+
logger.warn("Rule application failed", {
|
|
55852
|
+
ruleId: rule.id,
|
|
55853
|
+
file: filePath,
|
|
55854
|
+
error: error.message
|
|
55855
|
+
});
|
|
55856
|
+
}
|
|
55857
|
+
return findings;
|
|
55858
|
+
}
|
|
55859
|
+
/**
|
|
55860
|
+
* Get all files to scan
|
|
55861
|
+
*/
|
|
55862
|
+
async getFilesToScan(scanDir, rootDir) {
|
|
55863
|
+
const files = [];
|
|
55864
|
+
const scanDirectory = async (dir) => {
|
|
55865
|
+
try {
|
|
55866
|
+
const entries = await readdir(dir);
|
|
55867
|
+
for (const entry of entries) {
|
|
55868
|
+
const fullPath = join(dir, entry);
|
|
55869
|
+
const relativePath = relative(rootDir, fullPath);
|
|
55870
|
+
if (this.isExcluded(relativePath)) {
|
|
55871
|
+
continue;
|
|
55872
|
+
}
|
|
55873
|
+
const stats = await stat(fullPath);
|
|
55874
|
+
if (stats.isDirectory()) {
|
|
55875
|
+
await scanDirectory(fullPath);
|
|
55876
|
+
} else if (stats.isFile()) {
|
|
55877
|
+
const ext = extname$1(fullPath);
|
|
55878
|
+
if ([".ts", ".js", ".mts", ".mjs", ".tsx", ".jsx"].includes(ext)) {
|
|
55879
|
+
files.push(fullPath);
|
|
55880
|
+
}
|
|
55881
|
+
}
|
|
55882
|
+
}
|
|
55883
|
+
} catch (error) {
|
|
55884
|
+
logger.warn("Error reading directory", {
|
|
55885
|
+
dir,
|
|
55886
|
+
error: error.message
|
|
55887
|
+
});
|
|
55888
|
+
}
|
|
55889
|
+
};
|
|
55890
|
+
await scanDirectory(scanDir);
|
|
55891
|
+
return files;
|
|
55892
|
+
}
|
|
55893
|
+
/**
|
|
55894
|
+
* Check if a path should be excluded
|
|
55895
|
+
*/
|
|
55896
|
+
isExcluded(relativePath) {
|
|
55897
|
+
const defaultExclusions = [
|
|
55898
|
+
"node_modules",
|
|
55899
|
+
"dist",
|
|
55900
|
+
"build",
|
|
55901
|
+
".git",
|
|
55902
|
+
"coverage",
|
|
55903
|
+
".nyc_output",
|
|
55904
|
+
"*.test.ts",
|
|
55905
|
+
"*.spec.ts",
|
|
55906
|
+
"__tests__",
|
|
55907
|
+
"__mocks__"
|
|
55908
|
+
];
|
|
55909
|
+
const exclusions = [...defaultExclusions, ...this.config.excludePatterns];
|
|
55910
|
+
for (const pattern of exclusions) {
|
|
55911
|
+
if (pattern.includes("*")) {
|
|
55912
|
+
const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*");
|
|
55913
|
+
const regex = new RegExp(regexPattern);
|
|
55914
|
+
if (regex.test(relativePath)) {
|
|
55915
|
+
return true;
|
|
55916
|
+
}
|
|
55917
|
+
} else {
|
|
55918
|
+
if (relativePath.includes(pattern)) {
|
|
55919
|
+
return true;
|
|
55920
|
+
}
|
|
55921
|
+
}
|
|
55922
|
+
}
|
|
55923
|
+
return false;
|
|
55924
|
+
}
|
|
55925
|
+
/**
|
|
55926
|
+
* Filter findings by severity threshold
|
|
55927
|
+
*/
|
|
55928
|
+
filterBySeverity(findings) {
|
|
55929
|
+
const severityOrder = {
|
|
55930
|
+
low: 1,
|
|
55931
|
+
medium: 2,
|
|
55932
|
+
high: 3,
|
|
55933
|
+
critical: 4
|
|
55934
|
+
};
|
|
55935
|
+
const threshold = severityOrder[this.config.severityThreshold];
|
|
55936
|
+
return findings.filter(
|
|
55937
|
+
(finding) => severityOrder[finding.severity] >= threshold
|
|
55938
|
+
);
|
|
55939
|
+
}
|
|
55940
|
+
/**
|
|
55941
|
+
* Get detection rules
|
|
55942
|
+
*/
|
|
55943
|
+
getRules() {
|
|
55944
|
+
return [...this.rules];
|
|
55945
|
+
}
|
|
55946
|
+
/**
|
|
55947
|
+
* Add a custom detection rule
|
|
55948
|
+
*/
|
|
55949
|
+
addRule(rule) {
|
|
55950
|
+
this.rules.push(rule);
|
|
55951
|
+
logger.debug("Detection rule added", { ruleId: rule.id });
|
|
55952
|
+
}
|
|
55953
|
+
/**
|
|
55954
|
+
* Load rules from YAML file
|
|
55955
|
+
*/
|
|
55956
|
+
async loadRulesFromFile(filePath) {
|
|
55957
|
+
try {
|
|
55958
|
+
const content = await readFile(filePath, "utf-8");
|
|
55959
|
+
const yaml5 = await import('js-yaml');
|
|
55960
|
+
const parsed = yaml5.load(content);
|
|
55961
|
+
if (parsed.rules && Array.isArray(parsed.rules)) {
|
|
55962
|
+
for (const rule of parsed.rules) {
|
|
55963
|
+
this.addRule(rule);
|
|
55964
|
+
}
|
|
55965
|
+
logger.info("Detection rules loaded from file", {
|
|
55966
|
+
filePath,
|
|
55967
|
+
ruleCount: parsed.rules.length
|
|
55968
|
+
});
|
|
55969
|
+
}
|
|
55970
|
+
} catch (error) {
|
|
55971
|
+
logger.warn("Failed to load detection rules", {
|
|
55972
|
+
filePath,
|
|
55973
|
+
error: error.message
|
|
55974
|
+
});
|
|
55975
|
+
}
|
|
55976
|
+
}
|
|
55977
|
+
};
|
|
55978
|
+
function createDefaultBugfixConfig(overrides) {
|
|
55979
|
+
return {
|
|
55980
|
+
maxBugs: 10,
|
|
55981
|
+
maxDurationMinutes: 60,
|
|
55982
|
+
maxTokens: 5e5,
|
|
55983
|
+
maxRetriesPerBug: 3,
|
|
55984
|
+
minConfidence: 0.7,
|
|
55985
|
+
bugTypes: ["timer_leak", "missing_destroy", "promise_timeout_leak"],
|
|
55986
|
+
severityThreshold: "medium",
|
|
55987
|
+
excludePatterns: [],
|
|
55988
|
+
dryRun: false,
|
|
55989
|
+
requireTests: true,
|
|
55990
|
+
requireTypecheck: true,
|
|
55991
|
+
generateTests: false,
|
|
55992
|
+
verbose: false,
|
|
55993
|
+
...overrides
|
|
55994
|
+
};
|
|
55995
|
+
}
|
|
55996
|
+
|
|
55997
|
+
// src/core/bugfix/bug-fixer.ts
|
|
55998
|
+
init_esm_shims();
|
|
55999
|
+
init_logger();
|
|
56000
|
+
var DEFAULT_FIX_TEMPLATES = [
|
|
56001
|
+
{
|
|
56002
|
+
id: "add_unref",
|
|
56003
|
+
name: "Add .unref() to interval",
|
|
56004
|
+
description: "Add .unref() call after setInterval to prevent blocking process exit",
|
|
56005
|
+
bugType: "timer_leak",
|
|
56006
|
+
template: `
|
|
56007
|
+
// Replace setInterval with createSafeInterval for automatic cleanup
|
|
56008
|
+
import { createSafeInterval } from '@/shared/utils';
|
|
56009
|
+
|
|
56010
|
+
// Or add .unref() manually:
|
|
56011
|
+
// const interval = setInterval(callback, ms);
|
|
56012
|
+
// if (interval.unref) interval.unref();
|
|
56013
|
+
`,
|
|
56014
|
+
imports: ['createSafeInterval from "@/shared/utils"'],
|
|
56015
|
+
confidence: 0.9
|
|
56016
|
+
},
|
|
56017
|
+
{
|
|
56018
|
+
id: "add_destroy_method",
|
|
56019
|
+
name: "Add destroy() method",
|
|
56020
|
+
description: "Add destroy() method that calls removeAllListeners()",
|
|
56021
|
+
bugType: "missing_destroy",
|
|
56022
|
+
template: `
|
|
56023
|
+
/**
|
|
56024
|
+
* Clean up resources and remove all event listeners.
|
|
56025
|
+
*/
|
|
56026
|
+
destroy(): void {
|
|
56027
|
+
this.removeAllListeners();
|
|
56028
|
+
}
|
|
56029
|
+
`,
|
|
56030
|
+
confidence: 0.85
|
|
56031
|
+
},
|
|
56032
|
+
{
|
|
56033
|
+
id: "use_disposable_eventemitter",
|
|
56034
|
+
name: "Extend DisposableEventEmitter",
|
|
56035
|
+
description: "Replace EventEmitter with DisposableEventEmitter for automatic cleanup",
|
|
56036
|
+
bugType: "missing_destroy",
|
|
56037
|
+
template: `
|
|
56038
|
+
// Change: extends EventEmitter
|
|
56039
|
+
// To: extends DisposableEventEmitter
|
|
56040
|
+
|
|
56041
|
+
import { DisposableEventEmitter } from '@/shared/utils';
|
|
56042
|
+
|
|
56043
|
+
// Then implement onDestroy() hook:
|
|
56044
|
+
protected onDestroy(): void {
|
|
56045
|
+
// Custom cleanup logic
|
|
56046
|
+
}
|
|
56047
|
+
`,
|
|
56048
|
+
imports: ['DisposableEventEmitter from "@/shared/utils"'],
|
|
56049
|
+
confidence: 0.9
|
|
56050
|
+
},
|
|
56051
|
+
{
|
|
56052
|
+
id: "wrap_with_timeout",
|
|
56053
|
+
name: "Wrap with withTimeout",
|
|
56054
|
+
description: "Use withTimeout() utility for automatic cleanup",
|
|
56055
|
+
bugType: "promise_timeout_leak",
|
|
56056
|
+
template: `
|
|
56057
|
+
import { withTimeout } from '@/shared/utils';
|
|
56058
|
+
|
|
56059
|
+
// Replace manual timeout handling with:
|
|
56060
|
+
const result = await withTimeout(promise, timeoutMs, {
|
|
56061
|
+
message: 'Operation timed out'
|
|
56062
|
+
});
|
|
56063
|
+
`,
|
|
56064
|
+
imports: ['withTimeout from "@/shared/utils"'],
|
|
56065
|
+
confidence: 0.85
|
|
56066
|
+
}
|
|
56067
|
+
];
|
|
56068
|
+
var BugFixer = class {
|
|
56069
|
+
templates;
|
|
56070
|
+
backupDir;
|
|
56071
|
+
backups;
|
|
56072
|
+
// filePath -> backupPath
|
|
56073
|
+
constructor(backupDir) {
|
|
56074
|
+
this.templates = /* @__PURE__ */ new Map();
|
|
56075
|
+
this.backupDir = backupDir || join(process.cwd(), ".automatosx", "backups");
|
|
56076
|
+
this.backups = /* @__PURE__ */ new Map();
|
|
56077
|
+
for (const template of DEFAULT_FIX_TEMPLATES) {
|
|
56078
|
+
this.templates.set(template.id, template);
|
|
56079
|
+
}
|
|
56080
|
+
logger.debug("BugFixer initialized", {
|
|
56081
|
+
templateCount: this.templates.size,
|
|
56082
|
+
backupDir: this.backupDir
|
|
56083
|
+
});
|
|
56084
|
+
}
|
|
56085
|
+
/**
|
|
56086
|
+
* Apply a fix for a bug finding
|
|
56087
|
+
*
|
|
56088
|
+
* @param finding - Bug finding to fix
|
|
56089
|
+
* @param rootDir - Root directory of the project
|
|
56090
|
+
* @param dryRun - If true, don't actually modify files
|
|
56091
|
+
* @returns Fix attempt result
|
|
56092
|
+
*/
|
|
56093
|
+
async applyFix(finding, rootDir, dryRun = false) {
|
|
56094
|
+
const startTime = Date.now();
|
|
56095
|
+
const attemptId = randomUUID();
|
|
56096
|
+
const filePath = join(rootDir, finding.file);
|
|
56097
|
+
logger.info("Applying fix", {
|
|
56098
|
+
bugId: finding.id,
|
|
56099
|
+
file: finding.file,
|
|
56100
|
+
type: finding.type,
|
|
56101
|
+
dryRun
|
|
56102
|
+
});
|
|
56103
|
+
try {
|
|
56104
|
+
const originalContent = await readFile(filePath, "utf-8");
|
|
56105
|
+
const lines = originalContent.split("\n");
|
|
56106
|
+
const strategy = this.determineStrategy(finding);
|
|
56107
|
+
if (!strategy) {
|
|
56108
|
+
return this.createAttempt(attemptId, finding.id, 1, "manual_review", "", "skipped", startTime, "No automatic fix available");
|
|
56109
|
+
}
|
|
56110
|
+
const { fixedContent, diff } = await this.generateFix(finding, originalContent, lines, strategy);
|
|
56111
|
+
if (!fixedContent || fixedContent === originalContent) {
|
|
56112
|
+
return this.createAttempt(attemptId, finding.id, 1, strategy, "", "skipped", startTime, "No changes needed");
|
|
56113
|
+
}
|
|
56114
|
+
if (dryRun) {
|
|
56115
|
+
logger.info("Dry run - fix not applied", {
|
|
56116
|
+
bugId: finding.id,
|
|
56117
|
+
strategy,
|
|
56118
|
+
diffLength: diff.length
|
|
56119
|
+
});
|
|
56120
|
+
return this.createAttempt(attemptId, finding.id, 1, strategy, diff, "applied", startTime);
|
|
56121
|
+
}
|
|
56122
|
+
await this.createBackup(filePath);
|
|
56123
|
+
await writeFile(filePath, fixedContent, "utf-8");
|
|
56124
|
+
logger.info("Fix applied", {
|
|
56125
|
+
bugId: finding.id,
|
|
56126
|
+
file: finding.file,
|
|
56127
|
+
strategy
|
|
56128
|
+
});
|
|
56129
|
+
return this.createAttempt(attemptId, finding.id, 1, strategy, diff, "applied", startTime);
|
|
56130
|
+
} catch (error) {
|
|
56131
|
+
logger.error("Fix application failed", {
|
|
56132
|
+
bugId: finding.id,
|
|
56133
|
+
file: finding.file,
|
|
56134
|
+
error: error.message
|
|
56135
|
+
});
|
|
56136
|
+
return this.createAttempt(attemptId, finding.id, 1, "unknown", "", "failed", startTime, error.message);
|
|
56137
|
+
}
|
|
56138
|
+
}
|
|
56139
|
+
/**
|
|
56140
|
+
* Rollback a fix
|
|
56141
|
+
*
|
|
56142
|
+
* @param filePath - File to rollback
|
|
56143
|
+
* @returns True if rollback successful
|
|
56144
|
+
*/
|
|
56145
|
+
async rollback(filePath) {
|
|
56146
|
+
const backupPath = this.backups.get(filePath);
|
|
56147
|
+
if (!backupPath || !existsSync(backupPath)) {
|
|
56148
|
+
logger.warn("No backup found for rollback", { filePath });
|
|
56149
|
+
return false;
|
|
56150
|
+
}
|
|
56151
|
+
try {
|
|
56152
|
+
await copyFile(backupPath, filePath);
|
|
56153
|
+
await unlink(backupPath);
|
|
56154
|
+
this.backups.delete(filePath);
|
|
56155
|
+
logger.info("Fix rolled back", { filePath });
|
|
56156
|
+
return true;
|
|
56157
|
+
} catch (error) {
|
|
56158
|
+
logger.error("Rollback failed", {
|
|
56159
|
+
filePath,
|
|
56160
|
+
error: error.message
|
|
56161
|
+
});
|
|
56162
|
+
return false;
|
|
56163
|
+
}
|
|
56164
|
+
}
|
|
56165
|
+
/**
|
|
56166
|
+
* Rollback all fixes in this session
|
|
56167
|
+
*/
|
|
56168
|
+
async rollbackAll() {
|
|
56169
|
+
let rolledBack = 0;
|
|
56170
|
+
for (const filePath of this.backups.keys()) {
|
|
56171
|
+
if (await this.rollback(filePath)) {
|
|
56172
|
+
rolledBack++;
|
|
56173
|
+
}
|
|
56174
|
+
}
|
|
56175
|
+
logger.info("All fixes rolled back", { count: rolledBack });
|
|
56176
|
+
return rolledBack;
|
|
56177
|
+
}
|
|
56178
|
+
/**
|
|
56179
|
+
* Clean up backups (call after successful verification)
|
|
56180
|
+
*/
|
|
56181
|
+
async cleanupBackups() {
|
|
56182
|
+
for (const [filePath, backupPath] of this.backups.entries()) {
|
|
56183
|
+
try {
|
|
56184
|
+
if (existsSync(backupPath)) {
|
|
56185
|
+
await unlink(backupPath);
|
|
56186
|
+
}
|
|
56187
|
+
this.backups.delete(filePath);
|
|
56188
|
+
} catch (error) {
|
|
56189
|
+
logger.warn("Failed to cleanup backup", {
|
|
56190
|
+
filePath,
|
|
56191
|
+
backupPath,
|
|
56192
|
+
error: error.message
|
|
56193
|
+
});
|
|
56194
|
+
}
|
|
56195
|
+
}
|
|
56196
|
+
logger.debug("Backups cleaned up");
|
|
56197
|
+
}
|
|
56198
|
+
/**
|
|
56199
|
+
* Determine fix strategy for a finding
|
|
56200
|
+
*/
|
|
56201
|
+
determineStrategy(finding) {
|
|
56202
|
+
if (finding.fixStrategy) {
|
|
56203
|
+
return finding.fixStrategy;
|
|
56204
|
+
}
|
|
56205
|
+
for (const template of this.templates.values()) {
|
|
56206
|
+
if (template.bugType === finding.type) {
|
|
56207
|
+
return template.id;
|
|
56208
|
+
}
|
|
56209
|
+
}
|
|
56210
|
+
const autoFixableTypes = ["timer_leak", "missing_destroy"];
|
|
56211
|
+
if (autoFixableTypes.includes(finding.type)) {
|
|
56212
|
+
return `auto_fix_${finding.type}`;
|
|
56213
|
+
}
|
|
56214
|
+
return null;
|
|
56215
|
+
}
|
|
56216
|
+
/**
|
|
56217
|
+
* Generate fix for a finding
|
|
56218
|
+
*/
|
|
56219
|
+
async generateFix(finding, originalContent, lines, strategy) {
|
|
56220
|
+
let fixedContent = originalContent;
|
|
56221
|
+
let diff = "";
|
|
56222
|
+
switch (strategy) {
|
|
56223
|
+
case "add_unref":
|
|
56224
|
+
({ fixedContent, diff } = this.applyAddUnrefFix(finding, originalContent, lines));
|
|
56225
|
+
break;
|
|
56226
|
+
case "add_destroy_method":
|
|
56227
|
+
({ fixedContent, diff } = this.applyAddDestroyMethodFix(finding, originalContent, lines));
|
|
56228
|
+
break;
|
|
56229
|
+
case "use_disposable_eventemitter":
|
|
56230
|
+
({ fixedContent, diff } = this.applyUseDisposableEventEmitterFix(finding, originalContent, lines));
|
|
56231
|
+
break;
|
|
56232
|
+
case "auto_fix_timer_leak":
|
|
56233
|
+
({ fixedContent, diff } = this.applyAddUnrefFix(finding, originalContent, lines));
|
|
56234
|
+
break;
|
|
56235
|
+
case "auto_fix_missing_destroy":
|
|
56236
|
+
({ fixedContent, diff } = this.applyAddDestroyMethodFix(finding, originalContent, lines));
|
|
56237
|
+
break;
|
|
56238
|
+
default:
|
|
56239
|
+
logger.warn("Unknown fix strategy", { strategy });
|
|
56240
|
+
}
|
|
56241
|
+
return { fixedContent, diff };
|
|
56242
|
+
}
|
|
56243
|
+
/**
|
|
56244
|
+
* Apply add .unref() fix
|
|
56245
|
+
*/
|
|
56246
|
+
applyAddUnrefFix(finding, originalContent, lines) {
|
|
56247
|
+
const lineIndex = finding.lineStart - 1;
|
|
56248
|
+
const line = lines[lineIndex];
|
|
56249
|
+
if (!line) {
|
|
56250
|
+
return { fixedContent: originalContent, diff: "" };
|
|
56251
|
+
}
|
|
56252
|
+
const setIntervalPattern = /(\w+)\s*=\s*setInterval\s*\([^)]+\)\s*;?/;
|
|
56253
|
+
const match = line.match(setIntervalPattern);
|
|
56254
|
+
if (match) {
|
|
56255
|
+
const varName = match[1];
|
|
56256
|
+
const nextLines = lines.slice(lineIndex + 1, lineIndex + 5).join("\n");
|
|
56257
|
+
if (nextLines.includes(`${varName}.unref`) || nextLines.includes(`${varName}?.unref`)) {
|
|
56258
|
+
return { fixedContent: originalContent, diff: "" };
|
|
56259
|
+
}
|
|
56260
|
+
const indent = line.match(/^(\s*)/)?.[1] || "";
|
|
56261
|
+
const unrefLine = `${indent}if (${varName}.unref) ${varName}.unref();`;
|
|
56262
|
+
const newLines = [...lines];
|
|
56263
|
+
newLines.splice(lineIndex + 1, 0, unrefLine);
|
|
56264
|
+
const fixedContent = newLines.join("\n");
|
|
56265
|
+
const diff = `@@ -${finding.lineStart},1 +${finding.lineStart},2 @@
|
|
56266
|
+
${line}
|
|
56267
|
+
+${unrefLine}`;
|
|
56268
|
+
return { fixedContent, diff };
|
|
56269
|
+
}
|
|
56270
|
+
const directSetIntervalPattern = /setInterval\s*\(/;
|
|
56271
|
+
if (directSetIntervalPattern.test(line)) {
|
|
56272
|
+
line.match(/^(\s*)/)?.[1] || "";
|
|
56273
|
+
const newLine = line.replace(
|
|
56274
|
+
/(setInterval\s*\([^)]+\))/,
|
|
56275
|
+
"const _interval = $1; if (_interval.unref) _interval.unref()"
|
|
56276
|
+
);
|
|
56277
|
+
const newLines = [...lines];
|
|
56278
|
+
newLines[lineIndex] = newLine;
|
|
56279
|
+
const fixedContent = newLines.join("\n");
|
|
56280
|
+
const diff = `@@ -${finding.lineStart},1 +${finding.lineStart},1 @@
|
|
56281
|
+
-${line}
|
|
56282
|
+
+${newLine}`;
|
|
56283
|
+
return { fixedContent, diff };
|
|
56284
|
+
}
|
|
56285
|
+
return { fixedContent: originalContent, diff: "" };
|
|
56286
|
+
}
|
|
56287
|
+
/**
|
|
56288
|
+
* Apply add destroy() method fix
|
|
56289
|
+
*/
|
|
56290
|
+
applyAddDestroyMethodFix(finding, originalContent, lines) {
|
|
56291
|
+
const classPattern = /class\s+(\w+)\s+extends\s+(?:EventEmitter|DisposableEventEmitter)/;
|
|
56292
|
+
let classStartLine = -1;
|
|
56293
|
+
for (let i = 0; i < lines.length; i++) {
|
|
56294
|
+
const currentLine = lines[i];
|
|
56295
|
+
if (!currentLine) continue;
|
|
56296
|
+
const match = currentLine.match(classPattern);
|
|
56297
|
+
if (match && match[1]) {
|
|
56298
|
+
classStartLine = i;
|
|
56299
|
+
match[1];
|
|
56300
|
+
break;
|
|
56301
|
+
}
|
|
56302
|
+
}
|
|
56303
|
+
if (classStartLine === -1) {
|
|
56304
|
+
return { fixedContent: originalContent, diff: "" };
|
|
56305
|
+
}
|
|
56306
|
+
let braceCount = 0;
|
|
56307
|
+
let classEndLine = -1;
|
|
56308
|
+
for (let i = classStartLine; i < lines.length; i++) {
|
|
56309
|
+
const line = lines[i];
|
|
56310
|
+
if (!line) continue;
|
|
56311
|
+
braceCount += (line.match(/{/g) || []).length;
|
|
56312
|
+
braceCount -= (line.match(/}/g) || []).length;
|
|
56313
|
+
if (braceCount === 0 && i > classStartLine) {
|
|
56314
|
+
classEndLine = i;
|
|
56315
|
+
break;
|
|
56316
|
+
}
|
|
56317
|
+
}
|
|
56318
|
+
if (classEndLine === -1) {
|
|
56319
|
+
return { fixedContent: originalContent, diff: "" };
|
|
56320
|
+
}
|
|
56321
|
+
let indent = " ";
|
|
56322
|
+
for (let i = classStartLine + 1; i < classEndLine; i++) {
|
|
56323
|
+
const indentLine = lines[i];
|
|
56324
|
+
if (!indentLine) continue;
|
|
56325
|
+
const indentMatch = indentLine.match(/^(\s+)\S/);
|
|
56326
|
+
if (indentMatch && indentMatch[1]) {
|
|
56327
|
+
indent = indentMatch[1];
|
|
56328
|
+
break;
|
|
56329
|
+
}
|
|
56330
|
+
}
|
|
56331
|
+
const destroyMethod = [
|
|
56332
|
+
"",
|
|
56333
|
+
`${indent}/**`,
|
|
56334
|
+
`${indent} * Clean up resources and remove all event listeners.`,
|
|
56335
|
+
`${indent} */`,
|
|
56336
|
+
`${indent}destroy(): void {`,
|
|
56337
|
+
`${indent} this.removeAllListeners();`,
|
|
56338
|
+
`${indent}}`
|
|
56339
|
+
].join("\n");
|
|
56340
|
+
const newLines = [...lines];
|
|
56341
|
+
newLines.splice(classEndLine, 0, destroyMethod);
|
|
56342
|
+
const fixedContent = newLines.join("\n");
|
|
56343
|
+
const diff = `@@ -${classEndLine + 1},1 +${classEndLine + 1},8 @@
|
|
56344
|
+
+${destroyMethod}
|
|
56345
|
+
${lines[classEndLine]}`;
|
|
56346
|
+
return { fixedContent, diff };
|
|
56347
|
+
}
|
|
56348
|
+
/**
|
|
56349
|
+
* Apply use DisposableEventEmitter fix
|
|
56350
|
+
*/
|
|
56351
|
+
applyUseDisposableEventEmitterFix(finding, originalContent, lines) {
|
|
56352
|
+
let fixedContent = originalContent.replace(
|
|
56353
|
+
/extends\s+EventEmitter\b/g,
|
|
56354
|
+
"extends DisposableEventEmitter"
|
|
56355
|
+
);
|
|
56356
|
+
if (!originalContent.includes("DisposableEventEmitter")) {
|
|
56357
|
+
const importPattern = /^import\s+.*from\s+['"][^'"]+['"];?\s*$/m;
|
|
56358
|
+
const lastImportMatch = originalContent.match(new RegExp(importPattern.source + "(?!.*" + importPattern.source + ")", "s"));
|
|
56359
|
+
if (lastImportMatch) {
|
|
56360
|
+
const importStatement = `import { DisposableEventEmitter } from '@/shared/utils';`;
|
|
56361
|
+
const insertPos = lastImportMatch.index + lastImportMatch[0].length;
|
|
56362
|
+
fixedContent = fixedContent.slice(0, insertPos) + "\n" + importStatement + fixedContent.slice(insertPos);
|
|
56363
|
+
}
|
|
56364
|
+
}
|
|
56365
|
+
const diff = '--- EventEmitter\n+++ DisposableEventEmitter\n+ import { DisposableEventEmitter } from "@/shared/utils";';
|
|
56366
|
+
return { fixedContent, diff };
|
|
56367
|
+
}
|
|
56368
|
+
/**
|
|
56369
|
+
* Create backup of a file
|
|
56370
|
+
*/
|
|
56371
|
+
async createBackup(filePath) {
|
|
56372
|
+
if (!existsSync(this.backupDir)) {
|
|
56373
|
+
await mkdir(this.backupDir, { recursive: true });
|
|
56374
|
+
}
|
|
56375
|
+
const backupName = `${basename(filePath)}.${Date.now()}.bak`;
|
|
56376
|
+
const backupPath = join(this.backupDir, backupName);
|
|
56377
|
+
await copyFile(filePath, backupPath);
|
|
56378
|
+
this.backups.set(filePath, backupPath);
|
|
56379
|
+
logger.debug("Backup created", { filePath, backupPath });
|
|
56380
|
+
return backupPath;
|
|
56381
|
+
}
|
|
56382
|
+
/**
|
|
56383
|
+
* Create a fix attempt result
|
|
56384
|
+
*/
|
|
56385
|
+
createAttempt(id, bugId, attemptNumber, strategy, diff, status, startTime, error) {
|
|
56386
|
+
return {
|
|
56387
|
+
id,
|
|
56388
|
+
bugId,
|
|
56389
|
+
attemptNumber,
|
|
56390
|
+
strategy,
|
|
56391
|
+
diff,
|
|
56392
|
+
status,
|
|
56393
|
+
error,
|
|
56394
|
+
attemptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
56395
|
+
durationMs: Date.now() - startTime
|
|
56396
|
+
};
|
|
56397
|
+
}
|
|
56398
|
+
/**
|
|
56399
|
+
* Add a custom fix template
|
|
56400
|
+
*/
|
|
56401
|
+
addTemplate(template) {
|
|
56402
|
+
this.templates.set(template.id, template);
|
|
56403
|
+
logger.debug("Fix template added", { templateId: template.id });
|
|
56404
|
+
}
|
|
56405
|
+
/**
|
|
56406
|
+
* Get all fix templates
|
|
56407
|
+
*/
|
|
56408
|
+
getTemplates() {
|
|
56409
|
+
return Array.from(this.templates.values());
|
|
56410
|
+
}
|
|
56411
|
+
};
|
|
56412
|
+
|
|
56413
|
+
// src/core/bugfix/verification-gate.ts
|
|
56414
|
+
init_esm_shims();
|
|
56415
|
+
init_logger();
|
|
56416
|
+
var DEFAULT_OPTIONS4 = {
|
|
56417
|
+
typecheck: true,
|
|
56418
|
+
tests: true,
|
|
56419
|
+
checkNewErrors: true,
|
|
56420
|
+
checkCoverage: false,
|
|
56421
|
+
timeout: 12e4,
|
|
56422
|
+
// 2 minutes
|
|
56423
|
+
testCommand: "npm test",
|
|
56424
|
+
typecheckCommand: "npm run typecheck",
|
|
56425
|
+
cwd: process.cwd()
|
|
56426
|
+
};
|
|
56427
|
+
var VerificationGate = class {
|
|
56428
|
+
options;
|
|
56429
|
+
constructor(options) {
|
|
56430
|
+
this.options = { ...DEFAULT_OPTIONS4, ...options };
|
|
56431
|
+
logger.debug("VerificationGate initialized", {
|
|
56432
|
+
typecheck: this.options.typecheck,
|
|
56433
|
+
tests: this.options.tests,
|
|
56434
|
+
timeout: this.options.timeout
|
|
56435
|
+
});
|
|
56436
|
+
}
|
|
56437
|
+
/**
|
|
56438
|
+
* Verify a fix passes all gates
|
|
56439
|
+
*
|
|
56440
|
+
* @param finding - Bug finding that was fixed
|
|
56441
|
+
* @param affectedFiles - Files affected by the fix
|
|
56442
|
+
* @returns Verification result
|
|
56443
|
+
*/
|
|
56444
|
+
async verify(finding, affectedFiles) {
|
|
56445
|
+
const startTime = Date.now();
|
|
56446
|
+
logger.info("Starting verification", {
|
|
56447
|
+
bugId: finding.id,
|
|
56448
|
+
file: finding.file,
|
|
56449
|
+
affectedFiles
|
|
56450
|
+
});
|
|
56451
|
+
const result = {
|
|
56452
|
+
success: true,
|
|
56453
|
+
typecheckPassed: true,
|
|
56454
|
+
testsPassed: true,
|
|
56455
|
+
noNewErrors: true,
|
|
56456
|
+
affectedTests: [],
|
|
56457
|
+
failedTests: [],
|
|
56458
|
+
newErrors: [],
|
|
56459
|
+
durationMs: 0
|
|
56460
|
+
};
|
|
56461
|
+
try {
|
|
56462
|
+
if (this.options.typecheck) {
|
|
56463
|
+
logger.debug("Running typecheck gate");
|
|
56464
|
+
const typecheckResult = await this.runTypecheck();
|
|
56465
|
+
result.typecheckPassed = typecheckResult.success;
|
|
56466
|
+
if (!typecheckResult.success) {
|
|
56467
|
+
result.success = false;
|
|
56468
|
+
result.newErrors = typecheckResult.errors;
|
|
56469
|
+
logger.warn("Typecheck failed", {
|
|
56470
|
+
bugId: finding.id,
|
|
56471
|
+
errors: typecheckResult.errors.slice(0, 5)
|
|
56472
|
+
});
|
|
56473
|
+
} else {
|
|
56474
|
+
logger.debug("Typecheck passed");
|
|
56475
|
+
}
|
|
56476
|
+
}
|
|
56477
|
+
if (this.options.tests && result.typecheckPassed) {
|
|
56478
|
+
logger.debug("Running test gate");
|
|
56479
|
+
const testResult = await this.runTests(affectedFiles);
|
|
56480
|
+
result.testsPassed = testResult.success;
|
|
56481
|
+
result.affectedTests = testResult.affectedTests;
|
|
56482
|
+
result.failedTests = testResult.failedTests;
|
|
56483
|
+
if (!testResult.success) {
|
|
56484
|
+
result.success = false;
|
|
56485
|
+
logger.warn("Tests failed", {
|
|
56486
|
+
bugId: finding.id,
|
|
56487
|
+
failed: testResult.failedTests.slice(0, 5)
|
|
56488
|
+
});
|
|
56489
|
+
} else {
|
|
56490
|
+
logger.debug("Tests passed", { count: testResult.affectedTests.length });
|
|
56491
|
+
}
|
|
56492
|
+
}
|
|
56493
|
+
if (this.options.checkNewErrors && result.typecheckPassed && result.testsPassed) {
|
|
56494
|
+
result.noNewErrors = true;
|
|
56495
|
+
}
|
|
56496
|
+
if (this.options.checkCoverage && result.success) {
|
|
56497
|
+
result.coverageMaintained = true;
|
|
56498
|
+
}
|
|
56499
|
+
} catch (error) {
|
|
56500
|
+
result.success = false;
|
|
56501
|
+
result.newErrors = [error.message];
|
|
56502
|
+
logger.error("Verification error", {
|
|
56503
|
+
bugId: finding.id,
|
|
56504
|
+
error: error.message
|
|
56505
|
+
});
|
|
56506
|
+
}
|
|
56507
|
+
result.durationMs = Date.now() - startTime;
|
|
56508
|
+
logger.info("Verification complete", {
|
|
56509
|
+
bugId: finding.id,
|
|
56510
|
+
success: result.success,
|
|
56511
|
+
typecheckPassed: result.typecheckPassed,
|
|
56512
|
+
testsPassed: result.testsPassed,
|
|
56513
|
+
durationMs: result.durationMs
|
|
56514
|
+
});
|
|
56515
|
+
return result;
|
|
56516
|
+
}
|
|
56517
|
+
/**
|
|
56518
|
+
* Run TypeScript typecheck
|
|
56519
|
+
*/
|
|
56520
|
+
async runTypecheck() {
|
|
56521
|
+
return this.runCommand(this.options.typecheckCommand, "typecheck");
|
|
56522
|
+
}
|
|
56523
|
+
/**
|
|
56524
|
+
* Run tests for affected files
|
|
56525
|
+
*/
|
|
56526
|
+
async runTests(affectedFiles) {
|
|
56527
|
+
const result = await this.runCommand(this.options.testCommand, "test");
|
|
56528
|
+
return {
|
|
56529
|
+
success: result.success,
|
|
56530
|
+
affectedTests: affectedFiles.map((f) => `${f} tests`),
|
|
56531
|
+
failedTests: result.success ? [] : result.errors
|
|
56532
|
+
};
|
|
56533
|
+
}
|
|
56534
|
+
/**
|
|
56535
|
+
* Run a shell command
|
|
56536
|
+
*/
|
|
56537
|
+
async runCommand(command, name) {
|
|
56538
|
+
return new Promise((resolve13) => {
|
|
56539
|
+
const parts = command.split(" ");
|
|
56540
|
+
const cmd = parts[0];
|
|
56541
|
+
const args = parts.slice(1);
|
|
56542
|
+
const errors = [];
|
|
56543
|
+
let stderr = "";
|
|
56544
|
+
if (!cmd) {
|
|
56545
|
+
resolve13({ success: false, errors: ["Empty command"] });
|
|
56546
|
+
return;
|
|
56547
|
+
}
|
|
56548
|
+
logger.debug(`Running ${name}`, { command });
|
|
56549
|
+
const proc = spawn(cmd, args, {
|
|
56550
|
+
cwd: this.options.cwd,
|
|
56551
|
+
shell: true,
|
|
56552
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
56553
|
+
});
|
|
56554
|
+
const timeoutId = setTimeout(() => {
|
|
56555
|
+
proc.kill("SIGTERM");
|
|
56556
|
+
errors.push(`${name} timed out after ${this.options.timeout}ms`);
|
|
56557
|
+
}, this.options.timeout);
|
|
56558
|
+
if (timeoutId.unref) {
|
|
56559
|
+
timeoutId.unref();
|
|
56560
|
+
}
|
|
56561
|
+
proc.stderr?.on("data", (data) => {
|
|
56562
|
+
stderr += data.toString();
|
|
56563
|
+
});
|
|
56564
|
+
proc.on("close", (code) => {
|
|
56565
|
+
clearTimeout(timeoutId);
|
|
56566
|
+
if (code === 0) {
|
|
56567
|
+
resolve13({ success: true, errors: [] });
|
|
56568
|
+
} else {
|
|
56569
|
+
const errorLines = stderr.split("\n").filter((line) => line.includes("error") || line.includes("Error") || line.includes("FAIL")).slice(0, 10);
|
|
56570
|
+
resolve13({
|
|
56571
|
+
success: false,
|
|
56572
|
+
errors: errorLines.length > 0 ? errorLines : [`${name} failed with exit code ${code}`]
|
|
56573
|
+
});
|
|
56574
|
+
}
|
|
56575
|
+
});
|
|
56576
|
+
proc.on("error", (err) => {
|
|
56577
|
+
clearTimeout(timeoutId);
|
|
56578
|
+
resolve13({
|
|
56579
|
+
success: false,
|
|
56580
|
+
errors: [err.message]
|
|
56581
|
+
});
|
|
56582
|
+
});
|
|
56583
|
+
});
|
|
56584
|
+
}
|
|
56585
|
+
/**
|
|
56586
|
+
* Quick verification (typecheck only)
|
|
56587
|
+
*/
|
|
56588
|
+
async quickVerify(finding) {
|
|
56589
|
+
const result = await this.runTypecheck();
|
|
56590
|
+
return result.success;
|
|
56591
|
+
}
|
|
56592
|
+
/**
|
|
56593
|
+
* Full verification (all gates)
|
|
56594
|
+
*/
|
|
56595
|
+
async fullVerify(finding, affectedFiles) {
|
|
56596
|
+
return this.verify(finding, affectedFiles);
|
|
56597
|
+
}
|
|
56598
|
+
};
|
|
56599
|
+
|
|
56600
|
+
// src/core/bugfix/bugfix-controller.ts
|
|
56601
|
+
init_esm_shims();
|
|
56602
|
+
init_logger();
|
|
56603
|
+
var BugfixController = class {
|
|
56604
|
+
config;
|
|
56605
|
+
rootDir;
|
|
56606
|
+
state = "IDLE";
|
|
56607
|
+
sessionId;
|
|
56608
|
+
startTime = 0;
|
|
56609
|
+
totalTokens = 0;
|
|
56610
|
+
// Components
|
|
56611
|
+
detector;
|
|
56612
|
+
fixer;
|
|
56613
|
+
verifier;
|
|
56614
|
+
// Session data
|
|
56615
|
+
findings = [];
|
|
56616
|
+
attempts = [];
|
|
56617
|
+
currentBugIndex = 0;
|
|
56618
|
+
retryCount = /* @__PURE__ */ new Map();
|
|
56619
|
+
// Callbacks
|
|
56620
|
+
onProgress;
|
|
56621
|
+
onBugFound;
|
|
56622
|
+
onFixApplied;
|
|
56623
|
+
onVerification;
|
|
56624
|
+
constructor(options = {}) {
|
|
56625
|
+
this.config = createDefaultBugfixConfig(options.config);
|
|
56626
|
+
this.rootDir = options.rootDir || process.cwd();
|
|
56627
|
+
this.sessionId = randomUUID();
|
|
56628
|
+
this.detector = new BugDetector(this.config);
|
|
56629
|
+
this.fixer = new BugFixer();
|
|
56630
|
+
this.verifier = new VerificationGate({
|
|
56631
|
+
typecheck: this.config.requireTypecheck,
|
|
56632
|
+
tests: this.config.requireTests,
|
|
56633
|
+
cwd: this.rootDir
|
|
56634
|
+
});
|
|
56635
|
+
this.onProgress = options.onProgress;
|
|
56636
|
+
this.onBugFound = options.onBugFound;
|
|
56637
|
+
this.onFixApplied = options.onFixApplied;
|
|
56638
|
+
this.onVerification = options.onVerification;
|
|
56639
|
+
logger.debug("BugfixController initialized", {
|
|
56640
|
+
sessionId: this.sessionId,
|
|
56641
|
+
rootDir: this.rootDir,
|
|
56642
|
+
config: this.config
|
|
56643
|
+
});
|
|
56644
|
+
}
|
|
56645
|
+
/**
|
|
56646
|
+
* Execute autonomous bugfix workflow
|
|
56647
|
+
*
|
|
56648
|
+
* @returns Bugfix session result
|
|
56649
|
+
*/
|
|
56650
|
+
async execute() {
|
|
56651
|
+
this.startTime = Date.now();
|
|
56652
|
+
this.state = "SCANNING";
|
|
56653
|
+
logger.info("Starting bugfix session", {
|
|
56654
|
+
sessionId: this.sessionId,
|
|
56655
|
+
rootDir: this.rootDir,
|
|
56656
|
+
maxBugs: this.config.maxBugs,
|
|
56657
|
+
dryRun: this.config.dryRun
|
|
56658
|
+
});
|
|
56659
|
+
this.emitProgress("Starting bug scan...");
|
|
56660
|
+
try {
|
|
56661
|
+
while (this.shouldContinue()) {
|
|
56662
|
+
const currentState = this.state;
|
|
56663
|
+
if (currentState === "IDLE") {
|
|
56664
|
+
this.state = "SCANNING";
|
|
56665
|
+
} else if (currentState === "SCANNING") {
|
|
56666
|
+
await this.handleScanning();
|
|
56667
|
+
} else if (currentState === "ANALYZING") {
|
|
56668
|
+
await this.handleAnalyzing();
|
|
56669
|
+
} else if (currentState === "PLANNING") {
|
|
56670
|
+
await this.handlePlanning();
|
|
56671
|
+
} else if (currentState === "FIXING") {
|
|
56672
|
+
await this.handleFixing();
|
|
56673
|
+
} else if (currentState === "VERIFYING") {
|
|
56674
|
+
await this.handleVerifying();
|
|
56675
|
+
} else if (currentState === "LEARNING") {
|
|
56676
|
+
await this.handleLearning();
|
|
56677
|
+
} else if (currentState === "ITERATING") {
|
|
56678
|
+
await this.handleIterating();
|
|
56679
|
+
} else if (currentState === "COMPLETE" || currentState === "FAILED") {
|
|
56680
|
+
break;
|
|
56681
|
+
}
|
|
56682
|
+
}
|
|
56683
|
+
return this.buildResult();
|
|
56684
|
+
} catch (error) {
|
|
56685
|
+
this.state = "FAILED";
|
|
56686
|
+
logger.error("Bugfix session failed", {
|
|
56687
|
+
sessionId: this.sessionId,
|
|
56688
|
+
error: error.message,
|
|
56689
|
+
state: this.state
|
|
56690
|
+
});
|
|
56691
|
+
return this.buildResult(error.message);
|
|
56692
|
+
}
|
|
56693
|
+
}
|
|
56694
|
+
/**
|
|
56695
|
+
* Check if execution should continue
|
|
56696
|
+
*/
|
|
56697
|
+
shouldContinue() {
|
|
56698
|
+
if (this.state === "COMPLETE" || this.state === "FAILED") {
|
|
56699
|
+
return false;
|
|
56700
|
+
}
|
|
56701
|
+
const elapsedMinutes = (Date.now() - this.startTime) / 1e3 / 60;
|
|
56702
|
+
if (elapsedMinutes >= this.config.maxDurationMinutes) {
|
|
56703
|
+
logger.warn("Time limit exceeded", {
|
|
56704
|
+
elapsed: elapsedMinutes,
|
|
56705
|
+
limit: this.config.maxDurationMinutes
|
|
56706
|
+
});
|
|
56707
|
+
this.state = "COMPLETE";
|
|
56708
|
+
return false;
|
|
56709
|
+
}
|
|
56710
|
+
if (this.totalTokens >= this.config.maxTokens) {
|
|
56711
|
+
logger.warn("Token limit exceeded", {
|
|
56712
|
+
tokens: this.totalTokens,
|
|
56713
|
+
limit: this.config.maxTokens
|
|
56714
|
+
});
|
|
56715
|
+
this.state = "COMPLETE";
|
|
56716
|
+
return false;
|
|
56717
|
+
}
|
|
56718
|
+
const fixedCount = this.attempts.filter((a) => a.status === "verified").length;
|
|
56719
|
+
if (fixedCount >= this.config.maxBugs) {
|
|
56720
|
+
logger.info("Max bugs fixed", {
|
|
56721
|
+
fixed: fixedCount,
|
|
56722
|
+
limit: this.config.maxBugs
|
|
56723
|
+
});
|
|
56724
|
+
this.state = "COMPLETE";
|
|
56725
|
+
return false;
|
|
56726
|
+
}
|
|
56727
|
+
return true;
|
|
56728
|
+
}
|
|
56729
|
+
/**
|
|
56730
|
+
* Handle SCANNING state
|
|
56731
|
+
*/
|
|
56732
|
+
async handleScanning() {
|
|
56733
|
+
this.emitProgress("Scanning for bugs...");
|
|
56734
|
+
this.findings = await this.detector.scan(this.rootDir);
|
|
56735
|
+
if (this.findings.length === 0) {
|
|
56736
|
+
this.emitProgress("No bugs found!");
|
|
56737
|
+
this.state = "COMPLETE";
|
|
56738
|
+
return;
|
|
56739
|
+
}
|
|
56740
|
+
this.emitProgress(`Found ${this.findings.length} bugs`);
|
|
56741
|
+
for (const finding of this.findings) {
|
|
56742
|
+
this.onBugFound?.(finding);
|
|
56743
|
+
}
|
|
56744
|
+
this.state = "ANALYZING";
|
|
56745
|
+
}
|
|
56746
|
+
/**
|
|
56747
|
+
* Handle ANALYZING state
|
|
56748
|
+
*/
|
|
56749
|
+
async handleAnalyzing() {
|
|
56750
|
+
this.emitProgress("Analyzing bugs...");
|
|
56751
|
+
this.findings = this.findings.filter((f) => f.confidence >= this.config.minConfidence);
|
|
56752
|
+
if (this.findings.length === 0) {
|
|
56753
|
+
this.emitProgress("No bugs above confidence threshold");
|
|
56754
|
+
this.state = "COMPLETE";
|
|
56755
|
+
return;
|
|
56756
|
+
}
|
|
56757
|
+
this.emitProgress(`${this.findings.length} bugs to fix`);
|
|
56758
|
+
this.currentBugIndex = 0;
|
|
56759
|
+
this.state = "PLANNING";
|
|
56760
|
+
}
|
|
56761
|
+
/**
|
|
56762
|
+
* Handle PLANNING state
|
|
56763
|
+
*/
|
|
56764
|
+
async handlePlanning() {
|
|
56765
|
+
const finding = this.findings[this.currentBugIndex];
|
|
56766
|
+
if (!finding) {
|
|
56767
|
+
this.state = "COMPLETE";
|
|
56768
|
+
return;
|
|
56769
|
+
}
|
|
56770
|
+
this.emitProgress(`Planning fix for bug ${this.currentBugIndex + 1}/${this.findings.length}`, {
|
|
56771
|
+
file: finding.file,
|
|
56772
|
+
type: finding.type,
|
|
56773
|
+
severity: finding.severity
|
|
56774
|
+
});
|
|
56775
|
+
if (!finding.fixStrategy) {
|
|
56776
|
+
logger.info("Bug requires manual review", {
|
|
56777
|
+
bugId: finding.id,
|
|
56778
|
+
type: finding.type
|
|
56779
|
+
});
|
|
56780
|
+
const skippedAttempt = {
|
|
56781
|
+
id: randomUUID(),
|
|
56782
|
+
bugId: finding.id,
|
|
56783
|
+
attemptNumber: 1,
|
|
56784
|
+
strategy: "manual_review",
|
|
56785
|
+
diff: "",
|
|
56786
|
+
status: "skipped",
|
|
56787
|
+
error: "No automatic fix available",
|
|
56788
|
+
attemptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
56789
|
+
durationMs: 0
|
|
56790
|
+
};
|
|
56791
|
+
this.attempts.push(skippedAttempt);
|
|
56792
|
+
this.currentBugIndex++;
|
|
56793
|
+
if (this.currentBugIndex >= this.findings.length) {
|
|
56794
|
+
this.state = "COMPLETE";
|
|
56795
|
+
}
|
|
56796
|
+
return;
|
|
56797
|
+
}
|
|
56798
|
+
this.state = "FIXING";
|
|
56799
|
+
}
|
|
56800
|
+
/**
|
|
56801
|
+
* Handle FIXING state
|
|
56802
|
+
*/
|
|
56803
|
+
async handleFixing() {
|
|
56804
|
+
const finding = this.findings[this.currentBugIndex];
|
|
56805
|
+
if (!finding) {
|
|
56806
|
+
this.state = "COMPLETE";
|
|
56807
|
+
return;
|
|
56808
|
+
}
|
|
56809
|
+
this.emitProgress(`Fixing: ${finding.file}:${finding.lineStart}`, {
|
|
56810
|
+
type: finding.type,
|
|
56811
|
+
strategy: finding.fixStrategy
|
|
56812
|
+
});
|
|
56813
|
+
const attempt = await this.fixer.applyFix(finding, this.rootDir, this.config.dryRun);
|
|
56814
|
+
this.attempts.push(attempt);
|
|
56815
|
+
this.onFixApplied?.(finding, attempt);
|
|
56816
|
+
if (attempt.status === "applied") {
|
|
56817
|
+
this.state = "VERIFYING";
|
|
56818
|
+
} else if (attempt.status === "skipped") {
|
|
56819
|
+
this.emitProgress(`Skipped: ${attempt.error || "No changes needed"}`);
|
|
56820
|
+
this.currentBugIndex++;
|
|
56821
|
+
this.state = this.currentBugIndex >= this.findings.length ? "COMPLETE" : "PLANNING";
|
|
56822
|
+
} else {
|
|
56823
|
+
this.state = "ITERATING";
|
|
56824
|
+
}
|
|
56825
|
+
}
|
|
56826
|
+
/**
|
|
56827
|
+
* Handle VERIFYING state
|
|
56828
|
+
*/
|
|
56829
|
+
async handleVerifying() {
|
|
56830
|
+
const finding = this.findings[this.currentBugIndex];
|
|
56831
|
+
const lastAttempt = this.attempts[this.attempts.length - 1];
|
|
56832
|
+
if (!finding || !lastAttempt) {
|
|
56833
|
+
this.state = "COMPLETE";
|
|
56834
|
+
return;
|
|
56835
|
+
}
|
|
56836
|
+
this.emitProgress(`Verifying fix for ${finding.file}...`);
|
|
56837
|
+
if (!this.config.dryRun) {
|
|
56838
|
+
const result = await this.verifier.verify(finding, [finding.file]);
|
|
56839
|
+
lastAttempt.verificationResult = result;
|
|
56840
|
+
this.onVerification?.(finding, result.success);
|
|
56841
|
+
if (result.success) {
|
|
56842
|
+
lastAttempt.status = "verified";
|
|
56843
|
+
this.emitProgress("Fix verified!", {
|
|
56844
|
+
typecheck: result.typecheckPassed,
|
|
56845
|
+
tests: result.testsPassed
|
|
56846
|
+
});
|
|
56847
|
+
this.state = "LEARNING";
|
|
56848
|
+
} else {
|
|
56849
|
+
lastAttempt.status = "failed";
|
|
56850
|
+
lastAttempt.error = result.newErrors.join("; ") || "Verification failed";
|
|
56851
|
+
this.emitProgress("Verification failed, rolling back...", {
|
|
56852
|
+
errors: result.newErrors
|
|
56853
|
+
});
|
|
56854
|
+
await this.fixer.rollback(finding.file);
|
|
56855
|
+
this.state = "ITERATING";
|
|
56856
|
+
}
|
|
56857
|
+
} else {
|
|
56858
|
+
lastAttempt.status = "verified";
|
|
56859
|
+
this.emitProgress("Dry run - skipping verification");
|
|
56860
|
+
this.state = "LEARNING";
|
|
56861
|
+
}
|
|
56862
|
+
}
|
|
56863
|
+
/**
|
|
56864
|
+
* Handle LEARNING state
|
|
56865
|
+
*/
|
|
56866
|
+
async handleLearning() {
|
|
56867
|
+
const finding = this.findings[this.currentBugIndex];
|
|
56868
|
+
if (!finding) {
|
|
56869
|
+
this.state = "COMPLETE";
|
|
56870
|
+
return;
|
|
56871
|
+
}
|
|
56872
|
+
this.emitProgress("Storing pattern to knowledge base...");
|
|
56873
|
+
logger.info("Pattern learned", {
|
|
56874
|
+
bugId: finding.id,
|
|
56875
|
+
type: finding.type,
|
|
56876
|
+
file: finding.file
|
|
56877
|
+
});
|
|
56878
|
+
this.currentBugIndex++;
|
|
56879
|
+
if (this.currentBugIndex >= this.findings.length) {
|
|
56880
|
+
this.state = "COMPLETE";
|
|
56881
|
+
} else {
|
|
56882
|
+
this.state = "PLANNING";
|
|
56883
|
+
}
|
|
56884
|
+
}
|
|
56885
|
+
/**
|
|
56886
|
+
* Handle ITERATING state (retry logic)
|
|
56887
|
+
*/
|
|
56888
|
+
async handleIterating() {
|
|
56889
|
+
const finding = this.findings[this.currentBugIndex];
|
|
56890
|
+
if (!finding) {
|
|
56891
|
+
this.state = "COMPLETE";
|
|
56892
|
+
return;
|
|
56893
|
+
}
|
|
56894
|
+
const currentRetries = this.retryCount.get(finding.id) || 0;
|
|
56895
|
+
if (currentRetries >= this.config.maxRetriesPerBug) {
|
|
56896
|
+
this.emitProgress(`Max retries reached for ${finding.file}`, {
|
|
56897
|
+
retries: currentRetries
|
|
56898
|
+
});
|
|
56899
|
+
this.currentBugIndex++;
|
|
56900
|
+
if (this.currentBugIndex >= this.findings.length) {
|
|
56901
|
+
this.state = "COMPLETE";
|
|
56902
|
+
} else {
|
|
56903
|
+
this.state = "PLANNING";
|
|
56904
|
+
}
|
|
56905
|
+
return;
|
|
56906
|
+
}
|
|
56907
|
+
this.retryCount.set(finding.id, currentRetries + 1);
|
|
56908
|
+
this.emitProgress(`Retrying fix (attempt ${currentRetries + 2})...`);
|
|
56909
|
+
this.state = "FIXING";
|
|
56910
|
+
}
|
|
56911
|
+
/**
|
|
56912
|
+
* Build final result
|
|
56913
|
+
*/
|
|
56914
|
+
buildResult(error) {
|
|
56915
|
+
const stats = this.calculateStats();
|
|
56916
|
+
return {
|
|
56917
|
+
sessionId: this.sessionId,
|
|
56918
|
+
startedAt: new Date(this.startTime).toISOString(),
|
|
56919
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
56920
|
+
config: this.config,
|
|
56921
|
+
findings: this.findings,
|
|
56922
|
+
attempts: this.attempts,
|
|
56923
|
+
stats,
|
|
56924
|
+
finalState: this.state,
|
|
56925
|
+
error
|
|
56926
|
+
};
|
|
56927
|
+
}
|
|
56928
|
+
/**
|
|
56929
|
+
* Calculate session statistics
|
|
56930
|
+
*/
|
|
56931
|
+
calculateStats() {
|
|
56932
|
+
const verified = this.attempts.filter((a) => a.status === "verified").length;
|
|
56933
|
+
const failed = this.attempts.filter((a) => a.status === "failed").length;
|
|
56934
|
+
const skipped = this.attempts.filter((a) => a.status === "skipped").length;
|
|
56935
|
+
const bugsByType = {
|
|
56936
|
+
timer_leak: 0,
|
|
56937
|
+
missing_destroy: 0,
|
|
56938
|
+
promise_timeout_leak: 0,
|
|
56939
|
+
event_leak: 0,
|
|
56940
|
+
resource_leak: 0,
|
|
56941
|
+
race_condition: 0,
|
|
56942
|
+
memory_leak: 0,
|
|
56943
|
+
uncaught_promise: 0,
|
|
56944
|
+
deprecated_api: 0,
|
|
56945
|
+
security_issue: 0,
|
|
56946
|
+
type_error: 0,
|
|
56947
|
+
test_failure: 0,
|
|
56948
|
+
custom: 0
|
|
56949
|
+
};
|
|
56950
|
+
for (const finding of this.findings) {
|
|
56951
|
+
bugsByType[finding.type] = (bugsByType[finding.type] || 0) + 1;
|
|
56952
|
+
}
|
|
56953
|
+
const bugsBySeverity = {
|
|
56954
|
+
low: 0,
|
|
56955
|
+
medium: 0,
|
|
56956
|
+
high: 0,
|
|
56957
|
+
critical: 0
|
|
56958
|
+
};
|
|
56959
|
+
for (const finding of this.findings) {
|
|
56960
|
+
bugsBySeverity[finding.severity] = (bugsBySeverity[finding.severity] || 0) + 1;
|
|
56961
|
+
}
|
|
56962
|
+
let stopReason = "complete";
|
|
56963
|
+
const elapsedMinutes = (Date.now() - this.startTime) / 1e3 / 60;
|
|
56964
|
+
if (this.state === "FAILED") {
|
|
56965
|
+
stopReason = "error";
|
|
56966
|
+
} else if (verified >= this.config.maxBugs) {
|
|
56967
|
+
stopReason = "max_bugs";
|
|
56968
|
+
} else if (elapsedMinutes >= this.config.maxDurationMinutes) {
|
|
56969
|
+
stopReason = "max_time";
|
|
56970
|
+
} else if (this.totalTokens >= this.config.maxTokens) {
|
|
56971
|
+
stopReason = "max_tokens";
|
|
56972
|
+
}
|
|
56973
|
+
return {
|
|
56974
|
+
bugsFound: this.findings.length,
|
|
56975
|
+
bugsFixed: verified,
|
|
56976
|
+
bugsFailed: failed,
|
|
56977
|
+
bugsSkipped: skipped,
|
|
56978
|
+
totalAttempts: this.attempts.length,
|
|
56979
|
+
successRate: this.attempts.length > 0 ? verified / this.attempts.length : 0,
|
|
56980
|
+
totalDurationMs: Date.now() - this.startTime,
|
|
56981
|
+
totalTokens: this.totalTokens,
|
|
56982
|
+
patternsLearned: verified,
|
|
56983
|
+
// Each verified fix is a learned pattern
|
|
56984
|
+
regressions: 0,
|
|
56985
|
+
// Should always be 0 with verification
|
|
56986
|
+
stopReason,
|
|
56987
|
+
bugsByType,
|
|
56988
|
+
bugsBySeverity
|
|
56989
|
+
};
|
|
56990
|
+
}
|
|
56991
|
+
/**
|
|
56992
|
+
* Emit progress update
|
|
56993
|
+
*/
|
|
56994
|
+
emitProgress(message, data) {
|
|
56995
|
+
logger.info(message, { sessionId: this.sessionId, ...data });
|
|
56996
|
+
this.onProgress?.(message, data);
|
|
56997
|
+
}
|
|
56998
|
+
/**
|
|
56999
|
+
* Get current state
|
|
57000
|
+
*/
|
|
57001
|
+
getState() {
|
|
57002
|
+
return this.state;
|
|
57003
|
+
}
|
|
57004
|
+
/**
|
|
57005
|
+
* Get current statistics
|
|
57006
|
+
*/
|
|
57007
|
+
getStats() {
|
|
57008
|
+
return this.calculateStats();
|
|
57009
|
+
}
|
|
57010
|
+
/**
|
|
57011
|
+
* Stop execution
|
|
57012
|
+
*/
|
|
57013
|
+
async stop() {
|
|
57014
|
+
logger.info("Stopping bugfix session", { sessionId: this.sessionId });
|
|
57015
|
+
this.state = "COMPLETE";
|
|
57016
|
+
await this.fixer.cleanupBackups();
|
|
57017
|
+
}
|
|
57018
|
+
};
|
|
57019
|
+
|
|
57020
|
+
// src/cli/commands/bugfix.ts
|
|
57021
|
+
init_logger();
|
|
57022
|
+
init_path_resolver();
|
|
57023
|
+
function formatSeverity(severity) {
|
|
57024
|
+
switch (severity) {
|
|
57025
|
+
case "critical":
|
|
57026
|
+
return chalk5.bgRed.white(" CRITICAL ");
|
|
57027
|
+
case "high":
|
|
57028
|
+
return chalk5.red("[HIGH]");
|
|
57029
|
+
case "medium":
|
|
57030
|
+
return chalk5.yellow("[MED]");
|
|
57031
|
+
case "low":
|
|
57032
|
+
return chalk5.gray("[LOW]");
|
|
57033
|
+
default: {
|
|
57034
|
+
const s = severity;
|
|
57035
|
+
return `[${s.toUpperCase()}]`;
|
|
57036
|
+
}
|
|
57037
|
+
}
|
|
57038
|
+
}
|
|
57039
|
+
function formatBugType(type) {
|
|
57040
|
+
const typeNames = {
|
|
57041
|
+
timer_leak: "Timer leak",
|
|
57042
|
+
missing_destroy: "Missing destroy()",
|
|
57043
|
+
promise_timeout_leak: "Promise timeout leak",
|
|
57044
|
+
event_leak: "Event listener leak",
|
|
57045
|
+
resource_leak: "Resource leak",
|
|
57046
|
+
race_condition: "Race condition",
|
|
57047
|
+
memory_leak: "Memory leak",
|
|
57048
|
+
uncaught_promise: "Uncaught promise",
|
|
57049
|
+
deprecated_api: "Deprecated API",
|
|
57050
|
+
security_issue: "Security issue",
|
|
57051
|
+
type_error: "Type error",
|
|
57052
|
+
test_failure: "Test failure",
|
|
57053
|
+
custom: "Custom"
|
|
57054
|
+
};
|
|
57055
|
+
return typeNames[type] || type;
|
|
57056
|
+
}
|
|
57057
|
+
var bugfixCommand = {
|
|
57058
|
+
command: "bugfix",
|
|
57059
|
+
describe: "Find and fix bugs in the codebase",
|
|
57060
|
+
builder: {
|
|
57061
|
+
auto: {
|
|
57062
|
+
type: "boolean",
|
|
57063
|
+
default: false,
|
|
57064
|
+
describe: "Run autonomously without prompts"
|
|
57065
|
+
},
|
|
57066
|
+
scope: {
|
|
57067
|
+
type: "string",
|
|
57068
|
+
describe: "Limit scan to directory (e.g., src/core/)"
|
|
57069
|
+
},
|
|
57070
|
+
severity: {
|
|
57071
|
+
type: "string",
|
|
57072
|
+
choices: ["low", "medium", "high", "critical"],
|
|
57073
|
+
default: "medium",
|
|
57074
|
+
describe: "Minimum severity to fix"
|
|
57075
|
+
},
|
|
57076
|
+
"max-iterations": {
|
|
57077
|
+
type: "number",
|
|
57078
|
+
default: 10,
|
|
57079
|
+
describe: "Maximum bugs to fix per session"
|
|
57080
|
+
},
|
|
57081
|
+
"dry-run": {
|
|
57082
|
+
type: "boolean",
|
|
57083
|
+
default: false,
|
|
57084
|
+
describe: "Preview fixes without applying"
|
|
57085
|
+
},
|
|
57086
|
+
verbose: {
|
|
57087
|
+
type: "boolean",
|
|
57088
|
+
default: false,
|
|
57089
|
+
describe: "Verbose output"
|
|
57090
|
+
},
|
|
57091
|
+
quiet: {
|
|
57092
|
+
type: "boolean",
|
|
57093
|
+
default: false,
|
|
57094
|
+
describe: "Minimal output"
|
|
57095
|
+
},
|
|
57096
|
+
types: {
|
|
57097
|
+
type: "array",
|
|
57098
|
+
describe: "Bug types to scan for",
|
|
57099
|
+
choices: [
|
|
57100
|
+
"timer_leak",
|
|
57101
|
+
"missing_destroy",
|
|
57102
|
+
"promise_timeout_leak",
|
|
57103
|
+
"event_leak",
|
|
57104
|
+
"resource_leak"
|
|
57105
|
+
]
|
|
57106
|
+
}
|
|
57107
|
+
},
|
|
57108
|
+
handler: async (argv) => {
|
|
57109
|
+
const spinner = ora8();
|
|
57110
|
+
const findings = [];
|
|
57111
|
+
const fixedBugs = [];
|
|
57112
|
+
const failedBugs = [];
|
|
57113
|
+
const skippedBugs = [];
|
|
57114
|
+
const rootDir = await detectProjectRoot() || process.cwd();
|
|
57115
|
+
const config = {
|
|
57116
|
+
maxBugs: argv.maxIterations || 10,
|
|
57117
|
+
severityThreshold: argv.severity || "medium",
|
|
57118
|
+
scope: argv.scope,
|
|
57119
|
+
dryRun: argv.dryRun || false,
|
|
57120
|
+
verbose: argv.verbose || false,
|
|
57121
|
+
bugTypes: argv.types || ["timer_leak", "missing_destroy", "promise_timeout_leak"]
|
|
57122
|
+
};
|
|
57123
|
+
if (!argv.quiet) {
|
|
57124
|
+
console.log(chalk5.cyan("\n\u{1F527} AutomatosX Bug Fixer\n"));
|
|
57125
|
+
if (argv.dryRun) {
|
|
57126
|
+
console.log(chalk5.yellow(" \u26A0\uFE0F Dry run mode - no changes will be made\n"));
|
|
57127
|
+
}
|
|
57128
|
+
if (argv.scope) {
|
|
57129
|
+
console.log(chalk5.gray(` Scope: ${argv.scope}`));
|
|
57130
|
+
}
|
|
57131
|
+
console.log(chalk5.gray(` Severity threshold: ${argv.severity}`));
|
|
57132
|
+
console.log(chalk5.gray(` Max bugs: ${argv.maxIterations}`));
|
|
57133
|
+
console.log();
|
|
57134
|
+
}
|
|
57135
|
+
try {
|
|
57136
|
+
const controller = new BugfixController({
|
|
57137
|
+
config,
|
|
57138
|
+
rootDir,
|
|
57139
|
+
onProgress: (message, data) => {
|
|
57140
|
+
if (argv.quiet) return;
|
|
57141
|
+
if (message.startsWith("Scanning")) {
|
|
57142
|
+
spinner.start(message);
|
|
57143
|
+
} else if (message.includes("Found")) {
|
|
57144
|
+
spinner.succeed(message);
|
|
57145
|
+
} else if (message.startsWith("Fixing")) {
|
|
57146
|
+
spinner.start(message);
|
|
57147
|
+
} else if (message.includes("verified") || message.includes("Verified")) {
|
|
57148
|
+
spinner.succeed(message);
|
|
57149
|
+
} else if (message.includes("failed") || message.includes("Failed")) {
|
|
57150
|
+
spinner.fail(message);
|
|
57151
|
+
} else if (message.includes("Skipped")) {
|
|
57152
|
+
spinner.info(message);
|
|
57153
|
+
} else if (argv.verbose) {
|
|
57154
|
+
spinner.info(message);
|
|
57155
|
+
}
|
|
57156
|
+
},
|
|
57157
|
+
onBugFound: (finding) => {
|
|
57158
|
+
findings.push(finding);
|
|
57159
|
+
},
|
|
57160
|
+
onFixApplied: (finding, attempt) => {
|
|
57161
|
+
if (attempt.status === "verified") {
|
|
57162
|
+
fixedBugs.push(finding);
|
|
57163
|
+
} else if (attempt.status === "failed") {
|
|
57164
|
+
failedBugs.push(finding);
|
|
57165
|
+
} else if (attempt.status === "skipped") {
|
|
57166
|
+
skippedBugs.push(finding);
|
|
57167
|
+
}
|
|
57168
|
+
},
|
|
57169
|
+
onVerification: (finding, success) => {
|
|
57170
|
+
if (!argv.quiet && argv.verbose) {
|
|
57171
|
+
const icon = success ? "\u2713" : "\u2717";
|
|
57172
|
+
console.log(chalk5.gray(` ${icon} Verification: ${success ? "passed" : "failed"}`));
|
|
57173
|
+
}
|
|
57174
|
+
}
|
|
57175
|
+
});
|
|
57176
|
+
const result = await controller.execute();
|
|
57177
|
+
if (!argv.quiet) {
|
|
57178
|
+
spinner.stop();
|
|
57179
|
+
console.log("\n" + chalk5.cyan("\u2501".repeat(50)));
|
|
57180
|
+
console.log(chalk5.cyan.bold(" Results"));
|
|
57181
|
+
console.log(chalk5.cyan("\u2501".repeat(50)) + "\n");
|
|
57182
|
+
if (findings.length > 0) {
|
|
57183
|
+
console.log(chalk5.bold("\u{1F4CB} Bugs found:\n"));
|
|
57184
|
+
for (const finding of findings.slice(0, 20)) {
|
|
57185
|
+
const severity = formatSeverity(finding.severity);
|
|
57186
|
+
const type = formatBugType(finding.type);
|
|
57187
|
+
console.log(` ${severity} ${type} in ${chalk5.cyan(finding.file)}:${finding.lineStart}`);
|
|
57188
|
+
}
|
|
57189
|
+
if (findings.length > 20) {
|
|
57190
|
+
console.log(chalk5.gray(` ... and ${findings.length - 20} more`));
|
|
57191
|
+
}
|
|
57192
|
+
console.log();
|
|
57193
|
+
}
|
|
57194
|
+
if (fixedBugs.length > 0) {
|
|
57195
|
+
console.log(chalk5.green.bold(`\u2705 Fixed: ${fixedBugs.length} bug(s)
|
|
57196
|
+
`));
|
|
57197
|
+
for (const bug of fixedBugs.slice(0, 10)) {
|
|
57198
|
+
console.log(chalk5.green(` \u2713 ${formatBugType(bug.type)} in ${bug.file}:${bug.lineStart}`));
|
|
57199
|
+
}
|
|
57200
|
+
if (fixedBugs.length > 10) {
|
|
57201
|
+
console.log(chalk5.gray(` ... and ${fixedBugs.length - 10} more`));
|
|
57202
|
+
}
|
|
57203
|
+
console.log();
|
|
57204
|
+
}
|
|
57205
|
+
if (failedBugs.length > 0) {
|
|
57206
|
+
console.log(chalk5.red.bold(`\u274C Failed: ${failedBugs.length} bug(s)
|
|
57207
|
+
`));
|
|
57208
|
+
for (const bug of failedBugs.slice(0, 5)) {
|
|
57209
|
+
console.log(chalk5.red(` \u2717 ${formatBugType(bug.type)} in ${bug.file}:${bug.lineStart}`));
|
|
57210
|
+
}
|
|
57211
|
+
console.log();
|
|
57212
|
+
}
|
|
57213
|
+
if (skippedBugs.length > 0) {
|
|
57214
|
+
console.log(chalk5.yellow.bold(`\u26A0\uFE0F Skipped (manual review needed): ${skippedBugs.length} bug(s)
|
|
57215
|
+
`));
|
|
57216
|
+
for (const bug of skippedBugs.slice(0, 5)) {
|
|
57217
|
+
console.log(chalk5.yellow(` \u2192 ${formatBugType(bug.type)} in ${bug.file}:${bug.lineStart}`));
|
|
57218
|
+
}
|
|
57219
|
+
console.log();
|
|
57220
|
+
}
|
|
57221
|
+
console.log(chalk5.cyan("\u2501".repeat(50)));
|
|
57222
|
+
console.log(chalk5.bold(" Summary"));
|
|
57223
|
+
console.log(chalk5.cyan("\u2501".repeat(50)));
|
|
57224
|
+
console.log();
|
|
57225
|
+
console.log(` Bugs found: ${result.stats.bugsFound}`);
|
|
57226
|
+
console.log(` Bugs fixed: ${chalk5.green(result.stats.bugsFixed.toString())}`);
|
|
57227
|
+
console.log(` Bugs failed: ${result.stats.bugsFailed > 0 ? chalk5.red(result.stats.bugsFailed.toString()) : "0"}`);
|
|
57228
|
+
console.log(` Bugs skipped: ${result.stats.bugsSkipped}`);
|
|
57229
|
+
console.log(` Success rate: ${(result.stats.successRate * 100).toFixed(1)}%`);
|
|
57230
|
+
console.log(` Duration: ${(result.stats.totalDurationMs / 1e3).toFixed(1)}s`);
|
|
57231
|
+
console.log(` Stop reason: ${result.stats.stopReason}`);
|
|
57232
|
+
console.log();
|
|
57233
|
+
if (argv.dryRun) {
|
|
57234
|
+
console.log(chalk5.yellow(" \u2139\uFE0F This was a dry run. Run without --dry-run to apply fixes.\n"));
|
|
57235
|
+
}
|
|
57236
|
+
} else {
|
|
57237
|
+
console.log(JSON.stringify({
|
|
57238
|
+
found: result.stats.bugsFound,
|
|
57239
|
+
fixed: result.stats.bugsFixed,
|
|
57240
|
+
failed: result.stats.bugsFailed,
|
|
57241
|
+
skipped: result.stats.bugsSkipped,
|
|
57242
|
+
successRate: result.stats.successRate,
|
|
57243
|
+
durationMs: result.stats.totalDurationMs
|
|
57244
|
+
}));
|
|
57245
|
+
}
|
|
57246
|
+
if (result.stats.bugsFailed > 0) {
|
|
57247
|
+
process.exitCode = 1;
|
|
57248
|
+
}
|
|
57249
|
+
} catch (error) {
|
|
57250
|
+
spinner.fail(chalk5.red(`Error: ${error.message}`));
|
|
57251
|
+
logger.error("Bugfix command failed", { error: error.message });
|
|
57252
|
+
process.exitCode = 1;
|
|
57253
|
+
}
|
|
57254
|
+
}
|
|
57255
|
+
};
|
|
57256
|
+
|
|
55577
57257
|
// src/cli/index.ts
|
|
55578
57258
|
installExitHandlers();
|
|
55579
57259
|
var VERSION2 = getVersion();
|
|
@@ -55596,7 +57276,7 @@ globalTracker.mark("cli_start");
|
|
|
55596
57276
|
type: "string",
|
|
55597
57277
|
description: "Path to custom config file",
|
|
55598
57278
|
global: true
|
|
55599
|
-
}).command(setupCommand).command(initCommand).command(configureCommand).command(agentCommand).command(listCommand).command(runCommand).command(resumeCommand).command(runsCommand).command(sessionCommand).command(workspaceCommand).command(cacheCommand).command(configCommand).command(statusCommand4).command(doctorCommand2).command(cleanupCommand2).command(analyticsCommand).command(memoryCommand).command(mcpCommand).command(geminiCommand).command(providerLimitsCommand).command(providersCommand).command(flagsCommand).command(specCommand).command(genCommand).command(updateCommand).command(uninstallCommand).command(modeCommand).command(debugInstructionsCommand).command(planCommand).command(iterateCommand).command(reviewCommand).demandCommand(1, "You must provide a command. Run --help for usage.").help().version(VERSION2).alias("h", "help").alias("v", "version").strict().wrap(Math.min(120, yargs().terminalWidth())).parse();
|
|
57279
|
+
}).command(setupCommand).command(initCommand).command(configureCommand).command(agentCommand).command(listCommand).command(runCommand).command(resumeCommand).command(runsCommand).command(sessionCommand).command(workspaceCommand).command(cacheCommand).command(configCommand).command(statusCommand4).command(doctorCommand2).command(cleanupCommand2).command(analyticsCommand).command(memoryCommand).command(mcpCommand).command(geminiCommand).command(providerLimitsCommand).command(providersCommand).command(flagsCommand).command(specCommand).command(genCommand).command(updateCommand).command(uninstallCommand).command(modeCommand).command(debugInstructionsCommand).command(planCommand).command(iterateCommand).command(reviewCommand).command(bugfixCommand).demandCommand(1, "You must provide a command. Run --help for usage.").help().version(VERSION2).alias("h", "help").alias("v", "version").strict().wrap(Math.min(120, yargs().terminalWidth())).parse();
|
|
55600
57280
|
globalTracker.mark("yargs_parse_end");
|
|
55601
57281
|
globalTracker.measure("yargs_parsing", "yargs_parse_start", "yargs_parse_end");
|
|
55602
57282
|
globalTracker.mark("options_setup_start");
|