@aiready/agents 0.3.5 → 0.3.7

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,42 @@
1
-
2
- 
3
- > @aiready/agents@0.3.3 test /Users/pengcao/projects/aiready/packages/agents
4
- > vitest run
5
-
6
- [?25l
7
-  RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/agents
8
-
9
- ✓ src/__tests__/smoke.test.ts (1 test) 1ms
10
-
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)
15
-
16
- [?25h
1
+
2
+ > @aiready/agents@0.3.6 test /Users/pengcao/projects/aiready/packages/agents
3
+ > vitest run
4
+
5
+
6
+  RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/agents
7
+
8
+ ✓ src/__tests__/smoke.test.ts (1 test) 1ms
9
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should successfully connect to MCP, execute agent, and parse JSON response
10
+ [RemediationSwarm] Connecting to MCP servers...
11
+
12
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should successfully connect to MCP, execute agent, and parse JSON response
13
+ [RemediationSwarm] Executing agent logic...
14
+
15
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should successfully connect to MCP, execute agent, and parse JSON response
16
+ [RemediationSwarm] Cleaning up MCP connections...
17
+
18
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should handle agent failures and return error status
19
+ [RemediationSwarm] Connecting to MCP servers...
20
+
21
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should handle agent failures and return error status
22
+ [RemediationSwarm] Executing agent logic...
23
+
24
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should handle agent failures and return error status
25
+ [RemediationSwarm] Cleaning up MCP connections...
26
+
27
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should fallback to raw text if agent fails to return valid JSON
28
+ [RemediationSwarm] Connecting to MCP servers...
29
+
30
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should fallback to raw text if agent fails to return valid JSON
31
+ [RemediationSwarm] Executing agent logic...
32
+
33
+ stdout | src/__tests__/remediation-swarm.test.ts > RemediationSwarm (MCP Powered) > should fallback to raw text if agent fails to return valid JSON
34
+ [RemediationSwarm] Cleaning up MCP connections...
35
+
36
+ ✓ src/__tests__/remediation-swarm.test.ts (3 tests) 5ms
37
+
38
+  Test Files  2 passed (2)
39
+  Tests  4 passed (4)
40
+  Start at  15:39:52
41
+  Duration  307ms (transform 217ms, setup 0ms, import 267ms, tests 6ms, environment 0ms)
42
+
package/package.json CHANGED
@@ -1,19 +1,22 @@
1
1
  {
2
2
  "name": "@aiready/agents",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
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.6",
11
- "@aiready/cli": "0.14.7"
13
+ "@aiready/cli": "0.14.10",
14
+ "@aiready/core": "0.23.8"
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
+ });
@@ -11,7 +11,7 @@ export const ImpactAgent = new Agent({
11
11
  Analysis Logic:
12
12
  1. Base Cost: Assume an average developer interacts with this code 20 times/day via AI.
13
13
  2. Token Impact: Calculate token reduction based on "Cognitive Load" and "Context Window" savings.
14
- 3. Model Pricing: Use a blended rate of $10 per 1M tokens (GPT-4o/Claude 3.5 Sonnet average).
14
+ 3. Model Pricing: Use a blended rate of $0.50 per 1M tokens (GPT-5.4-Mini/Gemini 3.1 Pro average).
15
15
  4. Formulas:
16
16
  - MonthlySavings = (TokensSavedPerInteraction * 20 * 22 days) * (Rate / 1,000,000)
17
17
 
@@ -20,7 +20,7 @@ export const ImpactAgent = new Agent({
20
20
  - confidenceScore: number (0.0 to 1.0)
21
21
  - breakdown: string (explanation of the math)
22
22
  `,
23
- model: 'openai/gpt-4o',
23
+ model: 'openai/gpt-5.4-mini',
24
24
  });
25
25
 
26
26
  export const ImpactSchema = z.object({
@@ -17,7 +17,7 @@ export const PrioritizationAgent = new Agent({
17
17
  - rank: 'P0' | 'P1' | 'P2' | 'P3'
18
18
  - reasoning: string explanation of the ranking decision.
19
19
  `,
20
- model: 'openai/gpt-4o',
20
+ model: 'openai/gpt-5.4-mini',
21
21
  });
22
22
 
23
23
  export const PrioritySchema = z.object({
@@ -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
- model: 'openai/gpt-4o',
23
+ model: 'openai/gpt-5.4-mini',
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
 
@@ -19,7 +19,7 @@ export const RenameAgent = new Agent({
19
19
  - Propose a mapping of old -> new names.
20
20
  - Execute the rename across all affected files.
21
21
  `,
22
- model: 'openai/gpt-4o',
22
+ model: 'openai/gpt-5.4-mini',
23
23
  });
24
24
 
25
25
  export const RenameResultSchema = z.object({
@@ -18,7 +18,7 @@ export const RestructureAgent = new Agent({
18
18
  - strategy: string (explanation of the architectural vision)
19
19
  - impact: { cognitiveLoadReduction: number (0-100), fragmentationReduction: number (0-100) }
20
20
  `,
21
- model: 'openai/gpt-4o',
21
+ model: 'openai/gpt-5.4-mini',
22
22
  });
23
23
 
24
24
  export const RestructureSchema = z.object({
package/src/risk-agent.ts CHANGED
@@ -20,7 +20,7 @@ export const RiskAssessmentAgent = new Agent({
20
20
  - autoApprove: boolean (true only for 'low' risk)
21
21
  - reasoning: string
22
22
  `,
23
- model: 'openai/gpt-4o',
23
+ model: 'openai/gpt-5.4-mini',
24
24
  });
25
25
 
26
26
  export const RiskSchema = z.object({
@@ -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
+ }
@@ -18,7 +18,7 @@ export const ValidationAgent = new Agent({
18
18
  - errors: array of { file: string, message: string, line?: number }
19
19
  - feedbackForRefactor: string (detailed instructions for the RefactorAgent on how to fix these errors)
20
20
  `,
21
- model: 'openai/gpt-4o',
21
+ model: 'openai/gpt-5.4-mini',
22
22
  });
23
23
 
24
24
  export const ValidationSchema = z.object({
@@ -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-5.4-mini',
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
+ };