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.
- package/dist/lib/date-math.js +57 -0
- package/dist/tools/issue-tools.js +42 -0
- package/dist/tools/project-tools.js +43 -1
- package/package.json +1 -1
|
@@ -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(
|
|
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"),
|