@aiready/agents 0.3.4 → 0.3.6

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.
@@ -1,16 +1,44 @@
1
1
 
2
2
  
3
- > @aiready/agents@0.3.3 test /Users/pengcao/projects/aiready/packages/agents
3
+ > @aiready/agents@0.3.5 test /Users/pengcao/projects/aiready/packages/agents
4
4
  > vitest run
5
5
 
6
6
  [?25l
7
7
   RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/agents
8
8
 
9
- ✓ src/__tests__/smoke.test.ts (1 test) 1ms
9
+ ✓ src/__tests__/smoke.test.ts (1 test) 3ms
10
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should successfully connect to MCP, execute agent, and parse JSON response
11
+ [RemediationSwarm] Connecting to MCP servers...
10
12
 
11
-  Test Files  1 passed (1)
12
-  Tests  1 passed (1)
13
-  Start at  10:36:36
14
-  Duration  217ms (transform 23ms, setup 0ms, import 29ms, tests 1ms, environment 0ms)
13
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should successfully connect to MCP, execute agent, and parse JSON response
14
+ [RemediationSwarm] Executing agent logic...
15
+
16
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should successfully connect to MCP, execute agent, and parse JSON response
17
+ [RemediationSwarm] Cleaning up MCP connections...
18
+
19
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should handle agent failures and return error status
20
+ [RemediationSwarm] Connecting to MCP servers...
21
+
22
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should handle agent failures and return error status
23
+ [RemediationSwarm] Executing agent logic...
24
+
25
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should handle agent failures and return error status
26
+ [RemediationSwarm] Cleaning up MCP connections...
27
+
28
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should fallback to raw text if agent fails to return valid JSON
29
+ [RemediationSwarm] Connecting to MCP servers...
30
+
31
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should fallback to raw text if agent fails to return valid JSON
32
+ [RemediationSwarm] Executing agent logic...
33
+
34
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should fallback to raw text if agent fails to return valid JSON
35
+ [RemediationSwarm] Cleaning up MCP connections...
36
+
37
+ ✓ src/__tests__/remediation-swarm.test.ts (3 tests) 5ms
38
+
39
+  Test Files  2 passed (2)
40
+  Tests  4 passed (4)
41
+  Start at  10:12:50
42
+  Duration  308ms (transform 206ms, setup 0ms, import 251ms, tests 8ms, environment 0ms)
15
43
 
16
44
  [?25h
package/package.json CHANGED
@@ -1,19 +1,22 @@
1
1
  {
2
2
  "name": "@aiready/agents",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Agentic remediation and analysis system for AIReady",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "dependencies": {
8
8
  "@mastra/core": "^1.0.0",
9
+ "@modelcontextprotocol/sdk": "^1.27.1",
10
+ "@octokit/rest": "^21.0.0",
11
+ "isomorphic-git": "^1.25.0",
9
12
  "zod": "^4.0.0",
10
- "@aiready/core": "0.23.4",
11
- "@aiready/cli": "0.14.5"
13
+ "@aiready/core": "0.23.7",
14
+ "@aiready/cli": "0.14.9"
12
15
  },
13
16
  "devDependencies": {
17
+ "@types/node": "^24.0.0",
14
18
  "typescript": "^5.0.0",
15
- "vitest": "^4.0.0",
16
- "@types/node": "^24.0.0"
19
+ "vitest": "^4.0.0"
17
20
  },
18
21
  "scripts": {
19
22
  "test": "vitest run",
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { RemediationSwarm } from '../workflows/remediation-swarm';
3
+ import { MCPAdapter } from '../tools/mcp-adapter';
4
+ import { Agent } from '@mastra/core/agent';
5
+
6
+ // Mock MCPAdapter
7
+ vi.mock('../tools/mcp-adapter', () => {
8
+ const MockAdapter = vi.fn();
9
+ MockAdapter.prototype.connect = vi.fn().mockResolvedValue(undefined);
10
+ MockAdapter.prototype.disconnect = vi.fn().mockResolvedValue(undefined);
11
+ MockAdapter.prototype.getMastraTools = vi.fn().mockResolvedValue({
12
+ 'list-files': { id: 'list-files', execute: vi.fn() },
13
+ 'create-pr': { id: 'create-pr', execute: vi.fn() },
14
+ });
15
+ return { MCPAdapter: MockAdapter };
16
+ });
17
+
18
+ // Mock Mastra Agent
19
+ vi.mock('@mastra/core/agent', () => {
20
+ const MockAgent = vi.fn();
21
+ MockAgent.prototype.generate = vi.fn().mockResolvedValue({
22
+ text: JSON.stringify({
23
+ status: 'success',
24
+ diff: 'test-diff',
25
+ prUrl: 'https://github.com/test/repo/pull/1',
26
+ prNumber: 1,
27
+ explanation: 'Fixed duplication in auth.ts',
28
+ }),
29
+ });
30
+ return { Agent: MockAgent };
31
+ });
32
+
33
+ describe('RemediationSwarm (MCP Powered)', () => {
34
+ beforeEach(() => {
35
+ vi.clearAllMocks();
36
+ });
37
+
38
+ it('should successfully connect to MCP, execute agent, and parse JSON response', async () => {
39
+ const input = {
40
+ remediation: { id: 'rem-1', type: 'consolidation' },
41
+ repo: { url: 'https://github.com/test/repo' },
42
+ rootDir: '/tmp/test-repo',
43
+ config: {
44
+ githubToken: 'gh-token-123',
45
+ openaiApiKey: 'oa-key-456',
46
+ },
47
+ };
48
+
49
+ const result = await RemediationSwarm.execute(input);
50
+
51
+ // Verify MCP Adapters were created and connected
52
+ expect(MCPAdapter).toHaveBeenCalledTimes(2);
53
+
54
+ const mockInstances = vi.mocked(MCPAdapter).mock.instances;
55
+ for (const instance of mockInstances) {
56
+ expect(instance.connect).toHaveBeenCalled();
57
+ expect(instance.getMastraTools).toHaveBeenCalled();
58
+ expect(instance.disconnect).toHaveBeenCalled();
59
+ }
60
+
61
+ // Verify result structure
62
+ expect(result.ok).toBe(true);
63
+ if (result.ok) {
64
+ expect(result.value.status).toBe('success');
65
+ expect(result.value.prUrl).toBe('https://github.com/test/repo/pull/1');
66
+ }
67
+ });
68
+
69
+ it('should handle agent failures and return error status', async () => {
70
+ // Override mock for this specific test
71
+ const MockAgent = vi.mocked(Agent);
72
+ (MockAgent.prototype.generate as any).mockResolvedValueOnce({
73
+ text: JSON.stringify({
74
+ status: 'failure',
75
+ explanation: 'Could not find the duplicated files',
76
+ }),
77
+ });
78
+
79
+ const input = {
80
+ remediation: { id: 'rem-2' },
81
+ repo: { url: 'https://github.com/test/repo' },
82
+ rootDir: '/tmp/test',
83
+ config: { githubToken: 'token' },
84
+ };
85
+
86
+ const result = await RemediationSwarm.execute(input);
87
+
88
+ expect(result.ok).toBe(true);
89
+ if (result.ok) {
90
+ expect(result.value.status).toBe('failure');
91
+ expect(result.value.explanation).toBe(
92
+ 'Could not find the duplicated files'
93
+ );
94
+ }
95
+ });
96
+
97
+ it('should fallback to raw text if agent fails to return valid JSON', async () => {
98
+ const MockAgent = vi.mocked(Agent);
99
+ (MockAgent.prototype.generate as any).mockResolvedValueOnce({
100
+ text: 'Applied fixes manually. PR created at https://github.com/test/repo/pull/99',
101
+ });
102
+
103
+ const input = {
104
+ remediation: { id: 'rem-3' },
105
+ repo: { url: 'https://github.com/test/repo' },
106
+ rootDir: '/tmp/test',
107
+ config: { githubToken: 'token' },
108
+ };
109
+
110
+ const result = await RemediationSwarm.execute(input);
111
+
112
+ expect(result.ok).toBe(true);
113
+ if (result.ok) {
114
+ expect(result.value.status).toBe('success');
115
+ expect(result.value.explanation).toContain('fallback to text response');
116
+ }
117
+ });
118
+ });
@@ -1,32 +1,37 @@
1
1
  import { Agent } from '@mastra/core/agent';
2
2
  import { z } from 'zod';
3
+ import { githubTools } from './tools/github';
4
+ import { fsTools } from './tools/fs';
3
5
 
4
6
  export const RefactorAgent = new Agent({
5
7
  id: 'refactor-agent',
6
8
  name: 'Refactor Agent',
7
9
  instructions: `
8
10
  You are an expert full-stack engineer specialized in code consolidation and refactoring.
9
- Your task is to take detected code duplicates or fragmentation issues and consolidate them into reusable components or utilities.
10
-
11
- Follow these rules:
12
- 1. Preserve functionality: The behavior of the code must remain identical.
13
- 2. Follow patterns: Match the existing project's implementation patterns (e.g., React hooks, utility functions).
14
- 3. Type safety: Ensure all changes are correctly typed in TypeScript.
15
- 4. Minimal changes: Only modify what is necessary to resolve the duplication.
11
+ Your task is to take a detected code duplication or fragmentation issue and fix it.
16
12
 
17
13
  Workflow:
18
- - Read the affected files.
19
- - Extract the common logic into a new or existing shared location.
20
- - Update the original call sites to use the consolidated logic.
21
- - Verify with type checking if possible.
14
+ 1. Research: Read the affected files using 'read-file'.
15
+ 2. Branching: Create a new branch remotely using 'create-branch' AND locally using 'checkout-branch'.
16
+ 3. Remediation: Consolidate the logic and write the changes using 'write-file'.
17
+ Ensure type safety and preserve functionality.
18
+ 4. Persist: Commit and push the changes using 'commit-and-push'.
19
+ 5. Finalize: Create a Pull Request with a clear description using 'create-pr'.
20
+
21
+ Return a summary of your actions, including the PR URL and a unified diff of your changes.
22
22
  `,
23
23
  model: 'openai/gpt-4o',
24
+ tools: {
25
+ ...githubTools,
26
+ ...fsTools,
27
+ },
24
28
  });
25
29
 
26
30
  export const RefactorResultSchema = z.object({
27
31
  status: z.enum(['success', 'failure']),
28
32
  diff: z.string(),
29
- filesModified: z.array(z.string()),
33
+ prUrl: z.string().optional(),
34
+ prNumber: z.number().optional(),
30
35
  explanation: z.string(),
31
36
  });
32
37
 
@@ -0,0 +1,137 @@
1
+ import { createTool } from '@mastra/core/tools';
2
+ import { z } from 'zod';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import * as git from 'isomorphic-git';
6
+ import http from 'isomorphic-git/http/node';
7
+
8
+ export const fsTools = {
9
+ readFile: createTool({
10
+ id: 'read-file',
11
+ description: 'Read the contents of a file',
12
+ inputSchema: z.object({
13
+ rootDir: z.string(),
14
+ filePath: z.string(),
15
+ }),
16
+ outputSchema: z.object({
17
+ content: z.string().optional(),
18
+ error: z.string().optional(),
19
+ }),
20
+ execute: async ({ rootDir, filePath }) => {
21
+ try {
22
+ const fullPath = path.join(rootDir, filePath);
23
+ const content = fs.readFileSync(fullPath, 'utf-8');
24
+ return { content };
25
+ } catch (error) {
26
+ return {
27
+ error: error instanceof Error ? error.message : 'Unknown error',
28
+ };
29
+ }
30
+ },
31
+ }),
32
+
33
+ writeFile: createTool({
34
+ id: 'write-file',
35
+ description: 'Write contents to a file',
36
+ inputSchema: z.object({
37
+ rootDir: z.string(),
38
+ filePath: z.string(),
39
+ content: z.string(),
40
+ }),
41
+ outputSchema: z.object({
42
+ success: z.boolean(),
43
+ error: z.string().optional(),
44
+ }),
45
+ execute: async ({ rootDir, filePath, content }) => {
46
+ try {
47
+ const fullPath = path.join(rootDir, filePath);
48
+ // Ensure directory exists
49
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
50
+ fs.writeFileSync(fullPath, content, 'utf-8');
51
+ return { success: true };
52
+ } catch (error) {
53
+ return {
54
+ success: false,
55
+ error: error instanceof Error ? error.message : 'Unknown error',
56
+ };
57
+ }
58
+ },
59
+ }),
60
+
61
+ commitAndPush: createTool({
62
+ id: 'commit-and-push',
63
+ description: 'Commit all changes and push to a branch',
64
+ inputSchema: z.object({
65
+ rootDir: z.string(),
66
+ branchName: z.string(),
67
+ message: z.string(),
68
+ githubToken: z.string(),
69
+ }),
70
+ outputSchema: z.object({
71
+ success: z.boolean(),
72
+ error: z.string().optional(),
73
+ }),
74
+ execute: async ({ rootDir, branchName, message, githubToken }) => {
75
+ try {
76
+ // 1. Add all changed files
77
+ await git.add({ fs, dir: rootDir, filepath: '.' });
78
+
79
+ // 2. Commit
80
+ await git.commit({
81
+ fs,
82
+ dir: rootDir,
83
+ message,
84
+ author: {
85
+ name: 'AIReady Remediation Agent',
86
+ email: 'agents@getaiready.dev',
87
+ },
88
+ });
89
+
90
+ // 3. Push
91
+ await git.push({
92
+ fs,
93
+ http,
94
+ dir: rootDir,
95
+ ref: branchName,
96
+ onAuth: () => ({ username: githubToken, password: '' }),
97
+ });
98
+
99
+ return { success: true };
100
+ } catch (error) {
101
+ console.error('[FSTool] Error during commit/push:', error);
102
+ return {
103
+ success: false,
104
+ error: error instanceof Error ? error.message : 'Unknown error',
105
+ };
106
+ }
107
+ },
108
+ }),
109
+
110
+ checkoutBranch: createTool({
111
+ id: 'checkout-branch',
112
+ description: 'Create and/or checkout a local branch',
113
+ inputSchema: z.object({
114
+ rootDir: z.string(),
115
+ branchName: z.string(),
116
+ create: z.boolean().default(true),
117
+ }),
118
+ outputSchema: z.object({
119
+ success: z.boolean(),
120
+ error: z.string().optional(),
121
+ }),
122
+ execute: async ({ rootDir, branchName, create }) => {
123
+ try {
124
+ if (create) {
125
+ await git.branch({ fs, dir: rootDir, ref: branchName });
126
+ }
127
+ await git.checkout({ fs, dir: rootDir, ref: branchName, force: true });
128
+ return { success: true };
129
+ } catch (error) {
130
+ return {
131
+ success: false,
132
+ error: error instanceof Error ? error.message : 'Unknown error',
133
+ };
134
+ }
135
+ },
136
+ }),
137
+ };
@@ -1,29 +1,57 @@
1
1
  import { createTool } from '@mastra/core/tools';
2
2
  import { z } from 'zod';
3
+ import { Octokit } from '@octokit/rest';
3
4
 
4
- // Note: In a real implementation, this would use Octokit with appropriate auth
5
5
  export const githubTools = {
6
6
  createBranch: createTool({
7
7
  id: 'create-branch',
8
8
  description: 'Create a new branch in the repository',
9
9
  inputSchema: z.object({
10
- repoId: z.string(),
11
- branchName: z.string(),
12
- baseBranch: z.string().default('main'),
10
+ repoUrl: z.string().describe('The full URL of the repository'),
11
+ branchName: z.string().describe('The name of the new branch'),
12
+ baseBranch: z
13
+ .string()
14
+ .default('main')
15
+ .describe('The base branch to branch off from'),
16
+ githubToken: z.string().describe('GitHub Personal Access Token'),
13
17
  }),
14
18
  outputSchema: z.object({
15
19
  success: z.boolean(),
16
- branchUrl: z.string().optional(),
20
+ branchName: z.string().optional(),
21
+ error: z.string().optional(),
17
22
  }),
18
- execute: async (input) => {
19
- console.log(
20
- `[GitHubTool] Creating branch ${input.branchName} from ${input.baseBranch}`
21
- );
22
- // Mock implementation
23
- return {
24
- success: true,
25
- branchUrl: `https://github.com/placeholder/${input.branchName}`,
26
- };
23
+ execute: async ({ repoUrl, branchName, baseBranch, githubToken }) => {
24
+ try {
25
+ const octokit = new Octokit({ auth: githubToken });
26
+ const [owner, repo] = repoUrl
27
+ .replace('https://github.com/', '')
28
+ .replace('git@github.com:', '')
29
+ .replace('.git', '')
30
+ .split('/');
31
+
32
+ // 1. Get the SHA of the base branch
33
+ const { data: ref } = await octokit.git.getRef({
34
+ owner,
35
+ repo,
36
+ ref: `heads/${baseBranch}`,
37
+ });
38
+
39
+ // 2. Create the new branch
40
+ await octokit.git.createRef({
41
+ owner,
42
+ repo,
43
+ ref: `refs/heads/${branchName}`,
44
+ sha: ref.object.sha,
45
+ });
46
+
47
+ return { success: true, branchName };
48
+ } catch (error) {
49
+ console.error('[GitHubTool] Error creating branch:', error);
50
+ return {
51
+ success: false,
52
+ error: error instanceof Error ? error.message : 'Unknown error',
53
+ };
54
+ }
27
55
  },
28
56
  }),
29
57
 
@@ -31,25 +59,49 @@ export const githubTools = {
31
59
  id: 'create-pr',
32
60
  description: 'Create a Pull Request for a branch',
33
61
  inputSchema: z.object({
34
- repoId: z.string(),
35
- title: z.string(),
36
- body: z.string(),
37
- head: z.string(),
38
- base: z.string().default('main'),
62
+ repoUrl: z.string().describe('The full URL of the repository'),
63
+ title: z.string().describe('PR Title'),
64
+ body: z.string().describe('PR Description/Body'),
65
+ head: z.string().describe('The branch containing the changes'),
66
+ base: z.string().default('main').describe('The branch to merge into'),
67
+ githubToken: z.string().describe('GitHub Personal Access Token'),
39
68
  }),
40
69
  outputSchema: z.object({
41
70
  success: z.boolean(),
42
71
  prNumber: z.number().optional(),
43
72
  prUrl: z.string().optional(),
73
+ error: z.string().optional(),
44
74
  }),
45
- execute: async (input) => {
46
- console.log(`[GitHubTool] Creating PR: ${input.title}`);
47
- // Mock implementation
48
- return {
49
- success: true,
50
- prNumber: 123,
51
- prUrl: 'https://github.com/placeholder/pull/123',
52
- };
75
+ execute: async ({ repoUrl, title, body, head, base, githubToken }) => {
76
+ try {
77
+ const octokit = new Octokit({ auth: githubToken });
78
+ const [owner, repo] = repoUrl
79
+ .replace('https://github.com/', '')
80
+ .replace('git@github.com:', '')
81
+ .replace('.git', '')
82
+ .split('/');
83
+
84
+ const { data: pr } = await octokit.pulls.create({
85
+ owner,
86
+ repo,
87
+ title,
88
+ body,
89
+ head,
90
+ base: base || 'main',
91
+ });
92
+
93
+ return {
94
+ success: true,
95
+ prNumber: pr.number,
96
+ prUrl: pr.html_url,
97
+ };
98
+ } catch (error) {
99
+ console.error('[GitHubTool] Error creating PR:', error);
100
+ return {
101
+ success: false,
102
+ error: error instanceof Error ? error.message : 'Unknown error',
103
+ };
104
+ }
53
105
  },
54
106
  }),
55
107
  };
@@ -0,0 +1,105 @@
1
+ import { createTool } from '@mastra/core/tools';
2
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema,
7
+ } from '@modelcontextprotocol/sdk/types.js';
8
+ import { z } from 'zod';
9
+
10
+ /**
11
+ * MCPAdapter allows connecting to external MCP servers and exposing their tools to Mastra agents.
12
+ */
13
+ export class MCPAdapter {
14
+ private client: Client;
15
+ private transport: StdioClientTransport;
16
+
17
+ constructor(
18
+ command: string,
19
+ args: string[] = [],
20
+ env: Record<string, string> = {}
21
+ ) {
22
+ this.transport = new StdioClientTransport({
23
+ command,
24
+ args,
25
+ env: { ...process.env, ...env } as any,
26
+ });
27
+
28
+ this.client = new Client({ name: 'aiready-mcp-adapter', version: '1.0.0' });
29
+ }
30
+
31
+ async connect() {
32
+ await this.client.connect(this.transport);
33
+ }
34
+
35
+ async disconnect() {
36
+ await this.transport.close();
37
+ }
38
+
39
+ /**
40
+ * Discovers tools from the MCP server and wraps them as Mastra tools.
41
+ */
42
+ async getMastraTools(): Promise<Record<string, any>> {
43
+ const { tools } = await (this.client as any).request(
44
+ ListToolsRequestSchema,
45
+ {}
46
+ );
47
+ const mastraTools: Record<string, any> = {};
48
+
49
+ for (const tool of tools) {
50
+ mastraTools[tool.name] = createTool({
51
+ id: tool.name,
52
+ description: tool.description || `MCP Tool: ${tool.name}`,
53
+ inputSchema: this.mapJsonSchemaToZod(tool.inputSchema),
54
+ execute: async (args) => {
55
+ const result = await (this.client as any).request(
56
+ CallToolRequestSchema,
57
+ {
58
+ name: tool.name,
59
+ arguments: args,
60
+ }
61
+ );
62
+ return result;
63
+ },
64
+ });
65
+ }
66
+
67
+ return mastraTools;
68
+ }
69
+
70
+ /**
71
+ * Simple mapper from JSON Schema (MCP) to Zod (Mastra)
72
+ * Note: This is a basic implementation and might need enhancement for complex schemas.
73
+ */
74
+ private mapJsonSchemaToZod(schema: any): z.ZodObject<any> {
75
+ const shape: Record<string, z.ZodTypeAny> = {};
76
+
77
+ if (schema.type === 'object' && schema.properties) {
78
+ for (const [key, value] of Object.entries<any>(schema.properties)) {
79
+ let zodType: z.ZodTypeAny = z.any();
80
+
81
+ if (value.type === 'string') {
82
+ zodType = z.string();
83
+ } else if (value.type === 'number') {
84
+ zodType = z.number();
85
+ } else if (value.type === 'boolean') {
86
+ zodType = z.boolean();
87
+ } else if (value.type === 'array') {
88
+ zodType = z.array(z.any());
89
+ }
90
+
91
+ if (value.description) {
92
+ zodType = zodType.describe(value.description);
93
+ }
94
+
95
+ if (schema.required?.includes(key)) {
96
+ shape[key] = zodType;
97
+ } else {
98
+ shape[key] = zodType.optional();
99
+ }
100
+ }
101
+ }
102
+
103
+ return z.object(shape);
104
+ }
105
+ }
@@ -1,18 +1,128 @@
1
- import { Workflow } from '@mastra/core/workflows';
2
- import { z } from 'zod';
3
- import { PrioritizationAgent } from '../prioritization-agent';
4
- import { ImpactAgent } from '../impact-agent';
5
- import { RestructureAgent } from '../restructure-agent';
6
- import { RefactorAgent } from '../refactor-agent';
7
- import { ValidationAgent } from '../validation-agent';
8
-
9
- export const RemediationSwarm = new Workflow({
10
- id: 'remediation-swarm',
11
- inputSchema: z.any(),
12
- outputSchema: z.any(),
13
- })
14
- .then(PrioritizationAgent as any)
15
- .then(ImpactAgent as any)
16
- .then(RestructureAgent as any)
17
- .then(RefactorAgent as any)
18
- .then(ValidationAgent as any);
1
+ import { Agent } from '@mastra/core/agent';
2
+ import { MCPAdapter } from '../tools/mcp-adapter';
3
+
4
+ // Standard MCP server commands (configured for a standard Node environment)
5
+ // In a real SST/Lambda environment, these should be pre-installed or bundled.
6
+ const GITHUB_MCP_COMMAND = 'npx';
7
+ const GITHUB_MCP_ARGS = ['-y', '@modelcontextprotocol/server-github'];
8
+
9
+ const FILESYSTEM_MCP_COMMAND = 'npx';
10
+ const FILESYSTEM_MCP_ARGS = ['-y', '@modelcontextprotocol/server-filesystem'];
11
+
12
+ export const RemediationSwarm = {
13
+ execute: async (input: any) => {
14
+ const { remediation, repo, rootDir, config } = input;
15
+
16
+ // 1. Setup MCP Adapters
17
+ const githubAdapter = new MCPAdapter(GITHUB_MCP_COMMAND, GITHUB_MCP_ARGS, {
18
+ GITHUB_PERSONAL_ACCESS_TOKEN: config.githubToken,
19
+ });
20
+
21
+ const fsAdapter = new MCPAdapter(FILESYSTEM_MCP_COMMAND, [
22
+ ...FILESYSTEM_MCP_ARGS,
23
+ rootDir,
24
+ ]);
25
+
26
+ try {
27
+ console.log('[RemediationSwarm] Connecting to MCP servers...');
28
+ await Promise.all([githubAdapter.connect(), fsAdapter.connect()]);
29
+
30
+ const githubTools = await githubAdapter.getMastraTools();
31
+ const fsTools = await fsAdapter.getMastraTools();
32
+
33
+ // 2. Create the Agent with MCP Tools
34
+ const agent = new Agent({
35
+ id: 'refactor-agent-mcp',
36
+ name: 'Refactor Agent (MCP Powered)',
37
+ instructions: `
38
+ You are an expert full-stack engineer specialized in code consolidation and refactoring.
39
+ Your task is to take a detected code duplication or fragmentation issue and fix it.
40
+
41
+ Context:
42
+ - Repository: ${repo.url}
43
+ - Local Path: ${rootDir} (You have full access via filesystem tools)
44
+
45
+ Available Toolsets:
46
+ - GitHub: Use for creating branches, commits, and pull requests.
47
+ - Filesystem: Use for reading and writing files in ${rootDir}.
48
+
49
+ Workflow:
50
+ 1. Research: Read the affected files using filesystem tools.
51
+ 2. Branching: Create a new branch remotely using GitHub tools AND locally using Git/Filesystem tools if available.
52
+ 3. Remediation: Consolidate the logic and write the changes using filesystem tools.
53
+ 4. Persist: Commit and push the changes using GitHub/Git tools.
54
+ 5. Finalize: Create a Pull Request with a clear description using GitHub tools.
55
+
56
+ You MUST provide your final response as a VALID JSON object with this structure:
57
+ {
58
+ "status": "success" | "failure",
59
+ "diff": "unified diff string",
60
+ "prUrl": "URL of the created PR",
61
+ "prNumber": 123,
62
+ "explanation": "Brief explanation of changes"
63
+ }
64
+ `,
65
+ model: 'openai/gpt-4o',
66
+ tools: {
67
+ ...githubTools,
68
+ ...fsTools,
69
+ },
70
+ });
71
+
72
+ console.log('[RemediationSwarm] Executing agent logic...');
73
+
74
+ const prompt = `
75
+ Remediate this issue: ${JSON.stringify(remediation)}
76
+ The repository is cloned at: ${rootDir}
77
+ `;
78
+
79
+ const result = await agent.generate(prompt);
80
+
81
+ const text = result.text || '';
82
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
83
+
84
+ if (jsonMatch) {
85
+ try {
86
+ const parsed = JSON.parse(jsonMatch[0]);
87
+ return {
88
+ ok: true,
89
+ value: parsed,
90
+ };
91
+ } catch (parseErr) {
92
+ console.error(
93
+ '[RemediationSwarm] JSON Parse Error:',
94
+ parseErr,
95
+ 'Text:',
96
+ text
97
+ );
98
+ }
99
+ }
100
+
101
+ return {
102
+ ok: true,
103
+ value: {
104
+ status: 'success',
105
+ diff: text,
106
+ explanation:
107
+ 'Applied refactoring successfully (fallback to text response)',
108
+ },
109
+ };
110
+ } catch (error) {
111
+ console.error('[RemediationSwarm] Execution error:', error);
112
+ return {
113
+ ok: false,
114
+ error:
115
+ error instanceof Error
116
+ ? error.message
117
+ : 'Unknown error during swarm execution',
118
+ };
119
+ } finally {
120
+ // 3. Cleanup MCP Connections
121
+ console.log('[RemediationSwarm] Cleaning up MCP connections...');
122
+ await Promise.allSettled([
123
+ githubAdapter.disconnect(),
124
+ fsAdapter.disconnect(),
125
+ ]);
126
+ }
127
+ },
128
+ };