ralph-hero-mcp-server 2.4.43 → 2.4.48
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/lib/dashboard.js +156 -0
- package/dist/lib/helpers.js +13 -13
- package/dist/lib/routing-config.js +3 -3
- package/dist/tools/batch-tools.js +7 -7
- package/dist/tools/hygiene-tools.js +1 -1
- package/dist/tools/issue-tools.js +14 -14
- package/dist/tools/project-management-tools.js +25 -25
- package/dist/tools/project-tools.js +1 -1
- package/dist/tools/relationship-tools.js +9 -9
- package/dist/tools/routing-tools.js +2 -2
- package/dist/tools/view-tools.js +2 -2
- package/package.json +1 -1
package/dist/lib/dashboard.js
CHANGED
|
@@ -219,6 +219,52 @@ export function detectHealthIssues(phases, config = DEFAULT_HEALTH_CONFIG) {
|
|
|
219
219
|
return warnings;
|
|
220
220
|
}
|
|
221
221
|
// ---------------------------------------------------------------------------
|
|
222
|
+
// detectCrossProjectHealth
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
/**
|
|
225
|
+
* Detect health issues that span multiple projects.
|
|
226
|
+
*
|
|
227
|
+
* Currently detects:
|
|
228
|
+
* - unbalanced_workload: one project has >3x active items vs another
|
|
229
|
+
*
|
|
230
|
+
* "Active" = states in STATE_ORDER that are NOT terminal and NOT "Backlog".
|
|
231
|
+
*/
|
|
232
|
+
export function detectCrossProjectHealth(breakdowns) {
|
|
233
|
+
const warnings = [];
|
|
234
|
+
const projectNumbers = Object.keys(breakdowns).map(Number);
|
|
235
|
+
// Count active items per project
|
|
236
|
+
const activeCounts = [];
|
|
237
|
+
for (const pn of projectNumbers) {
|
|
238
|
+
const { phases } = breakdowns[pn];
|
|
239
|
+
let active = 0;
|
|
240
|
+
for (const phase of phases) {
|
|
241
|
+
if (phase.state !== "Backlog" &&
|
|
242
|
+
!TERMINAL_STATES.includes(phase.state) &&
|
|
243
|
+
STATE_ORDER.includes(phase.state)) {
|
|
244
|
+
active += phase.count;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (active > 0) {
|
|
248
|
+
activeCounts.push({ projectNumber: pn, count: active });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Need at least 2 projects with active items to compare
|
|
252
|
+
if (activeCounts.length < 2) {
|
|
253
|
+
return warnings;
|
|
254
|
+
}
|
|
255
|
+
const maxEntry = activeCounts.reduce((a, b) => a.count > b.count ? a : b);
|
|
256
|
+
const minEntry = activeCounts.reduce((a, b) => a.count < b.count ? a : b);
|
|
257
|
+
if (maxEntry.count > 3 * minEntry.count) {
|
|
258
|
+
warnings.push({
|
|
259
|
+
type: "unbalanced_workload",
|
|
260
|
+
severity: "warning",
|
|
261
|
+
message: `Unbalanced workload: project ${maxEntry.projectNumber} has ${maxEntry.count} active items vs project ${minEntry.projectNumber} with ${minEntry.count}`,
|
|
262
|
+
issues: [],
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return warnings;
|
|
266
|
+
}
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
222
268
|
// computeArchiveStats
|
|
223
269
|
// ---------------------------------------------------------------------------
|
|
224
270
|
const ARCHIVE_TERMINAL_STATES = new Set(["Done", "Canceled"]);
|
|
@@ -275,6 +321,45 @@ export function buildDashboard(items, config = DEFAULT_HEALTH_CONFIG, now = Date
|
|
|
275
321
|
const phases = aggregateByPhase(items, now, config);
|
|
276
322
|
const warnings = detectHealthIssues(phases, config);
|
|
277
323
|
const archive = computeArchiveStats(items, now, config.archiveThresholdDays, config.doneWindowDays);
|
|
324
|
+
// Per-project breakdown (only when items span multiple projects)
|
|
325
|
+
const projectGroups = new Map();
|
|
326
|
+
for (const item of items) {
|
|
327
|
+
if (item.projectNumber !== undefined) {
|
|
328
|
+
const group = projectGroups.get(item.projectNumber);
|
|
329
|
+
if (group) {
|
|
330
|
+
group.push(item);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
projectGroups.set(item.projectNumber, [item]);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
let projectBreakdowns;
|
|
338
|
+
if (projectGroups.size >= 2) {
|
|
339
|
+
projectBreakdowns = {};
|
|
340
|
+
for (const [pn, projectItems] of projectGroups) {
|
|
341
|
+
const pPhases = aggregateByPhase(projectItems, now, config);
|
|
342
|
+
const pWarnings = detectHealthIssues(pPhases, config);
|
|
343
|
+
projectBreakdowns[pn] = {
|
|
344
|
+
projectTitle: projectItems[0].projectTitle ?? `Project ${pn}`,
|
|
345
|
+
phases: pPhases,
|
|
346
|
+
health: { ok: pWarnings.length === 0, warnings: pWarnings },
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// Cross-project health detection
|
|
350
|
+
const crossWarnings = detectCrossProjectHealth(projectBreakdowns);
|
|
351
|
+
if (crossWarnings.length > 0) {
|
|
352
|
+
warnings.push(...crossWarnings);
|
|
353
|
+
// Re-sort by severity
|
|
354
|
+
const severityRank = {
|
|
355
|
+
critical: 0,
|
|
356
|
+
warning: 1,
|
|
357
|
+
info: 2,
|
|
358
|
+
};
|
|
359
|
+
warnings.sort((a, b) => (severityRank[a.severity] ?? 99) -
|
|
360
|
+
(severityRank[b.severity] ?? 99));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
278
363
|
return {
|
|
279
364
|
generatedAt: new Date(now).toISOString(),
|
|
280
365
|
totalIssues: items.length,
|
|
@@ -284,6 +369,7 @@ export function buildDashboard(items, config = DEFAULT_HEALTH_CONFIG, now = Date
|
|
|
284
369
|
warnings,
|
|
285
370
|
},
|
|
286
371
|
archive,
|
|
372
|
+
...(projectBreakdowns ? { projectBreakdowns } : {}),
|
|
287
373
|
};
|
|
288
374
|
}
|
|
289
375
|
// ---------------------------------------------------------------------------
|
|
@@ -352,6 +438,45 @@ export function formatMarkdown(data, issuesPerPhase = 10) {
|
|
|
352
438
|
}
|
|
353
439
|
}
|
|
354
440
|
}
|
|
441
|
+
// Per-project breakdown (only for multi-project)
|
|
442
|
+
if (data.projectBreakdowns &&
|
|
443
|
+
Object.keys(data.projectBreakdowns).length > 1) {
|
|
444
|
+
lines.push("");
|
|
445
|
+
lines.push("## Per-Project Breakdown");
|
|
446
|
+
const sortedProjects = Object.entries(data.projectBreakdowns)
|
|
447
|
+
.map(([pn, bd]) => ({ projectNumber: Number(pn), ...bd }))
|
|
448
|
+
.sort((a, b) => a.projectNumber - b.projectNumber);
|
|
449
|
+
for (const project of sortedProjects) {
|
|
450
|
+
lines.push("");
|
|
451
|
+
lines.push(`### ${project.projectTitle}`);
|
|
452
|
+
lines.push("");
|
|
453
|
+
const nonZeroPhases = project.phases.filter((p) => p.count > 0);
|
|
454
|
+
if (nonZeroPhases.length > 0) {
|
|
455
|
+
lines.push("| Phase | Count |");
|
|
456
|
+
lines.push("|-------|------:|");
|
|
457
|
+
for (const phase of nonZeroPhases) {
|
|
458
|
+
lines.push(`| ${phase.state} | ${phase.count} |`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
lines.push("_No active items_");
|
|
463
|
+
}
|
|
464
|
+
lines.push("");
|
|
465
|
+
if (project.health.ok) {
|
|
466
|
+
lines.push("**Health**: All clear");
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
for (const w of project.health.warnings) {
|
|
470
|
+
const icon = w.severity === "critical"
|
|
471
|
+
? "[CRITICAL]"
|
|
472
|
+
: w.severity === "warning"
|
|
473
|
+
? "[WARNING]"
|
|
474
|
+
: "[INFO]";
|
|
475
|
+
lines.push(`- ${icon} ${w.message}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
355
480
|
return lines.join("\n");
|
|
356
481
|
}
|
|
357
482
|
/**
|
|
@@ -393,6 +518,37 @@ export function formatAscii(data) {
|
|
|
393
518
|
if (data.archive) {
|
|
394
519
|
lines.push(`Archive: ${data.archive.eligibleForArchive} eligible (threshold: ${data.archive.archiveThresholdDays}d), ${data.archive.recentlyCompleted} recent`);
|
|
395
520
|
}
|
|
521
|
+
// Per-project breakdown (only for multi-project)
|
|
522
|
+
if (data.projectBreakdowns &&
|
|
523
|
+
Object.keys(data.projectBreakdowns).length > 1) {
|
|
524
|
+
lines.push("");
|
|
525
|
+
lines.push("--- Per-Project ---");
|
|
526
|
+
const sortedProjects = Object.entries(data.projectBreakdowns)
|
|
527
|
+
.map(([pn, bd]) => ({ projectNumber: Number(pn), ...bd }))
|
|
528
|
+
.sort((a, b) => a.projectNumber - b.projectNumber);
|
|
529
|
+
for (const project of sortedProjects) {
|
|
530
|
+
lines.push("");
|
|
531
|
+
lines.push(project.projectTitle);
|
|
532
|
+
const nonZeroPhases = project.phases.filter((p) => p.count > 0);
|
|
533
|
+
if (nonZeroPhases.length > 0) {
|
|
534
|
+
const projMax = Math.max(1, ...nonZeroPhases.map((p) => p.count));
|
|
535
|
+
for (const phase of nonZeroPhases) {
|
|
536
|
+
const label = phase.state.padStart(20);
|
|
537
|
+
const barLen = Math.round((phase.count / projMax) * 20);
|
|
538
|
+
const bar = barLen > 0 ? "\u2588".repeat(barLen) : "\u2591";
|
|
539
|
+
lines.push(`${label} ${bar} ${phase.count}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (project.health.ok) {
|
|
543
|
+
lines.push(" Health: OK");
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
for (const w of project.health.warnings) {
|
|
547
|
+
lines.push(` ${w.severity.toUpperCase()}: ${w.message}`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
396
552
|
return lines.join("\n");
|
|
397
553
|
}
|
|
398
554
|
//# sourceMappingURL=dashboard.js.map
|
package/dist/lib/helpers.js
CHANGED
|
@@ -86,8 +86,8 @@ export async function resolveIssueNodeId(client, owner, repo, number) {
|
|
|
86
86
|
// ---------------------------------------------------------------------------
|
|
87
87
|
// Helper: Resolve issue's project item ID (for field updates)
|
|
88
88
|
// ---------------------------------------------------------------------------
|
|
89
|
-
export async function resolveProjectItemId(client, fieldCache, owner, repo, issueNumber) {
|
|
90
|
-
const projectId = fieldCache.getProjectId();
|
|
89
|
+
export async function resolveProjectItemId(client, fieldCache, owner, repo, issueNumber, projectNumber) {
|
|
90
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
91
91
|
if (!projectId) {
|
|
92
92
|
throw new Error("Field cache not populated - cannot resolve project item ID");
|
|
93
93
|
}
|
|
@@ -121,18 +121,18 @@ export async function resolveProjectItemId(client, fieldCache, owner, repo, issu
|
|
|
121
121
|
// ---------------------------------------------------------------------------
|
|
122
122
|
// Helper: Update a single-select field value on a project item
|
|
123
123
|
// ---------------------------------------------------------------------------
|
|
124
|
-
export async function updateProjectItemField(client, fieldCache, projectItemId, fieldName, optionName) {
|
|
125
|
-
const projectId = fieldCache.getProjectId();
|
|
124
|
+
export async function updateProjectItemField(client, fieldCache, projectItemId, fieldName, optionName, projectNumber) {
|
|
125
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
126
126
|
if (!projectId) {
|
|
127
127
|
throw new Error("Field cache not populated");
|
|
128
128
|
}
|
|
129
|
-
const fieldId = fieldCache.getFieldId(fieldName);
|
|
129
|
+
const fieldId = fieldCache.getFieldId(fieldName, projectNumber);
|
|
130
130
|
if (!fieldId) {
|
|
131
131
|
throw new Error(`Field "${fieldName}" not found in project`);
|
|
132
132
|
}
|
|
133
|
-
const optionId = fieldCache.resolveOptionId(fieldName, optionName);
|
|
133
|
+
const optionId = fieldCache.resolveOptionId(fieldName, optionName, projectNumber);
|
|
134
134
|
if (!optionId) {
|
|
135
|
-
const validOptions = fieldCache.getOptionNames(fieldName);
|
|
135
|
+
const validOptions = fieldCache.getOptionNames(fieldName, projectNumber);
|
|
136
136
|
throw new Error(`Option "${optionName}" not found for field "${fieldName}". ` +
|
|
137
137
|
`Valid options: ${validOptions.join(", ")}`);
|
|
138
138
|
}
|
|
@@ -150,8 +150,8 @@ export async function updateProjectItemField(client, fieldCache, projectItemId,
|
|
|
150
150
|
// ---------------------------------------------------------------------------
|
|
151
151
|
// Helper: Get current field value for an issue's project item
|
|
152
152
|
// ---------------------------------------------------------------------------
|
|
153
|
-
export async function getCurrentFieldValue(client, fieldCache, owner, repo, issueNumber, fieldName) {
|
|
154
|
-
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, issueNumber);
|
|
153
|
+
export async function getCurrentFieldValue(client, fieldCache, owner, repo, issueNumber, fieldName, projectNumber) {
|
|
154
|
+
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, issueNumber, projectNumber);
|
|
155
155
|
const result = await client.query(`query($itemId: ID!) {
|
|
156
156
|
node(id: $itemId) {
|
|
157
157
|
... on ProjectV2Item {
|
|
@@ -286,18 +286,18 @@ export function resolveFullConfig(client, args) {
|
|
|
286
286
|
* Sync the default Status field to match a Workflow State change.
|
|
287
287
|
* Best-effort: logs warning on failure but does not throw.
|
|
288
288
|
*/
|
|
289
|
-
export async function syncStatusField(client, fieldCache, projectItemId, workflowState) {
|
|
289
|
+
export async function syncStatusField(client, fieldCache, projectItemId, workflowState, projectNumber) {
|
|
290
290
|
const targetStatus = WORKFLOW_STATE_TO_STATUS[workflowState];
|
|
291
291
|
if (!targetStatus)
|
|
292
292
|
return;
|
|
293
|
-
const statusFieldId = fieldCache.getFieldId("Status");
|
|
293
|
+
const statusFieldId = fieldCache.getFieldId("Status", projectNumber);
|
|
294
294
|
if (!statusFieldId)
|
|
295
295
|
return;
|
|
296
|
-
const statusOptionId = fieldCache.resolveOptionId("Status", targetStatus);
|
|
296
|
+
const statusOptionId = fieldCache.resolveOptionId("Status", targetStatus, projectNumber);
|
|
297
297
|
if (!statusOptionId)
|
|
298
298
|
return;
|
|
299
299
|
try {
|
|
300
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Status", targetStatus);
|
|
300
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Status", targetStatus, projectNumber);
|
|
301
301
|
}
|
|
302
302
|
catch {
|
|
303
303
|
// Best-effort sync - don't fail the primary operation
|
|
@@ -75,7 +75,7 @@ export async function loadRoutingConfig(configPath) {
|
|
|
75
75
|
*
|
|
76
76
|
* Skips disabled rules (enabled === false).
|
|
77
77
|
*/
|
|
78
|
-
export function validateRulesLive(config, fieldCache) {
|
|
78
|
+
export function validateRulesLive(config, fieldCache, projectNumber) {
|
|
79
79
|
const errors = [];
|
|
80
80
|
for (let i = 0; i < config.rules.length; i++) {
|
|
81
81
|
const rule = config.rules[i];
|
|
@@ -84,9 +84,9 @@ export function validateRulesLive(config, fieldCache) {
|
|
|
84
84
|
continue;
|
|
85
85
|
// Validate workflowState references
|
|
86
86
|
if (rule.action.workflowState) {
|
|
87
|
-
const optionId = fieldCache.resolveOptionId("Workflow State", rule.action.workflowState);
|
|
87
|
+
const optionId = fieldCache.resolveOptionId("Workflow State", rule.action.workflowState, projectNumber);
|
|
88
88
|
if (optionId === undefined) {
|
|
89
|
-
const valid = fieldCache.getOptionNames("Workflow State");
|
|
89
|
+
const valid = fieldCache.getOptionNames("Workflow State", projectNumber);
|
|
90
90
|
errors.push({
|
|
91
91
|
phase: "live_validation",
|
|
92
92
|
path: ["rules", String(i), "action", "workflowState"],
|
|
@@ -181,16 +181,16 @@ export function registerBatchTools(server, client, fieldCache) {
|
|
|
181
181
|
const { owner, repo, projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
182
182
|
// Ensure field cache is populated
|
|
183
183
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
184
|
-
const projectId = fieldCache.getProjectId();
|
|
184
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
185
185
|
if (!projectId) {
|
|
186
186
|
return toolError("Could not resolve project ID");
|
|
187
187
|
}
|
|
188
188
|
// Validate option names up front (before any API calls)
|
|
189
189
|
for (const op of args.operations) {
|
|
190
190
|
const projectFieldName = FIELD_NAME_MAP[op.field];
|
|
191
|
-
const optionId = fieldCache.resolveOptionId(projectFieldName, op.value);
|
|
191
|
+
const optionId = fieldCache.resolveOptionId(projectFieldName, op.value, projectNumber);
|
|
192
192
|
if (!optionId) {
|
|
193
|
-
const validOptions = fieldCache.getOptionNames(projectFieldName);
|
|
193
|
+
const validOptions = fieldCache.getOptionNames(projectFieldName, projectNumber);
|
|
194
194
|
return toolError(`Invalid value "${op.value}" for field "${op.field}". ` +
|
|
195
195
|
`Valid options: ${validOptions.join(", ")}`);
|
|
196
196
|
}
|
|
@@ -280,8 +280,8 @@ export function registerBatchTools(server, client, fieldCache) {
|
|
|
280
280
|
for (let opIdx = 0; opIdx < args.operations.length; opIdx++) {
|
|
281
281
|
const op = args.operations[opIdx];
|
|
282
282
|
const projectFieldName = FIELD_NAME_MAP[op.field];
|
|
283
|
-
const fieldId = fieldCache.getFieldId(projectFieldName);
|
|
284
|
-
const optionId = fieldCache.resolveOptionId(projectFieldName, op.value);
|
|
283
|
+
const fieldId = fieldCache.getFieldId(projectFieldName, projectNumber);
|
|
284
|
+
const optionId = fieldCache.resolveOptionId(projectFieldName, op.value, projectNumber);
|
|
285
285
|
updates.push({
|
|
286
286
|
alias: `u${num}_${opIdx}`,
|
|
287
287
|
itemId: issue.projectItemId,
|
|
@@ -295,9 +295,9 @@ export function registerBatchTools(server, client, fieldCache) {
|
|
|
295
295
|
if (op.field === "workflow_state") {
|
|
296
296
|
const targetStatus = WORKFLOW_STATE_TO_STATUS[op.value];
|
|
297
297
|
if (targetStatus) {
|
|
298
|
-
const statusFieldId = fieldCache.getFieldId("Status");
|
|
298
|
+
const statusFieldId = fieldCache.getFieldId("Status", projectNumber);
|
|
299
299
|
const statusOptionId = statusFieldId
|
|
300
|
-
? fieldCache.resolveOptionId("Status", targetStatus)
|
|
300
|
+
? fieldCache.resolveOptionId("Status", targetStatus, projectNumber)
|
|
301
301
|
: undefined;
|
|
302
302
|
if (statusFieldId && statusOptionId) {
|
|
303
303
|
updates.push({
|
|
@@ -61,7 +61,7 @@ export function registerHygieneTools(server, client, fieldCache) {
|
|
|
61
61
|
}
|
|
62
62
|
// Ensure field cache
|
|
63
63
|
await ensureFieldCache(client, fieldCache, owner, projectNumber);
|
|
64
|
-
const projectId = fieldCache.getProjectId();
|
|
64
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
65
65
|
if (!projectId) {
|
|
66
66
|
return toolError("Could not resolve project ID");
|
|
67
67
|
}
|
|
@@ -122,7 +122,7 @@ export function registerIssueTools(server, client, fieldCache) {
|
|
|
122
122
|
const { owner, repo, projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
123
123
|
// Ensure field cache is populated
|
|
124
124
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
125
|
-
const projectId = fieldCache.getProjectId();
|
|
125
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
126
126
|
if (!projectId) {
|
|
127
127
|
return toolError("Could not resolve project ID");
|
|
128
128
|
}
|
|
@@ -575,7 +575,7 @@ export function registerIssueTools(server, client, fieldCache) {
|
|
|
575
575
|
.getCache()
|
|
576
576
|
.set(`issue-node-id:${owner}/${repo}#${issue.number}`, issue.id, 30 * 60 * 1000);
|
|
577
577
|
// Step 4: Add to project
|
|
578
|
-
const projectId = fieldCache.getProjectId();
|
|
578
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
579
579
|
if (!projectId) {
|
|
580
580
|
return toolError("Could not resolve project ID for adding issue to project");
|
|
581
581
|
}
|
|
@@ -594,13 +594,13 @@ export function registerIssueTools(server, client, fieldCache) {
|
|
|
594
594
|
.set(`project-item-id:${owner}/${repo}#${issue.number}`, projectItemId, 30 * 60 * 1000);
|
|
595
595
|
// Step 5: Set field values
|
|
596
596
|
if (args.workflowState) {
|
|
597
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Workflow State", args.workflowState);
|
|
597
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Workflow State", args.workflowState, projectNumber);
|
|
598
598
|
}
|
|
599
599
|
if (args.estimate) {
|
|
600
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Estimate", args.estimate);
|
|
600
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Estimate", args.estimate, projectNumber);
|
|
601
601
|
}
|
|
602
602
|
if (args.priority) {
|
|
603
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Priority", args.priority);
|
|
603
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Priority", args.priority, projectNumber);
|
|
604
604
|
}
|
|
605
605
|
return toolSuccess({
|
|
606
606
|
number: issue.number,
|
|
@@ -725,13 +725,13 @@ export function registerIssueTools(server, client, fieldCache) {
|
|
|
725
725
|
// Ensure field cache is populated
|
|
726
726
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
727
727
|
// Get current state for the response
|
|
728
|
-
const previousState = await getCurrentFieldValue(client, fieldCache, owner, repo, args.number, "Workflow State");
|
|
728
|
+
const previousState = await getCurrentFieldValue(client, fieldCache, owner, repo, args.number, "Workflow State", projectNumber);
|
|
729
729
|
// Resolve project item ID
|
|
730
|
-
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number);
|
|
730
|
+
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number, projectNumber);
|
|
731
731
|
// Update the field with the resolved state
|
|
732
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Workflow State", resolvedState);
|
|
732
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Workflow State", resolvedState, projectNumber);
|
|
733
733
|
// Sync default Status field (best-effort, one-way)
|
|
734
|
-
await syncStatusField(client, fieldCache, projectItemId, resolvedState);
|
|
734
|
+
await syncStatusField(client, fieldCache, projectItemId, resolvedState, projectNumber);
|
|
735
735
|
const result = {
|
|
736
736
|
number: args.number,
|
|
737
737
|
previousState: previousState || "(unknown)",
|
|
@@ -768,8 +768,8 @@ export function registerIssueTools(server, client, fieldCache) {
|
|
|
768
768
|
try {
|
|
769
769
|
const { owner, repo, projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
770
770
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
771
|
-
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number);
|
|
772
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Estimate", args.estimate);
|
|
771
|
+
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number, projectNumber);
|
|
772
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Estimate", args.estimate, projectNumber);
|
|
773
773
|
return toolSuccess({
|
|
774
774
|
number: args.number,
|
|
775
775
|
estimate: args.estimate,
|
|
@@ -800,8 +800,8 @@ export function registerIssueTools(server, client, fieldCache) {
|
|
|
800
800
|
try {
|
|
801
801
|
const { owner, repo, projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
802
802
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
803
|
-
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number);
|
|
804
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Priority", args.priority);
|
|
803
|
+
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number, projectNumber);
|
|
804
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Priority", args.priority, projectNumber);
|
|
805
805
|
return toolSuccess({
|
|
806
806
|
number: args.number,
|
|
807
807
|
priority: args.priority,
|
|
@@ -1057,7 +1057,7 @@ export function registerIssueTools(server, client, fieldCache) {
|
|
|
1057
1057
|
const { owner, repo, projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
1058
1058
|
// Ensure field cache is populated
|
|
1059
1059
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
1060
|
-
const projectId = fieldCache.getProjectId();
|
|
1060
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
1061
1061
|
if (!projectId) {
|
|
1062
1062
|
return toolError("Could not resolve project ID");
|
|
1063
1063
|
}
|
|
@@ -35,11 +35,11 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
35
35
|
try {
|
|
36
36
|
const { owner, repo, projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
37
37
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
38
|
-
const projectId = fieldCache.getProjectId();
|
|
38
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
39
39
|
if (!projectId) {
|
|
40
40
|
return toolError("Could not resolve project ID");
|
|
41
41
|
}
|
|
42
|
-
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number);
|
|
42
|
+
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number, projectNumber);
|
|
43
43
|
if (args.unarchive) {
|
|
44
44
|
await client.projectMutate(`mutation($projectId: ID!, $itemId: ID!) {
|
|
45
45
|
unarchiveProjectV2Item(input: {
|
|
@@ -84,11 +84,11 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
84
84
|
try {
|
|
85
85
|
const { owner, repo, projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
86
86
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
87
|
-
const projectId = fieldCache.getProjectId();
|
|
87
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
88
88
|
if (!projectId) {
|
|
89
89
|
return toolError("Could not resolve project ID");
|
|
90
90
|
}
|
|
91
|
-
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number);
|
|
91
|
+
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number, projectNumber);
|
|
92
92
|
await client.projectMutate(`mutation($projectId: ID!, $itemId: ID!) {
|
|
93
93
|
deleteProjectV2Item(input: {
|
|
94
94
|
projectId: $projectId,
|
|
@@ -122,7 +122,7 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
122
122
|
try {
|
|
123
123
|
const { owner, repo, projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
124
124
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
125
|
-
const projectId = fieldCache.getProjectId();
|
|
125
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
126
126
|
if (!projectId) {
|
|
127
127
|
return toolError("Could not resolve project ID");
|
|
128
128
|
}
|
|
@@ -164,7 +164,7 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
164
164
|
try {
|
|
165
165
|
const { projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
166
166
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
167
|
-
const projectId = fieldCache.getProjectId();
|
|
167
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
168
168
|
if (!projectId) {
|
|
169
169
|
return toolError("Could not resolve project ID");
|
|
170
170
|
}
|
|
@@ -232,17 +232,17 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
232
232
|
try {
|
|
233
233
|
const { owner, repo, projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
234
234
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
235
|
-
const projectId = fieldCache.getProjectId();
|
|
235
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
236
236
|
if (!projectId) {
|
|
237
237
|
return toolError("Could not resolve project ID");
|
|
238
238
|
}
|
|
239
|
-
const fieldId = fieldCache.getFieldId(args.field);
|
|
239
|
+
const fieldId = fieldCache.getFieldId(args.field, projectNumber);
|
|
240
240
|
if (!fieldId) {
|
|
241
|
-
const validFields = fieldCache.getFieldNames();
|
|
241
|
+
const validFields = fieldCache.getFieldNames(projectNumber);
|
|
242
242
|
return toolError(`Field "${args.field}" not found in project. ` +
|
|
243
243
|
`Valid fields: ${validFields.join(", ")}`);
|
|
244
244
|
}
|
|
245
|
-
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number);
|
|
245
|
+
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number, projectNumber);
|
|
246
246
|
await client.projectMutate(`mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!) {
|
|
247
247
|
clearProjectV2ItemFieldValue(input: {
|
|
248
248
|
projectId: $projectId,
|
|
@@ -280,7 +280,7 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
280
280
|
try {
|
|
281
281
|
const { projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
282
282
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
283
|
-
const projectId = fieldCache.getProjectId();
|
|
283
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
284
284
|
if (!projectId) {
|
|
285
285
|
return toolError("Could not resolve project ID");
|
|
286
286
|
}
|
|
@@ -296,15 +296,15 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
296
296
|
const projectItemId = result.addProjectV2DraftIssue.projectItem.id;
|
|
297
297
|
const fieldsSet = [];
|
|
298
298
|
if (args.workflowState) {
|
|
299
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Workflow State", args.workflowState);
|
|
299
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Workflow State", args.workflowState, projectNumber);
|
|
300
300
|
fieldsSet.push("Workflow State");
|
|
301
301
|
}
|
|
302
302
|
if (args.priority) {
|
|
303
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Priority", args.priority);
|
|
303
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Priority", args.priority, projectNumber);
|
|
304
304
|
fieldsSet.push("Priority");
|
|
305
305
|
}
|
|
306
306
|
if (args.estimate) {
|
|
307
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Estimate", args.estimate);
|
|
307
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Estimate", args.estimate, projectNumber);
|
|
308
308
|
fieldsSet.push("Estimate");
|
|
309
309
|
}
|
|
310
310
|
return toolSuccess({
|
|
@@ -375,14 +375,14 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
375
375
|
try {
|
|
376
376
|
const { owner, repo, projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
377
377
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
378
|
-
const projectId = fieldCache.getProjectId();
|
|
378
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
379
379
|
if (!projectId) {
|
|
380
380
|
return toolError("Could not resolve project ID");
|
|
381
381
|
}
|
|
382
|
-
const itemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number);
|
|
382
|
+
const itemId = await resolveProjectItemId(client, fieldCache, owner, repo, args.number, projectNumber);
|
|
383
383
|
let afterId;
|
|
384
384
|
if (args.afterNumber !== undefined) {
|
|
385
|
-
afterId = await resolveProjectItemId(client, fieldCache, owner, repo, args.afterNumber);
|
|
385
|
+
afterId = await resolveProjectItemId(client, fieldCache, owner, repo, args.afterNumber, projectNumber);
|
|
386
386
|
}
|
|
387
387
|
await client.projectMutate(`mutation($projectId: ID!, $itemId: ID!, $afterId: ID) {
|
|
388
388
|
updateProjectV2ItemPosition(input: {
|
|
@@ -457,7 +457,7 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
457
457
|
}
|
|
458
458
|
const { projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
459
459
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
460
|
-
const projectId = fieldCache.getProjectId();
|
|
460
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
461
461
|
if (!projectId) {
|
|
462
462
|
return toolError("Could not resolve project ID");
|
|
463
463
|
}
|
|
@@ -499,13 +499,13 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
499
499
|
}
|
|
500
500
|
const { projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
501
501
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
502
|
-
const projectId = fieldCache.getProjectId();
|
|
502
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
503
503
|
if (!projectId) {
|
|
504
504
|
return toolError("Could not resolve project ID");
|
|
505
505
|
}
|
|
506
|
-
const fieldId = fieldCache.getFieldId(args.field);
|
|
506
|
+
const fieldId = fieldCache.getFieldId(args.field, projectNumber);
|
|
507
507
|
if (!fieldId) {
|
|
508
|
-
const validFields = fieldCache.getFieldNames();
|
|
508
|
+
const validFields = fieldCache.getFieldNames(projectNumber);
|
|
509
509
|
return toolError(`Field "${args.field}" not found in project. ` +
|
|
510
510
|
`Valid fields: ${validFields.join(", ")}`);
|
|
511
511
|
}
|
|
@@ -562,7 +562,7 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
562
562
|
}
|
|
563
563
|
const { projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
564
564
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
565
|
-
const projectId = fieldCache.getProjectId();
|
|
565
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
566
566
|
if (!projectId) {
|
|
567
567
|
return toolError("Could not resolve project ID");
|
|
568
568
|
}
|
|
@@ -639,7 +639,7 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
639
639
|
try {
|
|
640
640
|
const { projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
641
641
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
642
|
-
const projectId = fieldCache.getProjectId();
|
|
642
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
643
643
|
if (!projectId) {
|
|
644
644
|
return toolError("Could not resolve project ID");
|
|
645
645
|
}
|
|
@@ -813,7 +813,7 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
813
813
|
try {
|
|
814
814
|
const { projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
815
815
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
816
|
-
const projectId = fieldCache.getProjectId();
|
|
816
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
817
817
|
if (!projectId) {
|
|
818
818
|
return toolError("Could not resolve project ID");
|
|
819
819
|
}
|
|
@@ -949,7 +949,7 @@ export function registerProjectManagementTools(server, client, fieldCache) {
|
|
|
949
949
|
try {
|
|
950
950
|
const { projectNumber, projectOwner } = resolveFullConfig(client, args);
|
|
951
951
|
await ensureFieldCache(client, fieldCache, projectOwner, projectNumber);
|
|
952
|
-
const projectId = fieldCache.getProjectId();
|
|
952
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
953
953
|
if (!projectId) {
|
|
954
954
|
return toolError("Could not resolve project ID");
|
|
955
955
|
}
|
|
@@ -577,7 +577,7 @@ export function registerProjectTools(server, client, fieldCache) {
|
|
|
577
577
|
}
|
|
578
578
|
// Ensure field cache is populated
|
|
579
579
|
await ensureFieldCache(client, fieldCache, owner, projectNumber);
|
|
580
|
-
const projectId = fieldCache.getProjectId();
|
|
580
|
+
const projectId = fieldCache.getProjectId(projectNumber);
|
|
581
581
|
if (!projectId) {
|
|
582
582
|
return toolError("Could not resolve project ID");
|
|
583
583
|
}
|
|
@@ -452,7 +452,7 @@ export function registerRelationshipTools(server, client, fieldCache) {
|
|
|
452
452
|
for (const issueNum of issueNumbers) {
|
|
453
453
|
try {
|
|
454
454
|
// Get current workflow state
|
|
455
|
-
const currentState = await getCurrentFieldValue(client, fieldCache, owner, repo, issueNum, "Workflow State");
|
|
455
|
+
const currentState = await getCurrentFieldValue(client, fieldCache, owner, repo, issueNum, "Workflow State", projectNumber);
|
|
456
456
|
if (!currentState) {
|
|
457
457
|
skipped.push({
|
|
458
458
|
number: issueNum,
|
|
@@ -473,10 +473,10 @@ export function registerRelationshipTools(server, client, fieldCache) {
|
|
|
473
473
|
continue;
|
|
474
474
|
}
|
|
475
475
|
// Advance the issue
|
|
476
|
-
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, issueNum);
|
|
477
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Workflow State", args.targetState);
|
|
476
|
+
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, issueNum, projectNumber);
|
|
477
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Workflow State", args.targetState, projectNumber);
|
|
478
478
|
// Sync default Status field (best-effort, one-way)
|
|
479
|
-
await syncStatusField(client, fieldCache, projectItemId, args.targetState);
|
|
479
|
+
await syncStatusField(client, fieldCache, projectItemId, args.targetState, projectNumber);
|
|
480
480
|
advanced.push({
|
|
481
481
|
number: issueNum,
|
|
482
482
|
fromState: currentState,
|
|
@@ -573,7 +573,7 @@ export function registerRelationshipTools(server, client, fieldCache) {
|
|
|
573
573
|
const childStates = [];
|
|
574
574
|
let minStateIdx = Infinity;
|
|
575
575
|
for (const sibling of siblings) {
|
|
576
|
-
const currentState = await getCurrentFieldValue(client, fieldCache, owner, repo, sibling.number, "Workflow State");
|
|
576
|
+
const currentState = await getCurrentFieldValue(client, fieldCache, owner, repo, sibling.number, "Workflow State", projectNumber);
|
|
577
577
|
const state = currentState || "unknown";
|
|
578
578
|
childStates.push({
|
|
579
579
|
number: sibling.number,
|
|
@@ -614,7 +614,7 @@ export function registerRelationshipTools(server, client, fieldCache) {
|
|
|
614
614
|
});
|
|
615
615
|
}
|
|
616
616
|
// Get parent's current workflow state
|
|
617
|
-
const parentState = await getCurrentFieldValue(client, fieldCache, owner, repo, parentNumber, "Workflow State");
|
|
617
|
+
const parentState = await getCurrentFieldValue(client, fieldCache, owner, repo, parentNumber, "Workflow State", projectNumber);
|
|
618
618
|
// Check if parent is already at or past the target state
|
|
619
619
|
const parentIdx = stateIndex(parentState || "");
|
|
620
620
|
if (parentIdx >= minStateIdx) {
|
|
@@ -631,9 +631,9 @@ export function registerRelationshipTools(server, client, fieldCache) {
|
|
|
631
631
|
});
|
|
632
632
|
}
|
|
633
633
|
// Advance the parent
|
|
634
|
-
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, parentNumber);
|
|
635
|
-
await updateProjectItemField(client, fieldCache, projectItemId, "Workflow State", minState);
|
|
636
|
-
await syncStatusField(client, fieldCache, projectItemId, minState);
|
|
634
|
+
const projectItemId = await resolveProjectItemId(client, fieldCache, owner, repo, parentNumber, projectNumber);
|
|
635
|
+
await updateProjectItemField(client, fieldCache, projectItemId, "Workflow State", minState, projectNumber);
|
|
636
|
+
await syncStatusField(client, fieldCache, projectItemId, minState, projectNumber);
|
|
637
637
|
return toolSuccess({
|
|
638
638
|
advanced: true,
|
|
639
639
|
parent: {
|
|
@@ -99,9 +99,9 @@ export function registerRoutingTools(server, client, fieldCache) {
|
|
|
99
99
|
for (let i = 0; i < config.rules.length; i++) {
|
|
100
100
|
const rule = config.rules[i];
|
|
101
101
|
if (rule.action.workflowState) {
|
|
102
|
-
const optionId = fieldCache.resolveOptionId("Workflow State", rule.action.workflowState);
|
|
102
|
+
const optionId = fieldCache.resolveOptionId("Workflow State", rule.action.workflowState, projectNumber);
|
|
103
103
|
if (optionId === undefined) {
|
|
104
|
-
const valid = fieldCache.getOptionNames("Workflow State");
|
|
104
|
+
const valid = fieldCache.getOptionNames("Workflow State", projectNumber);
|
|
105
105
|
errors.push({
|
|
106
106
|
ruleIndex: i,
|
|
107
107
|
field: "action.workflowState",
|
package/dist/tools/view-tools.js
CHANGED
|
@@ -99,9 +99,9 @@ export function registerViewTools(server, client, fieldCache) {
|
|
|
99
99
|
}
|
|
100
100
|
// Ensure field cache is populated
|
|
101
101
|
await ensureFieldCache(client, fieldCache, owner, projectNumber);
|
|
102
|
-
const fieldId = fieldCache.getFieldId(args.fieldName);
|
|
102
|
+
const fieldId = fieldCache.getFieldId(args.fieldName, projectNumber);
|
|
103
103
|
if (!fieldId) {
|
|
104
|
-
return toolError(`Field "${args.fieldName}" not found. Available fields: ${fieldCache.getFieldNames().join(", ")}`);
|
|
104
|
+
return toolError(`Field "${args.fieldName}" not found. Available fields: ${fieldCache.getFieldNames(projectNumber).join(", ")}`);
|
|
105
105
|
}
|
|
106
106
|
const result = await client.projectMutate(`mutation($fieldId: ID!, $options: [ProjectV2SingleSelectFieldOptionInput!]!) {
|
|
107
107
|
updateProjectV2Field(input: {
|