ralph-hero-mcp-server 2.5.120 → 2.5.123

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Pure ranker library for the `ralph_hero__hello_directions` MCP tool.
2
+ * Pure ranker library for the `ralph_hero__next_actions` MCP tool.
3
3
  *
4
4
  * Computes up to N deterministic directions for a session-briefing
5
5
  * companion. All functions are side-effect free: time is injected via
@@ -1,13 +1,10 @@
1
1
  /**
2
2
  * MCP tools wrapping the pure ranker at `lib/directions.ts`.
3
3
  *
4
- * Exposes two tools that share a single implementation:
5
- * - `ralph_hero__next_actions` (current name; accepts `audience` param)
6
- * - `ralph_hero__hello_directions` (DEPRECATED alias; fixed at audience="human")
7
- *
8
- * Both return a fixed-shape JSON payload with up to N ranked "directions"
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
4
+ * Exposes `ralph_hero__next_actions` accepts `audience` param and returns
5
+ * a fixed-shape JSON payload with up to N ranked "directions" for the
6
+ * `hello` skill's session briefing. Open PRs are fetched internally via the
7
+ * configured GitHub token's `repo` scope; callers no longer pass an
11
8
  * `openPRs` argument.
12
9
  *
13
10
  * Behaviour:
@@ -222,11 +219,8 @@ export async function fetchOpenPRs(client, repos) {
222
219
  return out;
223
220
  }
224
221
  // ---------------------------------------------------------------------------
225
- // Shared implementation — extracted so both `hello_directions` (deprecated)
226
- // and `next_actions` (current) can route through the same code path. Also
227
- // exported so the deprecated `pick_actionable_issue` wrapper in
228
- // `issue-tools.ts` can delegate without duplicating the data-fetch +
229
- // scoring pipeline.
222
+ // Shared implementation — extracted so the `next_actions` tool can route
223
+ // through a single code path.
230
224
  // ---------------------------------------------------------------------------
231
225
  export function makeRunDirections(client, fieldCache) {
232
226
  return async function runDirections(args) {
@@ -313,49 +307,6 @@ export function makeRunDirections(client, fieldCache) {
313
307
  // ---------------------------------------------------------------------------
314
308
  export function registerDirectionsTools(server, client, fieldCache) {
315
309
  const runDirections = makeRunDirections(client, fieldCache);
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`.", {
317
- owner: z
318
- .string()
319
- .optional()
320
- .describe("GitHub owner. Defaults to RALPH_GH_OWNER env var."),
321
- projectNumbers: z
322
- .array(z.coerce.number())
323
- .optional()
324
- .describe("Project numbers to include. Defaults to RALPH_GH_PROJECT_NUMBERS or single configured project."),
325
- limit: z
326
- .number()
327
- .int()
328
- .nonnegative()
329
- .optional()
330
- .default(3)
331
- .describe("Max directions to return (default: 3)."),
332
- stuckThresholdHours: z
333
- .number()
334
- .nonnegative()
335
- .optional()
336
- .default(48)
337
- .describe("Hours before a non-lock issue is considered stale (default: 48, unit: hours). Pulls from STUCK_THRESHOLD_HOURS in src/lib/thresholds.ts."),
338
- lockStaleHours: z
339
- .number()
340
- .nonnegative()
341
- .optional()
342
- .default(24)
343
- .describe("Hours before a lock-state issue is considered stalled (default: 24, unit: hours). Pulls from LOCK_STALE_HOURS in src/lib/thresholds.ts."),
344
- treeRecentDoneDays: z
345
- .number()
346
- .nonnegative()
347
- .optional()
348
- .default(7)
349
- .describe("Days within which a sibling Done event still pulls a tree forward (default: 7, unit: days). Pulls from RECENT_WINDOW_DAYS in src/lib/thresholds.ts."),
350
- prStaleHours: z
351
- .number()
352
- .nonnegative()
353
- .optional()
354
- .default(24)
355
- .describe("Hours before an open PR is considered stale (default: 24, unit: hours). Pulls from PR_STALE_HOURS in src/lib/thresholds.ts."),
356
- }, async (args) => {
357
- return await runDirections({ ...args, audience: "human" });
358
- });
359
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`.", {
360
311
  owner: z
361
312
  .string()
@@ -10,7 +10,6 @@ import { detectGroup } from "../lib/group-detection.js";
10
10
  import { detectPipelinePosition, OVERSIZED_ESTIMATES, } from "../lib/pipeline-detection.js";
11
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
- import { makeRunDirections } from "./directions-tools.js";
14
13
  import { resolveState } from "../lib/state-resolution.js";
15
14
  import { parseDateMath } from "../lib/date-math.js";
16
15
  import { expandProfile } from "../lib/filter-profiles.js";
@@ -1184,157 +1183,6 @@ export function registerIssueTools(server, client, fieldCache) {
1184
1183
  return toolError(`Failed to create comment: ${message}`);
1185
1184
  }
1186
1185
  });
1187
- // -------------------------------------------------------------------------
1188
- // ralph_hero__pick_actionable_issue [DEPRECATED]
1189
- //
1190
- // Thin wrapper that delegates to the shared `runDirections` helper from
1191
- // `directions-tools.ts` (audience="agent"). Preserves the legacy
1192
- // `{ found, issue, group, alternatives }` shape so existing callers
1193
- // (justfile recipes, hero/team allowlists) keep working until removal in
1194
- // 2.7.0. Same backwards-compat pattern as `hello_directions` from Phase 2.
1195
- //
1196
- // Migration: callers should switch to
1197
- // ralph_hero__next_actions(limit=1, audience="agent")
1198
- // and consume the rank-1 (recommended) entry directly.
1199
- // -------------------------------------------------------------------------
1200
- const runDirections = makeRunDirections(client, fieldCache);
1201
- server.tool("ralph_hero__pick_actionable_issue", "[DEPRECATED — use ralph_hero__next_actions(limit=1, audience='agent') instead. Removed in 2.7.0.] Find the highest-priority issue matching a workflow state that is not blocked or locked. Returns: found, issue (with number, title, workflowState, estimate, priority, group context), alternatives count. Used by dispatch loop to find work for idle teammates.", {
1202
- owner: z
1203
- .string()
1204
- .optional()
1205
- .describe("GitHub owner. Defaults to GITHUB_OWNER env var"),
1206
- repo: z
1207
- .string()
1208
- .optional()
1209
- .describe("Repository name. Defaults to GITHUB_REPO env var"),
1210
- projectNumber: z.coerce.number().optional()
1211
- .describe("Project number override (defaults to configured project)"),
1212
- workflowState: z
1213
- .string()
1214
- .optional()
1215
- .describe("Target workflow state (e.g., 'Research Needed', 'Ready for Plan'). Optional — when omitted, the rank-1 (recommended) direction across all actionable phases is returned (same as next_actions(limit=1, audience='agent'))."),
1216
- maxEstimate: z
1217
- .string()
1218
- .optional()
1219
- .default("S")
1220
- .describe("Maximum estimate to include (XS, S, M, L, XL). Default: S"),
1221
- }, async (args) => {
1222
- try {
1223
- // Validate workflow state (only when provided — wrapper allows
1224
- // omission so it can mirror next_actions(limit=1, audience='agent')).
1225
- if (args.workflowState !== undefined && !isValidState(args.workflowState)) {
1226
- return toolError(`Unknown workflow state '${args.workflowState}'. ` +
1227
- `Valid states: ${VALID_STATES.join(", ")}. ` +
1228
- `Recovery: retry with a valid state name. ` +
1229
- `Common states for dispatch: 'Research Needed' (for researchers), ` +
1230
- `'Ready for Plan' (for planners), 'Plan in Review' (for reviewers).`);
1231
- }
1232
- // Validate estimate
1233
- const validEstimates = ["XS", "S", "M", "L", "XL"];
1234
- const maxEstimate = args.maxEstimate || "S";
1235
- if (!validEstimates.includes(maxEstimate)) {
1236
- return toolError(`Unknown estimate '${maxEstimate}'. ` +
1237
- `Valid estimates: ${validEstimates.join(", ")}. ` +
1238
- `Recovery: retry with a valid estimate or omit for default (S).`);
1239
- }
1240
- const { owner, repo } = resolveFullConfig(client, args);
1241
- // Delegate to the shared ranker with audience="agent". Use a
1242
- // generous limit so we have enough entries to filter by
1243
- // workflowState + maxEstimate while still finding the highest
1244
- // priority candidate. The ranker uses priority + audience-aware
1245
- // estimate penalty as the dominant ordering signal, which matches
1246
- // the legacy P0 -> P3 sort.
1247
- const directionsResult = await runDirections({
1248
- owner,
1249
- projectNumbers: args.projectNumber !== undefined ? [args.projectNumber] : undefined,
1250
- limit: 50,
1251
- audience: "agent",
1252
- });
1253
- if (directionsResult.isError) {
1254
- // Surface the underlying error verbatim so callers see the same
1255
- // recovery hints they would from next_actions.
1256
- return directionsResult;
1257
- }
1258
- const payload = JSON.parse(directionsResult.content[0].text);
1259
- // Restrict to plain "issue" directions — exclude PRs (kind="pr"),
1260
- // lock-stale items (kind="lock-stale"), and tree-continue picks
1261
- // (kind="tree-continue") since legacy semantics returned a single
1262
- // ready-to-claim issue.
1263
- let issueDirections = payload.directions.filter((d) => d.kind === "issue" && d.issue !== null);
1264
- // Apply legacy filters: workflowState (if provided) and maxEstimate.
1265
- if (args.workflowState !== undefined) {
1266
- issueDirections = issueDirections.filter((d) => d.issue.workflowState === args.workflowState);
1267
- }
1268
- const maxIdx = validEstimates.indexOf(maxEstimate);
1269
- issueDirections = issueDirections.filter((d) => {
1270
- const est = d.issue.estimate;
1271
- if (!est)
1272
- return true;
1273
- const estIdx = validEstimates.indexOf(est);
1274
- if (estIdx < 0)
1275
- return true;
1276
- return estIdx <= maxIdx;
1277
- });
1278
- // Drop blocked items (legacy behavior). The ranker already strips
1279
- // these unless that would empty the candidate set; the "blocked"
1280
- // tag still rides along when it kept them as a fallback.
1281
- issueDirections = issueDirections.filter((d) => !d.tags.includes("blocked"));
1282
- if (issueDirections.length === 0) {
1283
- return toolSuccess({
1284
- found: false,
1285
- issue: null,
1286
- alternatives: 0,
1287
- });
1288
- }
1289
- const best = issueDirections[0].issue;
1290
- const issueNumber = best.number;
1291
- // Detect group context for the picked issue (best-effort).
1292
- let group = null;
1293
- try {
1294
- const groupResult = await detectGroup(client, owner, repo, issueNumber);
1295
- group = {
1296
- isGroup: groupResult.isGroup,
1297
- primary: {
1298
- number: groupResult.groupPrimary.number,
1299
- title: groupResult.groupPrimary.title,
1300
- },
1301
- members: groupResult.groupTickets.map((t) => ({
1302
- number: t.number,
1303
- title: t.title,
1304
- state: t.state,
1305
- order: t.order,
1306
- })),
1307
- totalTickets: groupResult.totalTickets,
1308
- };
1309
- }
1310
- catch {
1311
- // Group detection is best-effort
1312
- group = null;
1313
- }
1314
- return toolSuccess({
1315
- found: true,
1316
- issue: {
1317
- number: issueNumber,
1318
- title: best.title,
1319
- // Body is not fetched by the ranker's data pipeline — kept
1320
- // empty for backward compat. Callers needing the body should
1321
- // chain a `get_issue` call (or migrate to `next_actions`).
1322
- description: "",
1323
- workflowState: best.workflowState,
1324
- estimate: best.estimate || null,
1325
- priority: best.priority || null,
1326
- isLocked: false,
1327
- blockedBy: [],
1328
- },
1329
- group,
1330
- alternatives: issueDirections.length - 1,
1331
- });
1332
- }
1333
- catch (error) {
1334
- const message = error instanceof Error ? error.message : String(error);
1335
- return toolError(`Failed to pick actionable issue: ${message}`);
1336
- }
1337
- });
1338
1186
  }
1339
1187
  function getFieldValue(item, fieldName) {
1340
1188
  const fieldValue = item.fieldValues.nodes.find((fv) => fv.field?.name === fieldName &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-hero-mcp-server",
3
- "version": "2.5.120",
3
+ "version": "2.5.123",
4
4
  "description": "MCP server for GitHub Projects V2 - Ralph workflow automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",