ralph-hero-mcp-server 2.4.34 → 2.4.36

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/dist/index.js CHANGED
@@ -63,6 +63,12 @@ function initGitHubClient() {
63
63
  const projectNumber = resolveEnv("RALPH_GH_PROJECT_NUMBER")
64
64
  ? parseInt(resolveEnv("RALPH_GH_PROJECT_NUMBER"), 10)
65
65
  : undefined;
66
+ const projectNumbers = resolveEnv("RALPH_GH_PROJECT_NUMBERS")
67
+ ? resolveEnv("RALPH_GH_PROJECT_NUMBERS")
68
+ .split(",")
69
+ .map((s) => parseInt(s.trim(), 10))
70
+ .filter((n) => !isNaN(n))
71
+ : undefined;
66
72
  if (!owner) {
67
73
  console.error("[ralph-hero] Warning: RALPH_GH_OWNER not set.\n" +
68
74
  "Most tools require this. Set in your environment or .claude/ralph-hero.local.md");
@@ -85,6 +91,7 @@ function initGitHubClient() {
85
91
  owner: owner || undefined,
86
92
  repo: repo || undefined,
87
93
  projectNumber,
94
+ projectNumbers,
88
95
  projectOwner: projectOwner || undefined,
89
96
  });
90
97
  }
package/dist/lib/cache.js CHANGED
@@ -72,84 +72,111 @@ export class SessionCache {
72
72
  return `query:${normalized}:${varsKey}`;
73
73
  }
74
74
  }
75
- // ---------------------------------------------------------------------------
76
- // Field Option Cache
77
- // ---------------------------------------------------------------------------
78
75
  /**
79
76
  * Maps field names to option names to option IDs for Projects V2
80
77
  * single-select fields. Populated by get_project, consumed by tools
81
78
  * that need to resolve human-readable names to GraphQL node IDs.
79
+ *
80
+ * Supports multiple projects keyed by project number. When projectNumber
81
+ * is omitted from method calls, the first populated project is used
82
+ * (backward compatible with single-project callers).
82
83
  */
