ralph-hero-mcp-server 2.5.75 → 2.5.80
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 +3 -0
- package/dist/lib/activity.js +90 -0
- package/dist/lib/directions.js +52 -1
- package/dist/tools/activity-tools.js +36 -0
- package/dist/tools/directions-tools.js +137 -67
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28,6 +28,7 @@ import { registerDebugTools } from "./tools/debug-tools.js";
|
|
|
28
28
|
import { registerDecomposeTools } from "./tools/decompose-tools.js";
|
|
29
29
|
import { registerViewTools } from "./tools/view-tools.js";
|
|
30
30
|
import { registerPlanGraphTools } from "./tools/plan-graph-tools.js";
|
|
31
|
+
import { registerActivityTools } from "./tools/activity-tools.js";
|
|
31
32
|
/**
|
|
32
33
|
* Initialize the GitHub client from environment variables.
|
|
33
34
|
*/
|
|
@@ -401,6 +402,8 @@ async function main() {
|
|
|
401
402
|
registerViewTools(server, client, fieldCache);
|
|
402
403
|
// Plan graph sync tool (sync plan dependency edges to GitHub)
|
|
403
404
|
registerPlanGraphTools(server, client);
|
|
405
|
+
// Activity log reader (recent_activity tool — pure filesystem, no GitHub client)
|
|
406
|
+
registerActivityTools(server);
|
|
404
407
|
// Debug tools (only when RALPH_DEBUG=true)
|
|
405
408
|
if (process.env.RALPH_DEBUG === 'true') {
|
|
406
409
|
registerDebugTools(server, client);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure read library for the local ralph-hero activity log.
|
|
3
|
+
*
|
|
4
|
+
* The log lives at `~/.ralph-hero/activity/YYYY/MM/DD.jsonl` (or a
|
|
5
|
+
* configurable root). One JSON object per line, append-only. Hooks
|
|
6
|
+
* write the file via `record-activity.sh`; this library only reads.
|
|
7
|
+
*
|
|
8
|
+
* Determinism: pure functions. Time is injected via `ActivityReadConfig.now`
|
|
9
|
+
* for tests. Filesystem reads are the only side effect.
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
export function readActivity(config) {
|
|
14
|
+
if (!fs.existsSync(config.rootDir)) {
|
|
15
|
+
return { events: [], cursor_advanced_to: null, skipped_lines: 0 };
|
|
16
|
+
}
|
|
17
|
+
const sinceTs = config.since ? new Date(config.since).getTime() : 0;
|
|
18
|
+
const untilTs = config.until ? new Date(config.until).getTime() : Number.MAX_SAFE_INTEGER;
|
|
19
|
+
if (config.since && Number.isNaN(sinceTs)) {
|
|
20
|
+
throw new Error(`Invalid 'since' format: ${config.since}`);
|
|
21
|
+
}
|
|
22
|
+
if (config.until && Number.isNaN(untilTs)) {
|
|
23
|
+
throw new Error(`Invalid 'until' format: ${config.until}`);
|
|
24
|
+
}
|
|
25
|
+
const events = [];
|
|
26
|
+
let skipped = 0;
|
|
27
|
+
// Walk YYYY/MM/DD structure
|
|
28
|
+
const years = safeReadDir(config.rootDir).filter((d) => /^\d{4}$/.test(d)).sort();
|
|
29
|
+
for (const y of years) {
|
|
30
|
+
const yDir = path.join(config.rootDir, y);
|
|
31
|
+
const months = safeReadDir(yDir).filter((d) => /^\d{2}$/.test(d)).sort();
|
|
32
|
+
for (const m of months) {
|
|
33
|
+
const mDir = path.join(yDir, m);
|
|
34
|
+
const days = safeReadDir(mDir).filter((d) => /^\d{2}\.jsonl$/.test(d)).sort();
|
|
35
|
+
for (const dFile of days) {
|
|
36
|
+
const filePath = path.join(mDir, dFile);
|
|
37
|
+
const content = safeReadFile(filePath);
|
|
38
|
+
if (content === null)
|
|
39
|
+
continue;
|
|
40
|
+
for (const line of content.split("\n")) {
|
|
41
|
+
if (line.trim() === "")
|
|
42
|
+
continue;
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(line);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
skipped++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const eventTs = new Date(parsed.ts).getTime();
|
|
52
|
+
if (Number.isNaN(eventTs)) {
|
|
53
|
+
skipped++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (eventTs < sinceTs || eventTs > untilTs)
|
|
57
|
+
continue;
|
|
58
|
+
if (config.category !== "all" && parsed.category !== config.category)
|
|
59
|
+
continue;
|
|
60
|
+
if (config.kinds !== null && !config.kinds.includes(parsed.kind))
|
|
61
|
+
continue;
|
|
62
|
+
if (config.project !== null && parsed.project !== config.project)
|
|
63
|
+
continue;
|
|
64
|
+
events.push(parsed);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
events.sort((a, b) => a.ts.localeCompare(b.ts));
|
|
70
|
+
const limited = events.slice(0, config.limit);
|
|
71
|
+
const cursor = limited.length > 0 ? limited[limited.length - 1].ts : null;
|
|
72
|
+
return { events: limited, cursor_advanced_to: cursor, skipped_lines: skipped };
|
|
73
|
+
}
|
|
74
|
+
function safeReadDir(dir) {
|
|
75
|
+
try {
|
|
76
|
+
return fs.readdirSync(dir);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function safeReadFile(filePath) {
|
|
83
|
+
try {
|
|
84
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=activity.js.map
|
package/dist/lib/directions.js
CHANGED
|
@@ -34,6 +34,7 @@ export const DEFAULT_RANK_CONFIG = {
|
|
|
34
34
|
lockStaleHours: 24,
|
|
35
35
|
treeRecentDoneDays: 7,
|
|
36
36
|
prStaleHours: 24,
|
|
37
|
+
audience: "human",
|
|
37
38
|
};
|
|
38
39
|
// ---------------------------------------------------------------------------
|
|
39
40
|
// Internal scoring constants
|
|
@@ -60,6 +61,18 @@ const STALE_BOOST = -50;
|
|
|
60
61
|
const LOCK_STALE_BOOST = -100;
|
|
61
62
|
const TREE_CONTINUE_BOOST = -75;
|
|
62
63
|
const PR_REVIEW_REQUIRED_BOOST = -200;
|
|
64
|
+
/**
|
|
65
|
+
* Per-estimate penalty applied when audience === "agent". Larger items
|
|
66
|
+
* cost more (positive score), pushing them down the ranking so agent
|
|
67
|
+
* loops dispatch on XS/S items first.
|
|
68
|
+
*/
|
|
69
|
+
const ESTIMATE_PENALTY = {
|
|
70
|
+
XS: 0,
|
|
71
|
+
S: 0,
|
|
72
|
+
M: 20,
|
|
73
|
+
L: 40,
|
|
74
|
+
XL: 60,
|
|
75
|
+
};
|
|
63
76
|
const HOUR_MS = 60 * 60 * 1000;
|
|
64
77
|
const DAY_MS = 24 * HOUR_MS;
|
|
65
78
|
// ---------------------------------------------------------------------------
|
|
@@ -100,6 +113,20 @@ function hasOpenBlockers(item) {
|
|
|
100
113
|
function isLockState(state) {
|
|
101
114
|
return state !== null && LOCK_STATES.includes(state);
|
|
102
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Returns a positive score adjustment when audience === "agent" so larger
|
|
118
|
+
* estimates get pushed down the ranking. Returns 0 for human audience
|
|
119
|
+
* (existing behavior). Items with unknown estimate get a mid-tier penalty
|
|
120
|
+
* (30) so unestimated work doesn't accidentally outrank XS/S items.
|
|
121
|
+
*/
|
|
122
|
+
function audiencePenalty(item, audience) {
|
|
123
|
+
if (audience !== "agent")
|
|
124
|
+
return 0;
|
|
125
|
+
const est = item.estimate;
|
|
126
|
+
if (est === null || est === undefined)
|
|
127
|
+
return 30; // unknown: mid penalty
|
|
128
|
+
return ESTIMATE_PENALTY[est] ?? 30;
|
|
129
|
+
}
|
|
103
130
|
// ---------------------------------------------------------------------------
|
|
104
131
|
// Detection helpers
|
|
105
132
|
// ---------------------------------------------------------------------------
|
|
@@ -176,6 +203,9 @@ export function detectTreeContinue(item, allItems, config) {
|
|
|
176
203
|
export function scoreIssue(item, allItems, config) {
|
|
177
204
|
const tags = [];
|
|
178
205
|
let score = priorityScore(item.priority) + phaseScore(item.workflowState);
|
|
206
|
+
// Audience-aware estimate penalty (no-op for "human"; pushes XL items
|
|
207
|
+
// down for "agent" so autonomous loops favor XS/S work).
|
|
208
|
+
score += audiencePenalty(item, config.audience);
|
|
179
209
|
const lockStale = detectLockStale(item, config);
|
|
180
210
|
const treeContinue = detectTreeContinue(item, allItems, config);
|
|
181
211
|
// Stale boost (non-lock states only)
|
|
@@ -295,7 +325,20 @@ export function buildReason(kind, issue, pr, tags, config, linkedIssueNumber = n
|
|
|
295
325
|
const hours = Math.round(ageHours(issue.updatedAt, config.now));
|
|
296
326
|
const days = Math.max(1, Math.floor(hours / 24));
|
|
297
327
|
const dayLabel = days === 1 ? "day" : "days";
|
|
298
|
-
|
|
328
|
+
const priority = issue.priority;
|
|
329
|
+
if (priority === "P0") {
|
|
330
|
+
return `P0 stalled in ${phase} for ${days} ${dayLabel} — top of the queue`;
|
|
331
|
+
}
|
|
332
|
+
if (priority === "P1") {
|
|
333
|
+
return `P1 stalled in ${phase} for ${days} ${dayLabel} — likely the most unblocking thing`;
|
|
334
|
+
}
|
|
335
|
+
if (priority === "P2") {
|
|
336
|
+
return `Sitting in ${phase} for ${days} ${dayLabel} — small unblock if you have a moment`;
|
|
337
|
+
}
|
|
338
|
+
if (priority === "P3") {
|
|
339
|
+
return `Low-priority item in ${phase} for ${days} ${dayLabel}`;
|
|
340
|
+
}
|
|
341
|
+
return `Unprioritized in ${phase} for ${days} ${dayLabel}`;
|
|
299
342
|
}
|
|
300
343
|
if (issue.priority === "P0") {
|
|
301
344
|
return `P0 in ${phase} — top of the queue`;
|
|
@@ -423,6 +466,7 @@ export function rankDirections(items, openPRs, config) {
|
|
|
423
466
|
const reason = buildReason(c.kind, c.item, null, c.tags, config, null);
|
|
424
467
|
return {
|
|
425
468
|
rank,
|
|
469
|
+
recommended: false,
|
|
426
470
|
kind: c.kind,
|
|
427
471
|
issue: toDirectionIssue(c.item),
|
|
428
472
|
pr: null,
|
|
@@ -436,6 +480,7 @@ export function rankDirections(items, openPRs, config) {
|
|
|
436
480
|
const reason = buildReason("pr", null, p.pr, p.tags, config, p.linkedIssueNumber);
|
|
437
481
|
return {
|
|
438
482
|
rank,
|
|
483
|
+
recommended: false,
|
|
439
484
|
kind: "pr",
|
|
440
485
|
issue: null,
|
|
441
486
|
pr: toDirectionPR(p.pr),
|
|
@@ -444,6 +489,12 @@ export function rankDirections(items, openPRs, config) {
|
|
|
444
489
|
score: p.score,
|
|
445
490
|
};
|
|
446
491
|
});
|
|
492
|
+
// Mark the top-ranked entry as recommended. Both modes use this
|
|
493
|
+
// flag for selection: interactive picker pre-selects it; headless
|
|
494
|
+
// orchestrators dispatch on it.
|
|
495
|
+
if (directions.length > 0) {
|
|
496
|
+
directions[0].recommended = true;
|
|
497
|
+
}
|
|
447
498
|
return directions;
|
|
448
499
|
}
|
|
449
500
|
//# sourceMappingURL=directions.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import { readActivity } from "../lib/activity.js";
|
|
5
|
+
import { toolSuccess, toolError } from "../types.js";
|
|
6
|
+
function defaultActivityRoot() {
|
|
7
|
+
return process.env.RALPH_ACTIVITY_DIR ?? path.join(os.homedir(), ".ralph-hero", "activity");
|
|
8
|
+
}
|
|
9
|
+
export function registerActivityTools(server) {
|
|
10
|
+
server.tool("ralph_hero__recent_activity", "Read structured events from the local ralph-hero activity log since a cursor. Used by /catch-up to synthesize 'what changed since last time' narratives. Pure read; the log is written by harness hooks.", {
|
|
11
|
+
since: z.string().nullable().default(null).describe("ISO8601 timestamp lower bound; null = all of today"),
|
|
12
|
+
until: z.string().nullable().default(null).describe("Optional ISO8601 upper bound"),
|
|
13
|
+
kinds: z.array(z.string()).nullable().default(null).describe("Filter by event kind (e.g., ['pr_opened','issue_advanced'])"),
|
|
14
|
+
category: z.enum(["work", "meta", "all"]).default("work").describe("Filter by category; default 'work' excludes meta noise"),
|
|
15
|
+
project: z.string().nullable().default(null).describe("Filter by project name"),
|
|
16
|
+
limit: z.number().int().min(1).default(100).describe("Max events to return"),
|
|
17
|
+
}, async (params) => {
|
|
18
|
+
try {
|
|
19
|
+
const result = readActivity({
|
|
20
|
+
rootDir: defaultActivityRoot(),
|
|
21
|
+
since: params.since ?? null,
|
|
22
|
+
until: params.until ?? null,
|
|
23
|
+
kinds: params.kinds ?? null,
|
|
24
|
+
category: (params.category ?? "work"),
|
|
25
|
+
project: params.project ?? null,
|
|
26
|
+
limit: params.limit ?? 100,
|
|
27
|
+
now: new Date(),
|
|
28
|
+
});
|
|
29
|
+
return toolSuccess(result);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
return toolError(err instanceof Error ? err.message : String(err));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=activity-tools.js.map
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP
|
|
2
|
+
* MCP tools wrapping the pure ranker at `lib/directions.ts`.
|
|
3
3
|
*
|
|
4
|
-
* Exposes
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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. The skill is responsible for
|
|
10
|
+
* fetching open PRs (via `gh pr list`) and passing them in as a parameter
|
|
11
|
+
* — the MCP server does not itself open an Octokit-style PR API surface.
|
|
9
12
|
*
|
|
10
13
|
* Behaviour:
|
|
11
14
|
* 1. Resolve owner + project numbers from args or client config.
|
|
@@ -32,68 +35,27 @@ import { toolSuccess, toolError, resolveProjectOwner, resolveProjectNumbers, } f
|
|
|
32
35
|
// ---------------------------------------------------------------------------
|
|
33
36
|
const HOUR_MS = 60 * 60 * 1000;
|
|
34
37
|
// ---------------------------------------------------------------------------
|
|
35
|
-
//
|
|
38
|
+
// Shared schema fragments
|
|
36
39
|
// ---------------------------------------------------------------------------
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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) => {
|
|
40
|
+
const openPRSchema = z.object({
|
|
41
|
+
number: z.number(),
|
|
42
|
+
title: z.string(),
|
|
43
|
+
url: z.string(),
|
|
44
|
+
isDraft: z.boolean(),
|
|
45
|
+
reviewDecision: z
|
|
46
|
+
.string()
|
|
47
|
+
.nullable()
|
|
48
|
+
.describe("REVIEW_REQUIRED | APPROVED | CHANGES_REQUESTED | null"),
|
|
49
|
+
headRefName: z.string(),
|
|
50
|
+
createdAt: z.string().describe("ISO timestamp from gh pr list"),
|
|
51
|
+
});
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Shared implementation — extracted so both `hello_directions` (deprecated)
|
|
54
|
+
// and `next_actions` (current) can route through the same code path. Keep
|
|
55
|
+
// file-private (no export) — alias lives in this same file.
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
function makeRunDirections(client, fieldCache) {
|
|
58
|
+
return async function runDirections(args) {
|
|
97
59
|
try {
|
|
98
60
|
const owner = args.owner || resolveProjectOwner(client.config);
|
|
99
61
|
if (!owner) {
|
|
@@ -124,6 +86,7 @@ export function registerDirectionsTools(server, client, fieldCache) {
|
|
|
124
86
|
lockStaleHours: args.lockStaleHours ?? DEFAULT_RANK_CONFIG.lockStaleHours,
|
|
125
87
|
treeRecentDoneDays: args.treeRecentDoneDays ?? DEFAULT_RANK_CONFIG.treeRecentDoneDays,
|
|
126
88
|
prStaleHours: args.prStaleHours ?? DEFAULT_RANK_CONFIG.prStaleHours,
|
|
89
|
+
audience: args.audience,
|
|
127
90
|
now,
|
|
128
91
|
};
|
|
129
92
|
// Compute PR ageHours at the boundary so the lib never reads the wall clock.
|
|
@@ -154,6 +117,113 @@ export function registerDirectionsTools(server, client, fieldCache) {
|
|
|
154
117
|
const message = error instanceof Error ? error.message : String(error);
|
|
155
118
|
return toolError(`Failed to compute hello directions: ${message}`);
|
|
156
119
|
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Register
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
export function registerDirectionsTools(server, client, fieldCache) {
|
|
126
|
+
const runDirections = makeRunDirections(client, fieldCache);
|
|
127
|
+
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.", {
|
|
128
|
+
owner: z
|
|
129
|
+
.string()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe("GitHub owner. Defaults to RALPH_GH_OWNER env var."),
|
|
132
|
+
projectNumbers: z
|
|
133
|
+
.array(z.coerce.number())
|
|
134
|
+
.optional()
|
|
135
|
+
.describe("Project numbers to include. Defaults to RALPH_GH_PROJECT_NUMBERS or single configured project."),
|
|
136
|
+
limit: z
|
|
137
|
+
.number()
|
|
138
|
+
.int()
|
|
139
|
+
.nonnegative()
|
|
140
|
+
.optional()
|
|
141
|
+
.default(3)
|
|
142
|
+
.describe("Max directions to return (default: 3)."),
|
|
143
|
+
stuckThresholdHours: z
|
|
144
|
+
.number()
|
|
145
|
+
.nonnegative()
|
|
146
|
+
.optional()
|
|
147
|
+
.default(48)
|
|
148
|
+
.describe("Hours before a non-lock issue is considered stale (default: 48)."),
|
|
149
|
+
lockStaleHours: z
|
|
150
|
+
.number()
|
|
151
|
+
.nonnegative()
|
|
152
|
+
.optional()
|
|
153
|
+
.default(24)
|
|
154
|
+
.describe("Hours before a lock-state issue is considered stalled (default: 24)."),
|
|
155
|
+
treeRecentDoneDays: z
|
|
156
|
+
.number()
|
|
157
|
+
.nonnegative()
|
|
158
|
+
.optional()
|
|
159
|
+
.default(7)
|
|
160
|
+
.describe("Days within which a sibling Done event still pulls a tree forward (default: 7)."),
|
|
161
|
+
prStaleHours: z
|
|
162
|
+
.number()
|
|
163
|
+
.nonnegative()
|
|
164
|
+
.optional()
|
|
165
|
+
.default(24)
|
|
166
|
+
.describe("Hours before an open PR is considered stale (default: 24)."),
|
|
167
|
+
openPRs: z
|
|
168
|
+
.array(openPRSchema)
|
|
169
|
+
.optional()
|
|
170
|
+
.default([])
|
|
171
|
+
.describe("Open PRs gathered by the caller (e.g. via `gh pr list`). Drafts and APPROVED PRs are filtered internally."),
|
|
172
|
+
}, async (args) => {
|
|
173
|
+
return await runDirections({ ...args, audience: "human" });
|
|
174
|
+
});
|
|
175
|
+
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 must be passed in as a parameter.", {
|
|
176
|
+
owner: z
|
|
177
|
+
.string()
|
|
178
|
+
.optional()
|
|
179
|
+
.describe("GitHub owner. Defaults to RALPH_GH_OWNER env var."),
|
|
180
|
+
projectNumbers: z
|
|
181
|
+
.array(z.coerce.number())
|
|
182
|
+
.optional()
|
|
183
|
+
.describe("Project numbers to include. Defaults to RALPH_GH_PROJECT_NUMBERS or single configured project."),
|
|
184
|
+
limit: z
|
|
185
|
+
.number()
|
|
186
|
+
.int()
|
|
187
|
+
.nonnegative()
|
|
188
|
+
.optional()
|
|
189
|
+
.default(3)
|
|
190
|
+
.describe("Max directions to return (default: 3)."),
|
|
191
|
+
audience: z
|
|
192
|
+
.enum(["human", "agent"])
|
|
193
|
+
.optional()
|
|
194
|
+
.default("human")
|
|
195
|
+
.describe("Tilts scoring per consumer; agent penalizes large estimates to honor autonomous-loop XS/S preference (default: human)."),
|
|
196
|
+
stuckThresholdHours: z
|
|
197
|
+
.number()
|
|
198
|
+
.nonnegative()
|
|
199
|
+
.optional()
|
|
200
|
+
.default(48)
|
|
201
|
+
.describe("Hours before a non-lock issue is considered stale (default: 48)."),
|
|
202
|
+
lockStaleHours: z
|
|
203
|
+
.number()
|
|
204
|
+
.nonnegative()
|
|
205
|
+
.optional()
|
|
206
|
+
.default(24)
|
|
207
|
+
.describe("Hours before a lock-state issue is considered stalled (default: 24)."),
|
|
208
|
+
treeRecentDoneDays: z
|
|
209
|
+
.number()
|
|
210
|
+
.nonnegative()
|
|
211
|
+
.optional()
|
|
212
|
+
.default(7)
|
|
213
|
+
.describe("Days within which a sibling Done event still pulls a tree forward (default: 7)."),
|
|
214
|
+
prStaleHours: z
|
|
215
|
+
.number()
|
|
216
|
+
.nonnegative()
|
|
217
|
+
.optional()
|
|
218
|
+
.default(24)
|
|
219
|
+
.describe("Hours before an open PR is considered stale (default: 24)."),
|
|
220
|
+
openPRs: z
|
|
221
|
+
.array(openPRSchema)
|
|
222
|
+
.optional()
|
|
223
|
+
.default([])
|
|
224
|
+
.describe("Open PRs gathered by the caller (e.g. via `gh pr list`). Drafts and APPROVED PRs are filtered internally."),
|
|
225
|
+
}, async (args) => {
|
|
226
|
+
return await runDirections({ ...args, audience: args.audience ?? "human" });
|
|
157
227
|
});
|
|
158
228
|
}
|
|
159
229
|
//# sourceMappingURL=directions-tools.js.map
|