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 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
- // Step 2: Create project (project operation)
141
- const createResult = await client.projectMutate(`mutation($ownerId: ID!, $title: String!) {
142
- createProjectV2(input: { ownerId: $ownerId, title: $title }) {
143
- projectV2 {
144
- id
145
- number
146
- url
147
- title
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
- }`, { ownerId, title: args.title });
151
- const project = createResult.createProjectV2.projectV2;
152
- // Step 3: Create custom fields
153
- const fieldResults = {};
154
- // Workflow State field
155
- const wsField = await createSingleSelectField(client, project.id, "Workflow State", WORKFLOW_STATE_OPTIONS);
156
- fieldResults["Workflow State"] = wsField;
157
- // Priority field
158
- const prioField = await createSingleSelectField(client, project.id, "Priority", PRIORITY_OPTIONS);
159
- fieldResults["Priority"] = prioField;
160
- // Estimate field
161
- const estField = await createSingleSelectField(client, project.id, "Estimate", ESTIMATE_OPTIONS);
162
- fieldResults["Estimate"] = estField;
163
- // Populate the field cache for this project
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
- // Clear any stale cache and force refresh
851
- fieldCache.clear();
852
- client.getCache().clear();
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-hero-mcp-server",
3
- "version": "2.4.37",
3
+ "version": "2.4.39",
4
4
  "description": "MCP server for GitHub Projects V2 - Ralph workflow automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",