ralph-hero-mcp-server 2.4.7 → 2.4.8

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.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Date-math expression parser for filtering by relative dates.
3
+ *
4
+ * Supports expressions like:
5
+ * @today - midnight UTC of current day
6
+ * @now - current instant
7
+ * @today-7d - 7 days before midnight UTC
8
+ * @now-24h - 24 hours ago
9
+ * @today+3d - 3 days in the future
10
+ *
11
+ * Offset units: h (hours), d (days), w (weeks), m (months)
12
+ * Also accepts absolute ISO dates as fallback (e.g., "2026-01-15").
13
+ */
14
+ const DATE_MATH_RE = /^@(today|now)(?:([+-])(\d+)([hdwm]))?$/i;
15
+ export function parseDateMath(expr, now = new Date()) {
16
+ const match = expr.match(DATE_MATH_RE);
17
+ if (match) {
18
+ const [, anchor, sign, amount, unit] = match;
19
+ const isToday = anchor.toLowerCase() === "today";
20
+ // Start from midnight UTC for @today, current instant for @now
21
+ let base;
22
+ if (isToday) {
23
+ base = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
24
+ }
25
+ else {
26
+ base = new Date(now.getTime());
27
+ }
28
+ // Apply offset if present
29
+ if (sign && amount && unit) {
30
+ const n = parseInt(amount, 10);
31
+ const direction = sign === "+" ? 1 : -1;
32
+ switch (unit.toLowerCase()) {
33
+ case "h":
34
+ base.setTime(base.getTime() + direction * n * 60 * 60 * 1000);
35
+ break;
36
+ case "d":
37
+ base.setTime(base.getTime() + direction * n * 24 * 60 * 60 * 1000);
38
+ break;
39
+ case "w":
40
+ base.setTime(base.getTime() + direction * n * 7 * 24 * 60 * 60 * 1000);
41
+ break;
42
+ case "m":
43
+ base.setUTCMonth(base.getUTCMonth() + direction * n);
44
+ break;
45
+ }
46
+ }
47
+ return base;
48
+ }
49
+ // Fallback: try parsing as absolute date
50
+ const parsed = new Date(expr);
51
+ if (isNaN(parsed.getTime())) {
52
+ throw new Error(`Invalid date expression: "${expr}". ` +
53
+ `Use @today-7d, @now-24h, or an ISO date (YYYY-MM-DD).`);
54
+ }
55
+ return parsed;
56
+ }
57
+ //# sourceMappingURL=date-math.js.map
@@ -10,6 +10,7 @@ import { detectGroup } from "../lib/group-detection.js";
10
10
  import { detectPipelinePosition, } from "../lib/pipeline-detection.js";
11
11
  import { isValidState, VALID_STATES, LOCK_STATES, } from "../lib/workflow-states.js";
12
12
  import { resolveState } from "../lib/state-resolution.js";
13
+ import { parseDateMath } from "../lib/date-math.js";
13
14
  import { toolSuccess, toolError } from "../types.js";
14
15
  import { ensureFieldCache, resolveIssueNodeId, resolveProjectItemId, updateProjectItemField, getCurrentFieldValue, resolveConfig, resolveFullConfig, syncStatusField, } from "../lib/helpers.js";
15
16
  // ---------------------------------------------------------------------------
