ralph-hero-mcp-server 2.5.74 → 2.5.75

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/index.js CHANGED
@@ -20,6 +20,7 @@ import { registerProjectTools } from "./tools/project-tools.js";
20
20
  import { registerIssueTools } from "./tools/issue-tools.js";
21
21
  import { registerRelationshipTools } from "./tools/relationship-tools.js";
22
22
  import { registerDashboardTools } from "./tools/dashboard-tools.js";
23
+ import { registerDirectionsTools } from "./tools/directions-tools.js";
23
24
  import { registerBatchTools } from "./tools/batch-tools.js";
24
25
  import { registerProjectManagementTools } from "./tools/project-management-tools.js";
25
26
  import { registerHygieneTools } from "./tools/hygiene-tools.js";
@@ -386,6 +387,8 @@ async function main() {
386
387
  registerRelationshipTools(server, client, fieldCache);
387
388
  // Dashboard and pipeline visualization tools
388
389
  registerDashboardTools(server, client, fieldCache);
390
+ // Hello directions tool — deterministic ranked action items
391
+ registerDirectionsTools(server, client, fieldCache);
389
392
  // Phase 5: Batch operations
390
393
  registerBatchTools(server, client, fieldCache);
391
394
  // Project management tools (archive, remove, add, link repo, clear field)
@@ -0,0 +1,159 @@
1
+ /**
2
+ * MCP tool wrapping the pure ranker at `lib/directions.ts`.
3
+ *
4
+ * Exposes `ralph_hero__hello_directions`: a single-tool, fixed-shape,
5
+ * deterministic surface that returns up to N ranked "directions" for the
6
+ * `hello` skill's session briefing. The skill is responsible for fetching
7
+ * open PRs (via `gh pr list`) and passing them in as a parameter — the
8
+ * MCP server does not itself open an Octokit-style PR API surface.
9
+ *
10
+ * Behaviour:
11
+ * 1. Resolve owner + project numbers from args or client config.
12
+ * 2. For each project, ensure the field cache is populated, then
13
+ * paginate `DASHBOARD_ITEMS_QUERY` to gather up to 500 items.
14
+ * 3. Convert raw items to `DashboardItem[]` via `toDashboardItems`.
15
+ * 4. Build a `RankConfig` from the args + defaults (with injected `now`).
16
+ * 5. Compute each PR's `ageHours` at the boundary and call
17
+ * `rankDirections(allItems, enrichedPRs, config)`.
18
+ * 6. Return `{ directions, fetchedAt, totalCandidates }`.
19
+ *
20
+ * Determinism: `fetchedAt` is the only time-varying field. Two consecutive
21
+ * calls on the same board state produce byte-identical `directions[]`
22
+ * (after stripping `fetchedAt`).
23
+ */
24
+ import { z } from "zod";
25
+ import { ensureFieldCache } from "../lib/helpers.js";
26
+ import { paginateConnection } from "../lib/pagination.js";
27
+ import { DASHBOARD_ITEMS_QUERY, toDashboardItems, } from "./dashboard-tools.js";
28
+ import { rankDirections, DEFAULT_RANK_CONFIG, } from "../lib/directions.js";
29
+ import { toolSuccess, toolError, resolveProjectOwner, resolveProjectNumbers, } from "../types.js";
30
+ // ---------------------------------------------------------------------------
31
+ // Constants
32
+ // ---------------------------------------------------------------------------
33
+ const HOUR_MS = 60 * 60 * 1000;
34
+ // ---------------------------------------------------------------------------
35
+ // Register
36
+ // ---------------------------------------------------------------------------
37
+ export function registerDirectionsTools(server, client, fieldCache) {
38
+ server.tool("ralph_hero__hello_directions", "Compute up to N deterministic 'directions' for the hello skill's session briefing. Single tool call returns a fixed-shape JSON payload with ranked issue/PR action items. Open PRs must be passed in as a parameter (the MCP server does not fetch them itself).", {
39
+ owner: z
40
+ .string()
41
+ .optional()
42
+ .describe("GitHub owner. Defaults to RALPH_GH_OWNER env var."),
43
+ projectNumbers: z
44
+ .array(z.coerce.number())
45
+ .optional()
46
+ .describe("Project numbers to include. Defaults to RALPH_GH_PROJECT_NUMBERS or single configured project."),
47
+ limit: z
48
+ .number()
49
+ .int()
50
+ .nonnegative()
51
+ .optional()
52
+ .default(3)
53
+ .describe("Max directions to return (default: 3)."),
54
+ stuckThresholdHours: z
55
+ .number()
56
+ .nonnegative()
57
+ .optional()
58
+ .default(48)
59
+ .describe("Hours before a non-lock issue is considered stale (default: 48)."),
60
+ lockStaleHours: z
61
+ .number()
62
+ .nonnegative()
63
+ .optional()
64
+ .default(24)
65
+ .describe("Hours before a lock-state issue is considered stalled (default: 24)."),
66
+ treeRecentDoneDays: z
67
+ .number()
68
+ .nonnegative()
69
+ .optional()
70
+ .default(7)
71
+ .describe("Days within which a sibling Done event still pulls a tree forward (default: 7)."),
72
+ prStaleHours: z
73
+ .number()
74
+ .nonnegative()
75
+ .optional()
76
+ .default(24)
77
+ .describe("Hours before an open PR is considered stale (default: 24)."),
78
+ openPRs: z
79
+ .array(z.object({
80
+ number: z.number(),
81
+ title: z.string(),
82
+ url: z.string(),
83
+ isDraft: z.boolean(),
84
+ reviewDecision: z
85
+ .string()
86
+ .nullable()
87
+ .describe("REVIEW_REQUIRED | APPROVED | CHANGES_REQUESTED | null"),
88
+ headRefName: z.string(),
89
+ createdAt: z
90
+ .string()
91
+ .describe("ISO timestamp from gh pr list"),
92
+ }))
93
+ .optional()
94
+ .default([])
95
+ .describe("Open PRs gathered by the caller (e.g. via `gh pr list`). Drafts and APPROVED PRs are filtered internally."),
96
+ }, async (args) => {
97
+ try {
98
+ const owner = args.owner || resolveProjectOwner(client.config);
99
+ if (!owner) {
100
+ return toolError("owner is required");
101
+ }
102
+ const projectNumbers = args.projectNumbers ?? resolveProjectNumbers(client.config);
103
+ if (projectNumbers.length === 0) {
104
+ return toolError("No project numbers configured. Set RALPH_GH_PROJECT_NUMBER or RALPH_GH_PROJECT_NUMBERS.");
105
+ }
106
+ // Inject `now` at the boundary so the lib remains deterministic.
107
+ const now = new Date();
108
+ const allItems = [];
109
+ for (const pn of projectNumbers) {
110
+ await ensureFieldCache(client, fieldCache, owner, pn);
111
+ const projectId = fieldCache.getProjectId(pn);
112
+ if (!projectId) {
113
+ // Defensive: ensureFieldCache succeeded but no projectId.
114
+ // Skip this project rather than blowing up the whole call.
115
+ continue;
116
+ }
117
+ const result = await paginateConnection((q, v) => client.projectQuery(q, v), DASHBOARD_ITEMS_QUERY, { projectId, first: 100 }, "node.items", { maxItems: 500 });
118
+ allItems.push(...toDashboardItems(result.nodes, pn));
119
+ }
120
+ // Build the RankConfig from args + defaults + injected `now`.
121
+ const config = {
122
+ limit: args.limit ?? DEFAULT_RANK_CONFIG.limit,
123
+ stuckThresholdHours: args.stuckThresholdHours ?? DEFAULT_RANK_CONFIG.stuckThresholdHours,
124
+ lockStaleHours: args.lockStaleHours ?? DEFAULT_RANK_CONFIG.lockStaleHours,
125
+ treeRecentDoneDays: args.treeRecentDoneDays ?? DEFAULT_RANK_CONFIG.treeRecentDoneDays,
126
+ prStaleHours: args.prStaleHours ?? DEFAULT_RANK_CONFIG.prStaleHours,
127
+ now,
128
+ };
129
+ // Compute PR ageHours at the boundary so the lib never reads the wall clock.
130
+ const enrichedPRs = (args.openPRs ?? []).map((pr) => {
131
+ const t = new Date(pr.createdAt).getTime();
132
+ const ageHours = Number.isNaN(t)
133
+ ? 0
134
+ : Math.max(0, (now.getTime() - t) / HOUR_MS);
135
+ return {
136
+ number: pr.number,
137
+ title: pr.title,
138
+ url: pr.url,
139
+ isDraft: pr.isDraft,
140
+ reviewDecision: pr.reviewDecision,
141
+ headRefName: pr.headRefName,
142
+ createdAt: pr.createdAt,
143
+ ageHours,
144
+ };
145
+ });
146
+ const directions = rankDirections(allItems, enrichedPRs, config);
147
+ return toolSuccess({
148
+ directions,
149
+ fetchedAt: now.toISOString(),
150
+ totalCandidates: allItems.length,
151
+ });
152
+ }
153
+ catch (error) {
154
+ const message = error instanceof Error ? error.message : String(error);
155
+ return toolError(`Failed to compute hello directions: ${message}`);
156
+ }
157
+ });
158
+ }
159
+ //# sourceMappingURL=directions-tools.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-hero-mcp-server",
3
- "version": "2.5.74",
3
+ "version": "2.5.75",
4
4
  "description": "MCP server for GitHub Projects V2 - Ralph workflow automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",