ralph-hero-mcp-server 2.4.37 → 2.4.39
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 +4 -0
- package/dist/tools/project-tools.js +107 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -69,6 +69,9 @@ function initGitHubClient() {
|
|
|
69
69
|
.map((s) => parseInt(s.trim(), 10))
|
|
70
70
|
.filter((n) => !isNaN(n))
|
|
71
71
|
: undefined;
|
|
72
|
+
const templateProjectNumber = resolveEnv("RALPH_GH_TEMPLATE_PROJECT")
|
|
73
|
+
? parseInt(resolveEnv("RALPH_GH_TEMPLATE_PROJECT"), 10)
|
|
74
|
+
: undefined;
|
|
72
75
|
if (!owner) {
|
|
73
76
|
console.error("[ralph-hero] Warning: RALPH_GH_OWNER not set.\n" +
|
|
74
77
|
"Most tools require this. Set in your environment or .claude/ralph-hero.local.md");
|
|
@@ -93,6 +96,7 @@ function initGitHubClient() {
|
|
|
93
96
|
projectNumber,
|
|
94
97
|
projectNumbers,
|
|
95
98
|
projectOwner: projectOwner || undefined,
|
|
99
|
+
templateProjectNumber,
|
|
96
100
|
});
|
|
97
101
|
}
|
|
98
102
|
/**
|
|
@@ -106,6 +106,11 @@ export function registerProjectTools(server, client, fieldCache) {
|
|
|
106
106
|
server.tool("ralph_hero__setup_project", "Create a new GitHub Project V2 with Workflow State, Priority, and Estimate custom fields", {
|
|
107
107
|
owner: z.string().describe("GitHub owner (user or org)"),
|
|
108
108
|
title: z.string().describe("Project title").default("Ralph Workflow"),
|
|
109
|
+
templateProjectNumber: z
|
|
110
|
+
.number()
|
|
111
|
+
.optional()
|
|
112
|
+
.describe("Template project number to copy from. Overrides RALPH_GH_TEMPLATE_PROJECT env var. " +
|
|
113
|
+
"When set, copies the template project (views, fields, automations) instead of creating blank."),
|
|
109
114
|
}, async (args) => {
|
|
110
115
|
try {
|
|
111
116
|
const owner = args.owner || resolveProjectOwner(client.config);
|
|
@@ -137,31 +142,86 @@ export function registerProjectTools(server, client, fieldCache) {
|
|
|
137
142
|
if (!ownerId) {
|
|
138
143
|
return toolError(`Owner "${owner}" not found as user or organization`);
|
|
139
144
|
}
|
|
140
|
-
//
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
// Resolve template project number: arg > config > undefined (blank)
|
|
146
|
+
const templatePN = args.templateProjectNumber ?? client.config.templateProjectNumber;
|
|
147
|
+
let project;
|
|
148
|
+
let fieldResults;
|
|
149
|
+
if (templatePN) {
|
|
150
|
+
// --- Copy path: clone template project ---
|
|
151
|
+
// 1. Resolve template project node ID
|
|
152
|
+
const templateProject = await fetchProject(client, owner, templatePN);
|
|
153
|
+
if (!templateProject) {
|
|
154
|
+
return toolError(`Template project #${templatePN} not found for owner "${owner}"`);
|
|
155
|
+
}
|
|
156
|
+
// 2. Copy via copyProjectV2
|
|
157
|
+
const copyResult = await client.projectMutate(`mutation($projectId: ID!, $ownerId: ID!, $title: String!) {
|
|
158
|
+
copyProjectV2(input: {
|
|
159
|
+
projectId: $projectId
|
|
160
|
+
ownerId: $ownerId
|
|
161
|
+
title: $title
|
|
162
|
+
includeDraftIssues: false
|
|
163
|
+
}) {
|
|
164
|
+
projectV2 { id number url title }
|
|
148
165
|
}
|
|
166
|
+
}`, {
|
|
167
|
+
projectId: templateProject.id,
|
|
168
|
+
ownerId,
|
|
169
|
+
title: args.title,
|
|
170
|
+
});
|
|
171
|
+
project = copyResult.copyProjectV2.projectV2;
|
|
172
|
+
// 3. Fetch fields from the copied project to build fieldResults
|
|
173
|
+
const copiedProject = await fetchProject(client, owner, project.number);
|
|
174
|
+
if (!copiedProject) {
|
|
175
|
+
return toolError(`Copied project #${project.number} not found after creation`);
|
|
176
|
+
}
|
|
177
|
+
fieldResults = {};
|
|
178
|
+
for (const f of copiedProject.fields.nodes) {
|
|
179
|
+
if (f.options) {
|
|
180
|
+
fieldResults[f.name] = {
|
|
181
|
+
id: f.id,
|
|
182
|
+
options: f.options.map((o) => o.name),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
149
186
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
187
|
+
else {
|
|
188
|
+
// --- Blank path: existing createProjectV2 + field creation ---
|
|
189
|
+
const createResult = await client.projectMutate(`mutation($ownerId: ID!, $title: String!) {
|
|
190
|
+
createProjectV2(input: { ownerId: $ownerId, title: $title }) {
|
|
191
|
+
projectV2 {
|
|
192
|
+
id
|
|
193
|
+
number
|
|
194
|
+
url
|
|
195
|
+
title
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}`, { ownerId, title: args.title });
|
|
199
|
+
project = createResult.createProjectV2.projectV2;
|
|
200
|
+
fieldResults = {};
|
|
201
|
+
// Workflow State field
|
|
202
|
+
const wsField = await createSingleSelectField(client, project.id, "Workflow State", WORKFLOW_STATE_OPTIONS);
|
|
203
|
+
fieldResults["Workflow State"] = wsField;
|
|
204
|
+
// Priority field
|
|
205
|
+
const prioField = await createSingleSelectField(client, project.id, "Priority", PRIORITY_OPTIONS);
|
|
206
|
+
fieldResults["Priority"] = prioField;
|
|
207
|
+
// Estimate field
|
|
208
|
+
const estField = await createSingleSelectField(client, project.id, "Estimate", ESTIMATE_OPTIONS);
|
|
209
|
+
fieldResults["Estimate"] = estField;
|
|
210
|
+
}
|
|
211
|
+
// Shared: cache hydration (both paths)
|
|
164
212
|
await ensureFieldCacheForNewProject(client, fieldCache, owner, project.number);
|
|
213
|
+
// Link configured repo to new project (best-effort, both paths)
|
|
214
|
+
let repoLink;
|
|
215
|
+
const configOwner = client.config.owner;
|
|
216
|
+
const configRepo = client.config.repo;
|
|
217
|
+
if (configOwner && configRepo) {
|
|
218
|
+
try {
|
|
219
|
+
repoLink = await linkRepoAfterSetup(client, project.id, configOwner, configRepo);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Best-effort - don't fail setup if linking fails
|
|
223
|
+
}
|
|
224
|
+
}
|
|
165
225
|
return toolSuccess({
|
|
166
226
|
project: {
|
|
167
227
|
id: project.id,
|
|
@@ -170,6 +230,10 @@ export function registerProjectTools(server, client, fieldCache) {
|
|
|
170
230
|
title: project.title,
|
|
171
231
|
},
|
|
172
232
|
fields: fieldResults,
|
|
233
|
+
...(templatePN && {
|
|
234
|
+
copiedFrom: { templateProjectNumber: templatePN },
|
|
235
|
+
}),
|
|
236
|
+
...(repoLink && { repositoryLink: repoLink }),
|
|
173
237
|
});
|
|
174
238
|
}
|
|
175
239
|
catch (error) {
|
|
@@ -847,9 +911,27 @@ async function createSingleSelectField(client, projectId, fieldName, options) {
|
|
|
847
911
|
};
|
|
848
912
|
}
|
|
849
913
|
async function ensureFieldCacheForNewProject(client, fieldCache, owner, number) {
|
|
850
|
-
//
|
|
851
|
-
fieldCache
|
|
852
|
-
client.getCache().
|
|
914
|
+
// Invalidate query cache to force fresh API responses for the new project.
|
|
915
|
+
// Do NOT clear fieldCache — other projects' data must be preserved (GH-242).
|
|
916
|
+
client.getCache().invalidatePrefix("query:");
|
|
853
917
|
await ensureFieldCache(client, fieldCache, owner, number);
|
|
854
918
|
}
|
|
919
|
+
async function linkRepoAfterSetup(client, projectId, repoOwner, repoName) {
|
|
920
|
+
const repoResult = await client.query(`query($repoOwner: String!, $repoName: String!) {
|
|
921
|
+
repository(owner: $repoOwner, name: $repoName) { id }
|
|
922
|
+
}`, { repoOwner, repoName }, { cache: true, cacheTtlMs: 60 * 60 * 1000 });
|
|
923
|
+
const repoId = repoResult.repository?.id;
|
|
924
|
+
if (!repoId) {
|
|
925
|
+
return { linked: false, repository: `${repoOwner}/${repoName}` };
|
|
926
|
+
}
|
|
927
|
+
await client.projectMutate(`mutation($projectId: ID!, $repositoryId: ID!) {
|
|
928
|
+
linkProjectV2ToRepository(input: {
|
|
929
|
+
projectId: $projectId,
|
|
930
|
+
repositoryId: $repositoryId
|
|
931
|
+
}) {
|
|
932
|
+
repository { id }
|
|
933
|
+
}
|
|
934
|
+
}`, { projectId, repositoryId: repoId });
|
|
935
|
+
return { linked: true, repository: `${repoOwner}/${repoName}` };
|
|
936
|
+
}
|
|
855
937
|
//# sourceMappingURL=project-tools.js.map
|