ralph-hero-mcp-server 2.5.116 → 2.5.118
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.js
CHANGED
|
@@ -512,7 +512,7 @@ export function buildDashboard(items, config = DEFAULT_HEALTH_CONFIG, now = Date
|
|
|
512
512
|
const iterationSection = buildIterationSection(items);
|
|
513
513
|
return {
|
|
514
514
|
generatedAt: new Date(now).toISOString(),
|
|
515
|
-
|
|
515
|
+
boardItems: items.length,
|
|
516
516
|
phases,
|
|
517
517
|
health: {
|
|
518
518
|
ok: warnings.length === 0,
|
|
@@ -536,7 +536,7 @@ export function formatMarkdown(data, issuesPerPhase = 10) {
|
|
|
536
536
|
lines.push(`# Pipeline Status`);
|
|
537
537
|
lines.push(`_Generated: ${data.generatedAt}_`);
|
|
538
538
|
lines.push("");
|
|
539
|
-
lines.push(`**
|
|
539
|
+
lines.push(`**Board items**: ${data.boardItems}`);
|
|
540
540
|
lines.push("");
|
|
541
541
|
// Phase table
|
|
542
542
|
lines.push("| Phase | Count | Points | Issues |");
|
package/dist/lib/hygiene.js
CHANGED
|
@@ -262,7 +262,7 @@ export function buildHygieneReport(items, config = DEFAULT_HYGIENE_CONFIG, now =
|
|
|
262
262
|
}
|
|
263
263
|
return {
|
|
264
264
|
generatedAt: new Date(now).toISOString(),
|
|
265
|
-
|
|
265
|
+
boardItems: items.length,
|
|
266
266
|
archiveCandidates,
|
|
267
267
|
staleItems,
|
|
268
268
|
orphanedItems,
|
|
@@ -294,7 +294,7 @@ export function formatHygieneMarkdown(report) {
|
|
|
294
294
|
lines.push("# Project Hygiene Report");
|
|
295
295
|
lines.push(`_Generated: ${report.generatedAt}_`);
|
|
296
296
|
lines.push("");
|
|
297
|
-
lines.push(`**
|
|
297
|
+
lines.push(`**Board items**: ${report.boardItems}`);
|
|
298
298
|
lines.push("");
|
|
299
299
|
// Summary
|
|
300
300
|
lines.push("## Summary");
|
|
@@ -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.", {
|
|
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).", {
|
|
24
24
|
owner: z
|
|
25
25
|
.string()
|
|
26
26
|
.optional()
|
|
@@ -6,19 +6,21 @@
|
|
|
6
6
|
* - `ralph_hero__hello_directions` (DEPRECATED alias; fixed at audience="human")
|
|
7
7
|
*
|
|
8
8
|
* Both return a fixed-shape JSON payload with up to N ranked "directions"
|
|
9
|
-
* for the `hello` skill's session briefing.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* for the `hello` skill's session briefing. Open PRs are fetched internally
|
|
10
|
+
* via the configured GitHub token's `repo` scope; callers no longer pass an
|
|
11
|
+
* `openPRs` argument.
|
|
12
12
|
*
|
|
13
13
|
* Behaviour:
|
|
14
14
|
* 1. Resolve owner + project numbers from args or client config.
|
|
15
15
|
* 2. For each project, ensure the field cache is populated, then
|
|
16
16
|
* paginate `DASHBOARD_ITEMS_QUERY` to gather up to 500 items.
|
|
17
17
|
* 3. Convert raw items to `DashboardItem[]` via `toDashboardItems`.
|
|
18
|
-
* 4.
|
|
19
|
-
*
|
|
18
|
+
* 4. Derive the unique `repo:owner/name` set from items and run the
|
|
19
|
+
* internal `fetchOpenPRs` helper to gather open PRs.
|
|
20
|
+
* 5. Build a `RankConfig` from the args + defaults (with injected `now`).
|
|
21
|
+
* 6. Compute each PR's `ageHours` at the boundary and call
|
|
20
22
|
* `rankDirections(allItems, enrichedPRs, config)`.
|
|
21
|
-
*
|
|
23
|
+
* 7. Return `{ directions, fetchedAt, boardItems }`.
|
|
22
24
|
*
|
|
23
25
|
* Determinism: `fetchedAt` is the only time-varying field. Two consecutive
|
|
24
26
|
* calls on the same board state produce byte-identical `directions[]`
|
|
@@ -149,21 +151,76 @@ async function buildUnblockSignalMap(client, items, now) {
|
|
|
149
151
|
}
|
|
150
152
|
return map;
|
|
151
153
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Derive the unique `owner/repo` set from the project items so the PR search
|
|
156
|
+
* mirrors the project's repo scope. Items lacking a `repository` field (e.g.
|
|
157
|
+
* draft items) are skipped. Returns deterministic order (sorted) so the
|
|
158
|
+
* downstream search query is stable.
|
|
159
|
+
*/
|
|
160
|
+
function uniqueRepos(items) {
|
|
161
|
+
const seen = new Set();
|
|
162
|
+
for (const item of items) {
|
|
163
|
+
if (item.repository)
|
|
164
|
+
seen.add(item.repository);
|
|
165
|
+
}
|
|
166
|
+
return Array.from(seen).sort();
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Fetch open PRs via GraphQL `search(type: ISSUE)` filtered to
|
|
170
|
+
* `is:pr is:open repo:<nameWithOwner>`. One query per repo — the project's
|
|
171
|
+
* repo set is typically tiny (1–3 repos) and most boards are single-repo so
|
|
172
|
+
* the call shape matches the previous `gh pr list` cost.
|
|
173
|
+
*
|
|
174
|
+
* Returns the raw shape consumed by `runDirections`'s age-enrichment pass.
|
|
175
|
+
* Errors are swallowed and logged via `console.error` so a token without
|
|
176
|
+
* `repo` scope cannot block direction computation — PR-kind directions
|
|
177
|
+
* simply don't surface.
|
|
178
|
+
*/
|
|
179
|
+
export async function fetchOpenPRs(client, repos) {
|
|
180
|
+
if (repos.length === 0)
|
|
181
|
+
return [];
|
|
182
|
+
const out = [];
|
|
183
|
+
for (const nameWithOwner of repos) {
|
|
184
|
+
const q = `is:pr is:open repo:${nameWithOwner}`;
|
|
185
|
+
try {
|
|
186
|
+
const data = await client.query(`query OpenPRs($q: String!) {
|
|
187
|
+
search(query: $q, type: ISSUE, first: 100) {
|
|
188
|
+
nodes {
|
|
189
|
+
... on PullRequest {
|
|
190
|
+
number
|
|
191
|
+
title
|
|
192
|
+
url
|
|
193
|
+
isDraft
|
|
194
|
+
reviewDecision
|
|
195
|
+
headRefName
|
|
196
|
+
createdAt
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}`, { q });
|
|
201
|
+
for (const pr of data.search.nodes) {
|
|
202
|
+
// Defensive: search responses can include empty fragments when the
|
|
203
|
+
// node is not a PullRequest. Skip rows missing required fields.
|
|
204
|
+
if (typeof pr.number !== "number" || !pr.url)
|
|
205
|
+
continue;
|
|
206
|
+
out.push({
|
|
207
|
+
number: pr.number,
|
|
208
|
+
title: pr.title,
|
|
209
|
+
url: pr.url,
|
|
210
|
+
isDraft: pr.isDraft,
|
|
211
|
+
reviewDecision: pr.reviewDecision,
|
|
212
|
+
headRefName: pr.headRefName,
|
|
213
|
+
createdAt: pr.createdAt,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
219
|
+
console.error(`[ralph-hero] fetchOpenPRs failed for repo ${nameWithOwner}: ${msg}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
167
224
|
// ---------------------------------------------------------------------------
|
|
168
225
|
// Shared implementation — extracted so both `hello_directions` (deprecated)
|
|
169
226
|
// and `next_actions` (current) can route through the same code path. Also
|
|
@@ -211,8 +268,14 @@ export function makeRunDirections(client, fieldCache) {
|
|
|
211
268
|
now,
|
|
212
269
|
unblockSignals,
|
|
213
270
|
};
|
|
271
|
+
// Fetch open PRs internally for the unique repo set covered by the
|
|
272
|
+
// project items. Replaces the caller-supplied `openPRs` parameter
|
|
273
|
+
// (removed in 2.6.0). One search query per repo; failures yield an
|
|
274
|
+
// empty list so the rest of the ranking still runs.
|
|
275
|
+
const repos = uniqueRepos(allItems);
|
|
276
|
+
const rawOpenPRs = await fetchOpenPRs(client, repos);
|
|
214
277
|
// Compute PR ageHours at the boundary so the lib never reads the wall clock.
|
|
215
|
-
const enrichedPRs =
|
|
278
|
+
const enrichedPRs = rawOpenPRs.map((pr) => {
|
|
216
279
|
const t = new Date(pr.createdAt).getTime();
|
|
217
280
|
const ageHours = Number.isNaN(t)
|
|
218
281
|
? 0
|
|
@@ -232,7 +295,11 @@ export function makeRunDirections(client, fieldCache) {
|
|
|
232
295
|
return toolSuccess({
|
|
233
296
|
directions,
|
|
234
297
|
fetchedAt: now.toISOString(),
|
|
235
|
-
|
|
298
|
+
// `boardItems` is the raw count of items on the project board pre-filter
|
|
299
|
+
// (sum across all configured project numbers). Uniform across discovery
|
|
300
|
+
// tools (next_actions, pipeline_dashboard, project_hygiene). The number
|
|
301
|
+
// of returned `directions` is bounded by `limit` and may be much smaller.
|
|
302
|
+
boardItems: allItems.length,
|
|
236
303
|
});
|
|
237
304
|
}
|
|
238
305
|
catch (error) {
|
|
@@ -246,7 +313,7 @@ export function makeRunDirections(client, fieldCache) {
|
|
|
246
313
|
// ---------------------------------------------------------------------------
|
|
247
314
|
export function registerDirectionsTools(server, client, fieldCache) {
|
|
248
315
|
const runDirections = makeRunDirections(client, fieldCache);
|
|
249
|
-
server.tool("ralph_hero__hello_directions", "[DEPRECATED — use ralph_hero__next_actions instead. Removed in 2.7.0.] Compute up to N deterministic 'directions' for the hello skill's session briefing. 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.", {
|
|
316
|
+
server.tool("ralph_hero__hello_directions", "[DEPRECATED — use ralph_hero__next_actions instead. Removed in 2.7.0.] Compute up to N deterministic 'directions' for the hello skill's session briefing. 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). 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. 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`.", {
|
|
250
317
|
owner: z
|
|
251
318
|
.string()
|
|
252
319
|
.optional()
|
|
@@ -286,15 +353,10 @@ export function registerDirectionsTools(server, client, fieldCache) {
|
|
|
286
353
|
.optional()
|
|
287
354
|
.default(24)
|
|
288
355
|
.describe("Hours before an open PR is considered stale (default: 24)."),
|
|
289
|
-
openPRs: z
|
|
290
|
-
.array(openPRSchema)
|
|
291
|
-
.optional()
|
|
292
|
-
.default([])
|
|
293
|
-
.describe("Open PRs gathered by the caller (e.g. via `gh pr list`). Drafts and APPROVED PRs are filtered internally."),
|
|
294
356
|
}, async (args) => {
|
|
295
357
|
return await runDirections({ ...args, audience: "human" });
|
|
296
358
|
});
|
|
297
|
-
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
|
|
359
|
+
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`.", {
|
|
298
360
|
owner: z
|
|
299
361
|
.string()
|
|
300
362
|
.optional()
|
|
@@ -339,11 +401,6 @@ export function registerDirectionsTools(server, client, fieldCache) {
|
|
|
339
401
|
.optional()
|
|
340
402
|
.default(24)
|
|
341
403
|
.describe("Hours before an open PR is considered stale (default: 24)."),
|
|
342
|
-
openPRs: z
|
|
343
|
-
.array(openPRSchema)
|
|
344
|
-
.optional()
|
|
345
|
-
.default([])
|
|
346
|
-
.describe("Open PRs gathered by the caller (e.g. via `gh pr list`). Drafts and APPROVED PRs are filtered internally."),
|
|
347
404
|
}, async (args) => {
|
|
348
405
|
return await runDirections({ ...args, audience: args.audience ?? "human" });
|
|
349
406
|
});
|
|
@@ -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.", {
|
|
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.", {
|
|
18
18
|
owner: z
|
|
19
19
|
.string()
|
|
20
20
|
.optional()
|
|
@@ -1248,7 +1248,6 @@ export function registerIssueTools(server, client, fieldCache) {
|
|
|
1248
1248
|
owner,
|
|
1249
1249
|
projectNumbers: args.projectNumber !== undefined ? [args.projectNumber] : undefined,
|
|
1250
1250
|
limit: 50,
|
|
1251
|
-
openPRs: [],
|
|
1252
1251
|
audience: "agent",
|
|
1253
1252
|
});
|
|
1254
1253
|
if (directionsResult.isError) {
|