ralph-hero-mcp-server 2.5.122 → 2.5.124
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/dashboard-fetch.js +1 -1
- package/dist/lib/pagination.js +44 -5
- package/dist/tools/dashboard-tools.js +1 -1
- package/dist/tools/directions-tools.js +2 -2
- package/dist/tools/hygiene-tools.js +1 -1
- package/dist/tools/issue-tools.js +2 -2
- package/dist/tools/relationship-tools.js +2 -2
- package/dist/tools/trends-tools.js +1 -1
- package/package.json +1 -1
|
@@ -186,7 +186,7 @@ export async function fetchDashboardItems(client, fieldCache, projectNumber) {
|
|
|
186
186
|
catch {
|
|
187
187
|
// Non-fatal — proceed without title.
|
|
188
188
|
}
|
|
189
|
-
const result = await paginateConnection((q, v) => client.projectQuery(q, v), DASHBOARD_ITEMS_QUERY, { projectId, first: 100 }, "node.items", {
|
|
189
|
+
const result = await paginateConnection((q, v) => client.projectQuery(q, v), DASHBOARD_ITEMS_QUERY, { projectId, first: 100 }, "node.items", { scanUntilExhausted: true });
|
|
190
190
|
items.push(...toDashboardItems(result.nodes, pn, projectTitle));
|
|
191
191
|
}
|
|
192
192
|
return { items, warnings };
|
package/dist/lib/pagination.js
CHANGED
|
@@ -26,19 +26,32 @@ function getNestedValue(obj, path) {
|
|
|
26
26
|
* @param variables - Variables for the query (cursor will be added/updated automatically)
|
|
27
27
|
* @param connectionPath - Dot-separated path to the connection in the response (e.g., "node.projectV2.items")
|
|
28
28
|
* @param options - Pagination options
|
|
29
|
-
* @returns All accumulated nodes from all pages
|
|
29
|
+
* @returns All accumulated nodes from all pages, plus a `truncated` flag.
|
|
30
30
|
*/
|
|
31
31
|
export async function paginateConnection(executeQuery, query, variables, connectionPath, options = {}) {
|
|
32
32
|
const pageSize = options.pageSize ?? 100;
|
|
33
33
|
const maxItems = options.maxItems ?? Infinity;
|
|
34
|
+
const scanUntilExhausted = options.scanUntilExhausted ?? false;
|
|
35
|
+
const until = options.until;
|
|
34
36
|
const allNodes = [];
|
|
35
37
|
let cursor = null;
|
|
36
38
|
let totalCount;
|
|
37
|
-
|
|
39
|
+
let truncated = false;
|
|
40
|
+
let untilStopped = false;
|
|
41
|
+
// When scanUntilExhausted is true, ignore the maxItems cap entirely.
|
|
42
|
+
// Otherwise, use maxItems as the upper bound.
|
|
43
|
+
const effectiveCap = scanUntilExhausted ? Infinity : maxItems;
|
|
44
|
+
while (allNodes.length < effectiveCap) {
|
|
45
|
+
// `first` is bounded by pageSize, but also by remaining capacity when a
|
|
46
|
+
// finite cap is in play. Under scanUntilExhausted, cap is Infinity so we
|
|
47
|
+
// always request a full page.
|
|
48
|
+
const remaining = effectiveCap === Infinity
|
|
49
|
+
? pageSize
|
|
50
|
+
: Math.min(pageSize, effectiveCap - allNodes.length);
|
|
38
51
|
const queryVars = {
|
|
39
52
|
...variables,
|
|
40
53
|
cursor,
|
|
41
|
-
first:
|
|
54
|
+
first: remaining,
|
|
42
55
|
};
|
|
43
56
|
const response = await executeQuery(query, queryVars);
|
|
44
57
|
const connection = getNestedValue(response, connectionPath);
|
|
@@ -48,12 +61,38 @@ export async function paginateConnection(executeQuery, query, variables, connect
|
|
|
48
61
|
if (connection.totalCount !== undefined) {
|
|
49
62
|
totalCount = connection.totalCount;
|
|
50
63
|
}
|
|
51
|
-
|
|
64
|
+
const pageNodes = connection.nodes;
|
|
65
|
+
allNodes.push(...pageNodes);
|
|
66
|
+
// Predicate-based early stop: evaluated AFTER appending the page so the
|
|
67
|
+
// caller always gets the page that triggered the stop fully included.
|
|
68
|
+
if (until) {
|
|
69
|
+
for (const node of pageNodes) {
|
|
70
|
+
if (!until(node, pageNodes, allNodes)) {
|
|
71
|
+
untilStopped = true;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (untilStopped) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
52
79
|
if (!connection.pageInfo.hasNextPage || !connection.pageInfo.endCursor) {
|
|
53
80
|
break;
|
|
54
81
|
}
|
|
82
|
+
// Detect cap-without-exhaustion truncation: we filled to maxItems but the
|
|
83
|
+
// connection still has more pages. Only meaningful when the caller is
|
|
84
|
+
// using a finite cap and is NOT in scan-until-exhausted mode.
|
|
85
|
+
if (!scanUntilExhausted &&
|
|
86
|
+
maxItems !== Infinity &&
|
|
87
|
+
allNodes.length >= maxItems &&
|
|
88
|
+
connection.pageInfo.hasNextPage) {
|
|
89
|
+
truncated = true;
|
|
90
|
+
const totalSuffix = totalCount !== undefined ? `, totalCount=${totalCount}` : "";
|
|
91
|
+
console.warn(`paginateConnection: truncated at maxItems=${maxItems} for connectionPath="${connectionPath}"${totalSuffix}; more pages available but not fetched. Pass { scanUntilExhausted: true } to fetch all items.`);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
55
94
|
cursor = connection.pageInfo.endCursor;
|
|
56
95
|
}
|
|
57
|
-
return { nodes: allNodes, totalCount };
|
|
96
|
+
return { nodes: allNodes, totalCount, truncated };
|
|
58
97
|
}
|
|
59
98
|
//# sourceMappingURL=pagination.js.map
|
|
@@ -20,7 +20,7 @@ export { DASHBOARD_ITEMS_QUERY, toDashboardItems };
|
|
|
20
20
|
// Register dashboard tools
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
export function registerDashboardTools(server, client, fieldCache) {
|
|
23
|
-
server.tool("ralph_hero__pipeline_dashboard", "Generate pipeline status dashboard with issue counts per workflow phase, health indicators, and formatted output. Returns structured data with optional markdown or ASCII rendering. Top-level `boardItems` is the raw count of all project items including PRs (uniform across discovery tools — next_actions, pipeline_dashboard, project_hygiene). Per-phase `count` values reflect that phase's bucket; for `Done` and `Canceled`, the count is bounded by `doneWindowDays` (default 7) and may be smaller than the actual phase membership. Per-iteration `totalIssues` is a distinct concept (iteration-scoped count).", {
|
|
23
|
+
server.tool("ralph_hero__pipeline_dashboard", "Generate pipeline status dashboard with issue counts per workflow phase, health indicators, and formatted output. Fetches all project items (full project scan, no silent 500-cap) so phase counts reflect every item regardless of board position. Returns structured data with optional markdown or ASCII rendering. Top-level `boardItems` is the raw count of all project items including PRs (uniform across discovery tools — next_actions, pipeline_dashboard, project_hygiene). Per-phase `count` values reflect that phase's bucket; for `Done` and `Canceled`, the count is bounded by `doneWindowDays` (default 7) and may be smaller than the actual phase membership. Per-iteration `totalIssues` is a distinct concept (iteration-scoped count).", {
|
|
24
24
|
owner: z
|
|
25
25
|
.string()
|
|
26
26
|
.optional()
|
|
@@ -244,7 +244,7 @@ export function makeRunDirections(client, fieldCache) {
|
|
|
244
244
|
// Skip this project rather than blowing up the whole call.
|
|
245
245
|
continue;
|
|
246
246
|
}
|
|
247
|
-
const result = await paginateConnection((q, v) => client.projectQuery(q, v), DASHBOARD_ITEMS_QUERY, { projectId, first: 100 }, "node.items", {
|
|
247
|
+
const result = await paginateConnection((q, v) => client.projectQuery(q, v), DASHBOARD_ITEMS_QUERY, { projectId, first: 100 }, "node.items", { scanUntilExhausted: true });
|
|
248
248
|
allItems.push(...toDashboardItems(result.nodes, pn));
|
|
249
249
|
}
|
|
250
250
|
// Compute unblock signals for any Human Needed candidates so the
|
|
@@ -307,7 +307,7 @@ export function makeRunDirections(client, fieldCache) {
|
|
|
307
307
|
// ---------------------------------------------------------------------------
|
|
308
308
|
export function registerDirectionsTools(server, client, fieldCache) {
|
|
309
309
|
const runDirections = makeRunDirections(client, fieldCache);
|
|
310
|
-
server.tool("ralph_hero__next_actions", "Compute up to N deterministic 'directions' (next actions) with one flagged `recommended: true`. Used by the /hello skill picker (interactive) and by headless orchestrators (auto-select recommended). Open PRs are fetched internally via the configured GitHub token's `repo` scope (one `is:pr is:open repo:owner/name` GraphQL search per unique repo represented in the project items) — callers no longer pass an `openPRs` argument. Each direction includes a structured signals object (staleDays, staleThresholdDays, tiedAtScore, estimateWeight, parentChainNote) for skills to synthesize prose. The legacy 'reason' string is @deprecated and removed in 2.7.0. When `audience='agent'` and no items are in actionable phases (Plan in Review, In Review, Ready for Plan, Research Needed) or otherwise surfacing (lock-stale, unblock-requested), the picker falls back to Backlog and null-state items so autopilot can drive triage. Fallback items receive a fixed score penalty so they never outrank actionable items when those exist; the fallback never fires for `audience='human'`. Returns `{ directions, fetchedAt, boardItems }` where `boardItems` is the raw count of items on the project board pre-filter (uniform across discovery tools); the returned `directions` array is bounded by `limit`.", {
|
|
310
|
+
server.tool("ralph_hero__next_actions", "Compute up to N deterministic 'directions' (next actions) with one flagged `recommended: true`. Fetches all project items (full project scan, no silent 500-cap) so candidate selection covers every item regardless of board position. Used by the /hello skill picker (interactive) and by headless orchestrators (auto-select recommended). Open PRs are fetched internally via the configured GitHub token's `repo` scope (one `is:pr is:open repo:owner/name` GraphQL search per unique repo represented in the project items) — callers no longer pass an `openPRs` argument. Each direction includes a structured signals object (staleDays, staleThresholdDays, tiedAtScore, estimateWeight, parentChainNote) for skills to synthesize prose. The legacy 'reason' string is @deprecated and removed in 2.7.0. When `audience='agent'` and no items are in actionable phases (Plan in Review, In Review, Ready for Plan, Research Needed) or otherwise surfacing (lock-stale, unblock-requested), the picker falls back to Backlog and null-state items so autopilot can drive triage. Fallback items receive a fixed score penalty so they never outrank actionable items when those exist; the fallback never fires for `audience='human'`. Returns `{ directions, fetchedAt, boardItems }` where `boardItems` is the raw count of items on the project board pre-filter (uniform across discovery tools); the returned `directions` array is bounded by `limit`.", {
|
|
311
311
|
owner: z
|
|
312
312
|
.string()
|
|
313
313
|
.optional()
|
|
@@ -14,7 +14,7 @@ import { toolSuccess, toolError, resolveProjectOwner, resolveProjectNumbers, } f
|
|
|
14
14
|
// Register hygiene tools
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
export function registerHygieneTools(server, client, fieldCache) {
|
|
17
|
-
server.tool("ralph_hero__project_hygiene", "Generate a project board hygiene report. Identifies archive candidates, stale items, orphaned backlog entries, missing fields, WIP violations, and duplicate candidates. Returns: report with 7 sections + summary stats. Top-level `boardItems` is the raw count of items on the project board pre-filter (uniform across discovery tools — next_actions, pipeline_dashboard, project_hygiene). Per-category counts in `summary` (archiveCandidateCount, staleCount, orphanCount, fieldCoveragePercent, wipViolationCount, duplicateCandidateCount) are post-filter and represent distinct buckets.", {
|
|
17
|
+
server.tool("ralph_hero__project_hygiene", "Generate a project board hygiene report. Identifies archive candidates, stale items, orphaned backlog entries, missing fields, WIP violations, and duplicate candidates. Fetches all project items (full project scan, no silent 500-cap) so hygiene checks cover every item regardless of board position. Returns: report with 7 sections + summary stats. Top-level `boardItems` is the raw count of items on the project board pre-filter (uniform across discovery tools — next_actions, pipeline_dashboard, project_hygiene). Per-category counts in `summary` (archiveCandidateCount, staleCount, orphanCount, fieldCoveragePercent, wipViolationCount, duplicateCandidateCount) are post-filter and represent distinct buckets.", {
|
|
18
18
|
owner: z
|
|
19
19
|
.string()
|
|
20
20
|
.optional()
|
|
@@ -24,7 +24,7 @@ export function registerIssueTools(server, client, fieldCache) {
|
|
|
24
24
|
// -------------------------------------------------------------------------
|
|
25
25
|
// ralph_hero__list_issues
|
|
26
26
|
// -------------------------------------------------------------------------
|
|
27
|
-
server.tool("ralph_hero__list_issues", "List issues from a GitHub repository with optional filters. Returns: number, title, state, workflowState, estimate, priority, iteration, labels, assignees. Use workflowState filter to find issues in a specific phase. Use iteration filter with @current/@next or sprint title. Recovery: if no results, broaden filters or check that issues exist in the project.", {
|
|
27
|
+
server.tool("ralph_hero__list_issues", "List issues from a GitHub repository with optional filters. Fetches all project items (full project scan, no silent 500-cap) and applies filters client-side, so items at any board position are visible regardless of default ordering. Returns: number, title, state, workflowState, estimate, priority, iteration, labels, assignees. Use workflowState filter to find issues in a specific phase. Use iteration filter with @current/@next or sprint title. Recovery: if no results, broaden filters or check that issues exist in the project.", {
|
|
28
28
|
owner: z
|
|
29
29
|
.string()
|
|
30
30
|
.optional()
|
|
@@ -186,7 +186,7 @@ export function registerIssueTools(server, client, fieldCache) {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
|
-
}`, { projectId, first: 100 }, "node.items", {
|
|
189
|
+
}`, { projectId, first: 100 }, "node.items", { scanUntilExhausted: true });
|
|
190
190
|
// Filter items
|
|
191
191
|
let items = itemsResult.nodes.filter((item) => item.type === "ISSUE" && item.content);
|
|
192
192
|
// Filter by issue state
|
|
@@ -673,7 +673,7 @@ export function registerRelationshipTools(server, client, fieldCache) {
|
|
|
673
673
|
// -------------------------------------------------------------------------
|
|
674
674
|
// ralph_hero__list_groups
|
|
675
675
|
// -------------------------------------------------------------------------
|
|
676
|
-
server.tool("ralph_hero__list_groups", "Discover all parent issues (groups) with sub-issues in a project. Returns parent info with child counts and optional child expansion. " +
|
|
676
|
+
server.tool("ralph_hero__list_groups", "Discover all parent issues (groups) with sub-issues in a project. Fetches all project items (full project scan, no silent 500-cap) so parents at any board position are visible regardless of default ordering; the internal lookupMap covers all items for child workflow-state resolution. Returns parent info with child counts and optional child expansion. " +
|
|
677
677
|
"Single paginated pass over project items — no per-group API calls needed.", {
|
|
678
678
|
owner: z
|
|
679
679
|
.string()
|
|
@@ -760,7 +760,7 @@ export function registerRelationshipTools(server, client, fieldCache) {
|
|
|
760
760
|
}
|
|
761
761
|
}
|
|
762
762
|
}
|
|
763
|
-
}`, { projectId, first: 100 }, "node.items", {
|
|
763
|
+
}`, { projectId, first: 100 }, "node.items", { scanUntilExhausted: true });
|
|
764
764
|
// Build lookup map: number -> { workflowState, estimate, priority }
|
|
765
765
|
const lookupMap = new Map();
|
|
766
766
|
for (const item of itemsResult.nodes) {
|
|
@@ -19,7 +19,7 @@ import { parseDateMath } from "../lib/date-math.js";
|
|
|
19
19
|
import { computeTrends } from "../lib/trends.js";
|
|
20
20
|
import { resolveProjectOwner, toolError, toolSuccess } from "../types.js";
|
|
21
21
|
export function registerTrendsTools(server, client, fieldCache) {
|
|
22
|
-
server.tool("ralph_hero__capture_snapshot", "Capture a single point-in-time snapshot of the project dashboard + metrics and append it to the partitioned JSONL file at ~/.ralph-hero/snapshots/<owner>/<projectNumber>.jsonl. Append-only, schema-versioned. Returns the snapshot row that was written.", {
|
|
22
|
+
server.tool("ralph_hero__capture_snapshot", "Capture a single point-in-time snapshot of the project dashboard + metrics and append it to the partitioned JSONL file at ~/.ralph-hero/snapshots/<owner>/<projectNumber>.jsonl. Fetches all project items (full project scan, no silent 500-cap) so per-phase WIP and totals reflect every item regardless of board position. Append-only, schema-versioned. Returns the snapshot row that was written.", {
|
|
23
23
|
projectNumber: z
|
|
24
24
|
.number()
|
|
25
25
|
.optional()
|