@vfarcic/dot-ai 1.10.1 → 1.11.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.
@@ -109,7 +109,7 @@ class ConsoleLogger {
109
109
  const timestamp = new Date().toISOString();
110
110
  const baseMessage = `[${timestamp}] ${level.toUpperCase()} [${this.component}] ${message}`;
111
111
  if (data) {
112
- return `${baseMessage} ${JSON.stringify(data, null, 2)}`;
112
+ return `${baseMessage} ${JSON.stringify(data)}`;
113
113
  }
114
114
  return baseMessage;
115
115
  }
@@ -23,6 +23,11 @@ export declare function scrubCredentials(message: string): string;
23
23
  export declare function getAuthenticatedUrl(repoUrl: string, token: string): string;
24
24
  export declare function getAuthToken(authConfig: GitAuthConfig): Promise<string>;
25
25
  export declare function getGitAuthConfigFromEnv(): GitAuthConfig;
26
+ /**
27
+ * Sanitize a relative path to prevent directory traversal.
28
+ * Rejects absolute paths and paths that escape the base directory.
29
+ */
30
+ export declare function sanitizeRelativePath(relativePath: string): string;
26
31
  export interface CloneOptions {
27
32
  branch?: string;
28
33
  depth?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../../src/core/git-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AASD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1E;AA2ED,wBAAsB,YAAY,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAc7E;AAED,wBAAgB,uBAAuB,IAAI,aAAa,CAyBvD;AAeD,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA6BhD;AAID,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA8B5E;AAID,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1C;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,CAAC,CAyErB"}
1
+ {"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../../src/core/git-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AASD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1E;AA2ED,wBAAsB,YAAY,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAc7E;AAED,wBAAgB,uBAAuB,IAAI,aAAa,CAyBvD;AAeD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CASjE;AAID,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA6BhD;AAID,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA8B5E;AAID,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1C;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,CAAC,CAyErB"}
@@ -53,6 +53,7 @@ exports.scrubCredentials = scrubCredentials;
53
53
  exports.getAuthenticatedUrl = getAuthenticatedUrl;
54
54
  exports.getAuthToken = getAuthToken;
55
55
  exports.getGitAuthConfigFromEnv = getGitAuthConfigFromEnv;
56
+ exports.sanitizeRelativePath = sanitizeRelativePath;
56
57
  exports.cloneRepo = cloneRepo;
57
58
  exports.pullRepo = pullRepo;
58
59
  exports.pushRepo = pushRepo;
@@ -161,6 +162,21 @@ function gitOptions(baseDir) {
161
162
  timeout: { block: GIT_TIMEOUT_MS },
162
163
  };
163
164
  }
