@vfarcic/dot-ai 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/internal-tools.d.ts +34 -1
- package/dist/core/internal-tools.d.ts.map +1 -1
- package/dist/core/internal-tools.js +116 -1
- package/dist/tools/remediate.d.ts +8 -0
- package/dist/tools/remediate.d.ts.map +1 -1
- package/dist/tools/remediate.js +159 -34
- package/package.json +2 -2
- package/prompts/remediate-system.md +5 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Internal Agentic-Loop Tools (PRD #407)
|
|
2
|
+
* Internal Agentic-Loop Tools (PRD #407, PRD #408)
|
|
3
3
|
*
|
|
4
4
|
* Tools that run locally in the MCP server, available to the AI
|
|
5
5
|
* during investigation loops alongside plugin tools. NOT exposed
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - git_clone: Clone a Git repo
|
|
10
10
|
* - fs_list: List files at a path
|
|
11
11
|
* - fs_read: Read a file at a path
|
|
12
|
+
* - git_create_pr: Create a PR with file changes (PRD #408)
|
|
12
13
|
*
|
|
13
14
|
* All filesystem operations are scoped to ./tmp/gitops-clones/
|
|
14
15
|
* to prevent path traversal attacks.
|
|
@@ -20,7 +21,39 @@ import type { AITool, ToolExecutor } from './ai-provider.interface.js';
|
|
|
20
21
|
* Exported for testing.
|
|
21
22
|
*/
|
|
22
23
|
export declare function validatePathWithinClones(inputPath: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Returns internal tools available to the AI during investigation.
|
|
26
|
+
* Note: git_create_pr is executor-only and not exposed to investigation.
|
|
27
|
+
*/
|
|
23
28
|
export declare function getInternalTools(): AITool[];
|
|
29
|
+
export interface GitCreatePrInput {
|
|
30
|
+
repoPath: string;
|
|
31
|
+
files: Array<{
|
|
32
|
+
path: string;
|
|
33
|
+
content: string;
|
|
34
|
+
}>;
|
|
35
|
+
title: string;
|
|
36
|
+
body?: string;
|
|
37
|
+
branchName: string;
|
|
38
|
+
baseBranch?: string;
|
|
39
|
+
}
|
|
40
|
+
export type GitCreatePrResult = {
|
|
41
|
+
success: true;
|
|
42
|
+
prUrl: string;
|
|
43
|
+
prNumber: number;
|
|
44
|
+
branch: string;
|
|
45
|
+
baseBranch: string;
|
|
46
|
+
filesChanged: string[];
|
|
47
|
+
} | {
|
|
48
|
+
success: true;
|
|
49
|
+
branch: string;
|
|
50
|
+
baseBranch: string;
|
|
51
|
+
filesChanged: string[];
|
|
52
|
+
error: string;
|
|
53
|
+
} | {
|
|
54
|
+
success: false;
|
|
55
|
+
error: string;
|
|
56
|
+
};
|
|
24
57
|
/**
|
|
25
58
|
* Create a ToolExecutor that handles internal agentic-loop tools.
|
|
26
59
|
* Designed to be passed as the fallbackExecutor to pluginManager.createToolExecutor().
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal-tools.d.ts","sourceRoot":"","sources":["../../src/core/internal-tools.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"internal-tools.d.ts","sourceRoot":"","sources":["../../src/core/internal-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAsBvE;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAUlE;AAkBD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAkD3C;AAiHD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,GAC9G;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC5F;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAgItC;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,CAqB1E;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,GAAE,MAAuB,GAAG,IAAI,CAoBxE"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Internal Agentic-Loop Tools (PRD #407)
|
|
3
|
+
* Internal Agentic-Loop Tools (PRD #407, PRD #408)
|
|
4
4
|
*
|
|
5
5
|
* Tools that run locally in the MCP server, available to the AI
|
|
6
6
|
* during investigation loops alongside plugin tools. NOT exposed
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - git_clone: Clone a Git repo
|
|
11
11
|
* - fs_list: List files at a path
|
|
12
12
|
* - fs_read: Read a file at a path
|
|
13
|
+
* - git_create_pr: Create a PR with file changes (PRD #408)
|
|
13
14
|
*
|
|
14
15
|
* All filesystem operations are scoped to ./tmp/gitops-clones/
|
|
15
16
|
* to prevent path traversal attacks.
|
|
@@ -47,6 +48,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
47
48
|
return result;
|
|
48
49
|
};
|
|
49
50
|
})();
|
|
51
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
52
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
53
|
+
};
|
|
50
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
55
|
exports.validatePathWithinClones = validatePathWithinClones;
|
|
52
56
|
exports.getInternalTools = getInternalTools;
|
|
@@ -56,6 +60,7 @@ const fs = __importStar(require("fs"));
|
|
|
56
60
|
const path = __importStar(require("path"));
|
|
57
61
|
const git_utils_js_1 = require("./git-utils.js");
|
|
58
62
|
const solution_utils_js_1 = require("./solution-utils.js");
|
|
63
|
+
const simple_git_1 = __importDefault(require("simple-git"));
|
|
59
64
|
const CLONES_SUBDIR = 'gitops-clones';
|
|
60
65
|
const MAX_FILE_SIZE = 100 * 1024; // 100KB
|
|
61
66
|
const DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
@@ -94,6 +99,10 @@ function repoUrlToDirectoryName(repoUrl) {
|
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
101
|
// ─── Tool definitions ───
|
|
102
|
+
/**
|
|
103
|
+
* Returns internal tools available to the AI during investigation.
|
|
104
|
+
* Note: git_create_pr is executor-only and not exposed to investigation.
|
|
105
|
+
*/
|
|
97
106
|
function getInternalTools() {
|
|
98
107
|
return [
|
|
99
108
|
{
|
|
@@ -138,6 +147,8 @@ function getInternalTools() {
|
|
|
138
147
|
required: ['path'],
|
|
139
148
|
},
|
|
140
149
|
},
|
|
150
|
+
// git_create_pr is executor-only - not exposed during investigation
|
|
151
|
+
// It's only available via createInternalToolExecutor() during execution
|
|
141
152
|
];
|
|
142
153
|
}
|
|
143
154
|
// ─── Handlers ───
|
|
@@ -235,6 +246,109 @@ function handleFsRead(args) {
|
|
|
235
246
|
}
|
|
236
247
|
return fs.readFileSync(resolved, 'utf-8');
|
|
237
248
|
}
|
|
249
|
+
async function handleGitCreatePr(args) {
|
|
250
|
+
const repoPath = args.repoPath;
|
|
251
|
+
const files = args.files;
|
|
252
|
+
const title = args.title;
|
|
253
|
+
const body = args.body || '';
|
|
254
|
+
const branchName = args.branchName;
|
|
255
|
+
const baseBranch = args.baseBranch || 'main';
|
|
256
|
+
if (!repoPath) {
|
|
257
|
+
return { success: false, error: 'repoPath is required' };
|
|
258
|
+
}
|
|
259
|
+
if (!files || !Array.isArray(files) || files.length === 0) {
|
|
260
|
+
return { success: false, error: 'files array is required and must not be empty' };
|
|
261
|
+
}
|
|
262
|
+
if (!title) {
|
|
263
|
+
return { success: false, error: 'title is required' };
|
|
264
|
+
}
|
|
265
|
+
if (!branchName) {
|
|
266
|
+
return { success: false, error: 'branchName is required' };
|
|
267
|
+
}
|
|
268
|
+
let resolvedRepoPath;
|
|
269
|
+
try {
|
|
270
|
+
resolvedRepoPath = validatePathWithinClones(repoPath);
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
return {
|
|
274
|
+
success: false,
|
|
275
|
+
error: `Invalid repo path: ${err instanceof Error ? err.message : String(err)}`,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (!fs.existsSync(resolvedRepoPath)) {
|
|
279
|
+
return {
|
|
280
|
+
success: false,
|
|
281
|
+
error: `Repository not found at path: ${repoPath}. It may have been cleaned up.`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const stat = fs.statSync(resolvedRepoPath);
|
|
285
|
+
if (!stat.isDirectory()) {
|
|
286
|
+
return { success: false, error: `Path is not a directory: ${repoPath}` };
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
const git = (0, simple_git_1.default)(resolvedRepoPath);
|
|
290
|
+
await git.checkout(baseBranch);
|
|
291
|
+
const pushResult = await (0, git_utils_js_1.pushRepo)(resolvedRepoPath, files, title, {
|
|
292
|
+
branch: branchName,
|
|
293
|
+
});
|
|
294
|
+
const authConfig = (0, git_utils_js_1.getGitAuthConfigFromEnv)();
|
|
295
|
+
const token = await (0, git_utils_js_1.getAuthToken)(authConfig);
|
|
296
|
+
const remotes = await git.getRemotes(true);
|
|
297
|
+
const origin = remotes.find(r => r.name === 'origin');
|
|
298
|
+
if (!origin?.refs?.fetch) {
|
|
299
|
+
return { success: false, error: 'Could not find origin remote URL' };
|
|
300
|
+
}
|
|
301
|
+
const remoteUrl = (0, git_utils_js_1.scrubCredentials)(origin.refs.fetch);
|
|
302
|
+
const repoMatch = remoteUrl.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
303
|
+
if (!repoMatch) {
|
|
304
|
+
return {
|
|
305
|
+
success: true,
|
|
306
|
+
branch: branchName,
|
|
307
|
+
baseBranch,
|
|
308
|
+
filesChanged: pushResult.filesAdded,
|
|
309
|
+
error: 'Automatic PR creation is only supported for GitHub repositories. Changes were pushed to the branch — create a PR/MR manually.',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
const ownerRepo = repoMatch[1];
|
|
313
|
+
const [owner, repo] = ownerRepo.split('/');
|
|
314
|
+
const prResponse = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls`, {
|
|
315
|
+
method: 'POST',
|
|
316
|
+
headers: {
|
|
317
|
+
Authorization: `Bearer ${token}`,
|
|
318
|
+
'Content-Type': 'application/json',
|
|
319
|
+
Accept: 'application/vnd.github+json',
|
|
320
|
+
'User-Agent': 'dot-ai-remediate',
|
|
321
|
+
},
|
|
322
|
+
body: JSON.stringify({
|
|
323
|
+
title,
|
|
324
|
+
body,
|
|
325
|
+
head: branchName,
|
|
326
|
+
base: baseBranch,
|
|
327
|
+
}),
|
|
328
|
+
signal: AbortSignal.timeout(30000),
|
|
329
|
+
});
|
|
330
|
+
if (!prResponse.ok) {
|
|
331
|
+
const errorBody = await prResponse.text();
|
|
332
|
+
return {
|
|
333
|
+
success: false,
|
|
334
|
+
error: `GitHub API error (${prResponse.status}): ${errorBody}`,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const prData = (await prResponse.json());
|
|
338
|
+
return {
|
|
339
|
+
success: true,
|
|
340
|
+
prUrl: prData.html_url,
|
|
341
|
+
prNumber: prData.number,
|
|
342
|
+
branch: branchName,
|
|
343
|
+
baseBranch,
|
|
344
|
+
filesChanged: pushResult.filesAdded,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
349
|
+
return { success: false, error: (0, git_utils_js_1.scrubCredentials)(message) };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
238
352
|
// ─── Combined executor ───
|
|
239
353
|
/**
|
|
240
354
|
* Create a ToolExecutor that handles internal agentic-loop tools.
|
|
@@ -245,6 +359,7 @@ function createInternalToolExecutor(sessionId) {
|
|
|
245
359
|
git_clone: (args) => handleGitClone(args, sessionId),
|
|
246
360
|
fs_list: handleFsList,
|
|
247
361
|
fs_read: handleFsRead,
|
|
362
|
+
git_create_pr: handleGitCreatePr,
|
|
248
363
|
};
|
|
249
364
|
return async (toolName, input) => {
|
|
250
365
|
const handler = handlers[toolName];
|
|
@@ -54,6 +54,7 @@ export interface RemediationAction {
|
|
|
54
54
|
rationale: string;
|
|
55
55
|
gitSource?: {
|
|
56
56
|
repoURL: string;
|
|
57
|
+
repoPath?: string;
|
|
57
58
|
branch: string;
|
|
58
59
|
files: Array<{
|
|
59
60
|
path: string;
|
|
@@ -102,6 +103,13 @@ export interface RemediateOutput {
|
|
|
102
103
|
results?: ExecutionResult[];
|
|
103
104
|
fallbackReason?: string;
|
|
104
105
|
mode?: 'manual' | 'automatic';
|
|
106
|
+
pullRequest?: {
|
|
107
|
+
url: string;
|
|
108
|
+
number: number;
|
|
109
|
+
branch: string;
|
|
110
|
+
baseBranch: string;
|
|
111
|
+
filesChanged: string[];
|
|
112
|
+
};
|
|
105
113
|
}
|
|
106
114
|
/**
|
|
107
115
|
* Remediate tool response type
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remediate.d.ts","sourceRoot":"","sources":["../../src/tools/remediate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,OAAO,EAEL,qBAAqB,EACtB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"remediate.d.ts","sourceRoot":"","sources":["../../src/tools/remediate.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/remediate.js
CHANGED
|
@@ -401,7 +401,31 @@ async function executeUserChoice(sessionManager, sessionId, choice, logger, requ
|
|
|
401
401
|
return await executeRemediationCommands(session, sessionManager, logger, requestId, currentInteractionId);
|
|
402
402
|
case 2: {
|
|
403
403
|
// Execute via agent
|
|
404
|
-
|
|
404
|
+
const actions = session.data.finalAnalysis.remediation.actions;
|
|
405
|
+
const gitSourceActions = actions.filter(a => a.gitSource && !a.command);
|
|
406
|
+
const kubectlActions = actions.filter(a => a.command && !a.gitSource);
|
|
407
|
+
if (gitSourceActions.length > 0 && kubectlActions.length === 0) {
|
|
408
|
+
return {
|
|
409
|
+
content: [
|
|
410
|
+
{
|
|
411
|
+
type: 'text',
|
|
412
|
+
text: JSON.stringify({
|
|
413
|
+
status: 'success',
|
|
414
|
+
sessionId: sessionId,
|
|
415
|
+
message: 'GitOps remediation detected - use automatic execution (choice 1) for PR creation',
|
|
416
|
+
remediation: session.data.finalAnalysis.remediation,
|
|
417
|
+
instructions: {
|
|
418
|
+
nextSteps: [
|
|
419
|
+
'This remediation requires GitOps PR creation which is handled automatically.',
|
|
420
|
+
'Please use choice 1 (Execute automatically) to create the PR.',
|
|
421
|
+
'Alternatively, manually apply the file changes from gitSource.files to your repository.',
|
|
422
|
+
],
|
|
423
|
+
},
|
|
424
|
+
}, null, 2),
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
};
|
|
428
|
+
}
|
|
405
429
|
const validationIntent = session.data.finalAnalysis.validationIntent ||
|
|
406
430
|
'Check the status of the affected resources to verify the issue has been resolved';
|
|
407
431
|
return {
|
|
@@ -453,6 +477,7 @@ async function executeRemediationCommands(session, sessionManager, logger, reque
|
|
|
453
477
|
const finalAnalysis = session.data.finalAnalysis;
|
|
454
478
|
let overallSuccess = true;
|
|
455
479
|
let executedCommandCount = 0;
|
|
480
|
+
let pullRequestInfo;
|
|
456
481
|
logger.info('Starting remediation command execution', {
|
|
457
482
|
requestId,
|
|
458
483
|
sessionId: session.sessionId,
|
|
@@ -463,21 +488,76 @@ async function executeRemediationCommands(session, sessionManager, logger, reque
|
|
|
463
488
|
const action = finalAnalysis.remediation.actions[i];
|
|
464
489
|
const actionId = `action_${i + 1}`;
|
|
465
490
|
try {
|
|
466
|
-
// PRD #
|
|
467
|
-
// instructions for GitOps-managed resources, not executable commands
|
|
491
|
+
// PRD #408: Handle gitSource actions — create PR instead of kubectl
|
|
468
492
|
if (action.gitSource && !action.command) {
|
|
469
|
-
logger.info('
|
|
493
|
+
logger.info('Processing gitSource remediation action', {
|
|
470
494
|
requestId,
|
|
471
495
|
sessionId: session.sessionId,
|
|
472
496
|
actionId,
|
|
473
497
|
repoURL: action.gitSource.repoURL,
|
|
498
|
+
repoPath: action.gitSource.repoPath,
|
|
474
499
|
});
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
500
|
+
if (!action.gitSource.repoPath) {
|
|
501
|
+
results.push({
|
|
502
|
+
action: `${actionId}: ${action.description} (failed: missing repoPath)`,
|
|
503
|
+
success: false,
|
|
504
|
+
output: 'Git-based remediation requires repoPath from investigation phase',
|
|
505
|
+
timestamp: new Date(),
|
|
506
|
+
});
|
|
507
|
+
overallSuccess = false;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const prInput = {
|
|
511
|
+
repoPath: action.gitSource.repoPath,
|
|
512
|
+
files: action.gitSource.files.map((f) => ({
|
|
513
|
+
path: f.path,
|
|
514
|
+
content: f.content,
|
|
515
|
+
})),
|
|
516
|
+
title: `fix: ${action.description}`,
|
|
517
|
+
body: `## Remediation\n\n${action.rationale}\n\n**Risk Level:** ${action.risk}`,
|
|
518
|
+
branchName: `remediate/${session.sessionId.slice(0, 12)}-${Date.now()}`,
|
|
519
|
+
baseBranch: action.gitSource.branch || 'main',
|
|
520
|
+
};
|
|
521
|
+
const prExecutor = (0, internal_tools_1.createInternalToolExecutor)(session.sessionId);
|
|
522
|
+
const prResult = (await prExecutor('git_create_pr', prInput));
|
|
523
|
+
if (prResult.success && 'prUrl' in prResult) {
|
|
524
|
+
const filesList = prResult.filesChanged && prResult.filesChanged.length > 0
|
|
525
|
+
? prResult.filesChanged.join(', ')
|
|
526
|
+
: 'none';
|
|
527
|
+
results.push({
|
|
528
|
+
action: `${actionId}: ${action.description} (PR created)`,
|
|
529
|
+
success: true,
|
|
530
|
+
output: `PR #${prResult.prNumber}: ${prResult.prUrl}\nBranch: ${prResult.branch}\nFiles changed: ${filesList}`,
|
|
531
|
+
timestamp: new Date(),
|
|
532
|
+
});
|
|
533
|
+
pullRequestInfo = {
|
|
534
|
+
url: prResult.prUrl,
|
|
535
|
+
number: prResult.prNumber,
|
|
536
|
+
branch: prResult.branch,
|
|
537
|
+
baseBranch: prResult.baseBranch,
|
|
538
|
+
filesChanged: prResult.filesChanged,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
else if (prResult.success && 'error' in prResult) {
|
|
542
|
+
const filesList = prResult.filesChanged && prResult.filesChanged.length > 0
|
|
543
|
+
? prResult.filesChanged.join(', ')
|
|
544
|
+
: 'none';
|
|
545
|
+
results.push({
|
|
546
|
+
action: `${actionId}: ${action.description} (branch pushed, manual PR needed)`,
|
|
547
|
+
success: true,
|
|
548
|
+
output: `Branch: ${prResult.branch}\nFiles changed: ${filesList}\nNote: ${prResult.error}`,
|
|
549
|
+
timestamp: new Date(),
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
overallSuccess = false;
|
|
554
|
+
results.push({
|
|
555
|
+
action: `${actionId}: ${action.description} (failed)`,
|
|
556
|
+
success: false,
|
|
557
|
+
output: prResult.error,
|
|
558
|
+
timestamp: new Date(),
|
|
559
|
+
});
|
|
560
|
+
}
|
|
481
561
|
continue;
|
|
482
562
|
}
|
|
483
563
|
logger.info('Executing remediation action', {
|
|
@@ -620,6 +700,7 @@ IMPORTANT: You MUST respond with the final JSON analysis format as specified in
|
|
|
620
700
|
success: false,
|
|
621
701
|
summary: 'Validation found remaining issues after remediation',
|
|
622
702
|
},
|
|
703
|
+
pullRequest: pullRequestInfo,
|
|
623
704
|
};
|
|
624
705
|
return {
|
|
625
706
|
content: [
|
|
@@ -657,6 +738,7 @@ IMPORTANT: You MUST respond with the final JSON analysis format as specified in
|
|
|
657
738
|
success: true,
|
|
658
739
|
summary: 'Validation confirmed issue resolution',
|
|
659
740
|
},
|
|
741
|
+
pullRequest: pullRequestInfo,
|
|
660
742
|
};
|
|
661
743
|
const content = [
|
|
662
744
|
{
|
|
@@ -684,6 +766,66 @@ IMPORTANT: You MUST respond with the final JSON analysis format as specified in
|
|
|
684
766
|
status: overallSuccess ? 'executed_successfully' : 'executed_with_errors',
|
|
685
767
|
executionResults: results,
|
|
686
768
|
});
|
|
769
|
+
const hasOnlyGitOps = executedCommandCount === 0 && pullRequestInfo !== undefined;
|
|
770
|
+
const prInfo = pullRequestInfo;
|
|
771
|
+
let nextSteps;
|
|
772
|
+
if (hasOnlyGitOps && prInfo) {
|
|
773
|
+
nextSteps = [
|
|
774
|
+
'Changes have been pushed to a Git branch for GitOps reconciliation:',
|
|
775
|
+
` PR: ${prInfo.url}`,
|
|
776
|
+
` Branch: ${prInfo.branch} → ${prInfo.baseBranch}`,
|
|
777
|
+
` Files changed: ${prInfo.filesChanged.join(', ')}`,
|
|
778
|
+
'',
|
|
779
|
+
'Next steps:',
|
|
780
|
+
' 1. Review and merge the PR in your Git repository',
|
|
781
|
+
' 2. Wait for Argo CD/Flux to sync the changes',
|
|
782
|
+
' 3. Verify the issue is resolved after reconciliation',
|
|
783
|
+
'',
|
|
784
|
+
`You can verify the fix by running: remediate("Verify that ${finalAnalysis.analysis.rootCause.toLowerCase()} has been resolved")`,
|
|
785
|
+
];
|
|
786
|
+
}
|
|
787
|
+
else if (overallSuccess) {
|
|
788
|
+
if (validationResult) {
|
|
789
|
+
nextSteps = [
|
|
790
|
+
'The following kubectl commands were executed to remediate the issue:',
|
|
791
|
+
...finalAnalysis.remediation.actions
|
|
792
|
+
.filter(a => a.command)
|
|
793
|
+
.map((action, index) => {
|
|
794
|
+
const resultIndex = finalAnalysis.remediation.actions.indexOf(action);
|
|
795
|
+
return ` ${index + 1}. ${action.command} ${results[resultIndex]?.success ? '✓' : '✗'}`;
|
|
796
|
+
}),
|
|
797
|
+
'Automatic validation has been completed - see validation results above',
|
|
798
|
+
'Monitor your cluster to ensure the issue remains resolved',
|
|
799
|
+
];
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
nextSteps = [
|
|
803
|
+
'The following kubectl commands were executed to remediate the issue:',
|
|
804
|
+
...finalAnalysis.remediation.actions
|
|
805
|
+
.filter(a => a.command)
|
|
806
|
+
.map((action, index) => {
|
|
807
|
+
const resultIndex = finalAnalysis.remediation.actions.indexOf(action);
|
|
808
|
+
return ` ${index + 1}. ${action.command} ${results[resultIndex]?.success ? '✓' : '✗'}`;
|
|
809
|
+
}),
|
|
810
|
+
`You can verify the fix by running: remediate("Verify that ${finalAnalysis.analysis.rootCause.toLowerCase()} has been resolved")`,
|
|
811
|
+
'Monitor your cluster to ensure the issue is fully resolved',
|
|
812
|
+
];
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
nextSteps = [
|
|
817
|
+
'The following kubectl commands were attempted:',
|
|
818
|
+
...finalAnalysis.remediation.actions
|
|
819
|
+
.filter(a => a.command)
|
|
820
|
+
.map((action, index) => {
|
|
821
|
+
const resultIndex = finalAnalysis.remediation.actions.indexOf(action);
|
|
822
|
+
return ` ${index + 1}. ${action.command} ${results[resultIndex]?.success ? '✓' : '✗'}`;
|
|
823
|
+
}),
|
|
824
|
+
'Some remediation commands failed - check the results above',
|
|
825
|
+
'Review the error messages and address any underlying issues',
|
|
826
|
+
'You may need to run additional commands or investigate further',
|
|
827
|
+
];
|
|
828
|
+
}
|
|
687
829
|
const response = {
|
|
688
830
|
status: overallSuccess ? 'success' : 'failed',
|
|
689
831
|
sessionId: session.sessionId,
|
|
@@ -691,37 +833,20 @@ IMPORTANT: You MUST respond with the final JSON analysis format as specified in
|
|
|
691
833
|
results: results,
|
|
692
834
|
executedCommands: results.map(r => r.action),
|
|
693
835
|
message: overallSuccess
|
|
694
|
-
?
|
|
836
|
+
? hasOnlyGitOps
|
|
837
|
+
? `Successfully created PR for ${results.length} GitOps remediation action(s)`
|
|
838
|
+
: `Successfully executed ${results.length} remediation actions`
|
|
695
839
|
: `Executed ${results.length} actions with ${results.filter(r => !r.success).length} failures`,
|
|
696
840
|
validation: validationResult,
|
|
697
841
|
instructions: {
|
|
698
|
-
showExecutedCommands:
|
|
699
|
-
showActualKubectlCommands:
|
|
700
|
-
nextSteps
|
|
701
|
-
? validationResult
|
|
702
|
-
? [
|
|
703
|
-
'The following kubectl commands were executed to remediate the issue:',
|
|
704
|
-
...finalAnalysis.remediation.actions.map((action, index) => ` ${index + 1}. ${action.command} ${results[index]?.success ? '✓' : '✗'}`),
|
|
705
|
-
'Automatic validation has been completed - see validation results above',
|
|
706
|
-
'Monitor your cluster to ensure the issue remains resolved',
|
|
707
|
-
]
|
|
708
|
-
: [
|
|
709
|
-
'The following kubectl commands were executed to remediate the issue:',
|
|
710
|
-
...finalAnalysis.remediation.actions.map((action, index) => ` ${index + 1}. ${action.command} ${results[index]?.success ? '✓' : '✗'}`),
|
|
711
|
-
`You can verify the fix by running: remediate("Verify that ${finalAnalysis.analysis.rootCause.toLowerCase()} has been resolved")`,
|
|
712
|
-
'Monitor your cluster to ensure the issue is fully resolved',
|
|
713
|
-
]
|
|
714
|
-
: [
|
|
715
|
-
'The following kubectl commands were attempted:',
|
|
716
|
-
...finalAnalysis.remediation.actions.map((action, index) => ` ${index + 1}. ${action.command} ${results[index]?.success ? '✓' : '✗'}`),
|
|
717
|
-
'Some remediation commands failed - check the results above',
|
|
718
|
-
'Review the error messages and address any underlying issues',
|
|
719
|
-
'You may need to run additional commands or investigate further',
|
|
720
|
-
],
|
|
842
|
+
showExecutedCommands: !hasOnlyGitOps,
|
|
843
|
+
showActualKubectlCommands: !hasOnlyGitOps,
|
|
844
|
+
nextSteps,
|
|
721
845
|
},
|
|
722
846
|
investigation: finalAnalysis.investigation,
|
|
723
847
|
analysis: finalAnalysis.analysis,
|
|
724
848
|
remediation: finalAnalysis.remediation,
|
|
849
|
+
pullRequest: pullRequestInfo,
|
|
725
850
|
};
|
|
726
851
|
logger.info('Remediation execution completed', {
|
|
727
852
|
requestId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vfarcic/dot-ai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance",
|
|
5
5
|
"mcpName": "io.github.vfarcic/dot-ai",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"vitest": "^3.2.4"
|
|
108
108
|
},
|
|
109
109
|
"dependencies": {
|
|
110
|
-
"@ai-sdk/alibaba": "^1.0.
|
|
110
|
+
"@ai-sdk/alibaba": "^1.0.13",
|
|
111
111
|
"@ai-sdk/amazon-bedrock": "^4.0.77",
|
|
112
112
|
"@ai-sdk/anthropic": "^3.0.58",
|
|
113
113
|
"@ai-sdk/google": "^3.0.43",
|
|
@@ -65,6 +65,7 @@ Once investigation is complete, respond with ONLY this JSON format:
|
|
|
65
65
|
"gitSource": {
|
|
66
66
|
"repoURL": "source repository URL — only when resource is GitOps-managed",
|
|
67
67
|
"branch": "branch name",
|
|
68
|
+
"repoPath": "relative path to cloned repo (as returned by git_clone) — required for GitOps remediation",
|
|
68
69
|
"files": [
|
|
69
70
|
{
|
|
70
71
|
"path": "path relative to repo root",
|
|
@@ -104,8 +105,9 @@ Once investigation is complete, respond with ONLY this JSON format:
|
|
|
104
105
|
After identifying the problematic resource, check whether it is managed by a GitOps controller (e.g., Argo CD, Flux).
|
|
105
106
|
|
|
106
107
|
**When GitOps management is detected**:
|
|
107
|
-
- Clone the source repo
|
|
108
|
-
-
|
|
108
|
+
- Clone the source repo and capture the local path for use as `repoPath` in the remediation actions
|
|
109
|
+
- Navigate and read the manifests to find the file(s) that need changing
|
|
110
|
+
- Include `gitSource` in your remediation actions with `repoPath`, `repoURL`, `branch`, and full corrected file contents for each file that needs modification
|
|
109
111
|
|
|
110
112
|
**When GitOps management is NOT detected**:
|
|
111
113
|
- Proceed with standard kubectl-based remediation
|
|
@@ -190,6 +192,7 @@ After identifying the problematic resource, check whether it is managed by a Git
|
|
|
190
192
|
"gitSource": {
|
|
191
193
|
"repoURL": "https://github.com/org/infra-repo.git",
|
|
192
194
|
"branch": "main",
|
|
195
|
+
"repoPath": "session-abc123/org-infra-repo",
|
|
193
196
|
"files": [
|
|
194
197
|
{
|
|
195
198
|
"path": "apps/production/deployment.yaml",
|