@@ -47,6 +48,18 @@ export function registerIssueTools(server, client, fieldCache) {
47
48
  .optional()
48
49
  .default("OPEN")
49
50
  .describe("Issue state filter (default: OPEN)"),
51
+ reason: z
52
+ .enum(["completed", "not_planned", "reopened"])
53
+ .optional()
54
+ .describe("Filter by close reason: completed, not_planned, reopened"),
55
+ updatedSince: z
56
+ .string()
57
+ .optional()
58
+ .describe("Include items updated on or after this date. Supports date-math (@today-7d, @now-24h) or ISO dates (YYYY-MM-DD)."),
59
+ updatedBefore: z
60
+ .string()
61
+ .optional()
62
+ .describe("Include items updated before this date. Supports date-math (@today-7d, @now-24h) or ISO dates (YYYY-MM-DD)."),
50
63
  orderBy: z
51
64
  .enum(["CREATED_AT", "UPDATED_AT", "COMMENTS"])
52
65
  .optional()
@@ -82,6 +95,7 @@ export function registerIssueTools(server, client, fieldCache) {
82
95
  title
83
96
  body
84
97
  state
98
+ stateReason
85
99
  url
86
100
  createdAt
87
101
  updatedAt
@@ -113,6 +127,14 @@ export function registerIssueTools(server, client, fieldCache) {
113
127
  return content?.state === args.state;
114
128
  });
115
129
  }
130
+ // Filter by close reason (stateReason)
131
+ if (args.reason) {
132
+ const reasonUpper = args.reason.toUpperCase();
133
+ items = items.filter((item) => {
134
+ const content = item.content;
135
+ return content?.stateReason === reasonUpper;
136
+ });
137
+ }
116
138
  // Filter by workflow state
117
139
  if (args.workflowState) {
118
140
  items = items.filter((item) => getFieldValue(item, "Workflow State") === args.workflowState);
@@ -144,6 +166,24 @@ export function registerIssueTools(server, client, fieldCache) {
144
166
  return title.includes(q) || body.includes(q);
145
167
  });
146
168
  }
169
+ // Filter by updatedSince
170
+ if (args.updatedSince) {
171
+ const since = parseDateMath(args.updatedSince).getTime();
172
+ items = items.filter((item) => {
173
+ const content = item.content;
174
+ const updatedAt = content?.updatedAt;
175
+ return updatedAt ? new Date(updatedAt).getTime() >= since : false;
176
+ });
177
+ }
178
+ // Filter by updatedBefore
179
+ if (args.updatedBefore) {
180
+ const before = parseDateMath(args.updatedBefore).getTime();
181
+ items = items.filter((item) => {
182
+ const content = item.content;
183
+ const updatedAt = content?.updatedAt;
184
+ return updatedAt ? new Date(updatedAt).getTime() < before : false;
185
+ });
186
+ }
147
187
  // Sort
