ralph-hero-mcp-server 2.4.28 → 2.4.31
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.
|
@@ -20,6 +20,7 @@ const SEMANTIC_INTENTS = {
|
|
|
20
20
|
ralph_plan: "Plan in Review",
|
|
21
21
|
ralph_impl: "In Review",
|
|
22
22
|
ralph_review: "In Progress",
|
|
23
|
+
ralph_merge: "Done",
|
|
23
24
|
},
|
|
24
25
|
__ESCALATE__: { "*": "Human Needed" },
|
|
25
26
|
__CLOSE__: { "*": "Done" },
|
|
@@ -40,6 +41,7 @@ const COMMAND_ALLOWED_STATES = {
|
|
|
40
41
|
ralph_impl: ["In Progress", "In Review", "Human Needed"],
|
|
41
42
|
ralph_review: ["In Progress", "Ready for Plan", "Human Needed"],
|
|
42
43
|
ralph_hero: ["In Review", "Human Needed"],
|
|
44
|
+
ralph_merge: ["Done", "Human Needed"],
|
|
43
45
|
};
|
|
44
46
|
// --- Helpers ---
|
|
45
47
|
function isSemanticIntent(state) {
|
|
@@ -231,6 +231,197 @@ export function registerProjectTools(server, client, fieldCache) {
|
|
|
231
231
|
}
|
|
232
232
|
});
|
|
233
233
|
// -------------------------------------------------------------------------
|
|
234
|
+
// ralph_hero__list_projects
|
|
235
|
+
// -------------------------------------------------------------------------
|
|
236
|
+
server.tool("ralph_hero__list_projects", "List all GitHub Projects V2 for an owner (user or organization). Returns project summaries with item/field/view counts. Supports open/closed filtering.", {
|
|
237
|
+
owner: z
|
|
238
|
+
.string()
|
|
239
|
+
.optional()
|
|
240
|
+
.describe("GitHub owner (user or org). Defaults to RALPH_GH_PROJECT_OWNER or RALPH_GH_OWNER env var"),
|
|
241
|
+
state: z
|
|
242
|
+
.enum(["open", "closed", "all"])
|
|
243
|
+
.optional()
|
|
244
|
+
.default("open")
|
|
245
|
+
.describe('Filter by project state: "open" (default), "closed", or "all"'),
|
|
246
|
+
limit: z
|
|
247
|
+
.number()
|
|
248
|
+
.optional()
|
|
249
|
+
.default(50)
|
|
250
|
+
.describe("Maximum number of projects to return (default: 50, max: 100)"),
|
|
251
|
+
}, async (args) => {
|
|
252
|
+
try {
|
|
253
|
+
const owner = args.owner || resolveProjectOwner(client.config);
|
|
254
|
+
if (!owner) {
|
|
255
|
+
return toolError("owner is required (set RALPH_GH_PROJECT_OWNER or RALPH_GH_OWNER env var or pass explicitly)");
|
|
256
|
+
}
|
|
257
|
+
const maxItems = Math.min(args.limit ?? 50, 100);
|
|
258
|
+
const LIST_PROJECTS_QUERY = `
|
|
259
|
+
query($owner: String!, $cursor: String, $first: Int!) {
|
|
260
|
+
OWNER_TYPE(login: $owner) {
|
|
261
|
+
projectsV2(first: $first, after: $cursor, orderBy: {field: TITLE, direction: ASC}) {
|
|
262
|
+
totalCount
|
|
263
|
+
pageInfo { hasNextPage endCursor }
|
|
264
|
+
nodes {
|
|
265
|
+
id
|
|
266
|
+
number
|
|
267
|
+
title
|
|
268
|
+
shortDescription
|
|
269
|
+
public
|
|
270
|
+
closed
|
|
271
|
+
url
|
|
272
|
+
items { totalCount }
|
|
273
|
+
fields { totalCount }
|
|
274
|
+
views { totalCount }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
`;
|
|
280
|
+
let allProjects = [];
|
|
281
|
+
let totalCount;
|
|
282
|
+
for (const ownerType of ["user", "organization"]) {
|
|
283
|
+
try {
|
|
284
|
+
const result = await paginateConnection((q, vars) => client.projectQuery(q, vars), LIST_PROJECTS_QUERY.replace("OWNER_TYPE", ownerType), { owner }, `${ownerType}.projectsV2`, { maxItems });
|
|
285
|
+
allProjects = result.nodes;
|
|
286
|
+
totalCount = result.totalCount;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Try next owner type
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Client-side state filtering
|
|
294
|
+
const filtered = args.state === "all"
|
|
295
|
+
? allProjects
|
|
296
|
+
: args.state === "closed"
|
|
297
|
+
? allProjects.filter((p) => p.closed)
|
|
298
|
+
: allProjects.filter((p) => !p.closed);
|
|
299
|
+
const projects = filtered.map((p) => ({
|
|
300
|
+
id: p.id,
|
|
301
|
+
number: p.number,
|
|
302
|
+
title: p.title,
|
|
303
|
+
shortDescription: p.shortDescription,
|
|
304
|
+
public: p.public,
|
|
305
|
+
closed: p.closed,
|
|
306
|
+
url: p.url,
|
|
307
|
+
itemCount: p.items?.totalCount ?? 0,
|
|
308
|
+
fieldCount: p.fields?.totalCount ?? 0,
|
|
309
|
+
viewCount: p.views?.totalCount ?? 0,
|
|
310
|
+
}));
|
|
311
|
+
return toolSuccess({
|
|
312
|
+
owner,
|
|
313
|
+
state: args.state,
|
|
314
|
+
projects,
|
|
315
|
+
totalCount: totalCount ?? projects.length,
|
|
316
|
+
returnedCount: projects.length,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
321
|
+
return toolError(`Failed to list projects: ${message}`);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
// -------------------------------------------------------------------------
|
|
325
|
+
// ralph_hero__copy_project
|
|
326
|
+
// -------------------------------------------------------------------------
|
|
327
|
+
server.tool("ralph_hero__copy_project", "Copy (duplicate) a GitHub Project V2, preserving views, custom fields, workflows, and insights. Does NOT copy items, collaborators, team links, or repository links.", {
|
|
328
|
+
sourceProjectNumber: z
|
|
329
|
+
.number()
|
|
330
|
+
.describe("Project number of the source project to copy"),
|
|
331
|
+
sourceOwner: z
|
|
332
|
+
.string()
|
|
333
|
+
.optional()
|
|
334
|
+
.describe("Owner of the source project. Defaults to RALPH_GH_PROJECT_OWNER or RALPH_GH_OWNER env var"),
|
|
335
|
+
title: z.string().describe("Title for the new project"),
|
|
336
|
+
targetOwner: z
|
|
337
|
+
.string()
|
|
338
|
+
.optional()
|
|
339
|
+
.describe("Owner for the new project. Defaults to sourceOwner. Supports cross-owner copy (e.g., personal to org)"),
|
|
340
|
+
includeDraftIssues: z
|
|
341
|
+
.boolean()
|
|
342
|
+
.optional()
|
|
343
|
+
.default(false)
|
|
344
|
+
.describe("Include draft issues from the source project in the copy (default: false)"),
|
|
345
|
+
}, async (args) => {
|
|
346
|
+
try {
|
|
347
|
+
const sourceOwner = args.sourceOwner || resolveProjectOwner(client.config);
|
|
348
|
+
if (!sourceOwner) {
|
|
349
|
+
return toolError("sourceOwner is required (set RALPH_GH_PROJECT_OWNER or RALPH_GH_OWNER env var or pass explicitly)");
|
|
350
|
+
}
|
|
351
|
+
// Step 1: Resolve source project node ID via fetchProject
|
|
352
|
+
const sourceProject = await fetchProject(client, sourceOwner, args.sourceProjectNumber);
|
|
353
|
+
if (!sourceProject) {
|
|
354
|
+
return toolError(`Source project #${args.sourceProjectNumber} not found for owner "${sourceOwner}"`);
|
|
355
|
+
}
|
|
356
|
+
// Step 2: Resolve target owner node ID (try user first, then org)
|
|
357
|
+
const targetOwnerLogin = args.targetOwner || sourceOwner;
|
|
358
|
+
let targetOwnerId;
|
|
359
|
+
try {
|
|
360
|
+
const userResult = await client.query(`query($login: String!) {
|
|
361
|
+
user(login: $login) { id }
|
|
362
|
+
}`, { login: targetOwnerLogin }, { cache: true });
|
|
363
|
+
targetOwnerId = userResult.user?.id;
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// Not a user, try org below
|
|
367
|
+
}
|
|
368
|
+
if (!targetOwnerId) {
|
|
369
|
+
try {
|
|
370
|
+
const orgResult = await client.query(`query($login: String!) {
|
|
371
|
+
organization(login: $login) { id }
|
|
372
|
+
}`, { login: targetOwnerLogin }, { cache: true });
|
|
373
|
+
targetOwnerId = orgResult.organization?.id;
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// Not an org either
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (!targetOwnerId) {
|
|
380
|
+
return toolError(`Target owner "${targetOwnerLogin}" not found as user or organization`);
|
|
381
|
+
}
|
|
382
|
+
// Step 3: Execute copyProjectV2 mutation
|
|
383
|
+
const copyResult = await client.projectMutate(`mutation($projectId: ID!, $ownerId: ID!, $title: String!, $includeDraftIssues: Boolean!) {
|
|
384
|
+
copyProjectV2(input: {
|
|
385
|
+
projectId: $projectId
|
|
386
|
+
ownerId: $ownerId
|
|
387
|
+
title: $title
|
|
388
|
+
includeDraftIssues: $includeDraftIssues
|
|
389
|
+
}) {
|
|
390
|
+
projectV2 {
|
|
391
|
+
id
|
|
392
|
+
number
|
|
393
|
+
url
|
|
394
|
+
title
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}`, {
|
|
398
|
+
projectId: sourceProject.id,
|
|
399
|
+
ownerId: targetOwnerId,
|
|
400
|
+
title: args.title,
|
|
401
|
+
includeDraftIssues: args.includeDraftIssues ?? false,
|
|
402
|
+
});
|
|
403
|
+
const newProject = copyResult.copyProjectV2.projectV2;
|
|
404
|
+
return toolSuccess({
|
|
405
|
+
project: {
|
|
406
|
+
id: newProject.id,
|
|
407
|
+
number: newProject.number,
|
|
408
|
+
url: newProject.url,
|
|
409
|
+
title: newProject.title,
|
|
410
|
+
},
|
|
411
|
+
copiedFrom: {
|
|
412
|
+
number: args.sourceProjectNumber,
|
|
413
|
+
owner: sourceOwner,
|
|
414
|
+
title: sourceProject.title,
|
|
415
|
+
},
|
|
416
|
+
note: "Copied views, custom fields, workflows, and insights. Items, collaborators, team links, and repository links were NOT copied.",
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
421
|
+
return toolError(`Failed to copy project: ${message}`);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
// -------------------------------------------------------------------------
|
|
234
425
|
// ralph_hero__list_project_items
|
|
235
426
|
// -------------------------------------------------------------------------
|
|
236
427
|
server.tool("ralph_hero__list_project_items", "List items in a GitHub Project V2, optionally filtered by Workflow State, Estimate, or Priority", {
|