165
+ // ─── Path safety ───
166
+ /**
167
+ * Sanitize a relative path to prevent directory traversal.
168
+ * Rejects absolute paths and paths that escape the base directory.
169
+ */
170
+ function sanitizeRelativePath(relativePath) {
171
+ if (relativePath.startsWith('/')) {
172
+ throw new Error('Relative path cannot be absolute');
173
+ }
174
+ const normalized = path.posix.normalize(relativePath);
175
+ if (normalized.startsWith('..') || path.posix.isAbsolute(normalized)) {
176
+ throw new Error('Relative path cannot escape target directory');
177
+ }
178
+ return normalized;
179
+ }
164
180
  async function cloneRepo(repoUrl, targetDir, opts) {
165
181
  const authConfig = getGitAuthConfigFromEnv();
166
182
  let cloneUrl;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Internal Agentic-Loop Tools (PRD #407)
3
+ *
4
+ * Tools that run locally in the MCP server, available to the AI
5
+ * during investigation loops alongside plugin tools. NOT exposed
6
+ * to client agents — only the AI inside toolLoop() calls these.
7
+ *
8
+ * Tools:
9
+ * - git_clone: Clone a Git repo
10
+ * - fs_list: List files at a path
11
+ * - fs_read: Read a file at a path
12
+ *
13
+ * All filesystem operations are scoped to ./tmp/gitops-clones/
14
+ * to prevent path traversal attacks.
15
+ */
16
+ import type { AITool, ToolExecutor } from './ai-provider.interface.js';
17
+ /**
18
+ * Validate that a relative path is safe and resolve it within the clones directory.
19
+ * Uses sanitizeRelativePath for traversal checks, then resolves to absolute.
20
+ * Exported for testing.
21
+ */
22
+ export declare function validatePathWithinClones(inputPath: string): string;
23
+ export declare function getInternalTools(): AITool[];
24
+ /**
25
+ * Create a ToolExecutor that handles internal agentic-loop tools.
26
+ * Designed to be passed as the fallbackExecutor to pluginManager.createToolExecutor().
27
+ */
28
+ export declare function createInternalToolExecutor(sessionId: string): ToolExecutor;
29
+ /**
30
+ * Remove session clone directories older than the TTL.
31
+ * Called at the start of each new remediate investigation.
32
+ * Non-blocking: runs cleanup in the background without delaying investigation.
33
+ */
34
+ export declare function cleanupOldClones(maxAgeMs?: number): void;
35
+ //# sourceMappingURL=internal-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-tools.d.ts","sourceRoot":"","sources":["../../src/core/internal-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAcvE;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAUlE;AAkBD,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAgD3C;AAmHD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,CAoB1E;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,GAAE,MAAuB,GAAG,IAAI,CAoBxE"}
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ /**
3
+ * Internal Agentic-Loop Tools (PRD #407)
4
+ *
5
+ * Tools that run locally in the MCP server, available to the AI
6
+ * during investigation loops alongside plugin tools. NOT exposed
7
+ * to client agents — only the AI inside toolLoop() calls these.
8
+ *
9
+ * Tools:
10
+ * - git_clone: Clone a Git repo
11
+ * - fs_list: List files at a path
12
+ * - fs_read: Read a file at a path
13
+ *
14
+ * All filesystem operations are scoped to ./tmp/gitops-clones/
15
+ * to prevent path traversal attacks.
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.validatePathWithinClones = validatePathWithinClones;
52
+ exports.getInternalTools = getInternalTools;
53
+ exports.createInternalToolExecutor = createInternalToolExecutor;
54
+ exports.cleanupOldClones = cleanupOldClones;
55
+ const fs = __importStar(require("fs"));
56
+ const path = __importStar(require("path"));
57
+ const git_utils_js_1 = require("./git-utils.js");
58
+ const solution_utils_js_1 = require("./solution-utils.js");
59
+ const CLONES_SUBDIR = 'gitops-clones';
60
+ const MAX_FILE_SIZE = 100 * 1024; // 100KB
61
+ const DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour
62
+ // ─── Path security ───
63
+ function getClonesDir() {
64
+ return path.resolve(process.cwd(), 'tmp', CLONES_SUBDIR);
65
+ }
66
+ /**
67
+ * Validate that a relative path is safe and resolve it within the clones directory.
68
+ * Uses sanitizeRelativePath for traversal checks, then resolves to absolute.
69
+ * Exported for testing.
70
+ */
71
+ function validatePathWithinClones(inputPath) {
72
+ // Decode URL-encoded characters (e.g., %2e%2e/ for ../) before validation
73
+ let decoded;
74
+ try {
75
+ decoded = decodeURIComponent(inputPath);
76
+ }
77
+ catch {
78
+ decoded = inputPath;
79
+ }
80
+ const sanitized = (0, git_utils_js_1.sanitizeRelativePath)(decoded);
81
+ return path.resolve(getClonesDir(), sanitized);
82
+ }
83
+ // ─── Repo name sanitization ───
84
+ function repoUrlToDirectoryName(repoUrl) {
85
+ try {
86
+ const url = new URL(repoUrl);
87
+ const repoPath = url.pathname
88
+ .replace(/^\//, '')
89
+ .replace(/\.git$/, '');
90
+ return (0, solution_utils_js_1.sanitizeIntentForLabel)(repoPath);
91
+ }
92
+ catch {
93
+ return (0, solution_utils_js_1.sanitizeIntentForLabel)(repoUrl.slice(0, 63));
94
+ }
95
+ }
96
+ // ─── Tool definitions ───
97
+ function getInternalTools() {
98
+ return [
99
+ {
100
+ name: 'git_clone',
101
+ description: 'Clone a Git repository. Returns a relative path to the cloned repo.',
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ repoUrl: {
106
+ type: 'string',
107
+ description: 'Repository URL (HTTPS)',
108
+ },
109
+ },
110
+ required: ['repoUrl'],
111
+ },
112
+ },
113
+ {
114
+ name: 'fs_list',
115
+ description: 'List files and directories at a relative path within the working directory.',
116
+ inputSchema: {
117
+ type: 'object',
118
+ properties: {
119
+ path: {
120
+ type: 'string',
121
+ description: 'Relative path to list',
122
+ },
123
+ },
124
+ required: ['path'],
125
+ },
126
+ },
127
+ {
128
+ name: 'fs_read',
129
+ description: 'Read file contents at a relative path within the working directory.',
130
+ inputSchema: {
131
+ type: 'object',
132
+ properties: {
133
+ path: {
134
+ type: 'string',
135
+ description: 'Relative path to file',
136
+ },
137
+ },
138
+ required: ['path'],
139
+ },
140
+ },
141
+ ];
142
+ }
143
+ // ─── Handlers ───
144
+ async function handleGitClone(args, sessionId) {
145
+ const repoUrl = args.repoUrl;
146
+ if (!repoUrl) {
147
+ return 'Error: repoUrl is required';
148
+ }
149
+ const repoName = repoUrlToDirectoryName(repoUrl);
150
+ const relativePath = path.join(sessionId, repoName);
151
+ const targetDir = path.join(getClonesDir(), relativePath);
152
+ if (fs.existsSync(targetDir)) {
153
+ return { localPath: relativePath, message: 'Repository already cloned' };
154
+ }
155
+ const parentDir = path.dirname(targetDir);
156
+ if (!fs.existsSync(parentDir)) {
157
+ fs.mkdirSync(parentDir, { recursive: true });
158
+ }
159
+ try {
160
+ const result = await (0, git_utils_js_1.cloneRepo)(repoUrl, targetDir, { depth: 1 });
161
+ return { localPath: relativePath, branch: result.branch };
162
+ }
163
+ catch (err) {
164
+ const message = err instanceof Error ? err.message : String(err);
165
+ return `Error cloning repository: ${(0, git_utils_js_1.scrubCredentials)(message)}`;
166
+ }
167
+ }
168
+ function handleFsList(args) {
169
+ const inputPath = args.path;
170
+ if (!inputPath) {
171
+ return 'Error: path is required';
172
+ }
173
+ let resolved;
174
+ try {
175
+ resolved = validatePathWithinClones(inputPath);
176
+ }
177
+ catch (err) {
178
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
179
+ }
180
+ if (!fs.existsSync(resolved)) {
181
+ return `Error: path does not exist: ${inputPath}`;
182
+ }
183
+ const stat = fs.statSync(resolved);
184
+ if (!stat.isDirectory()) {
185
+ return `Error: path is not a directory: ${inputPath}`;
186
+ }
187
+ const entries = fs.readdirSync(resolved, { withFileTypes: true });
188
+ return entries.map((entry) => ({
189
+ name: entry.name,
190
+ type: entry.isDirectory() ? 'directory' : 'file',
191
+ }));
192
+ }
193
+ function handleFsRead(args) {
194
+ const inputPath = args.path;
195
+ if (!inputPath) {
196
+ return 'Error: path is required';
197
+ }
198
+ let resolved;
199
+ try {
200
+ resolved = validatePathWithinClones(inputPath);
201
+ }
202
+ catch (err) {
203
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
204
+ }
205
+ if (!fs.existsSync(resolved)) {
206
+ return `Error: file does not exist: ${inputPath}`;
207
+ }
208
+ const stat = fs.statSync(resolved);
209
+ if (stat.isDirectory()) {
210
+ return `Error: path is a directory, not a file: ${inputPath}`;
211
+ }
212
+ // Binary detection: check first 8KB for null bytes
213
+ const detectBuffer = Buffer.alloc(Math.min(8192, stat.size));
214
+ const fd = fs.openSync(resolved, 'r');
215
+ try {
216
+ fs.readSync(fd, detectBuffer, 0, detectBuffer.length, 0);
217
+ }
218
+ finally {
219
+ fs.closeSync(fd);
220
+ }
221
+ if (detectBuffer.includes(0)) {
222
+ return 'Binary file, cannot display';
223
+ }
224
+ if (stat.size > MAX_FILE_SIZE) {
225
+ const buffer = Buffer.alloc(MAX_FILE_SIZE);
226
+ const fd = fs.openSync(resolved, 'r');
227
+ try {
228
+ const bytesRead = fs.readSync(fd, buffer, 0, MAX_FILE_SIZE, 0);
229
+ const content = buffer.toString('utf-8', 0, bytesRead);
230
+ return `${content}\n\n[Truncated: file exceeds ${MAX_FILE_SIZE / 1024}KB limit]`;
231
+ }
232
+ finally {
233
+ fs.closeSync(fd);
234
+ }
235
+ }
236
+ return fs.readFileSync(resolved, 'utf-8');
237
+ }
238
+ // ─── Combined executor ───
239
+ /**
240
+ * Create a ToolExecutor that handles internal agentic-loop tools.
241
+ * Designed to be passed as the fallbackExecutor to pluginManager.createToolExecutor().
242
+ */
243
+ function createInternalToolExecutor(sessionId) {
244
+ const handlers = {
245
+ git_clone: (args) => handleGitClone(args, sessionId),
246
+ fs_list: handleFsList,
247
+ fs_read: handleFsRead,
248
+ };
249
+ return async (toolName, input) => {
250
+ const handler = handlers[toolName];
251
+ if (!handler) {
252
+ return `Error: unknown internal tool: ${toolName}`;
253
+ }
254
+ if (typeof input !== 'object' || input === null || Array.isArray(input)) {
255
+ return 'Error: tool input must be an object';
256
+ }
257
+ return handler(input);
258
+ };
259
+ }
260
+ // ─── TTL cleanup ───
261
+ /**
262
+ * Remove session clone directories older than the TTL.
263
+ * Called at the start of each new remediate investigation.
264
+ * Non-blocking: runs cleanup in the background without delaying investigation.
265
+ */
266
+ function cleanupOldClones(maxAgeMs = DEFAULT_TTL_MS) {
267
+ const clonesDir = getClonesDir();
268
+ fs.promises.access(clonesDir).then(async () => {
269
+ const now = Date.now();
270
+ const entries = await fs.promises.readdir(clonesDir);
271
+ for (const entry of entries) {
272
+ const entryPath = path.join(clonesDir, entry);
273
+ try {
274
+ const stat = await fs.promises.stat(entryPath);
275
+ if (stat.isDirectory() && now - stat.mtimeMs > maxAgeMs) {
276
+ await fs.promises.rm(entryPath, { recursive: true, force: true });
277
+ }
278
+ }
279
+ catch {
280
+ // Ignore errors during cleanup (e.g., concurrent deletion)
281
+ }
282
+ }
283
+ }).catch(() => {
284
+ // Directory doesn't exist yet, nothing to clean
285
+ });
286
+ }
@@ -403,7 +403,7 @@ class MCPServer {
403
403
  // Handle CORS for browser-based clients
404
404
  res.setHeader('Access-Control-Allow-Origin', '*');
405
405
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
406
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Session-Id, Authorization');
406
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Session-Id, Authorization, X-Dot-AI-Authorization');
407
407
  if (req.method === 'OPTIONS') {
408
408
  res.writeHead(204);
409
409
  res.end();
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/interfaces/oauth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG1C;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,UAAU,CAyFhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/interfaces/oauth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG1C;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,UAAU,CA4FhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC"}
@@ -32,8 +32,10 @@ function checkBearerAuth(req) {
32
32
  message: 'Authentication is not configured. Set DOT_AI_AUTH_TOKEN in your deployment.',
33
33
  };
34
34
  }
35
- // Extract Authorization header
36
- const rawAuthHeader = req.headers['authorization'];
35
+ // Extract Authorization header, with X-Dot-AI-Authorization as fallback.
36
+ // The fallback supports Kubernetes API server proxy scenarios where the
37
+ // standard Authorization header is overwritten with a K8s bearer token.
38
+ const rawAuthHeader = req.headers['x-dot-ai-authorization'] || req.headers['authorization'];
37
39
  if (!rawAuthHeader) {
38
40
  return {
39
41
  authorized: false,
@@ -1 +1 @@
1
- {"version":3,"file":"push-to-git.d.ts","sourceRoot":"","sources":["../../src/tools/push-to-git.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,OAAO,EAAE,KAAK,EAA0B,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAShD,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAC/C,eAAO,MAAM,0BAA0B,0HACkF,CAAC;AAE1H,eAAO,MAAM,2BAA2B;;;;;;;;;CAwBvC,CAAC;AAEF,UAAU,aAAa;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAaD,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,cAAc,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,GACnD,OAAO,CAAC;IAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAAC,CAiUxD"}
1
+ {"version":3,"file":"push-to-git.d.ts","sourceRoot":"","sources":["../../src/tools/push-to-git.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,OAAO,EAAE,KAAK,EAA0B,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAUhD,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAC/C,eAAO,MAAM,0BAA0B,0HACkF,CAAC;AAE1H,eAAO,MAAM,2BAA2B;;;;;;;;;CAwBvC,CAAC;AAEF,UAAU,aAAa;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAGD,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,cAAc,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,GACnD,OAAO,CAAC;IAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAAC,CAiUxD"}
@@ -76,16 +76,6 @@ exports.PUSHTOGIT_TOOL_INPUT_SCHEMA = {
76
76
  .optional()
77
77
  .describe('INTERNAL ONLY - Do not populate. Used for evaluation dataset generation.'),
78
78
  };
79
- function sanitizeRelativePath(relativePath) {
80
- if (relativePath.startsWith('/')) {
81
- throw new Error('Relative path cannot be absolute');
82
- }
83
- const normalized = path.posix.normalize(relativePath);
84
- if (normalized.startsWith('..') || path.posix.isAbsolute(normalized)) {
85
- throw new Error('Relative path cannot escape target directory');
86
- }
87
- return normalized;
88
- }
89
79
  async function handlePushToGitTool(args, dotAI, logger, requestId, sessionManager) {
90
80
  return await error_handling_1.ErrorHandler.withErrorHandling(async () => {
91
81
  logger.info('Handling pushToGit request', {
@@ -204,7 +194,7 @@ async function handlePushToGitTool(args, dotAI, logger, requestId, sessionManage
204
194
  const manifestFiles = solution.generatedManifests.files;
205
195
  if (manifestFiles && manifestFiles.length > 0) {
206
196
  for (const file of manifestFiles) {
207
- const sanitizedPath = sanitizeRelativePath(file.relativePath);
197
+ const sanitizedPath = (0, git_utils_1.sanitizeRelativePath)(file.relativePath);
208
198
  files.push({
209
199
  path: path.posix.join(targetPath, sanitizedPath),
210
200
  content: file.content,
@@ -52,6 +52,15 @@ export interface RemediationAction {
52
52
  command?: string;
53
53
  risk: 'low' | 'medium' | 'high';
54
54
  rationale: string;
55
+ gitSource?: {
56
+ repoURL: string;
57
+ branch: string;
58
+ files: Array<{
59
+ path: string;
60
+ content: string;
61
+ description: string;
62
+ }>;
63
+ };
55
64
  }
56
65
  export interface ExecutionResult {
57
66
  action: string;
@@ -1 +1 @@
1
- {"version":3,"file":"remediate.d.ts","sourceRoot":"","sources":["../../src/tools/remediate.ts"],"names":[],"mappings":"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"}
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;AAYxB,OAAO,EAEL,qBAAqB,EACtB,MAAM,uBAAuB,CAAC;AA2B/B,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAC/C,eAAO,MAAM,0BAA0B,yfACgd,CAAC;AAGxf,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;CAsDvC,CAAC;AAGF,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;IACjE,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,eAAe,CAAC;IAChC,MAAM,EACF,eAAe,GACf,mBAAmB,GACnB,QAAQ,GACR,uBAAuB,GACvB,sBAAsB,GACtB,WAAW,CAAC;IAChB,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;CACtC;AAGD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,oBAAoB,CAAC;CAC5B,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE;QACV,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,KAAK,CAAC;YACX,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,MAAM,CAAC;YAChB,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC,CAAC;KACJ,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,wBAAwB,GAAG,oBAAoB,CAAC;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,EAAE,CAAC;KACxB,CAAC;IACF,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,WAAW,EAAE;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;QAC7B,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;KACjC,CAAC;IAEF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;CAC/B;AAID;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAmOD;;GAEG;AACH,UAAU,uBAAuB;IAC/B,WAAW,EAAE,QAAQ,GAAG,UAAU,GAAG,cAAc,CAAC;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;QAC7B,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;KACjC,CAAC;IACF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,GACjB,uBAAuB,CAyHzB;AA0dD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,qBAAqB,CAAC,CA8QhC"}
@@ -50,6 +50,7 @@ const fs = __importStar(require("fs"));
50
50
  const path = __importStar(require("path"));
51
51
  const request_context_1 = require("../interfaces/request-context");
52
52
  const rbac_1 = require("../core/rbac");
53
+ const internal_tools_1 = require("../core/internal-tools");
53
54
  // PRD #143 Milestone 1: Hybrid approach - AI can use kubectl_api_resources tool OR continue with JSON dataRequests
54
55
  // Tool metadata for direct MCP registration
55
56
  exports.REMEDIATE_TOOL_NAME = 'remediate';
@@ -146,15 +147,20 @@ async function conductInvestigation(session, sessionManager, aiProvider, logger,
146
147
  if (kubectlTools.length === 0) {
147
148
  throw new Error('No kubectl tools available from plugin. Ensure agentic-tools plugin is running.');
148
149
  }
149
- logger.debug('Starting toolLoop with kubectl investigation tools from plugin', {
150
+ // PRD #407: Combine plugin tools (allowlisted) with internal tools (git_clone, fs_list, fs_read)
151
+ const investigationTools = [...kubectlTools, ...(0, internal_tools_1.getInternalTools)()];
152
+ logger.debug('Starting toolLoop with investigation tools', {
150
153
  requestId,
151
154
  sessionId: session.sessionId,
152
- toolCount: kubectlTools.length,
153
- tools: kubectlTools.map(t => t.name),
155
+ toolCount: investigationTools.length,
156
+ tools: investigationTools.map(t => t.name),
154
157
  });
155
- // PRD #343: Create tool executor that routes through plugin
156
- const toolExecutor = pluginManager.createToolExecutor();
157
- // Use toolLoop for AI-driven investigation with kubectl tools
158
+ // PRD #407: Clean up old clone directories (non-blocking)
159
+ (0, internal_tools_1.cleanupOldClones)();
160
+ // PRD #407: Combined executor routes plugin tools to plugin, internal tools to local handlers
161
+ const internalExecutor = (0, internal_tools_1.createInternalToolExecutor)(session.sessionId);
162
+ const toolExecutor = pluginManager.createToolExecutor(internalExecutor);
163
+ // Use toolLoop for AI-driven investigation with all investigation tools
158
164
  // System prompt is static (cached), issue description is dynamic (userMessage)
159
165
  const operationName = isValidation
160
166
  ? 'remediate-validation'
@@ -162,7 +168,7 @@ async function conductInvestigation(session, sessionManager, aiProvider, logger,
162
168
  const result = await aiProvider.toolLoop({
163
169
  systemPrompt: systemPrompt,
164
170
  userMessage: `Investigate this Kubernetes issue: ${session.data.issue}`,
165
- tools: kubectlTools,
171
+ tools: investigationTools,
166
172
  toolExecutor: toolExecutor,
167
173
  maxIterations: maxIterations,
168
174
  operation: operationName,
@@ -436,6 +442,7 @@ async function executeRemediationCommands(session, sessionManager, logger, reque
436
442
  const results = [];
437
443
  const finalAnalysis = session.data.finalAnalysis;
438
444
  let overallSuccess = true;
445
+ let executedCommandCount = 0;
439
446
  logger.info('Starting remediation command execution', {
440
447
  requestId,
441
448
  sessionId: session.sessionId,
@@ -446,6 +453,23 @@ async function executeRemediationCommands(session, sessionManager, logger, reque
446
453
  const action = finalAnalysis.remediation.actions[i];
447
454
  const actionId = `action_${i + 1}`;
448
455
  try {
456
+ // PRD #407: Skip gitSource actions — these are Git-based remediation
457
+ // instructions for GitOps-managed resources, not executable commands
458
+ if (action.gitSource && !action.command) {
459
+ logger.info('Skipping gitSource remediation action (not executable)', {
460
+ requestId,
461
+ sessionId: session.sessionId,
462
+ actionId,
463
+ repoURL: action.gitSource.repoURL,
464
+ });
465
+ results.push({
466
+ action: `${actionId}: ${action.description} (skipped: Git-based)`,
467
+ success: false,
468
+ output: 'Git-based remediation — apply changes in the source repository',
469
+ timestamp: new Date(),
470
+ });
471
+ continue;
472
+ }
449
473
  logger.info('Executing remediation action', {
450
474
  requestId,
451
475
  sessionId: session.sessionId,
@@ -486,6 +510,7 @@ async function executeRemediationCommands(session, sessionManager, logger, reque
486
510
  else {
487
511
  output = String(response.result || '');
488
512
  }
513
+ executedCommandCount++;
489
514
  results.push({
490
515
  action: `${actionId}: ${action.description}`,
491
516
  success: true,
@@ -515,9 +540,9 @@ async function executeRemediationCommands(session, sessionManager, logger, reque
515
540
  });
516
541
  }
517
542
  }
518
- // Run automatic post-execution validation if all commands succeeded
543
+ // Run automatic post-execution validation if commands were executed and all succeeded
519
544
  let validationResult = null;
520
- if (overallSuccess && finalAnalysis.validationIntent) {
545
+ if (overallSuccess && executedCommandCount > 0 && finalAnalysis.validationIntent) {
521
546
  const validationIntent = finalAnalysis.validationIntent;
522
547
  try {
523
548
  // In automatic mode, wait for Kubernetes to apply changes before validating
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vfarcic/dot-ai",
3
- "version": "1.10.1",
3
+ "version": "1.11.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",
@@ -43,11 +43,11 @@ You help users perform Day 2 operations on Kubernetes applications through natur
43
43
 
44
44
  **Validation workflow**:
45
45
  1. Design your operational solution
46
- 2. Generate kubectl commands
46
+ 2. Generate the exact kubectl commands you intend to propose
47
47
  3. Run dry-run validation for each command
48
- 4. If validation fails, analyze the error and fix your command
48
+ 4. If validation fails, analyze the error, fix the command, and re-validate
49
49
  5. Retry validation until all commands pass
50
- 6. Only then complete analysis with the validated commands
50
+ 6. Copy the validated commands verbatim into your final JSON response — the commands array **MUST be identical** to what passed dry-run. If you modify a command after validation (even slightly), you MUST re-validate it.
51
51
 
52
52
  **Multiple iterations are expected**: You may need several dry-run attempts to get manifests correct (schema issues, field names, API versions). This is normal and expected.
53
53
 
@@ -59,9 +59,20 @@ Once investigation is complete, respond with ONLY this JSON format:
59
59
  "actions": [
60
60
  {
61
61
  "description": "Specific action to take",
62
- "command": "command to execute (kubectl, helm, etc.)",
62
+ "command": "command to execute (kubectl, helm, etc.) — omit if gitSource is provided",
63
63
  "risk": "low|medium|high",
64
- "rationale": "Why this action addresses the issue"
64
+ "rationale": "Why this action addresses the issue",
65
+ "gitSource": {
66
+ "repoURL": "source repository URL — only when resource is GitOps-managed",
67
+ "branch": "branch name",
68
+ "files": [
69
+ {
70
+ "path": "path relative to repo root",
71
+ "content": "full corrected file content",
72
+ "description": "what was changed and why"
73
+ }
74
+ ]
75
+ }
65
76
  }
66
77
  ],
67
78
  "risk": "low|medium|high"
@@ -88,6 +99,17 @@ Once investigation is complete, respond with ONLY this JSON format:
88
99
  - All components healthy
89
100
  - Set `actions: []` and explain why no issue found
90
101
 
102
+ ## GitOps Awareness
103
+
104
+ After identifying the problematic resource, check whether it is managed by a GitOps controller (e.g., Argo CD, Flux).
105
+
106
+ **When GitOps management is detected**:
107
+ - Clone the source repo, navigate and read the manifests to find the file(s) that need changing
108
+ - Include `gitSource` in your remediation actions with the repo URL, branch, and full corrected file contents for each file that needs modification
109
+
110
+ **When GitOps management is NOT detected**:
111
+ - Proceed with standard kubectl-based remediation
112
+
91
113
  ### Remediation Action Guidelines
92
114
 
93
115
  **Structure your solution efficiently**:
@@ -146,6 +168,44 @@ Once investigation is complete, respond with ONLY this JSON format:
146
168
  }
147
169
  ```
148
170
 
171
+ ## Example Response - GitOps-Managed Resource
172
+
173
+ ```json
174
+ {
175
+ "issueStatus": "active",
176
+ "rootCause": "Deployment 'api-server' has invalid image tag 'v2.broken' causing CrashLoopBackOff",
177
+ "confidence": 0.95,
178
+ "factors": [
179
+ "Pod is in CrashLoopBackOff with ImagePullBackOff events",
180
+ "Image tag 'v2.broken' does not exist in registry",
181
+ "Resource is managed by Argo CD Application 'api-server'"
182
+ ],
183
+ "remediation": {
184
+ "summary": "Update image tag in Git source to valid version",
185
+ "actions": [
186
+ {
187
+ "description": "Fix image tag in deployment manifest",
188
+ "risk": "low",
189
+ "rationale": "Changing image tag to latest stable version resolves the ImagePullBackOff. Argo CD will sync the change automatically.",
190
+ "gitSource": {
191
+ "repoURL": "https://github.com/org/infra-repo.git",
192
+ "branch": "main",
193
+ "files": [
194
+ {
195
+ "path": "apps/production/deployment.yaml",
196
+ "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: api-server\nspec:\n template:\n spec:\n containers:\n - name: api-server\n image: org/api-server:v2.1.0\n",
197
+ "description": "Changed image tag from 'v2.broken' to 'v2.1.0'"
198
+ }
199
+ ]
200
+ }
201
+ }
202
+ ],
203
+ "risk": "low"
204
+ },
205
+ "validationIntent": "Wait for Argo CD to sync, then verify pods are running with the correct image"
206
+ }
207
+ ```
208
+
149
209
  ## Example Response - No Issue Found
150
210
 
151
211
  ```json