ralph-hero-mcp-server 2.5.70 → 2.5.72
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 +106 -14
- package/dist/lib/hygiene.js +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* operations. Connects via stdio transport for use as a Claude Code
|
|
7
7
|
* plugin's bundled MCP server.
|
|
8
8
|
*/
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
9
10
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
12
|
import { createGitHubClient } from "./github-client.js";
|
|
@@ -34,27 +35,98 @@ function resolveEnv(name) {
|
|
|
34
35
|
return undefined;
|
|
35
36
|
return val;
|
|
36
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Module-level cache for the resolved `gh auth token` value.
|
|
40
|
+
*
|
|
41
|
+
* - `null` — not yet resolved (initial state).
|
|
42
|
+
* - `undefined` — resolved-to-failure (gh missing / unauthenticated / timed out).
|
|
43
|
+
* - `string` — resolved-to-success (the trimmed token).
|
|
44
|
+
*
|
|
45
|
+
* The `null` sentinel is intentional: `undefined` is itself a valid resolved
|
|
46
|
+
* value, so we need a separate "not-yet-resolved" marker.
|
|
47
|
+
*
|
|
48
|
+
* Exported for tests via `resetGhAuthTokenCache()` below.
|
|
49
|
+
*/
|
|
50
|
+
let cachedGhAuthToken = null;
|
|
51
|
+
/**
|
|
52
|
+
* Test-only helper to reset the module-level cache between cases.
|
|
53
|
+
* Production code never calls this — the cache is intentionally process-scoped.
|
|
54
|
+
*/
|
|
55
|
+
export function resetGhAuthTokenCache() {
|
|
56
|
+
cachedGhAuthToken = null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolve a GitHub token by invoking `gh auth token` as a subprocess.
|
|
60
|
+
*
|
|
61
|
+
* **Contract**: The result of this function flows ONLY into the internal
|
|
62
|
+
* `repoToken` variable. It is NEVER re-exported via `process.env.GH_TOKEN`
|
|
63
|
+
* or `process.env.GITHUB_TOKEN`. This preserves the anti-collision invariant
|
|
64
|
+
* documented in the contract test at
|
|
65
|
+
* `src/__tests__/init-config.test.ts` (the `forbiddenVars` list).
|
|
66
|
+
*
|
|
67
|
+
* The subprocess runs at most once per process (cached). On any failure
|
|
68
|
+
* (gh not installed, not authenticated, network timeout, etc.) the result
|
|
69
|
+
* is cached as `undefined` so subsequent callers don't re-incur the cost.
|
|
70
|
+
*
|
|
71
|
+
* - `timeout: 3000` — fail fast if gh is hung.
|
|
72
|
+
* - `stdio: ["ignore", "pipe", "ignore"]` — suppress stderr noise on the
|
|
73
|
+
* common "not authenticated" error path.
|
|
74
|
+
*
|
|
75
|
+
* @returns The trimmed token string on success, otherwise `undefined`.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveGhAuthToken() {
|
|
78
|
+
if (cachedGhAuthToken !== null) {
|
|
79
|
+
return cachedGhAuthToken;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const output = execSync("gh auth token", {
|
|
83
|
+
encoding: "utf8",
|
|
84
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
85
|
+
timeout: 3000,
|
|
86
|
+
});
|
|
87
|
+
// execSync with encoding: "utf8" returns a string; coerce defensively in
|
|
88
|
+
// case a mock returns a Buffer.
|
|
89
|
+
const trimmed = output.toString().trim();
|
|
90
|
+
cachedGhAuthToken = trimmed.length > 0 ? trimmed : undefined;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
cachedGhAuthToken = undefined;
|
|
94
|
+
}
|
|
95
|
+
return cachedGhAuthToken ?? undefined;
|
|
96
|
+
}
|
|
37
97
|
function initGitHubClient(debugLogger) {
|
|
38
98
|
// Repo token: for repository operations (issues, PRs, comments)
|
|
39
|
-
|
|
99
|
+
// Resolution chain (highest priority first):
|
|
100
|
+
// 1. RALPH_GH_REPO_TOKEN — explicit split-token repo override
|
|
101
|
+
// 2. RALPH_HERO_GITHUB_TOKEN — legacy single-token form
|
|
102
|
+
// 3. gh auth token — gh CLI keychain (subprocess, cached)
|
|
103
|
+
const repoToken = resolveEnv("RALPH_GH_REPO_TOKEN") ||
|
|
104
|
+
resolveEnv("RALPH_HERO_GITHUB_TOKEN") ||
|
|
105
|
+
resolveGhAuthToken();
|
|
40
106
|
// Project token: for Projects V2 operations (fields, workflow state)
|
|
41
|
-
// Falls back to repo token if not set
|
|
107
|
+
// Falls back to repo token if not set (transitively gains the gh source).
|
|
42
108
|
const projectToken = resolveEnv("RALPH_GH_PROJECT_TOKEN") || repoToken;
|
|
43
109
|
if (!repoToken) {
|
|
44
110
|
console.error("[ralph-hero] Error: No GitHub token found.\n" +
|
|
45
111
|
"\n" +
|
|
46
|
-
"
|
|
112
|
+
"Quickest fix — authenticate gh (recommended):\n" +
|
|
113
|
+
"\n" +
|
|
114
|
+
" gh auth login -s repo,project,read:org\n" +
|
|
115
|
+
"\n" +
|
|
116
|
+
"Then restart Claude Code. The MCP server will pick up the token\n" +
|
|
117
|
+
"from the gh CLI keychain automatically.\n" +
|
|
118
|
+
"\n" +
|
|
119
|
+
"Alternative — paste a PAT into Claude Code settings:\n" +
|
|
47
120
|
"\n" +
|
|
121
|
+
" Add to .claude/settings.local.json:\n" +
|
|
48
122
|
' {\n' +
|
|
49
123
|
' "env": {\n' +
|
|
50
124
|
' "RALPH_HERO_GITHUB_TOKEN": "ghp_your_token_here"\n' +
|
|
51
125
|
" }\n" +
|
|
52
126
|
" }\n" +
|
|
53
127
|
"\n" +
|
|
54
|
-
"
|
|
55
|
-
"\n" +
|
|
56
|
-
"Generate a token at: https://github.com/settings/tokens\n" +
|
|
57
|
-
"Required scopes: repo, project\n" +
|
|
128
|
+
" Generate a token at: https://github.com/settings/tokens\n" +
|
|
129
|
+
" Required scopes: repo, project\n" +
|
|
58
130
|
"\n" +
|
|
59
131
|
"For advanced setups (dual tokens, org projects), run /ralph-hero:setup.");
|
|
60
132
|
process.exit(1);
|
|
@@ -86,7 +158,9 @@ function initGitHubClient(debugLogger) {
|
|
|
86
158
|
}
|
|
87
159
|
const repoTokenSource = resolveEnv("RALPH_GH_REPO_TOKEN")
|
|
88
160
|
? "RALPH_GH_REPO_TOKEN"
|
|
89
|
-
: "RALPH_HERO_GITHUB_TOKEN"
|
|
161
|
+
: resolveEnv("RALPH_HERO_GITHUB_TOKEN")
|
|
162
|
+
? "RALPH_HERO_GITHUB_TOKEN"
|
|
163
|
+
: "gh auth (keychain)";
|
|
90
164
|
console.error(`[ralph-hero] Repo token: ${repoTokenSource}`);
|
|
91
165
|
if (projectToken !== repoToken) {
|
|
92
166
|
console.error(`[ralph-hero] Project token: RALPH_GH_PROJECT_TOKEN (separate)`);
|
|
@@ -221,7 +295,9 @@ function registerCoreTools(server, client) {
|
|
|
221
295
|
// Token source detection — re-derive which env vars resolved
|
|
222
296
|
const repoTokenSource = resolveEnv("RALPH_GH_REPO_TOKEN")
|
|
223
297
|
? "RALPH_GH_REPO_TOKEN"
|
|
224
|
-
: "RALPH_HERO_GITHUB_TOKEN"
|
|
298
|
+
: resolveEnv("RALPH_HERO_GITHUB_TOKEN")
|
|
299
|
+
? "RALPH_HERO_GITHUB_TOKEN"
|
|
300
|
+
: "gh auth (keychain)";
|
|
225
301
|
const projectTokenSource = resolveEnv("RALPH_GH_PROJECT_TOKEN")
|
|
226
302
|
? "RALPH_GH_PROJECT_TOKEN"
|
|
227
303
|
: repoTokenSource;
|
|
@@ -329,9 +405,25 @@ async function main() {
|
|
|
329
405
|
await server.connect(transport);
|
|
330
406
|
console.error("[ralph-hero] MCP server connected and ready.");
|
|
331
407
|
}
|
|
332
|
-
// Run
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
408
|
+
// Run only when invoked as the entry point (not when imported by tests).
|
|
409
|
+
// `process.argv[1]` is the script path Node was started with; if it ends with
|
|
410
|
+
// this module's compiled filename, we're the entry point. The fallback covers
|
|
411
|
+
// edge cases (e.g. argv[1] missing) by also accepting an environment opt-in.
|
|
412
|
+
const isEntryPoint = (() => {
|
|
413
|
+
try {
|
|
414
|
+
const argv1 = process.argv[1] ?? "";
|
|
415
|
+
return (argv1.endsWith("/dist/index.js") ||
|
|
416
|
+
argv1.endsWith("\\dist\\index.js") ||
|
|
417
|
+
process.env.RALPH_HERO_RUN_MAIN === "true");
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
})();
|
|
423
|
+
if (isEntryPoint) {
|
|
424
|
+
main().catch((error) => {
|
|
425
|
+
console.error("[ralph-hero] Fatal error:", error);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
});
|
|
428
|
+
}
|
|
337
429
|
//# sourceMappingURL=index.js.map
|
package/dist/lib/hygiene.js
CHANGED
|
@@ -35,6 +35,8 @@ function toHygieneItem(item, now) {
|
|
|
35
35
|
export function findArchiveCandidates(items, now, archiveDays) {
|
|
36
36
|
return items
|
|
37
37
|
.filter((item) => {
|
|
38
|
+
if (item.subIssueCount > 0)
|
|
39
|
+
return false;
|
|
38
40
|
const ws = item.workflowState;
|
|
39
41
|
if (!ws || !TERMINAL_STATES.includes(ws))
|
|
40
42
|
return false;
|