148
188
  items.sort((a, b) => {
149
189
  const ac = a.content;
@@ -162,7 +202,9 @@ export function registerIssueTools(server, client, fieldCache) {
162
202
  number: content?.number,
163
203
  title: content?.title,
164
204
  state: content?.state,
205
+ stateReason: content?.stateReason ?? null,
165
206
  url: content?.url,
207
+ updatedAt: content?.updatedAt ?? null,
166
208
  workflowState: getFieldValue(item, "Workflow State"),
167
209
  estimate: getFieldValue(item, "Estimate"),
168
210
  priority: getFieldValue(item, "Priority"),
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { z } from "zod";
8
8
  import { paginateConnection } from "../lib/pagination.js";
9
+ import { parseDateMath } from "../lib/date-math.js";
9
10
  import { toolSuccess, toolError } from "../types.js";
10
11
  import { resolveProjectOwner } from "../types.js";
11
12
  const WORKFLOW_STATE_OPTIONS = [
@@ -251,6 +252,18 @@ export function registerProjectTools(server, client, fieldCache) {
251
252
  .string()
252
253
  .optional()
253
254
  .describe("Filter by Priority name (P0, P1, P2, P3)"),
255
+ itemType: z
256
+ .enum(["ISSUE", "PULL_REQUEST", "DRAFT_ISSUE"])
257
+ .optional()
258
+ .describe("Filter by item type (ISSUE, PULL_REQUEST, DRAFT_ISSUE). Omit to include all types."),
259
+ updatedSince: z
260
+ .string()
261
+ .optional()
262
+ .describe("Include items updated on or after this date. Supports date-math (@today-7d, @now-24h) or ISO dates."),
263
+ updatedBefore: z
264
+ .string()
265
+ .optional()
266
+ .describe("Include items updated before this date. Supports date-math (@today-7d, @now-24h) or ISO dates."),
254
267
  limit: z
255
268
  .number()
256
269
  .optional()
@@ -272,6 +285,9 @@ export function registerProjectTools(server, client, fieldCache) {
272
285
  if (!projectId) {
273
286
  return toolError("Could not resolve project ID");
274
287
  }
288
+ // When filters are active, fetch more items to ensure adequate results after filtering
289
+ const hasFilters = args.updatedSince || args.updatedBefore || args.itemType;
290
+ const maxItems = hasFilters ? 500 : (args.limit || 50);
275
291
  // Fetch all project items with field values
276
292
  const itemsResult = await paginateConnection((q, v) => client.projectQuery(q, v), `query($projectId: ID!, $cursor: String, $first: Int!) {
277
293
  node(id: $projectId) {
@@ -288,6 +304,7 @@ export function registerProjectTools(server, client, fieldCache) {
288
304
  title
289
305
  state
290
306
  url
307
+ updatedAt
291
308
  labels(first: 10) { nodes { name } }
292
309
  assignees(first: 5) { nodes { login } }
293
310
  }
@@ -326,9 +343,13 @@ export function registerProjectTools(server, client, fieldCache) {
326
343
  }
327
344
  }
328
345
  }
329
- }`, { projectId, first: Math.min(args.limit || 50, 100) }, "node.items", { maxItems: args.limit || 50 });
346
+ }`, { projectId, first: Math.min(maxItems, 100) }, "node.items", { maxItems });
330
347
  // Filter items by field values
331
348
  let items = itemsResult.nodes;
349
+ // Filter by item type (broadest filter first to reduce working set)
350
+ if (args.itemType) {
351
+ items = items.filter((item) => item.type === args.itemType);
352
+ }
332
353
  if (args.workflowState) {
333
354
  items = items.filter((item) => getFieldValue(item, "Workflow State") === args.workflowState);
334
355
  }
@@ -338,6 +359,26 @@ export function registerProjectTools(server, client, fieldCache) {
338
359
  if (args.priority) {
339
360
  items = items.filter((item) => getFieldValue(item, "Priority") === args.priority);
340
361
  }
362
+ // Filter by updatedSince
363
+ if (args.updatedSince) {
364
+ const since = parseDateMath(args.updatedSince).getTime();
365
+ items = items.filter((item) => {
366
+ const content = item.content;
367
+ const updatedAt = content?.updatedAt;
368
+ return updatedAt ? new Date(updatedAt).getTime() >= since : false;
369
+ });
370
+ }
371
+ // Filter by updatedBefore
372
+ if (args.updatedBefore) {
373
+ const before = parseDateMath(args.updatedBefore).getTime();
374
+ items = items.filter((item) => {
375
+ const content = item.content;
376
+ const updatedAt = content?.updatedAt;
377
+ return updatedAt ? new Date(updatedAt).getTime() < before : false;
378
+ });
379
+ }
380
+ // Apply limit after filtering
381
+ items = items.slice(0, args.limit || 50);
341
382
  // Format response
342
383
  const formattedItems = items.map((item) => {
343
384
  const content = item.content;
@@ -348,6 +389,7 @@ export function registerProjectTools(server, client, fieldCache) {
348
389
  title: content?.title,
349
390
  state: content?.state,
350
391
  url: content?.url,
392
+ updatedAt: content?.updatedAt ?? null,
351
393
  workflowState: getFieldValue(item, "Workflow State"),
352
394
  estimate: getFieldValue(item, "Estimate"),
353
395
  priority: getFieldValue(item, "Priority"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-hero-mcp-server",
3
- "version": "2.4.7",
3
+ "version": "2.4.8",
4
4
  "description": "MCP server for GitHub Projects V2 - Ralph workflow automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",