ralph-hero-mcp-server 2.5.79 → 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/tools/activity-tools.js +36 -0
- 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
|
|
@@ -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
|