ralph-hero-mcp-server 2.4.108 → 2.4.109
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/lib/helpers.js +106 -1
- package/dist/lib/workflow-states.js +1 -0
- package/dist/tools/issue-tools.js +17 -2
- package/package.json +1 -1
package/dist/lib/helpers.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* implementations to their original versions.
|
|
7
7
|
*/
|
|
8
8
|
import { resolveProjectOwner } from "../types.js";
|
|
9
|
-
import { WORKFLOW_STATE_TO_STATUS } from "./workflow-states.js";
|
|
9
|
+
import { WORKFLOW_STATE_TO_STATUS, stateIndex } from "./workflow-states.js";
|
|
10
|
+
import { buildBatchResolveQuery, buildBatchFieldValueQuery, } from "../tools/batch-tools.js";
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Helper: Fetch project data for field cache population
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
@@ -338,4 +339,108 @@ export async function syncStatusField(client, fieldCache, projectItemId, workflo
|
|
|
338
339
|
// Best-effort sync - don't fail the primary operation
|
|
339
340
|
}
|
|
340
341
|
}
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
// Helper: Extract Workflow State from batch field value response
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
export function extractWorkflowState(item) {
|
|
346
|
+
const node = item?.fieldValues?.nodes?.find((fv) => fv.field?.name === "Workflow State" &&
|
|
347
|
+
fv.__typename === "ProjectV2ItemFieldSingleSelectValue");
|
|
348
|
+
return node?.name ?? null;
|
|
349
|
+
}
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
// Helper: Auto-advance parent when all siblings reach a gate state
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
/**
|
|
354
|
+
* Auto-advance the parent issue if all siblings have reached the same gate state.
|
|
355
|
+
* Uses batch queries for constant-time API cost (4-6 calls max regardless of sibling count).
|
|
356
|
+
* Best-effort: returns null on any failure without throwing.
|
|
357
|
+
*/
|
|
358
|
+
export async function autoAdvanceParent(client, fieldCache, owner, repo, issueNumber, gateState, projectNumber) {
|
|
359
|
+
try {
|
|
360
|
+
// Step A: Fetch parent number (1 query)
|
|
361
|
+
const parentResult = await client.query(`query($owner: String!, $repo: String!, $issueNum: Int!) {
|
|
362
|
+
repository(owner: $owner, name: $repo) {
|
|
363
|
+
issue(number: $issueNum) { parent { number } }
|
|
364
|
+
}
|
|
365
|
+
}`, { owner, repo, issueNum: issueNumber });
|
|
366
|
+
const parentNumber = parentResult.repository?.issue?.parent?.number;
|
|
367
|
+
if (!parentNumber)
|
|
368
|
+
return null;
|
|
369
|
+
// Step B: Fetch siblings (1 query)
|
|
370
|
+
const siblingResult = await client.query(`query($owner: String!, $repo: String!, $parentNum: Int!) {
|
|
371
|
+
repository(owner: $owner, name: $repo) {
|
|
372
|
+
issue(number: $parentNum) {
|
|
373
|
+
subIssues(first: 50) { nodes { number } }
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}`, { owner, repo, parentNum: parentNumber });
|
|
377
|
+
const siblings = siblingResult.repository?.issue?.subIssues?.nodes || [];
|
|
378
|
+
if (siblings.length === 0)
|
|
379
|
+
return { advanced: false, parentNumber };
|
|
380
|
+
// Step C: Batch resolve project item IDs (1 query)
|
|
381
|
+
const allNumbers = [...siblings.map((s) => s.number), parentNumber];
|
|
382
|
+
const { queryString: resolveQueryString, variables: resolveVars } = buildBatchResolveQuery(owner, repo, allNumbers);
|
|
383
|
+
const resolveResult = await client.query(resolveQueryString, resolveVars);
|
|
384
|
+
// Parse resolved IDs and cache them
|
|
385
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
386
|
+
if (!projectId)
|
|
387
|
+
return null;
|
|
388
|
+
const projectItemIds = [];
|
|
389
|
+
for (let i = 0; i < allNumbers.length; i++) {
|
|
390
|
+
const entry = resolveResult[`i${i}`];
|
|
391
|
+
const issue = entry?.issue;
|
|
392
|
+
if (!issue) {
|
|
393
|
+
projectItemIds.push(null);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
// Cache issue node ID
|
|
397
|
+
const issueCacheKey = `issue-node-id:${owner}/${repo}#${allNumbers[i]}`;
|
|
398
|
+
client.getCache().set(issueCacheKey, issue.id, 30 * 60 * 1000);
|
|
399
|
+
// Find matching project item
|
|
400
|
+
const projectItem = issue.projectItems?.nodes?.find((item) => item.project.id === projectId);
|
|
401
|
+
if (projectItem) {
|
|
402
|
+
const itemCacheKey = `project-item-id:${owner}/${repo}#${allNumbers[i]}`;
|
|
403
|
+
client.getCache().set(itemCacheKey, projectItem.id, 30 * 60 * 1000);
|
|
404
|
+
projectItemIds.push(projectItem.id);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
projectItemIds.push(null);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// All siblings + parent must have project item IDs
|
|
411
|
+
if (projectItemIds.some((id) => id === null)) {
|
|
412
|
+
return { advanced: false, parentNumber };
|
|
413
|
+
}
|
|
414
|
+
// Step D: Batch read field values (1 query)
|
|
415
|
+
const itemEntries = projectItemIds.map((id, i) => ({
|
|
416
|
+
alias: `fv${i}`,
|
|
417
|
+
itemId: id,
|
|
418
|
+
}));
|
|
419
|
+
const { queryString: fvQueryString, variables: fvVars } = buildBatchFieldValueQuery(itemEntries);
|
|
420
|
+
const fvResult = await client.query(fvQueryString, fvVars);
|
|
421
|
+
// Step E: Gate check (in-memory, zero cost)
|
|
422
|
+
const siblingCount = siblings.length;
|
|
423
|
+
const siblingStates = [];
|
|
424
|
+
for (let i = 0; i < siblingCount; i++) {
|
|
425
|
+
siblingStates.push(extractWorkflowState(fvResult[`fv${i}`]));
|
|
426
|
+
}
|
|
427
|
+
const allAtGate = siblingStates.every((state) => state === gateState);
|
|
428
|
+
if (!allAtGate)
|
|
429
|
+
return { advanced: false, parentNumber };
|
|
430
|
+
const parentIdx = allNumbers.length - 1;
|
|
431
|
+
const parentState = extractWorkflowState(fvResult[`fv${parentIdx}`]);
|
|
432
|
+
if (stateIndex(parentState || "") >= stateIndex(gateState)) {
|
|
433
|
+
return { advanced: false, parentNumber };
|
|
434
|
+
}
|
|
435
|
+
// Step F: Advance parent (1-2 mutations)
|
|
436
|
+
const parentItemId = projectItemIds[parentIdx];
|
|
437
|
+
await updateProjectItemField(client, fieldCache, parentItemId, "Workflow State", gateState, projectNumber);
|
|
438
|
+
await syncStatusField(client, fieldCache, parentItemId, gateState, projectNumber);
|
|
439
|
+
return { advanced: true, parentNumber, toState: gateState };
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
// Best-effort: don't fail the primary save_issue operation
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
341
446
|
//# sourceMappingURL=helpers.js.map
|
|
@@ -8,13 +8,13 @@ import { z } from "zod";
|
|
|
8
8
|
import { paginateConnection } from "../lib/pagination.js";
|
|
9
9
|
import { detectGroup } from "../lib/group-detection.js";
|
|
10
10
|
import { detectPipelinePosition, OVERSIZED_ESTIMATES, } from "../lib/pipeline-detection.js";
|
|
11
|
-
import { isValidState, VALID_STATES, LOCK_STATES, TERMINAL_STATES, WORKFLOW_STATE_TO_STATUS, } from "../lib/workflow-states.js";
|
|
11
|
+
import { isValidState, isParentGateState, VALID_STATES, LOCK_STATES, TERMINAL_STATES, WORKFLOW_STATE_TO_STATUS, } from "../lib/workflow-states.js";
|
|
12
12
|
import { buildBatchMutationQuery } from "./batch-tools.js";
|
|
13
13
|
import { resolveState } from "../lib/state-resolution.js";
|
|
14
14
|
import { parseDateMath } from "../lib/date-math.js";
|
|
15
15
|
import { expandProfile } from "../lib/filter-profiles.js";
|
|
16
16
|
import { toolSuccess, toolError } from "../types.js";
|
|
17
|
-
import { ensureFieldCache, resolveIssueNodeId, resolveProjectItemId, updateProjectItemField, resolveConfig, resolveFullConfig, resolveFullConfigOptionalRepo, syncStatusField, } from "../lib/helpers.js";
|
|
17
|
+
import { ensureFieldCache, resolveIssueNodeId, resolveProjectItemId, updateProjectItemField, resolveConfig, resolveFullConfig, resolveFullConfigOptionalRepo, syncStatusField, autoAdvanceParent, } from "../lib/helpers.js";
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
19
|
// Register issue tools
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
@@ -997,6 +997,21 @@ export function registerIssueTools(server, client, fieldCache) {
|
|
|
997
997
|
}
|
|
998
998
|
}`, { projectId, itemId: projectItemId, fieldId });
|
|
999
999
|
}
|
|
1000
|
+
// 4f. Auto-advance parent if we just moved to a gate state
|
|
1001
|
+
if (resolvedWorkflowState && isParentGateState(resolvedWorkflowState)) {
|
|
1002
|
+
try {
|
|
1003
|
+
const advanceResult = await autoAdvanceParent(client, fieldCache, owner, repo, args.number, resolvedWorkflowState, projectNumber);
|
|
1004
|
+
if (advanceResult?.advanced) {
|
|
1005
|
+
changes.parentAdvanced = {
|
|
1006
|
+
number: advanceResult.parentNumber,
|
|
1007
|
+
toState: advanceResult.toState,
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
catch {
|
|
1012
|
+
// Best-effort: don't fail the primary save_issue operation
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1000
1015
|
}
|
|
1001
1016
|
return toolSuccess({
|
|
1002
1017
|
number: args.number,
|