ralph-hero-mcp-server 2.4.17 → 2.4.22

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,55 @@
1
+ /**
2
+ * Named filter profiles for Ralph agent roles.
3
+ *
4
+ * Maps profile names (e.g., "analyst-triage", "builder-active") to sets of
5
+ * filter parameters for list_issues and list_project_items. Agents use profiles
6
+ * instead of hardcoding individual filter params.
7
+ */
8
+ // --- Profile Registry ---
9
+ /**
10
+ * Named filter profiles mapping to concrete filter parameter objects.
11
+ * Profiles use kebab-case with role prefix: role-purpose.
12
+ */
13
+ export const FILTER_PROFILES = {
14
+ "analyst-triage": {
15
+ workflowState: "Backlog",
16
+ // TODO: add `no: "estimate"` when GH-141 (has/no presence filters) lands
17
+ },
18
+ "analyst-research": {
19
+ workflowState: "Research Needed",
20
+ },
21
+ "builder-active": {
22
+ workflowState: "In Progress",
23
+ },
24
+ "builder-planned": {
25
+ workflowState: "Plan in Review",
26
+ },
27
+ "validator-review": {
28
+ workflowState: "Plan in Review",
29
+ // TODO: multi-value workflowState for "Plan in Review" OR "In Review" when supported
30
+ },
31
+ "integrator-merge": {
32
+ workflowState: "In Review",
33
+ },
34
+ };
35
+ /**
36
+ * Valid profile names, derived from the registry keys.
37
+ */
38
+ export const VALID_PROFILE_NAMES = Object.keys(FILTER_PROFILES);
39
+ // --- Public API ---
40
+ /**
41
+ * Expand a named profile into its filter parameters.
42
+ *
43
+ * Returns a shallow copy of the profile's filter params (safe to mutate).
44
+ * Throws if the profile name is not recognized.
45
+ */
46
+ export function expandProfile(name) {
47
+ const profile = FILTER_PROFILES[name];
48
+ if (!profile) {
49
+ throw new Error(`Unknown filter profile "${name}". ` +
50
+ `Valid profiles: ${VALID_PROFILE_NAMES.join(", ")}. ` +
51
+ `Recovery: retry with one of the valid profile names listed above.`);
52
+ }
53
+ return { ...profile };
54
+ }
55
+ //# sourceMappingURL=filter-profiles.js.map
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Velocity metrics, risk scoring, and auto-status determination — pure functions.
3
+ *
4
+ * All functions are side-effect-free: dashboard data in, metrics out.
5
+ * Designed to complement lib/dashboard.ts without modifying it.
6
+ */
7
+ export const DEFAULT_METRICS_CONFIG = {
8
+ velocityWindowDays: 7,
9
+ atRiskThreshold: 2,
10
+ offTrackThreshold: 6,
11
+ severityWeights: { critical: 3, warning: 1, info: 0 },
12
+ };
13
+ // ---------------------------------------------------------------------------
14
+ // calculateVelocity
15
+ // ---------------------------------------------------------------------------
16
+ /**
17
+ * Count items that moved to Done within a time window.
18
+ *
19
+ * Uses closedAt if available, otherwise falls back to updatedAt.
20
+ * Only counts items whose workflowState is "Done".
21
+ */
22
+ export function calculateVelocity(items, windowDays, now) {
23
+ const windowMs = windowDays * 24 * 60 * 60 * 1000;
24
+ return items.filter((item) => {
25
+ if (item.workflowState !== "Done")
26
+ return false;
27
+ const ts = item.closedAt ?? item.updatedAt;
28
+ return now - new Date(ts).getTime() <= windowMs;
29
+ }).length;
30
+ }
31
+ // ---------------------------------------------------------------------------
32
+ // calculateRiskScore
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Aggregate health warnings into a numeric risk score.
36
+ *
37
+ * Each warning contributes its severity weight. Unknown severities
38
+ * default to 0.
39
+ */
40
+ export function calculateRiskScore(warnings, weights) {
41
+ return warnings.reduce((score, w) => score + (weights[w.severity] ?? 0), 0);
42
+ }
43
+ // ---------------------------------------------------------------------------
44
+ // determineStatus
45
+ // ---------------------------------------------------------------------------
46
+ /**
47
+ * Map a risk score to a project health status using configurable thresholds.
48
+ */
49
+ export function determineStatus(riskScore, config) {
50
+ if (riskScore >= config.offTrackThreshold)
51
+ return "OFF_TRACK";
52
+ if (riskScore >= config.atRiskThreshold)
53
+ return "AT_RISK";
54
+ return "ON_TRACK";
55
+ }
56
+ // ---------------------------------------------------------------------------
57
+ // extractHighlights
58
+ // ---------------------------------------------------------------------------
59
+ /**
60
+ * Extract recently completed and newly added items from dashboard data.
61
+ *
62
+ * - recentlyCompleted: items in the "Done" phase (already time-filtered
63
+ * by aggregateByPhase)
64
+ * - newlyAdded: items in "Backlog" whose ageHours is within the window
65
+ * (approximation since DashboardItem lacks createdAt)
66
+ */
67
+ export function extractHighlights(data, windowDays, now) {
68
+ void now; // reserved for future createdAt-based filtering
69
+ const donePhase = data.phases.find((p) => p.state === "Done");
70
+ const recentlyCompleted = (donePhase?.issues ?? []).map((i) => ({
71
+ number: i.number,
72
+ title: i.title,
73
+ }));
74
+ const backlogPhase = data.phases.find((p) => p.state === "Backlog");
75
+ const maxAgeHours = windowDays * 24;
76
+ const newlyAdded = (backlogPhase?.issues ?? [])
77
+ .filter((i) => i.ageHours < maxAgeHours)
78
+ .map((i) => ({
79
+ number: i.number,
80
+ title: i.title,
81
+ }));
82
+ return { recentlyCompleted, newlyAdded };
83
+ }
84
+ // ---------------------------------------------------------------------------
85
+ // calculateMetrics
86
+ // ---------------------------------------------------------------------------
87
+ /**
88
+ * Convenience orchestrator: compute all metrics from raw items and
89
+ * dashboard data in one call.
90
+ */
91
+ export function calculateMetrics(items, data, config = DEFAULT_METRICS_CONFIG, now = Date.now()) {
92
+ const velocity = calculateVelocity(items, config.velocityWindowDays, now);
93
+ const riskScore = calculateRiskScore(data.health.warnings, config.severityWeights);
94
+ const status = determineStatus(riskScore, config);
95
+ const highlights = extractHighlights(data, config.velocityWindowDays, now);
96
+ return { velocity, riskScore, status, highlights };
97
+ }
98
+ //# sourceMappingURL=metrics.js.map
@@ -9,6 +9,7 @@ import { z } from "zod";
9
9
  import { paginateConnection } from "../lib/pagination.js";
