@juspay/yama 2.4.2 → 2.5.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/CHANGELOG.md +7 -0
- package/dist/cli/cli.js +74 -3
- package/dist/index.js +4 -0
- package/dist/v2/config/ConfigLoader.d.ts +7 -2
- package/dist/v2/config/ConfigLoader.js +29 -10
- package/dist/v2/config/DefaultConfig.js +13 -0
- package/dist/v2/core/LearningOrchestrator.js +21 -5
- package/dist/v2/core/MCPServerManager.d.ts +36 -3
- package/dist/v2/core/MCPServerManager.js +173 -6
- package/dist/v2/core/YamaV2Orchestrator.d.ts +20 -1
- package/dist/v2/core/YamaV2Orchestrator.js +168 -58
- package/dist/v2/exploration/ContextExplorerService.d.ts +9 -0
- package/dist/v2/exploration/ContextExplorerService.js +50 -9
- package/dist/v2/exploration/types.d.ts +1 -0
- package/dist/v2/learning/types.d.ts +2 -0
- package/dist/v2/memory/MemoryManager.d.ts +20 -4
- package/dist/v2/memory/MemoryManager.js +30 -5
- package/dist/v2/prompts/PromptBuilder.d.ts +10 -2
- package/dist/v2/prompts/PromptBuilder.js +70 -59
- package/dist/v2/prompts/ReviewSystemPrompt.d.ts +29 -1
- package/dist/v2/prompts/ReviewSystemPrompt.js +34 -43
- package/dist/v2/providers/ProviderToolset.d.ts +54 -0
- package/dist/v2/providers/ProviderToolset.js +389 -0
- package/dist/v2/types/config.types.d.ts +40 -9
- package/dist/v2/types/mcp.types.d.ts +105 -193
- package/dist/v2/types/mcp.types.js +9 -2
- package/dist/v2/types/v2.types.d.ts +7 -2
- package/dist/v2/utils/ProviderDetector.d.ts +57 -0
- package/dist/v2/utils/ProviderDetector.js +187 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [2.5.0](https://github.com/juspay/yama/compare/v2.4.2...v2.5.0) (2026-06-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **github:** add GitHub provider for PR reviews ([0b8e45f](https://github.com/juspay/yama/commit/0b8e45f30a95a30988c0dc6f84e929d2338108fe))
|
|
7
|
+
|
|
1
8
|
## [2.4.2](https://github.com/juspay/yama/compare/v2.4.1...v2.4.2) (2026-04-24)
|
|
2
9
|
|
|
3
10
|
|
package/dist/cli/cli.js
CHANGED
|
@@ -43,6 +43,8 @@ function setupReviewCommand() {
|
|
|
43
43
|
.option("--mode <mode>", "Review mode (pr|local)", "pr")
|
|
44
44
|
.option("-w, --workspace <workspace>", "Bitbucket workspace")
|
|
45
45
|
.option("-r, --repository <repository>", "Repository name")
|
|
46
|
+
.option("--owner <owner>", "GitHub owner/organization")
|
|
47
|
+
.option("--repo <repo>", "GitHub repository (alternative to --repository)")
|
|
46
48
|
.option("-p, --pr <id>", "Pull request ID")
|
|
47
49
|
.option("-b, --branch <branch>", "Branch name (finds PR automatically)")
|
|
48
50
|
.option("--repo-path <path>", "Local repository path (local mode)")
|
|
@@ -93,10 +95,39 @@ function setupReviewCommand() {
|
|
|
93
95
|
}
|
|
94
96
|
process.exit(result.decision === "BLOCKED" ? 1 : 0);
|
|
95
97
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
// Auto-detect provider and validate required parameters.
|
|
99
|
+
// --repo is GitHub-only; Bitbucket uses --repository so that
|
|
100
|
+
// 'yama review --workspace acme --repo service' is not misread as mixed.
|
|
101
|
+
const hasGitHub = options.owner || options.repo;
|
|
102
|
+
const hasBitbucket = options.workspace || options.repository;
|
|
103
|
+
if (hasGitHub && hasBitbucket) {
|
|
104
|
+
console.error("❌ Error: Cannot mix GitHub (--owner/--repo) and Bitbucket (--workspace/--repository) parameters");
|
|
98
105
|
process.exit(1);
|
|
99
106
|
}
|
|
107
|
+
if (!hasGitHub && !hasBitbucket) {
|
|
108
|
+
console.error("❌ Error: Either GitHub (--owner and --repo) or Bitbucket (--workspace and --repository) parameters are required");
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
if (hasGitHub) {
|
|
112
|
+
if (!options.owner) {
|
|
113
|
+
console.error("❌ Error: --owner is required when using GitHub parameters");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
if (!options.repo) {
|
|
117
|
+
console.error("❌ Error: --repo is required when using GitHub parameters");
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
if (!options.workspace) {
|
|
123
|
+
console.error("❌ Error: --workspace is required when using Bitbucket parameters");
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
if (!options.repository) {
|
|
127
|
+
console.error("❌ Error: --repository is required when using Bitbucket parameters");
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
100
131
|
if (!options.pr && !options.branch) {
|
|
101
132
|
console.error("❌ Error: Either --pr or --branch must be specified");
|
|
102
133
|
process.exit(1);
|
|
@@ -111,9 +142,17 @@ function setupReviewCommand() {
|
|
|
111
142
|
}
|
|
112
143
|
const request = {
|
|
113
144
|
mode: "pr",
|
|
145
|
+
provider: hasGitHub ? "github" : "bitbucket",
|
|
146
|
+
owner: options.owner,
|
|
147
|
+
repo: options.repo,
|
|
114
148
|
workspace: options.workspace,
|
|
115
149
|
repository: options.repository,
|
|
116
150
|
pullRequestId,
|
|
151
|
+
// GitHub SDK/prompt paths read prNumber; mirror pullRequestId for
|
|
152
|
+
// GitHub only so Bitbucket behavior stays unchanged.
|
|
153
|
+
prNumber: hasGitHub && typeof pullRequestId === "number"
|
|
154
|
+
? pullRequestId
|
|
155
|
+
: undefined,
|
|
117
156
|
branch: options.branch,
|
|
118
157
|
dryRun: globalOpts.dryRun || false,
|
|
119
158
|
verbose: globalOpts.verbose || false,
|
|
@@ -122,7 +161,9 @@ function setupReviewCommand() {
|
|
|
122
161
|
focus,
|
|
123
162
|
outputSchemaVersion: options.outputSchemaVersion,
|
|
124
163
|
};
|
|
125
|
-
|
|
164
|
+
// Thread the request into initialize so MCP servers are set up for the
|
|
165
|
+
// correct provider (GitHub vs Bitbucket) before review starts.
|
|
166
|
+
await yama.initialize(request.configPath, "pr", request);
|
|
126
167
|
console.log("🚀 Starting autonomous AI review...\n");
|
|
127
168
|
const result = options.reviewOnly
|
|
128
169
|
? await yama.startReview(request)
|
|
@@ -140,6 +181,8 @@ function setupReviewCommand() {
|
|
|
140
181
|
console.log("\n📄 Full Results:");
|
|
141
182
|
console.log(JSON.stringify(result, null, 2));
|
|
142
183
|
}
|
|
184
|
+
// Surface outputs for the composite GitHub Action (provider-agnostic).
|
|
185
|
+
await writeGitHubOutputs(result);
|
|
143
186
|
process.exit(result.decision === "BLOCKED" ? 1 : 0);
|
|
144
187
|
}
|
|
145
188
|
catch (error) {
|
|
@@ -152,6 +195,34 @@ function setupReviewCommand() {
|
|
|
152
195
|
}
|
|
153
196
|
});
|
|
154
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Append PR review outputs to the GitHub Actions output file so a composite
|
|
200
|
+
* Action can expose them as step outputs. No-op outside GitHub Actions (when
|
|
201
|
+
* GITHUB_OUTPUT is unset). Provider-agnostic: works for Bitbucket too.
|
|
202
|
+
*/
|
|
203
|
+
async function writeGitHubOutputs(result) {
|
|
204
|
+
const outputPath = process.env.GITHUB_OUTPUT;
|
|
205
|
+
if (!outputPath) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const issues = result.statistics?.issuesFound;
|
|
209
|
+
const summary = (result.summary || "").trim();
|
|
210
|
+
const lines = [
|
|
211
|
+
`decision=${result.decision}`,
|
|
212
|
+
`summary<<EOF\n${summary}\nEOF`,
|
|
213
|
+
`critical-issues=${issues?.critical ?? 0}`,
|
|
214
|
+
`major-issues=${issues?.major ?? 0}`,
|
|
215
|
+
`minor-issues=${issues?.minor ?? 0}`,
|
|
216
|
+
`total-comments=${result.statistics?.totalComments ?? 0}`,
|
|
217
|
+
];
|
|
218
|
+
try {
|
|
219
|
+
const fs = await import("fs/promises");
|
|
220
|
+
await fs.appendFile(outputPath, `${lines.join("\n")}\n`, "utf8");
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
console.error("⚠️ Failed to write GitHub Action outputs:", error.message);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
155
226
|
/**
|
|
156
227
|
* Enhance description command
|
|
157
228
|
*/
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
// ============================================================================
|
|
6
6
|
// Core Exports
|
|
7
7
|
// ============================================================================
|
|
8
|
+
// `YamaOrchestrator` is exported below as a class, which exposes it to SDK
|
|
9
|
+
// consumers as BOTH a value and a type (with all of its public methods). A
|
|
10
|
+
// separate `export type { YamaOrchestrator }` is intentionally omitted because
|
|
11
|
+
// it would collide with this value export (TS2300: Duplicate identifier).
|
|
8
12
|
export { YamaOrchestrator, createYama, YamaOrchestrator as YamaV2Orchestrator, createYama as createYamaV2, } from "./v2/core/YamaV2Orchestrator.js";
|
|
9
13
|
export { LearningOrchestrator, createLearningOrchestrator, } from "./v2/core/LearningOrchestrator.js";
|
|
10
14
|
export { ConfigLoader } from "./v2/config/ConfigLoader.js";
|
|
@@ -15,9 +15,14 @@ export declare class ConfigLoader {
|
|
|
15
15
|
*/
|
|
16
16
|
getConfig(): YamaConfig;
|
|
17
17
|
/**
|
|
18
|
-
* Validate configuration completeness and correctness
|
|
18
|
+
* Validate configuration completeness and correctness.
|
|
19
|
+
*
|
|
20
|
+
* @param mode "pr" requires VCS credentials; "local" is SDK-first and skips them.
|
|
21
|
+
* @param provider Detected VCS provider. When "github", Bitbucket env vars are
|
|
22
|
+
* NOT required (a GitHub token is required instead); otherwise
|
|
23
|
+
* Bitbucket credentials are validated as before.
|
|
19
24
|
*/
|
|
20
|
-
validate(mode?: "pr" | "local"): Promise<void>;
|
|
25
|
+
validate(mode?: "pr" | "local", provider?: "github" | "bitbucket"): Promise<void>;
|
|
21
26
|
/**
|
|
22
27
|
* Resolve configuration file path
|
|
23
28
|
*/
|
|
@@ -53,9 +53,14 @@ export class ConfigLoader {
|
|
|
53
53
|
return this.config;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* Validate configuration completeness and correctness
|
|
56
|
+
* Validate configuration completeness and correctness.
|
|
57
|
+
*
|
|
58
|
+
* @param mode "pr" requires VCS credentials; "local" is SDK-first and skips them.
|
|
59
|
+
* @param provider Detected VCS provider. When "github", Bitbucket env vars are
|
|
60
|
+
* NOT required (a GitHub token is required instead); otherwise
|
|
61
|
+
* Bitbucket credentials are validated as before.
|
|
57
62
|
*/
|
|
58
|
-
async validate(mode = "pr") {
|
|
63
|
+
async validate(mode = "pr", provider) {
|
|
59
64
|
if (!this.config) {
|
|
60
65
|
throw new ConfigurationError("No configuration to validate");
|
|
61
66
|
}
|
|
@@ -69,15 +74,29 @@ export class ConfigLoader {
|
|
|
69
74
|
}
|
|
70
75
|
// Local mode is SDK-first and does not require MCP credentials.
|
|
71
76
|
if (mode === "pr") {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
if (provider === "github") {
|
|
78
|
+
// GitHub provider: require a GitHub token instead of Bitbucket creds.
|
|
79
|
+
// Token resolution order mirrors MCPServerManager: GITHUB_TOKEN →
|
|
80
|
+
// GH_TOKEN → GITHUB_PERSONAL_ACCESS_TOKEN → GITHUB_ACCESS_TOKEN
|
|
81
|
+
// (GITHUB_ACCESS_TOKEN is the dedicated PAT used in Curator/DNV).
|
|
82
|
+
if (!process.env.GITHUB_TOKEN &&
|
|
83
|
+
!process.env.GH_TOKEN &&
|
|
84
|
+
!process.env.GITHUB_PERSONAL_ACCESS_TOKEN &&
|
|
85
|
+
!process.env.GITHUB_ACCESS_TOKEN) {
|
|
86
|
+
errors.push("GitHub provider selected but no GitHub token found. Set GITHUB_TOKEN (or GH_TOKEN / GITHUB_PERSONAL_ACCESS_TOKEN / GITHUB_ACCESS_TOKEN).");
|
|
87
|
+
}
|
|
78
88
|
}
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
else {
|
|
90
|
+
// Bitbucket provider (default): require Bitbucket credentials.
|
|
91
|
+
if (!process.env.BITBUCKET_USERNAME) {
|
|
92
|
+
errors.push("BITBUCKET_USERNAME environment variable not set");
|
|
93
|
+
}
|
|
94
|
+
if (!process.env.BITBUCKET_TOKEN) {
|
|
95
|
+
errors.push("BITBUCKET_TOKEN environment variable not set");
|
|
96
|
+
}
|
|
97
|
+
if (!process.env.BITBUCKET_BASE_URL) {
|
|
98
|
+
errors.push("BITBUCKET_BASE_URL environment variable not set");
|
|
99
|
+
}
|
|
81
100
|
}
|
|
82
101
|
if (this.config.mcpServers.jira.enabled) {
|
|
83
102
|
if (!process.env.JIRA_EMAIL) {
|
|
@@ -45,6 +45,19 @@ export class DefaultConfig {
|
|
|
45
45
|
bitbucket: {
|
|
46
46
|
blockedTools: [],
|
|
47
47
|
},
|
|
48
|
+
github: {
|
|
49
|
+
enabled: true,
|
|
50
|
+
url: "https://api.githubcopilot.com/mcp/",
|
|
51
|
+
transport: "http",
|
|
52
|
+
blockedTools: [
|
|
53
|
+
"push_files",
|
|
54
|
+
"create_or_update_file",
|
|
55
|
+
"create_branch",
|
|
56
|
+
"delete_file",
|
|
57
|
+
"create_pull_request_with_copilot",
|
|
58
|
+
"assign_copilot_to_issue",
|
|
59
|
+
],
|
|
60
|
+
},
|
|
48
61
|
jira: {
|
|
49
62
|
enabled: false, // Opt-in: users must explicitly enable Jira integration
|
|
50
63
|
blockedTools: [],
|
|
@@ -9,6 +9,7 @@ import { ConfigLoader } from "../config/ConfigLoader.js";
|
|
|
9
9
|
import { LangfusePromptManager } from "../prompts/LangfusePromptManager.js";
|
|
10
10
|
import { KnowledgeBaseManager } from "../learning/KnowledgeBaseManager.js";
|
|
11
11
|
import { MemoryManager } from "../memory/MemoryManager.js";
|
|
12
|
+
import { getProviderToolset, } from "../providers/ProviderToolset.js";
|
|
12
13
|
import { buildObservabilityConfigFromEnv, validateObservabilityConfig, } from "../utils/ObservabilityConfig.js";
|
|
13
14
|
export class LearningOrchestrator {
|
|
14
15
|
neurolink;
|
|
@@ -157,7 +158,7 @@ export class LearningOrchestrator {
|
|
|
157
158
|
for (const [category, items] of byCategory) {
|
|
158
159
|
parts.push(`${category}: ${items.join("; ")}`);
|
|
159
160
|
}
|
|
160
|
-
const ownerId = MemoryManager.buildOwnerId(request.workspace, request.repository);
|
|
161
|
+
const ownerId = MemoryManager.buildOwnerId(request.workspace, request.repository, request.provider || "bitbucket");
|
|
161
162
|
await this.neurolink.generate({
|
|
162
163
|
input: { text: parts.join(". ") },
|
|
163
164
|
provider: this.config.ai.provider,
|
|
@@ -228,17 +229,32 @@ export class LearningOrchestrator {
|
|
|
228
229
|
*/
|
|
229
230
|
buildFetchCommentsInstructions(request) {
|
|
230
231
|
const aiPatterns = this.config.knowledgeBase.aiAuthorPatterns.join(", ");
|
|
232
|
+
// Derive the PR-read tool name and identifier format from the provider
|
|
233
|
+
// toolset so a GitHub learn run asks for the tool it actually has.
|
|
234
|
+
// The learn flow's only provider signal is request.provider; default to
|
|
235
|
+
// bitbucket to preserve existing behavior.
|
|
236
|
+
const provider = request.provider === "github" ? "github" : "bitbucket";
|
|
237
|
+
const toolset = getProviderToolset(provider);
|
|
238
|
+
const prReadTool = toolset.prReadToolName;
|
|
239
|
+
// Bitbucket: workspace/repository/pull_request_id (byte-identical to before).
|
|
240
|
+
// GitHub: owner/repo/pull_number — the same repository identifiers carried
|
|
241
|
+
// by the Bitbucket-shaped request, relabelled to GitHub's vocabulary.
|
|
242
|
+
const prDetails = provider === "github"
|
|
243
|
+
? ` <owner>${request.workspace}</owner>
|
|
244
|
+
<repo>${request.repository}</repo>
|
|
245
|
+
<pull_number>${request.pullRequestId}</pull_number>`
|
|
246
|
+
: ` <workspace>${request.workspace}</workspace>
|
|
247
|
+
<repository>${request.repository}</repository>
|
|
248
|
+
<pull_request_id>${request.pullRequestId}</pull_request_id>`;
|
|
231
249
|
return `
|
|
232
250
|
<task>Fetch and analyze ALL PR comments for learning extraction</task>
|
|
233
251
|
|
|
234
252
|
<pr-details>
|
|
235
|
-
|
|
236
|
-
<repository>${request.repository}</repository>
|
|
237
|
-
<pull_request_id>${request.pullRequestId}</pull_request_id>
|
|
253
|
+
${prDetails}
|
|
238
254
|
</pr-details>
|
|
239
255
|
|
|
240
256
|
<instructions>
|
|
241
|
-
1. Use
|
|
257
|
+
1. Use ${prReadTool} tool to fetch the PR details including all comments/activities
|
|
242
258
|
2. Look through ALL comments on the PR (both active and resolved if available)
|
|
243
259
|
3. Identify AI-generated comments by:
|
|
244
260
|
- Author patterns: ${aiPatterns}
|
|
@@ -6,10 +6,21 @@ import { MCPServersConfig } from "../types/config.types.js";
|
|
|
6
6
|
export declare class MCPServerManager {
|
|
7
7
|
private initialized;
|
|
8
8
|
/**
|
|
9
|
-
* Setup
|
|
10
|
-
*
|
|
9
|
+
* Setup MCP servers based on detected provider
|
|
10
|
+
* GitHub MCP OR Bitbucket MCP (not both) - whichever is needed
|
|
11
|
+
* Jira is optional for both
|
|
11
12
|
*/
|
|
12
|
-
setupMCPServers(neurolink: any, config: MCPServersConfig): Promise<void>;
|
|
13
|
+
setupMCPServers(neurolink: any, config: MCPServersConfig, provider?: "github" | "bitbucket"): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Reset PR-mode MCP setup so {@link setupMCPServers} can re-register for a
|
|
16
|
+
* different provider within the same process (e.g. one orchestrator instance
|
|
17
|
+
* reviewing a Bitbucket PR then a GitHub PR). Removes the previously-registered
|
|
18
|
+
* provider server (Bitbucket or GitHub) so the new provider's server takes its
|
|
19
|
+
* place, and clears the `initialized` guard. The optional Jira server is left
|
|
20
|
+
* in place — it is provider-agnostic and shared. Single-provider runs never
|
|
21
|
+
* call this, so their behaviour is unchanged.
|
|
22
|
+
*/
|
|
23
|
+
resetForProviderSwitch(neurolink: any, previousProvider: "github" | "bitbucket"): Promise<void>;
|
|
13
24
|
/**
|
|
14
25
|
* Setup local git MCP server for SDK/local mode.
|
|
15
26
|
* Mandatory in local mode.
|
|
@@ -22,6 +33,28 @@ export declare class MCPServerManager {
|
|
|
22
33
|
* Setup Bitbucket MCP server (hardcoded, always enabled)
|
|
23
34
|
*/
|
|
24
35
|
private setupBitbucketMCP;
|
|
36
|
+
/**
|
|
37
|
+
* Setup GitHub MCP server.
|
|
38
|
+
*
|
|
39
|
+
* Registers GitHub's hosted REMOTE HTTP MCP server via NeuroLink, mirroring
|
|
40
|
+
* Curator's proven pattern (transport: "http" + Bearer auth header). The old
|
|
41
|
+
* `npx @github/github-mcp-server` package does not exist, so stdio is only
|
|
42
|
+
* supported for an explicitly-configured self-hosted / Docker server.
|
|
43
|
+
*
|
|
44
|
+
* URL + transport are config-driven (`mcpServers.github.{url,transport,command,args}`),
|
|
45
|
+
* defaulting to the remote HTTP endpoint.
|
|
46
|
+
*/
|
|
47
|
+
private setupGitHubMCP;
|
|
48
|
+
/**
|
|
49
|
+
* Build the remote HTTP GitHub MCP registration (default path).
|
|
50
|
+
* A Bearer token is required for the hosted endpoint.
|
|
51
|
+
*/
|
|
52
|
+
private buildGitHubHttpConfig;
|
|
53
|
+
/**
|
|
54
|
+
* Build a self-hosted / Docker stdio GitHub MCP registration.
|
|
55
|
+
* Only used when `mcpServers.github.transport === "stdio"`; requires `command`.
|
|
56
|
+
*/
|
|
57
|
+
private buildGitHubStdioConfig;
|
|
25
58
|
/**
|
|
26
59
|
* Setup Jira MCP server (hardcoded, optionally enabled)
|
|
27
60
|
*/
|
|
@@ -4,22 +4,52 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { MCPServerError } from "../types/v2.types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Default remote HTTP endpoint for GitHub's hosted MCP server.
|
|
9
|
+
* Overridable via `mcpServers.github.url`.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_GITHUB_MCP_URL = "https://api.githubcopilot.com/mcp/";
|
|
12
|
+
/**
|
|
13
|
+
* Default write tools blocked on the GitHub MCP server.
|
|
14
|
+
* Mirrors Curator's proven pattern: the AI never mutates the repo directly —
|
|
15
|
+
* the single write path is the review-comment / review-submit tools, which are
|
|
16
|
+
* intentionally NOT blocked here.
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_GITHUB_BLOCKED_TOOLS = [
|
|
19
|
+
"push_files",
|
|
20
|
+
"create_or_update_file",
|
|
21
|
+
"create_branch",
|
|
22
|
+
"delete_file",
|
|
23
|
+
"create_pull_request_with_copilot",
|
|
24
|
+
"assign_copilot_to_issue",
|
|
25
|
+
];
|
|
7
26
|
export class MCPServerManager {
|
|
8
27
|
// MCP servers are managed entirely by NeuroLink
|
|
9
28
|
// No need to track tools locally
|
|
10
29
|
initialized = false;
|
|
11
30
|
/**
|
|
12
|
-
* Setup
|
|
13
|
-
*
|
|
31
|
+
* Setup MCP servers based on detected provider
|
|
32
|
+
* GitHub MCP OR Bitbucket MCP (not both) - whichever is needed
|
|
33
|
+
* Jira is optional for both
|
|
14
34
|
*/
|
|
15
|
-
async setupMCPServers(neurolink, config) {
|
|
35
|
+
async setupMCPServers(neurolink, config, provider = "bitbucket") {
|
|
16
36
|
if (this.initialized) {
|
|
17
37
|
return;
|
|
18
38
|
}
|
|
19
39
|
console.log("🔌 Setting up MCP servers...");
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
console.log(` 📍 Provider: ${provider}`);
|
|
41
|
+
// Setup provider-specific MCP (only one needed)
|
|
42
|
+
if (provider === "github") {
|
|
43
|
+
// Fail fast rather than passing an undefined/disabled config downstream.
|
|
44
|
+
if (!config.github || config.github.enabled === false) {
|
|
45
|
+
throw new MCPServerError("GitHub provider selected but mcpServers.github is not enabled");
|
|
46
|
+
}
|
|
47
|
+
await this.setupGitHubMCP(neurolink, config.github);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
await this.setupBitbucketMCP(neurolink, config.bitbucket?.blockedTools);
|
|
51
|
+
}
|
|
52
|
+
// Setup Jira MCP (optional, works with both providers)
|
|
23
53
|
if (config.jira.enabled) {
|
|
24
54
|
await this.setupJiraMCP(neurolink, config.jira.blockedTools);
|
|
25
55
|
}
|
|
@@ -30,6 +60,26 @@ export class MCPServerManager {
|
|
|
30
60
|
await this.logDiagnostics(neurolink);
|
|
31
61
|
console.log("✅ MCP servers configured\n");
|
|
32
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Reset PR-mode MCP setup so {@link setupMCPServers} can re-register for a
|
|
65
|
+
* different provider within the same process (e.g. one orchestrator instance
|
|
66
|
+
* reviewing a Bitbucket PR then a GitHub PR). Removes the previously-registered
|
|
67
|
+
* provider server (Bitbucket or GitHub) so the new provider's server takes its
|
|
68
|
+
* place, and clears the `initialized` guard. The optional Jira server is left
|
|
69
|
+
* in place — it is provider-agnostic and shared. Single-provider runs never
|
|
70
|
+
* call this, so their behaviour is unchanged.
|
|
71
|
+
*/
|
|
72
|
+
async resetForProviderSwitch(neurolink, previousProvider) {
|
|
73
|
+
const serverName = previousProvider === "github" ? "github" : "bitbucket";
|
|
74
|
+
try {
|
|
75
|
+
await neurolink.removeExternalMCPServer?.(serverName);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Non-fatal: if removal fails the subsequent re-register will surface a
|
|
79
|
+
// clearer error. Proceed to allow re-setup.
|
|
80
|
+
}
|
|
81
|
+
this.initialized = false;
|
|
82
|
+
}
|
|
33
83
|
/**
|
|
34
84
|
* Setup local git MCP server for SDK/local mode.
|
|
35
85
|
* Mandatory in local mode.
|
|
@@ -145,6 +195,123 @@ export class MCPServerManager {
|
|
|
145
195
|
throw new MCPServerError(`Failed to setup Bitbucket MCP server: ${error.message}`);
|
|
146
196
|
}
|
|
147
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Setup GitHub MCP server.
|
|
200
|
+
*
|
|
201
|
+
* Registers GitHub's hosted REMOTE HTTP MCP server via NeuroLink, mirroring
|
|
202
|
+
* Curator's proven pattern (transport: "http" + Bearer auth header). The old
|
|
203
|
+
* `npx @github/github-mcp-server` package does not exist, so stdio is only
|
|
204
|
+
* supported for an explicitly-configured self-hosted / Docker server.
|
|
205
|
+
*
|
|
206
|
+
* URL + transport are config-driven (`mcpServers.github.{url,transport,command,args}`),
|
|
207
|
+
* defaulting to the remote HTTP endpoint.
|
|
208
|
+
*/
|
|
209
|
+
async setupGitHubMCP(neurolink, githubConfig) {
|
|
210
|
+
try {
|
|
211
|
+
console.log(" 🔧 Registering GitHub MCP server...");
|
|
212
|
+
// Be robust to a possibly-undefined config: default URL + transport +
|
|
213
|
+
// blockedTools so registration still works if the caller passes nothing.
|
|
214
|
+
const transport = githubConfig?.transport ?? "http";
|
|
215
|
+
// ADDITIVE, not replacing: always enforce the default write-block denylist
|
|
216
|
+
// and union it with any user-provided entries, so a user-supplied
|
|
217
|
+
// `blockedTools` can only EXTEND the denylist — never un-block write tools
|
|
218
|
+
// like push_files / create_or_update_file.
|
|
219
|
+
const blockedTools = Array.from(new Set([
|
|
220
|
+
...DEFAULT_GITHUB_BLOCKED_TOOLS,
|
|
221
|
+
...(githubConfig?.blockedTools ?? []),
|
|
222
|
+
]));
|
|
223
|
+
// An explicit token is REQUIRED. gh CLI auth (hosts.yml) cannot supply a
|
|
224
|
+
// Bearer token for the remote HTTP transport, so we do not accept it as a
|
|
225
|
+
// substitute. The hosted endpoint (api.githubcopilot.com) expects a real
|
|
226
|
+
// GitHub PAT — the ephemeral Actions GITHUB_TOKEN may be rejected. Token
|
|
227
|
+
// resolution order mirrors Curator's proven setup (GITHUB_ACCESS_TOKEN):
|
|
228
|
+
// GITHUB_TOKEN → GH_TOKEN → GITHUB_PERSONAL_ACCESS_TOKEN → GITHUB_ACCESS_TOKEN.
|
|
229
|
+
const ghToken = process.env.GITHUB_TOKEN ||
|
|
230
|
+
process.env.GH_TOKEN ||
|
|
231
|
+
process.env.GITHUB_PERSONAL_ACCESS_TOKEN ||
|
|
232
|
+
process.env.GITHUB_ACCESS_TOKEN;
|
|
233
|
+
if (!ghToken) {
|
|
234
|
+
throw new MCPServerError("Missing GitHub authentication: set GITHUB_TOKEN (or GH_TOKEN / " +
|
|
235
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN / GITHUB_ACCESS_TOKEN). The remote HTTP " +
|
|
236
|
+
"transport requires an explicit GitHub PAT; 'gh auth login' alone is " +
|
|
237
|
+
"not sufficient.");
|
|
238
|
+
}
|
|
239
|
+
const config = transport === "stdio"
|
|
240
|
+
? this.buildGitHubStdioConfig(githubConfig, ghToken, blockedTools)
|
|
241
|
+
: this.buildGitHubHttpConfig(githubConfig, ghToken, blockedTools);
|
|
242
|
+
await neurolink.addExternalMCPServer("github", config);
|
|
243
|
+
// Verify the server actually connected — NeuroLink resolves the promise
|
|
244
|
+
// even on timeout, so we must check the status explicitly (same pattern
|
|
245
|
+
// as the Bitbucket path).
|
|
246
|
+
const servers = await neurolink.listMCPServers();
|
|
247
|
+
const ghServer = (servers || []).find((s) => s.name === "github");
|
|
248
|
+
if (!ghServer ||
|
|
249
|
+
ghServer.status !== "connected" ||
|
|
250
|
+
ghServer.tools?.length === 0) {
|
|
251
|
+
throw new MCPServerError(`GitHub MCP server registered but not connected (status: ${ghServer?.status ?? "unknown"}, tools: ${ghServer?.tools?.length ?? 0}). Possible startup timeout.`);
|
|
252
|
+
}
|
|
253
|
+
console.log(` ✅ GitHub MCP server registered and tools available (transport: ${transport})`);
|
|
254
|
+
if (transport === "http") {
|
|
255
|
+
console.log(` 🌐 Remote endpoint: ${config.url}`);
|
|
256
|
+
}
|
|
257
|
+
console.log(` 🚫 Blocked write tools: ${blockedTools.join(", ")}`);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
throw new MCPServerError(`Failed to setup GitHub MCP server: ${error.message}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Build the remote HTTP GitHub MCP registration (default path).
|
|
265
|
+
* A Bearer token is required for the hosted endpoint.
|
|
266
|
+
*/
|
|
267
|
+
buildGitHubHttpConfig(githubConfig, ghToken, blockedTools) {
|
|
268
|
+
if (!ghToken) {
|
|
269
|
+
throw new MCPServerError("GitHub remote HTTP MCP server requires a token. Set GITHUB_TOKEN " +
|
|
270
|
+
"(or GH_TOKEN / GITHUB_PERSONAL_ACCESS_TOKEN / GITHUB_ACCESS_TOKEN); " +
|
|
271
|
+
"'gh auth login' alone is not sufficient for the remote transport.");
|
|
272
|
+
}
|
|
273
|
+
// The hosted endpoint is slow to connect (~3-4s TLS handshake + remote auth);
|
|
274
|
+
// a generous timeout + retry/backoff prevents spurious "not connected"
|
|
275
|
+
// failures. Values mirror Curator's production GitHub MCP registration.
|
|
276
|
+
return {
|
|
277
|
+
transport: "http",
|
|
278
|
+
url: githubConfig?.url ?? DEFAULT_GITHUB_MCP_URL,
|
|
279
|
+
headers: {
|
|
280
|
+
Authorization: `Bearer ${ghToken}`,
|
|
281
|
+
},
|
|
282
|
+
blockedTools,
|
|
283
|
+
timeout: 30000,
|
|
284
|
+
retryConfig: {
|
|
285
|
+
maxAttempts: 3,
|
|
286
|
+
initialDelay: 1000,
|
|
287
|
+
maxDelay: 10000,
|
|
288
|
+
backoffMultiplier: 2,
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Build a self-hosted / Docker stdio GitHub MCP registration.
|
|
294
|
+
* Only used when `mcpServers.github.transport === "stdio"`; requires `command`.
|
|
295
|
+
*/
|
|
296
|
+
buildGitHubStdioConfig(githubConfig, ghToken, blockedTools) {
|
|
297
|
+
const command = githubConfig?.command;
|
|
298
|
+
if (!command) {
|
|
299
|
+
throw new MCPServerError("GitHub stdio transport requires 'mcpServers.github.command' " +
|
|
300
|
+
"(e.g. a self-hosted/Docker GitHub MCP server binary).");
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
transport: "stdio",
|
|
304
|
+
command,
|
|
305
|
+
args: githubConfig?.args ?? [],
|
|
306
|
+
env: ghToken
|
|
307
|
+
? {
|
|
308
|
+
GITHUB_PERSONAL_ACCESS_TOKEN: ghToken,
|
|
309
|
+
GITHUB_TOKEN: ghToken,
|
|
310
|
+
}
|
|
311
|
+
: undefined,
|
|
312
|
+
blockedTools,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
148
315
|
/**
|
|
149
316
|
* Setup Jira MCP server (hardcoded, optionally enabled)
|
|
150
317
|
*/
|
|
@@ -16,16 +16,18 @@ export declare class YamaOrchestrator {
|
|
|
16
16
|
private config;
|
|
17
17
|
private initialized;
|
|
18
18
|
private mcpInitialized;
|
|
19
|
+
private mcpProvider;
|
|
19
20
|
private localGitMcpInitialized;
|
|
20
21
|
private exploreToolRegistered;
|
|
21
22
|
private currentToolContext;
|
|
22
23
|
private initOptions;
|
|
23
24
|
private bootstrapStandardsCache;
|
|
25
|
+
private detectedProvider;
|
|
24
26
|
constructor(options?: YamaInitOptions);
|
|
25
27
|
/**
|
|
26
28
|
* Initialize Yama with configuration and MCP servers
|
|
27
29
|
*/
|
|
28
|
-
initialize(configPath?: string, mode?: ReviewMode): Promise<void>;
|
|
30
|
+
initialize(configPath?: string, mode?: ReviewMode, request?: ReviewRequest): Promise<void>;
|
|
29
31
|
/**
|
|
30
32
|
* Start autonomous AI review
|
|
31
33
|
*/
|
|
@@ -97,6 +99,23 @@ export declare class YamaOrchestrator {
|
|
|
97
99
|
* Calculate statistics from session
|
|
98
100
|
*/
|
|
99
101
|
private calculateStatistics;
|
|
102
|
+
/**
|
|
103
|
+
* Count the changed files actually reviewed, from the session tool calls.
|
|
104
|
+
*
|
|
105
|
+
* Bitbucket: get_pull_request_diff is called once per file by design, so the
|
|
106
|
+
* raw count of diff-tool calls already equals the files reviewed. This path is
|
|
107
|
+
* byte-for-byte identical to the previous behaviour.
|
|
108
|
+
*
|
|
109
|
+
* GitHub: pull_request_read is overloaded — the SAME tool name serves
|
|
110
|
+
* method="get" (PR shell), "get_files" (changed-file list) and "get_diff"
|
|
111
|
+
* (unified diff). Counting every pull_request_read call therefore inflates the
|
|
112
|
+
* number. Instead, count DISTINCT file paths the review actually touched, taken
|
|
113
|
+
* from the inline pending-review comments (add_comment_to_pending_review →
|
|
114
|
+
* args.path). Fall back to the number of distinct get_files/get_diff reads when
|
|
115
|
+
* no inline comments were posted (e.g. a clean approval), so a reviewed-but-
|
|
116
|
+
* clean PR is not reported as 0 files.
|
|
117
|
+
*/
|
|
118
|
+
private countFilesReviewed;
|
|
100
119
|
/**
|
|
101
120
|
* Extract issue counts from comment tool calls
|
|
102
121
|
*/
|