83
84
  export class FieldOptionCache {
84
- /** fieldName -> optionName -> optionId */
85
- fields = new Map();
86
- /** fieldName -> fieldId */
87
- fieldIds = new Map();
88
- /** projectId for the cached fields */
89
- projectId;
85
+ projects = new Map();
86
+ /** Track the first populated project number for backward compat */
87
+ defaultProjectNumber;
90
88
  /**
91
89
  * Populate the cache from project field data.
92
90
  */
93
- populate(projectId, fields) {
94
- this.projectId = projectId;
95
- this.fields.clear();
96
- this.fieldIds.clear();
91
+ populate(projectNumber, projectId, fields) {
92
+ const fieldMap = new Map();
93
+ const fieldIdMap = new Map();
97
94
  for (const field of fields) {
98
- this.fieldIds.set(field.name, field.id);
95
+ fieldIdMap.set(field.name, field.id);
99
96
  if (field.options) {
100
97
  const optionMap = new Map();
101
98
  for (const option of field.options) {
102
99
  optionMap.set(option.name, option.id);
103
100
  }
104
- this.fields.set(field.name, optionMap);
101
+ fieldMap.set(field.name, optionMap);
105
102
  }
106
103
  }
104
+ this.projects.set(projectNumber, {
105
+ projectId,
106
+ fields: fieldMap,
107
+ fieldIds: fieldIdMap,
108
+ });
109
+ if (this.defaultProjectNumber === undefined) {
110
+ this.defaultProjectNumber = projectNumber;
111
+ }
107
112
  }
108
113
  /**
109
114
  * Resolve an option name to its ID for a given field.
110
115
  * Returns undefined if field or option not found.
111
116
  */
112
- resolveOptionId(fieldName, optionName) {
113
- return this.fields.get(fieldName)?.get(optionName);
117
+ resolveOptionId(fieldName, optionName, projectNumber) {
118
+ const entry = this.resolveEntry(projectNumber);
119
+ return entry?.fields.get(fieldName)?.get(optionName);
114
120
  }
115
121
  /**
116
122
  * Get the field ID for a field name.
117
123
  */
118
- getFieldId(fieldName) {
119
- return this.fieldIds.get(fieldName);
124
+ getFieldId(fieldName, projectNumber) {
125
+ const entry = this.resolveEntry(projectNumber);
126
+ return entry?.fieldIds.get(fieldName);
120
127
  }
121
128
  /**
122
129
  * Get the project ID for the cached fields.
123
130
  */
124
- getProjectId() {
125
- return this.projectId;
131
+ getProjectId(projectNumber) {
132
+ const entry = this.resolveEntry(projectNumber);
133
+ return entry?.projectId;
126
134
  }
127
135
  /**
128
136
  * Check if the cache has been populated.
137
+ * When projectNumber is provided, checks that specific project.
138
+ * When omitted, checks if any project is populated.
129
139
  */
130
- isPopulated() {
131
- return this.fields.size > 0;
140
+ isPopulated(projectNumber) {
141
+ if (projectNumber !== undefined) {
142
+ return this.projects.has(projectNumber);
143
+ }
144
+ return this.projects.size > 0;
132
145
  }
133
146
  /**
134
147
  * Get all option names for a field.
135
148
  */
136
- getOptionNames(fieldName) {
137
- const optionMap = this.fields.get(fieldName);
149
+ getOptionNames(fieldName, projectNumber) {
150
+ const entry = this.resolveEntry(projectNumber);
151
+ const optionMap = entry?.fields.get(fieldName);
138
152
  return optionMap ? Array.from(optionMap.keys()) : [];
139
153
  }
140
154
  /**
141
155
  * Get all field names.
142
156
  */
143
- getFieldNames() {
144
- return Array.from(this.fieldIds.keys());
157
+ getFieldNames(projectNumber) {
158
+ const entry = this.resolveEntry(projectNumber);
159
+ return entry ? Array.from(entry.fieldIds.keys()) : [];
145
160
  }
146
161
  /**
147
- * Clear the cache.
162
+ * Clear the cache (all projects).
148
163
  */
149
164
  clear() {
150
- this.fields.clear();
151
- this.fieldIds.clear();
152
- this.projectId = undefined;
165
+ this.projects.clear();
166
+ this.defaultProjectNumber = undefined;
167
+ }
168
+ /**
169
+ * Resolve the cache entry for a project number.
170
+ * Falls back to the first populated project when projectNumber is omitted.
171
+ */
172
+ resolveEntry(projectNumber) {
173
+ if (projectNumber !== undefined) {
174
+ return this.projects.get(projectNumber);
175
+ }
176
+ if (this.defaultProjectNumber !== undefined) {
177
+ return this.projects.get(this.defaultProjectNumber);
178
+ }
179
+ return undefined;
153
180
  }
154
181
  }
155
182
  //# sourceMappingURL=cache.js.map
@@ -50,14 +50,14 @@ async function fetchProjectForCache(client, owner, number) {
50
50
  // Helper: Ensure field option cache is populated
51
51
  // ---------------------------------------------------------------------------
52
52
  export async function ensureFieldCache(client, fieldCache, owner, projectNumber) {
53
- if (fieldCache.isPopulated())
53
+ if (fieldCache.isPopulated(projectNumber))
54
54
  return;
55
55
  // Fetch project to populate cache - try user first, then org
56
56
  const project = await fetchProjectForCache(client, owner, projectNumber);
57
57
  if (!project) {
58
58
  throw new Error(`Project #${projectNumber} not found for owner "${owner}"`);
59
59
  }
60
- fieldCache.populate(project.id, project.fields.nodes.map((f) => ({
60
+ fieldCache.populate(projectNumber, project.id, project.fields.nodes.map((f) => ({
61
61
  id: f.id,
62
62
  name: f.name,
63
63
  options: f.options,
@@ -269,9 +269,9 @@ export function resolveConfig(client, args) {
269
269
  // ---------------------------------------------------------------------------
270
270
  export function resolveFullConfig(client, args) {
271
271
  const { owner, repo } = resolveConfig(client, args);
272
- const projectNumber = client.config.projectNumber;
272
+ const projectNumber = args.projectNumber ?? client.config.projectNumber;
273
273
  if (!projectNumber) {
274
- throw new Error("projectNumber is required (set RALPH_GH_PROJECT_NUMBER env var)");
274
+ throw new Error("projectNumber is required (set RALPH_GH_PROJECT_NUMBER env var or pass explicitly)");
275
275
  }
276
276
  const projectOwner = resolveProjectOwner(client.config);
277
277
  if (!projectOwner) {
@@ -8,19 +8,19 @@
8
8
  import { z } from "zod";
9
9
  import { paginateConnection } from "../lib/pagination.js";
10
10
  import { buildDashboard, formatMarkdown, formatAscii, DEFAULT_HEALTH_CONFIG, } from "../lib/dashboard.js";
11
- import { toolSuccess, toolError, resolveProjectOwner } from "../types.js";
11
+ import { toolSuccess, toolError, resolveProjectOwner, resolveProjectNumbers } from "../types.js";
12
12
  import { calculateMetrics, DEFAULT_METRICS_CONFIG, } from "../lib/metrics.js";
13
13
  // ---------------------------------------------------------------------------
14
14
  // Helper: Ensure field option cache is populated
15
15
  // ---------------------------------------------------------------------------
16
16
  async function ensureFieldCache(client, fieldCache, owner, projectNumber) {
17
- if (fieldCache.isPopulated())
17
+ if (fieldCache.isPopulated(projectNumber))
18
18
  return;
19
19
  const project = await fetchProjectForCache(client, owner, projectNumber);
20
20
  if (!project) {
21
21
  throw new Error(`Project #${projectNumber} not found for owner "${owner}"`);
22
22
  }
23
- fieldCache.populate(project.id, project.fields.nodes.map((f) => ({
23
+ fieldCache.populate(projectNumber, project.id, project.fields.nodes.map((f) => ({
24
24
  id: f.id,
25
25
  name: f.name,
26
26
  options: f.options,
@@ -69,8 +69,10 @@ function getFieldValue(item, fieldName) {
69
69
  }
70
70
  /**
71
71
  * Convert raw GraphQL project items to DashboardItem[].
72
+ * When projectNumber/projectTitle are provided, they are set on each item
73
+ * for multi-project dashboard support.
72
74
  */
73
- export function toDashboardItems(raw) {
75
+ export function toDashboardItems(raw, projectNumber, projectTitle) {
74
76
  const items = [];
75
77
  for (const r of raw) {
76
78
  // Only include issues (not PRs or drafts)
@@ -88,6 +90,8 @@ export function toDashboardItems(raw) {
88
90
  estimate: getFieldValue(r, "Estimate"),
89
91
  assignees: r.content.assignees?.nodes?.map((a) => a.login) ?? [],
90
92
  blockedBy: [], // blockedBy requires separate queries; omit for now
93
+ ...(projectNumber !== undefined ? { projectNumber } : {}),
94
+ ...(projectTitle !== undefined ? { projectTitle } : {}),
91
95
  });
92
96
  }
93
97
  return items;
@@ -148,6 +152,10 @@ export function registerDashboardTools(server, client, fieldCache) {
148
152
  .string()
149
153
  .optional()
150
154
  .describe("GitHub owner. Defaults to GITHUB_OWNER env var"),
155
+ projectNumbers: z
156
+ .array(z.coerce.number())
157
+ .optional()
158
+ .describe("Project numbers to include. Defaults to RALPH_GH_PROJECT_NUMBERS or single configured project."),
151
159
  format: z
152
160
  .enum(["json", "markdown", "ascii"])
153
161
  .optional()
@@ -205,23 +213,45 @@ export function registerDashboardTools(server, client, fieldCache) {
205
213
  }, async (args) => {
206
214
  try {
207
215
  const owner = args.owner || resolveProjectOwner(client.config);
208
- const projectNumber = client.config.projectNumber;
209
216
  if (!owner) {
210
217
  return toolError("owner is required");
211
218
  }
212
- if (!projectNumber) {
213
- return toolError("project number is required");
219
+ // Resolve project numbers
220
+ const projectNumbers = args.projectNumbers
221
+ ?? resolveProjectNumbers(client.config);
222
+ if (projectNumbers.length === 0) {
223
+ return toolError("No project numbers configured. Set RALPH_GH_PROJECT_NUMBER or RALPH_GH_PROJECT_NUMBERS.");
214
224
  }
215
- // Ensure field cache
216
- await ensureFieldCache(client, fieldCache, owner, projectNumber);
217
- const projectId = fieldCache.getProjectId();
218
- if (!projectId) {
219
- return toolError("Could not resolve project ID");
225
+ // Fetch items from all projects
226
+ const allItems = [];
227
+ const fetchWarnings = [];
228
+ for (const pn of projectNumbers) {
229
+ try {
230
+ await ensureFieldCache(client, fieldCache, owner, pn);
231
+ }
232
+ catch (e) {
233
+ fetchWarnings.push(`Project #${pn}: ${e instanceof Error ? e.message : String(e)}, skipping`);
234
+ continue;
235
+ }
236
+ const projectId = fieldCache.getProjectId(pn);
237
+ if (!projectId) {
238
+ fetchWarnings.push(`Project #${pn}: could not resolve project ID, skipping`);
239
+ continue;
240
+ }
241
+ // Fetch project title
242
+ let projectTitle;
243
+ try {
244
+ const titleResult = await client.projectQuery(`query($projectId: ID!) { node(id: $projectId) { ... on ProjectV2 { title } } }`, { projectId });
245
+ projectTitle = titleResult.node?.title;
246
+ }
247
+ catch {
248
+ // Non-fatal -- proceed without title
249
+ }
250
+ // Fetch items
251
+ const result = await paginateConnection((q, v) => client.projectQuery(q, v), DASHBOARD_ITEMS_QUERY, { projectId, first: 100 }, "node.items", { maxItems: 500 });
252
+ const items = toDashboardItems(result.nodes, pn, projectTitle);
253
+ allItems.push(...items);
220
254
  }
221
- // Fetch all project items
222
- const result = await paginateConnection((q, v) => client.projectQuery(q, v), DASHBOARD_ITEMS_QUERY, { projectId, first: 100 }, "node.items", { maxItems: 500 });
223
- // Convert to dashboard items
224
- const dashboardItems = toDashboardItems(result.nodes);
225
255
  // Build health config
226
256
  const healthConfig = {
227
257
  ...DEFAULT_HEALTH_CONFIG,
@@ -231,8 +261,8 @@ export function registerDashboardTools(server, client, fieldCache) {
231
261
  doneWindowDays: args.doneWindowDays ?? 7,
232
262
  archiveThresholdDays: args.archiveThresholdDays ?? 14,
233
263
  };
234
- // Build dashboard
235
- const dashboard = buildDashboard(dashboardItems, healthConfig);
264
+ // Build dashboard from merged items
265
+ const dashboard = buildDashboard(allItems, healthConfig);
236
266
  // Strip health if not requested
237
267
  if (!args.includeHealth) {
238
268
  dashboard.health = { ok: true, warnings: [] };
@@ -251,7 +281,7 @@ export function registerDashboardTools(server, client, fieldCache) {
251
281
  atRiskThreshold: args.atRiskThreshold ?? 2,
252
282
  offTrackThreshold: args.offTrackThreshold ?? 6,
253
283
  };
254
- metrics = calculateMetrics(dashboardItems, dashboard, metricsConfig);
284
+ metrics = calculateMetrics(allItems, dashboard, metricsConfig);
255
285
  }
256
286
  // Format output
257
287
  const format = args.format ?? "json";
@@ -266,6 +296,7 @@ export function registerDashboardTools(server, client, fieldCache) {
266
296
  ...dashboard,
267
297
  ...(formatted !== undefined ? { formatted } : {}),
268
298
  ...(metrics !== undefined ? { metrics } : {}),
299
+ ...(fetchWarnings.length > 0 ? { fetchWarnings } : {}),
269
300
  });
270
301
  }
271
302
  catch (error) {
@@ -81,16 +81,16 @@ const ESTIMATE_OPTIONS = [
81
81
  // Helper: Ensure field option cache is populated
82
82
  // ---------------------------------------------------------------------------
83
83
  async function ensureFieldCache(client, fieldCache, owner, projectNumber) {
84
- if (fieldCache.isPopulated())
84
+ if (fieldCache.isPopulated(projectNumber))
85
85
  return;
86
86
  const project = await fetchProject(client, owner, projectNumber);
87
87
  if (!project) {
88
88
  throw new Error(`Project #${projectNumber} not found for owner "${owner}"`);
89
89
  }
90
- populateFieldCache(fieldCache, project);
90
+ populateFieldCache(fieldCache, project, projectNumber);
91
91
  }
92
- function populateFieldCache(fieldCache, project) {
93
- fieldCache.populate(project.id, project.fields.nodes.map((f) => ({
92
+ function populateFieldCache(fieldCache, project, projectNumber) {
93
+ fieldCache.populate(projectNumber, project.id, project.fields.nodes.map((f) => ({
94
94
  id: f.id,
95
95
  name: f.name,
96
96
  options: f.options,
@@ -205,7 +205,7 @@ export function registerProjectTools(server, client, fieldCache) {
205
205
  return toolError(`Project #${number} not found for owner "${owner}"`);
206
206
  }
207
207
  // Populate field cache
208
- populateFieldCache(fieldCache, result);
208
+ populateFieldCache(fieldCache, result, number);
209
209
  // Format response
210
210
  const fields = result.fields.nodes.map((f) => ({
211
211
  id: f.id,
@@ -325,7 +325,7 @@ export function registerRelationshipTools(server, client, fieldCache) {
325
325
  // -------------------------------------------------------------------------
326
326
  // ralph_hero__advance_children
327
327
  // -------------------------------------------------------------------------
328
- server.tool("ralph_hero__advance_children", "Advance all child/sub-issues of a parent to match the parent's new state. Only advances children that are in earlier workflow states. Returns what changed, what was skipped, and any errors.", {
328
+ server.tool("ralph_hero__advance_children", "Advance issues to a target workflow state. Provide either 'number' (parent issue, advances sub-issues) or 'issues' (explicit list of issue numbers). Only advances issues in earlier workflow states. Returns what changed, what was skipped, and any errors.", {
329
329
  owner: z
330
330
  .string()
331
331
  .optional()
@@ -334,12 +334,24 @@ export function registerRelationshipTools(server, client, fieldCache) {
334
334
  .string()
335
335
  .optional()
336
336
  .describe("Repository name. Defaults to GITHUB_REPO env var"),
337
- number: z.coerce.number().describe("Parent issue number"),
337
+ number: z
338
+ .coerce.number()
339
+ .optional()
340
+ .describe("Parent issue number (resolves sub-issues automatically)"),
341
+ issues: z
342
+ .array(z.coerce.number())
343
+ .optional()
344
+ .describe("Explicit list of issue numbers to advance (alternative to parent number)"),
338
345
  targetState: z
339
346
  .string()
340
- .describe("State to advance children to (e.g., 'Research Needed', 'Ready for Plan')"),
347
+ .describe("State to advance issues to (e.g., 'Research Needed', 'Ready for Plan')"),
341
348
  }, async (args) => {
342
349
  try {
350
+ // Validate: at least one of number or issues must be provided
351
+ if (args.number === undefined && (!args.issues || args.issues.length === 0)) {
352
+ return toolError("Either 'number' (parent issue) or 'issues' (explicit list) is required. " +
353
+ "Recovery: provide one of these parameters.");
354
+ }
343
355
  // Validate target state
344
356
  if (!isValidState(args.targetState)) {
345
357
  return toolError(`Unknown target state '${args.targetState}'. ` +
@@ -358,24 +370,32 @@ export function registerRelationshipTools(server, client, fieldCache) {
358
370
  }
359
371
  // Ensure field cache is populated
360
372
  await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
361
- // Fetch sub-issues
362
- const result = await client.query(`query($owner: String!, $repo: String!, $number: Int!) {
363
- repository(owner: $owner, name: $repo) {
364
- issue(number: $number) {
365
- number
366
- title
367
- subIssues(first: 50) {
368
- nodes { id number title state }
373
+ // Build issue list: from explicit `issues` param or from parent's sub-issues
374
+ let issueNumbers;
375
+ if (args.issues && args.issues.length > 0) {
376
+ // Explicit issue list takes precedence
377
+ issueNumbers = args.issues;
378
+ }
379
+ else {
380
+ // Fetch sub-issues from parent
381
+ const result = await client.query(`query($owner: String!, $repo: String!, $number: Int!) {
382
+ repository(owner: $owner, name: $repo) {
383
+ issue(number: $number) {
384
+ number
385
+ title
386
+ subIssues(first: 50) {
387
+ nodes { id number title state }
388
+ }
369
389
  }
370
390
  }
391
+ }`, { owner, repo, number: args.number });
392
+ const parentIssue = result.repository?.issue;
393
+ if (!parentIssue) {
394
+ return toolError(`Issue #${args.number} not found in ${owner}/${repo}`);
395
+ }
396
+ issueNumbers = parentIssue.subIssues.nodes.map((si) => si.number);
371
397
  }
372
- }`, { owner, repo, number: args.number });
373
- const parentIssue = result.repository?.issue;
374
- if (!parentIssue) {
375
- return toolError(`Issue #${args.number} not found in ${owner}/${repo}`);
376
- }
377
- const subIssues = parentIssue.subIssues.nodes;
378
- if (subIssues.length === 0) {
398
+ if (issueNumbers.length === 0) {
379
399
  return toolSuccess({
380
400
  advanced: [],
381
401
  skipped: [],
@@ -385,22 +405,22 @@ export function registerRelationshipTools(server, client, fieldCache) {
385
405
  const advanced = [];
386
406
  const skipped = [];
387
407
  const errors = [];
388
- for (const child of subIssues) {
408
+ for (const issueNum of issueNumbers) {
389
409
  try {
390
410
  // Get current workflow state
391
- const currentState = await getCurrentFieldValue(client, fieldCache, owner, repo, child.number, "Workflow State");
411
+ const currentState = await getCurrentFieldValue(client, fieldCache, owner, repo, issueNum, "Workflow State");
392
412
  if (!currentState) {
393
413
  skipped.push({
394
- number: child.number,
414
+ number: issueNum,
395
415
  currentState: "unknown",
396
416
  reason: "No workflow state set on issue",
397
417
  });
398
418
  continue;
399
419
  }
400
- // Only advance if child is in an earlier state
420
+ // Only advance if issue is in an earlier state
401
421
  if (!isEarlierState(currentState, args.targetState)) {
402
422
  skipped.push({
403
- number: child.number,
423
+ number: issueNum,
404
424
  currentState,
405
425
  reason: currentState === args.targetState
406
426
  ? "Already at target state"
@@ -408,13 +428,13 @@ export function registerRelationshipTools(server, client, fieldCache) {
408
428
  });
409
429
  continue;
410
430
  }
411
- // Advance the child
412
- const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, child.number);
431
+ // Advance the issue
432
+ const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, issueNum);
413
433
  await updateProjectItemField(client, fieldCache, projectItemId, "Workflow State", args.targetState);
414
434
  // Sync default Status field (best-effort, one-way)
415
435
  await syncStatusField(client, fieldCache, projectItemId, args.targetState);
416
436
  advanced.push({
417
- number: child.number,
437
+ number: issueNum,
418
438
  fromState: currentState,
419
439
  toState: args.targetState,
420
440
  });
@@ -422,8 +442,8 @@ export function registerRelationshipTools(server, client, fieldCache) {
422
442
  catch (error) {
423
443
  const message = error instanceof Error ? error.message : String(error);
424
444
  errors.push({
425
- number: child.number,
426
- error: `Failed to update: ${message}. Recovery: retry advance_children or update this child manually via update_workflow_state.`,
445
+ number: issueNum,
446
+ error: `Failed to update: ${message}. Recovery: retry advance_children or update this issue manually.`,
427
447
  });
428
448
  }
429
449
  }
@@ -143,7 +143,7 @@ export function registerViewTools(server, client, fieldCache) {
143
143
  });
144
144
  }
145
145
  async function ensureFieldCache(client, fieldCache, owner, projectNumber) {
146
- if (fieldCache.isPopulated())
146
+ if (fieldCache.isPopulated(projectNumber))
147
147
  return;
148
148
  const QUERY = `
149
149
  query($owner: String!, $number: Int!) {
@@ -171,7 +171,7 @@ async function ensureFieldCache(client, fieldCache, owner, projectNumber) {
171
171
  const result = await client.projectQuery(QUERY.replace("OWNER_TYPE", ownerType), { owner, number: projectNumber }, { cache: true });
172
172
  const project = result[ownerType]?.projectV2;
173
173
  if (project) {
174
- fieldCache.populate(project.id, project.fields.nodes.map((f) => ({
174
+ fieldCache.populate(projectNumber, project.id, project.fields.nodes.map((f) => ({
175
175
  id: f.id,
176
176
  name: f.name,
177
177
  options: f.options,
package/dist/types.js CHANGED
@@ -18,4 +18,15 @@ export function toolError(message) {
18
18
  export function resolveProjectOwner(config) {
19
19
  return config.projectOwner || config.owner;
20
20
  }
21
+ /**
22
+ * Return all configured project numbers.
23
+ * Prefers projectNumbers array; falls back to single projectNumber.
24
+ */
25
+ export function resolveProjectNumbers(config) {
26
+ if (config.projectNumbers?.length)
27
+ return config.projectNumbers;
28
+ if (config.projectNumber)
29
+ return [config.projectNumber];
30
+ return [];
31
+ }
21
32
  //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-hero-mcp-server",
3
- "version": "2.4.34",
3
+ "version": "2.4.36",
4
4
  "description": "MCP server for GitHub Projects V2 - Ralph workflow automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",