@juspay/yama 2.5.0 → 2.7.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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [2.7.0](https://github.com/juspay/yama/compare/v2.6.0...v2.7.0) (2026-06-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * **setup:** add one-command GitHub integration setup script ([6a014b0](https://github.com/juspay/yama/commit/6a014b05ccf12e4ca362316e5f69c9441a93618f))
7
+
8
+ # [2.6.0](https://github.com/juspay/yama/compare/v2.5.0...v2.6.0) (2026-06-04)
9
+
10
+
11
+ ### Features
12
+
13
+ * **action:** add Yama self-review workflow with LiteLLM/Vertex auth ([5ebb73f](https://github.com/juspay/yama/commit/5ebb73f2450951b5f97d97a6a3caa89377a150af))
14
+
1
15
  # [2.5.0](https://github.com/juspay/yama/compare/v2.4.2...v2.5.0) (2026-06-03)
2
16
 
3
17
 
@@ -45,6 +45,14 @@ export declare class MCPServerManager {
45
45
  * defaulting to the remote HTTP endpoint.
46
46
  */
47
47
  private setupGitHubMCP;
48
+ /**
49
+ * Poll {@link listMCPServers} until the named server reports "connected" with
50
+ * at least one tool, or the timeout elapses. Returns the last-seen server
51
+ * entry (possibly not-yet-connected) so the caller can decide how to proceed.
52
+ *
53
+ * Bounded by `timeoutMs` via the loop condition — it cannot run indefinitely.
54
+ */
55
+ private waitForMCPServer;
48
56
  /**
49
57
  * Build the remote HTTP GitHub MCP registration (default path).
50
58
  * A Bearer token is required for the hosted endpoint.
@@ -240,17 +240,26 @@ export class MCPServerManager {
240
240
  ? this.buildGitHubStdioConfig(githubConfig, ghToken, blockedTools)
241
241
  : this.buildGitHubHttpConfig(githubConfig, ghToken, blockedTools);
242
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) {
243
+ // The remote HTTP endpoint (api.githubcopilot.com) connects asynchronously
244
+ // TLS handshake + auth + tool discovery take ~3-4s, and NeuroLink may only
245
+ // finish discovery lazily on the first generate() call. Poll briefly for a
246
+ // healthy status, but for the HTTP transport DO NOT hard-fail if it is not
247
+ // yet "connected": the tools are resolved when the review runs (this mirrors
248
+ // Curator's proven pattern). For a local stdio server we keep the strict
249
+ // check, since a local process should connect promptly.
250
+ const ghServer = await this.waitForMCPServer(neurolink, "github", 12000);
251
+ const connected = ghServer?.status === "connected" && (ghServer?.tools?.length ?? 0) > 0;
252
+ if (transport === "stdio" && !connected) {
251
253
  throw new MCPServerError(`GitHub MCP server registered but not connected (status: ${ghServer?.status ?? "unknown"}, tools: ${ghServer?.tools?.length ?? 0}). Possible startup timeout.`);
252
254
  }
253
- console.log(` ✅ GitHub MCP server registered and tools available (transport: ${transport})`);
255
+ if (connected) {
256
+ console.log(` ✅ GitHub MCP server registered and tools available (transport: ${transport})`);
257
+ }
258
+ else {
259
+ console.warn(` ⚠️ GitHub MCP server registered but not yet reporting connected ` +
260
+ `(status: ${ghServer?.status ?? "unknown"}, tools: ${ghServer?.tools?.length ?? 0}). ` +
261
+ `Remote tools are discovered on first use; continuing.`);
262
+ }
254
263
  if (transport === "http") {
255
264
  console.log(` 🌐 Remote endpoint: ${config.url}`);
256
265
  }
@@ -260,6 +269,28 @@ export class MCPServerManager {
260
269
  throw new MCPServerError(`Failed to setup GitHub MCP server: ${error.message}`);
261
270
  }
262
271
  }
272
+ /**
273
+ * Poll {@link listMCPServers} until the named server reports "connected" with
274
+ * at least one tool, or the timeout elapses. Returns the last-seen server
275
+ * entry (possibly not-yet-connected) so the caller can decide how to proceed.
276
+ *
277
+ * Bounded by `timeoutMs` via the loop condition — it cannot run indefinitely.
278
+ */
279
+ async waitForMCPServer(neurolink, name, timeoutMs) {
280
+ const deadline = Date.now() + timeoutMs;
281
+ let last;
282
+ while (Date.now() < deadline) {
283
+ const servers = await neurolink
284
+ .listMCPServers()
285
+ .catch(() => []);
286
+ last = (servers || []).find((server) => server.name === name);
287
+ if (last?.status === "connected" && (last.tools?.length ?? 0) > 0) {
288
+ return last;
289
+ }
290
+ await new Promise((resolve) => setTimeout(resolve, 1000));
291
+ }
292
+ return last;
293
+ }
263
294
  /**
264
295
  * Build the remote HTTP GitHub MCP registration (default path).
265
296
  * A Bearer token is required for the hosted endpoint.
@@ -99,7 +99,7 @@ export class YamaOrchestrator {
99
99
  if (!this.mcpInitialized) {
100
100
  await this.mcpManager.setupMCPServers(this.neurolink, this.config.mcpServers, this.detectedProvider);
101
101
  if (this.explorer && this.config.ai.explore.enabled) {
102
- await this.explorer.initialize("pr");
102
+ await this.explorer.initialize("pr", this.detectedProvider);
103
103
  }
104
104
  this.mcpInitialized = true;
105
105
  this.mcpProvider = this.detectedProvider;
@@ -1,6 +1,7 @@
1
1
  import { YamaConfig } from "../types/config.types.js";
2
2
  import { SessionManager } from "../core/SessionManager.js";
3
3
  import { MemoryManager } from "../memory/MemoryManager.js";
4
+ import { type VCSProviderName } from "../providers/ProviderToolset.js";
4
5
  import { ExploreContextInput, ExploreExecutionResult, ExploreRuntimeContext } from "./types.js";
5
6
  export declare class ContextExplorerService {
6
7
  private readonly config;
@@ -14,7 +15,7 @@ export declare class ContextExplorerService {
14
15
  private prMcpInitialized;
15
16
  private localMcpInitialized;
16
17
  constructor(config: YamaConfig, sessionManager: SessionManager, memoryManager: MemoryManager | null, projectRoot?: string);
17
- initialize(mode: "pr" | "local"): Promise<void>;
18
+ initialize(mode: "pr" | "local", provider?: VCSProviderName): Promise<void>;
18
19
  explore(input: ExploreContextInput, runtimeContext: ExploreRuntimeContext): Promise<ExploreExecutionResult>;
19
20
  private normalizeInput;
20
21
  private buildCacheKey;
@@ -5,7 +5,7 @@ import { NeuroLink } from "@juspay/neurolink";
5
5
  import { MCPServerManager } from "../core/MCPServerManager.js";
6
6
  import { KnowledgeBaseManager } from "../learning/KnowledgeBaseManager.js";
7
7
  import { buildObservabilityConfigFromEnv, validateObservabilityConfig, } from "../utils/ObservabilityConfig.js";
8
- import { getProviderToolset } from "../providers/ProviderToolset.js";
8
+ import { getProviderToolset, } from "../providers/ProviderToolset.js";
9
9
  import { ExplorerPromptBuilder } from "./ExplorerPromptBuilder.js";
10
10
  import { RulesContextLoader } from "./RulesContextLoader.js";
11
11
  export class ContextExplorerService {
@@ -27,9 +27,12 @@ export class ContextExplorerService {
27
27
  this.rulesContextLoader = new RulesContextLoader(config.memoryBank, config.projectStandards, projectRoot);
28
28
  this.neurolink = this.initializeNeurolink();
29
29
  }
30
- async initialize(mode) {
30
+ async initialize(mode, provider = "bitbucket") {
31
31
  if (mode === "pr" && !this.prMcpInitialized) {
32
- await this.mcpManager.setupMCPServers(this.neurolink, this.config.mcpServers);
32
+ // The explore subagent gets its own MCP servers; they MUST match the
33
+ // detected provider, otherwise a GitHub run would try to start Bitbucket
34
+ // MCP (and fail on missing Bitbucket credentials).
35
+ await this.mcpManager.setupMCPServers(this.neurolink, this.config.mcpServers, provider);
33
36
  this.prMcpInitialized = true;
34
37
  return;
35
38
  }
@@ -39,7 +42,15 @@ export class ContextExplorerService {
39
42
  }
40
43
  }
41
44
  async explore(input, runtimeContext) {
42
- await this.initialize(runtimeContext.mode);
45
+ // Pass the runtime provider through when it is a known VCS provider;
46
+ // otherwise fall back to the read-only default. VCSProviderName is the
47
+ // single source of truth for supported providers, so adding one there is
48
+ // all that's needed for the explore subagent to honour it.
49
+ const explorerProvider = runtimeContext.provider === "github" ||
50
+ runtimeContext.provider === "bitbucket"
51
+ ? runtimeContext.provider
52
+ : "bitbucket";
53
+ await this.initialize(runtimeContext.mode, explorerProvider);
43
54
  const normalizedInput = this.normalizeInput(input);
44
55
  const cacheKey = this.buildCacheKey(normalizedInput, runtimeContext);
45
56
  if (this.config.ai.explore.cacheResults) {
@@ -255,7 +255,8 @@ class GitHubToolset {
255
255
  systemPromptToolsSection() {
256
256
  return ` <tool-usage>
257
257
  <tool name="pull_request_read">
258
- <use-when>Read PR state. method="get" once at the start for PR metadata and existing comments; method="get_files" to list changed files; method="get_diff" for the unified diff. Always pass owner, repo, pullNumber.</use-when>
258
+ <use-when>Read PR state by method: "get" for PR metadata; "get_review_comments" to load EXISTING inline review comments (each thread carries isResolved/isOutdated) so you never repeat an already-raised point; "get_reviews" for prior submitted reviews; "get_files" to list changed files; "get_diff" for the unified diff. Always pass owner, repo, pullNumber.</use-when>
259
+ <important>method="get" does NOT include review comments on GitHub — you MUST call method="get_review_comments" to see them.</important>
259
260
  </tool>
260
261
 
261
262
  <tool name="search_code">
@@ -305,11 +306,16 @@ class GitHubToolset {
305
306
  entry with severity=BLOCKING as a blocking criterion for this PR. If the block is
306
307
  missing or empty, fall back to <focus-areas> and <blocking-criteria>.
307
308
 
308
- STEP 2 — Read the PR shell
309
- Call pull_request_read(method="get", owner, repo, pullNumber) once to get PR
310
- metadata and existing comments, then pull_request_read(method="get_files", ...)
311
- to list the changed files. Build a mental map of which files exist and which
312
- already have comments. Do NOT request the full PR diff yet.
309
+ STEP 2 — Read the PR shell + EXISTING review comments
310
+ Call pull_request_read(method="get", owner, repo, pullNumber) for PR metadata,
311
+ then pull_request_read(method="get_review_comments", owner, repo, pullNumber) to
312
+ load existing inline review comments. (On GitHub, method="get" does NOT include
313
+ review comments you MUST call get_review_comments.) Build a map of which
314
+ file+line locations already have comments. Treat any point an existing comment
315
+ already raises as ALREADY HANDLED: do NOT post a duplicate, and do NOT re-raise it
316
+ in your decision — especially threads marked isResolved or isOutdated. Then call
317
+ pull_request_read(method="get_files", ...) to list the changed files. Do NOT
318
+ request the full PR diff yet.
313
319
 
314
320
  STEP 3 — Open ONE pending review
315
321
  Call pull_request_review_write(method="create", owner, repo, pullNumber) to open a
@@ -329,9 +335,13 @@ class GitHubToolset {
329
335
  e. Move to the next file. Never jump between files mid-review.
330
336
 
331
337
  STEP 5 — Decision (submit the review)
332
- After the last file, count issues by severity, apply <blocking-criteria>, and call
333
- pull_request_review_write(method="submit", owner, repo, pullNumber, body=&lt;summary&gt;)
334
- with event="APPROVE" when clean OR event="REQUEST_CHANGES" when blocking criteria are met.
338
+ Count ONLY the NEW issues you raised this run exclude any point already covered by
339
+ an existing review comment (from STEP 2), and exclude resolved/outdated threads. Apply
340
+ <blocking-criteria> to that NEW set only. A concern that was already raised and
341
+ answered/justified in an existing comment thread is NOT grounds to block again.
342
+ Then call pull_request_review_write(method="submit", owner, repo, pullNumber,
343
+ body=&lt;summary&gt;) with event="APPROVE" when there are no NEW blocking issues, or
344
+ event="REQUEST_CHANGES" only when NEW blocking criteria are met this run.
335
345
 
336
346
  STEP 6 — Summary comment
337
347
  Use the submit body above for the summary (file count, issue counts by severity, next
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/yama",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Enterprise-grade Pull Request automation toolkit with AI-powered code review and description enhancement",
5
5
  "keywords": [
6
6
  "pr",
@@ -160,7 +160,7 @@
160
160
  ],
161
161
  "overrides": {
162
162
  "@semantic-release/npm": "^13.1.2",
163
- "undici": "^5.28.5"
163
+ "undici": "~7.22.0"
164
164
  }
165
165
  },
166
166
  "lint-staged": {