10
10
  import { buildDashboard, formatMarkdown, formatAscii, DEFAULT_HEALTH_CONFIG, } from "../lib/dashboard.js";
11
11
  import { toolSuccess, toolError, resolveProjectOwner } from "../types.js";
12
+ import { calculateMetrics, DEFAULT_METRICS_CONFIG, } from "../lib/metrics.js";
12
13
  // ---------------------------------------------------------------------------
13
14
  // Helper: Ensure field option cache is populated
14
15
  // ---------------------------------------------------------------------------
@@ -176,6 +177,26 @@ export function registerDashboardTools(server, client, fieldCache) {
176
177
  .optional()
177
178
  .default(10)
178
179
  .describe("Max issues to list per phase (default: 10)"),
180
+ includeMetrics: z
181
+ .boolean()
182
+ .optional()
183
+ .default(false)
184
+ .describe("Include velocity metrics, risk score, and auto-status (default: false)"),
185
+ velocityWindowDays: z
186
+ .number()
187
+ .optional()
188
+ .default(7)
189
+ .describe("Days to look back for velocity calculation (default: 7)"),
190
+ atRiskThreshold: z
191
+ .number()
192
+ .optional()
193
+ .default(2)
194
+ .describe("Risk score threshold for AT_RISK status (default: 2)"),
195
+ offTrackThreshold: z
196
+ .number()
197
+ .optional()
198
+ .default(6)
199
+ .describe("Risk score threshold for OFF_TRACK status (default: 6)"),
179
200
  }, async (args) => {
180
201
  try {
181
202
  const owner = args.owner || resolveProjectOwner(client.config);
@@ -215,6 +236,17 @@ export function registerDashboardTools(server, client, fieldCache) {
215
236
  for (const phase of dashboard.phases) {
216
237
  phase.issues = phase.issues.slice(0, issuesPerPhase);
217
238
  }
239
+ // Compute metrics if requested
240
+ let metrics;
241
+ if (args.includeMetrics) {
242
+ const metricsConfig = {
243
+ ...DEFAULT_METRICS_CONFIG,
244
+ velocityWindowDays: args.velocityWindowDays ?? 7,
245
+ atRiskThreshold: args.atRiskThreshold ?? 2,
246
+ offTrackThreshold: args.offTrackThreshold ?? 6,
247
+ };
248
+ metrics = calculateMetrics(dashboardItems, dashboard, metricsConfig);
249
+ }
218
250
  // Format output
219
251
  const format = args.format ?? "json";
220
252
  let formatted;
@@ -227,6 +259,7 @@ export function registerDashboardTools(server, client, fieldCache) {
227
259
  return toolSuccess({
228
260
  ...dashboard,
229
261
  ...(formatted !== undefined ? { formatted } : {}),
262
+ ...(metrics !== undefined ? { metrics } : {}),
230
263
  });
231
264
  }
232
265
  catch (error) {
@@ -11,6 +11,7 @@ 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
13
  import { parseDateMath } from "../lib/date-math.js";
14
+ import { expandProfile } from "../lib/filter-profiles.js";
14
15
  import { toolSuccess, toolError } from "../types.js";
15
16
  import { ensureFieldCache, resolveIssueNodeId, resolveProjectItemId, updateProjectItemField, getCurrentFieldValue, resolveConfig, resolveFullConfig, syncStatusField, } from "../lib/helpers.js";
16
17
  // ---------------------------------------------------------------------------
@@ -29,6 +30,11 @@ export function registerIssueTools(server, client, fieldCache) {
29
30
  .string()
30
31
  .optional()
31
32
  .describe("Repository name. Defaults to GITHUB_REPO env var"),
33
+ profile: z
34
+ .string()
35
+ .optional()
36
+ .describe("Named filter profile (e.g., 'analyst-triage', 'builder-active'). " +
37
+ "Profile filters are defaults; explicit params override them."),
32
38
  workflowState: z
33
39
  .string()
34
40
  .optional()
@@ -72,6 +78,15 @@ export function registerIssueTools(server, client, fieldCache) {
72
78
  .describe("Max items to return (default 50)"),
73
79
  }, async (args) => {
74
80
  try {
81
+ // Expand profile into filter defaults (explicit args override)
82
+ if (args.profile) {
83
+ const profileFilters = expandProfile(args.profile);
84
+ for (const [key, value] of Object.entries(profileFilters)) {
85
+ if (args[key] === undefined) {
86
+ args[key] = value;
87
+ }
88
+ }
89
+ }
75
90
  const { owner, repo, projectNumber, projectOwner } = resolveFullConfig(client, args);
76
91
  // Ensure field cache is populated
77
92
  await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
@@ -7,6 +7,7 @@
7
7
  import { z } from "zod";
8
8
  import { paginateConnection } from "../lib/pagination.js";
9
9
  import { parseDateMath } from "../lib/date-math.js";
10
+ import { expandProfile } from "../lib/filter-profiles.js";
10
11
  import { toolSuccess, toolError } from "../types.js";
11
12
  import { resolveProjectOwner } from "../types.js";
12
13
  const WORKFLOW_STATE_OPTIONS = [
@@ -240,6 +241,11 @@ export function registerProjectTools(server, client, fieldCache) {
240
241
  .number()
241
242
  .optional()
242
243
  .describe("Project number. Defaults to RALPH_GH_PROJECT_NUMBER env var"),
244
+ profile: z
245
+ .string()
246
+ .optional()
247
+ .describe("Named filter profile (e.g., 'analyst-triage', 'builder-active'). " +
248
+ "Profile filters are defaults; explicit params override them."),
243
249
  workflowState: z
244
250
  .string()
245
251
  .optional()
@@ -271,6 +277,15 @@ export function registerProjectTools(server, client, fieldCache) {
271
277
  .describe("Max items to return (default 50)"),
272
278
  }, async (args) => {
273
279
  try {
280
+ // Expand profile into filter defaults (explicit args override)
281
+ if (args.profile) {
282
+ const profileFilters = expandProfile(args.profile);
283
+ for (const [key, value] of Object.entries(profileFilters)) {
284
+ if (args[key] === undefined) {
285
+ args[key] = value;
286
+ }
287
+ }
288
+ }
274
289
  const owner = args.owner || resolveProjectOwner(client.config);
275
290
  const projectNumber = args.number || client.config.projectNumber;
276
291
  if (!owner) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-hero-mcp-server",
3
- "version": "2.4.17",
3
+ "version": "2.4.22",
4
4
  "description": "MCP server for GitHub Projects V2 - Ralph workflow automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",