maxsimcli 4.9.0 → 4.11.0
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/README.md +180 -202
- package/dist/assets/CHANGELOG.md +57 -0
- package/dist/assets/templates/workflows/init-existing.md +93 -0
- package/dist/assets/templates/workflows/new-project.md +95 -2
- package/dist/cli.cjs +3964 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/mcp-server.cjs +277 -18
- package/dist/mcp-server.cjs.map +1 -1
- package/package.json +1 -1
package/dist/mcp-server.cjs
CHANGED
|
@@ -39,6 +39,7 @@ let child_process = require("child_process");
|
|
|
39
39
|
require("node:events");
|
|
40
40
|
let node_child_process = require("node:child_process");
|
|
41
41
|
let node_util = require("node:util");
|
|
42
|
+
let node_crypto = require("node:crypto");
|
|
42
43
|
|
|
43
44
|
//#region ../../node_modules/zod/v3/helpers/util.js
|
|
44
45
|
var util$2;
|
|
@@ -38059,9 +38060,9 @@ async function postPlanComment(phaseIssueNumber, planNumber, planContent) {
|
|
|
38059
38060
|
/**
|
|
38060
38061
|
* Close an issue. Optionally post a reason comment before closing.
|
|
38061
38062
|
*
|
|
38062
|
-
*
|
|
38063
|
+
* @param stateReason - 'completed' (default) or 'not_planned' (rollback/cancel)
|
|
38063
38064
|
*/
|
|
38064
|
-
async function closeIssue(issueNumber, reason) {
|
|
38065
|
+
async function closeIssue(issueNumber, reason, stateReason = "completed") {
|
|
38065
38066
|
return withGhResult(async () => {
|
|
38066
38067
|
const octokit = getOctokit();
|
|
38067
38068
|
const { owner, repo } = await getRepoInfo();
|
|
@@ -38076,7 +38077,7 @@ async function closeIssue(issueNumber, reason) {
|
|
|
38076
38077
|
repo,
|
|
38077
38078
|
issue_number: issueNumber,
|
|
38078
38079
|
state: "closed",
|
|
38079
|
-
state_reason:
|
|
38080
|
+
state_reason: stateReason
|
|
38080
38081
|
});
|
|
38081
38082
|
});
|
|
38082
38083
|
}
|
|
@@ -38098,7 +38099,7 @@ async function reopenIssue(issueNumber) {
|
|
|
38098
38099
|
/**
|
|
38099
38100
|
* Fetch a single issue by number.
|
|
38100
38101
|
*
|
|
38101
|
-
* Returns number, id, title, state, and
|
|
38102
|
+
* Returns number, id, title, state, body, updated_at, labels, and comments_url.
|
|
38102
38103
|
*/
|
|
38103
38104
|
async function getPhaseIssue(phaseIssueNumber) {
|
|
38104
38105
|
return withGhResult(async () => {
|
|
@@ -38109,12 +38110,16 @@ async function getPhaseIssue(phaseIssueNumber) {
|
|
|
38109
38110
|
repo,
|
|
38110
38111
|
issue_number: phaseIssueNumber
|
|
38111
38112
|
});
|
|
38113
|
+
const labels = response.data.labels.map((l) => typeof l === "string" ? l : l.name ?? "").filter(Boolean);
|
|
38112
38114
|
return {
|
|
38113
38115
|
number: response.data.number,
|
|
38114
38116
|
id: response.data.id,
|
|
38115
38117
|
title: response.data.title,
|
|
38116
38118
|
state: response.data.state,
|
|
38117
|
-
body: response.data.body ?? ""
|
|
38119
|
+
body: response.data.body ?? "",
|
|
38120
|
+
updated_at: response.data.updated_at,
|
|
38121
|
+
labels,
|
|
38122
|
+
comments_url: response.data.comments_url
|
|
38118
38123
|
};
|
|
38119
38124
|
});
|
|
38120
38125
|
}
|
|
@@ -38122,7 +38127,7 @@ async function getPhaseIssue(phaseIssueNumber) {
|
|
|
38122
38127
|
* List all sub-issues of a phase Issue.
|
|
38123
38128
|
*
|
|
38124
38129
|
* Uses GitHub's native sub-issues API.
|
|
38125
|
-
* Returns each sub-issue's number, id, title, and
|
|
38130
|
+
* Returns each sub-issue's number, id, title, state, and updated_at.
|
|
38126
38131
|
*/
|
|
38127
38132
|
async function listPhaseSubIssues(phaseIssueNumber) {
|
|
38128
38133
|
return withGhResult(async () => {
|
|
@@ -38136,7 +38141,8 @@ async function listPhaseSubIssues(phaseIssueNumber) {
|
|
|
38136
38141
|
number: issue.number,
|
|
38137
38142
|
id: issue.id,
|
|
38138
38143
|
title: issue.title,
|
|
38139
|
-
state: issue.state
|
|
38144
|
+
state: issue.state,
|
|
38145
|
+
updated_at: issue.updated_at ?? ""
|
|
38140
38146
|
}));
|
|
38141
38147
|
});
|
|
38142
38148
|
}
|
|
@@ -38258,6 +38264,13 @@ function mappingFilePath(cwd) {
|
|
|
38258
38264
|
return planningPath(cwd, MAPPING_FILENAME);
|
|
38259
38265
|
}
|
|
38260
38266
|
/**
|
|
38267
|
+
* Compute a SHA-256 hash of an issue body string.
|
|
38268
|
+
* Used for external edit detection (WIRE-06).
|
|
38269
|
+
*/
|
|
38270
|
+
function hashBody(body) {
|
|
38271
|
+
return (0, node_crypto.createHash)("sha256").update(body).digest("hex");
|
|
38272
|
+
}
|
|
38273
|
+
/**
|
|
38261
38274
|
* Load and parse the mapping file (local cache).
|
|
38262
38275
|
*
|
|
38263
38276
|
* Returns null if the file does not exist.
|
|
@@ -38287,6 +38300,39 @@ function saveMapping(cwd, mapping) {
|
|
|
38287
38300
|
node_fs.default.mkdirSync(dir, { recursive: true });
|
|
38288
38301
|
node_fs.default.writeFileSync(filePath, JSON.stringify(mapping, null, 2) + "\n", "utf-8");
|
|
38289
38302
|
}
|
|
38303
|
+
/**
|
|
38304
|
+
* Update a specific task's issue mapping within a phase.
|
|
38305
|
+
*
|
|
38306
|
+
* Load-modify-save pattern. Creates phase entry if it does not exist.
|
|
38307
|
+
* Merges partial data with existing entry (if any).
|
|
38308
|
+
*
|
|
38309
|
+
* @throws If mapping file does not exist (must be initialized first via saveMapping)
|
|
38310
|
+
*/
|
|
38311
|
+
function updateTaskMapping(cwd, phaseNum, taskId, data) {
|
|
38312
|
+
const mapping = loadMapping(cwd);
|
|
38313
|
+
if (!mapping) throw new Error("github-issues.json does not exist. Run project setup first.");
|
|
38314
|
+
if (!mapping.phases[phaseNum]) mapping.phases[phaseNum] = {
|
|
38315
|
+
tracking_issue: {
|
|
38316
|
+
number: 0,
|
|
38317
|
+
id: 0,
|
|
38318
|
+
node_id: "",
|
|
38319
|
+
item_id: "",
|
|
38320
|
+
status: "To Do"
|
|
38321
|
+
},
|
|
38322
|
+
plan: "",
|
|
38323
|
+
tasks: {}
|
|
38324
|
+
};
|
|
38325
|
+
const existing = mapping.phases[phaseNum].tasks[taskId];
|
|
38326
|
+
const defaults = {
|
|
38327
|
+
number: 0,
|
|
38328
|
+
id: 0,
|
|
38329
|
+
node_id: "",
|
|
38330
|
+
item_id: "",
|
|
38331
|
+
status: "To Do"
|
|
38332
|
+
};
|
|
38333
|
+
mapping.phases[phaseNum].tasks[taskId] = Object.assign(defaults, existing, data);
|
|
38334
|
+
saveMapping(cwd, mapping);
|
|
38335
|
+
}
|
|
38290
38336
|
|
|
38291
38337
|
//#endregion
|
|
38292
38338
|
//#region src/mcp/utils.ts
|
|
@@ -40313,10 +40359,24 @@ function registerGitHubTools(server) {
|
|
|
40313
40359
|
}
|
|
40314
40360
|
const result = await createPhaseIssue(phase_number, phase_name, goal, requirements, success_criteria);
|
|
40315
40361
|
if (!result.ok) return mcpError(`Phase issue creation failed: ${result.error}`, "Creation failed");
|
|
40316
|
-
|
|
40362
|
+
const responseData = {
|
|
40317
40363
|
issue_number: result.data.number,
|
|
40318
40364
|
issue_id: result.data.id
|
|
40319
|
-
}
|
|
40365
|
+
};
|
|
40366
|
+
const cwd = detectProjectRoot();
|
|
40367
|
+
if (cwd) {
|
|
40368
|
+
const mapping = loadMapping(cwd);
|
|
40369
|
+
if (mapping && mapping.project_number) {
|
|
40370
|
+
const addResult = await addItemToProject(mapping.project_number, result.data.number);
|
|
40371
|
+
if (addResult.ok) {
|
|
40372
|
+
responseData.item_id = addResult.data.itemId;
|
|
40373
|
+
responseData.project_number = mapping.project_number;
|
|
40374
|
+
const moveResult = await moveItemToStatus(mapping.project_number, addResult.data.itemId, "To Do");
|
|
40375
|
+
if (!moveResult.ok) responseData.board_warning = `Added to board but could not set status: ${moveResult.error}`;
|
|
40376
|
+
} else responseData.board_warning = `Issue created but could not add to board: ${addResult.error}`;
|
|
40377
|
+
}
|
|
40378
|
+
}
|
|
40379
|
+
return mcpSuccess(responseData, `Created phase issue #${result.data.number}: [Phase ${phase_number}] ${phase_name}`);
|
|
40320
40380
|
} catch (e) {
|
|
40321
40381
|
return mcpError(e.message, "Operation failed");
|
|
40322
40382
|
}
|
|
@@ -40337,11 +40397,24 @@ function registerGitHubTools(server) {
|
|
|
40337
40397
|
}
|
|
40338
40398
|
const result = await createTaskSubIssue(phase_number, task_id, title, body, parent_issue_number);
|
|
40339
40399
|
if (!result.ok) return mcpError(`Task issue creation failed: ${result.error}`, "Creation failed");
|
|
40340
|
-
|
|
40400
|
+
const responseData = {
|
|
40341
40401
|
issue_number: result.data.number,
|
|
40342
40402
|
issue_id: result.data.id,
|
|
40343
40403
|
parent_issue_number
|
|
40344
|
-
}
|
|
40404
|
+
};
|
|
40405
|
+
const cwd = detectProjectRoot();
|
|
40406
|
+
if (cwd) try {
|
|
40407
|
+
updateTaskMapping(cwd, phase_number, task_id, {
|
|
40408
|
+
number: result.data.number,
|
|
40409
|
+
id: result.data.id,
|
|
40410
|
+
node_id: "",
|
|
40411
|
+
item_id: "",
|
|
40412
|
+
status: "To Do"
|
|
40413
|
+
});
|
|
40414
|
+
} catch (mappingErr) {
|
|
40415
|
+
responseData.mapping_warning = `Issue created but mapping update failed: ${mappingErr.message}`;
|
|
40416
|
+
}
|
|
40417
|
+
return mcpSuccess(responseData, `Created task sub-issue #${result.data.number}: ${title}`);
|
|
40345
40418
|
} catch (e) {
|
|
40346
40419
|
return mcpError(e.message, "Operation failed");
|
|
40347
40420
|
}
|
|
@@ -40369,10 +40442,11 @@ function registerGitHubTools(server) {
|
|
|
40369
40442
|
return mcpError(e.message, "Operation failed");
|
|
40370
40443
|
}
|
|
40371
40444
|
});
|
|
40372
|
-
server.tool("mcp_close_issue", "Close a GitHub issue as completed.", {
|
|
40445
|
+
server.tool("mcp_close_issue", "Close a GitHub issue as completed or not planned.", {
|
|
40373
40446
|
issue_number: numberType().describe("GitHub issue number"),
|
|
40374
|
-
reason: stringType().optional().describe("Optional reason comment to post before closing")
|
|
40375
|
-
|
|
40447
|
+
reason: stringType().optional().describe("Optional reason comment to post before closing"),
|
|
40448
|
+
state_reason: enumType(["completed", "not_planned"]).optional().default("completed").describe("Close reason: completed (default) or not_planned (rollback)")
|
|
40449
|
+
}, async ({ issue_number, reason, state_reason }) => {
|
|
40376
40450
|
try {
|
|
40377
40451
|
try {
|
|
40378
40452
|
requireAuth();
|
|
@@ -40380,12 +40454,13 @@ function registerGitHubTools(server) {
|
|
|
40380
40454
|
if (e instanceof AuthError) return mcpAuthError$1(e);
|
|
40381
40455
|
throw e;
|
|
40382
40456
|
}
|
|
40383
|
-
const result = await closeIssue(issue_number, reason);
|
|
40457
|
+
const result = await closeIssue(issue_number, reason, state_reason);
|
|
40384
40458
|
if (!result.ok) return mcpError(`Close failed: ${result.error}`, "Close failed");
|
|
40385
40459
|
return mcpSuccess({
|
|
40386
40460
|
issue_number,
|
|
40387
|
-
closed: true
|
|
40388
|
-
|
|
40461
|
+
closed: true,
|
|
40462
|
+
state_reason
|
|
40463
|
+
}, `Issue #${issue_number} closed (${state_reason})`);
|
|
40389
40464
|
} catch (e) {
|
|
40390
40465
|
return mcpError(e.message, "Operation failed");
|
|
40391
40466
|
}
|
|
@@ -40424,7 +40499,10 @@ function registerGitHubTools(server) {
|
|
|
40424
40499
|
id: issue.id,
|
|
40425
40500
|
title: issue.title,
|
|
40426
40501
|
state: issue.state,
|
|
40427
|
-
body: issue.body
|
|
40502
|
+
body: issue.body,
|
|
40503
|
+
updated_at: issue.updated_at,
|
|
40504
|
+
labels: issue.labels,
|
|
40505
|
+
comments_url: issue.comments_url
|
|
40428
40506
|
}, `Issue #${issue.number}: ${issue.title} (${issue.state})`);
|
|
40429
40507
|
} catch (e) {
|
|
40430
40508
|
return mcpError(e.message, "Operation failed");
|
|
@@ -40501,6 +40579,136 @@ function registerGitHubTools(server) {
|
|
|
40501
40579
|
return mcpError(e.message, "Operation failed");
|
|
40502
40580
|
}
|
|
40503
40581
|
});
|
|
40582
|
+
server.tool("mcp_post_comment", "Post a comment on a GitHub issue.", {
|
|
40583
|
+
issue_number: numberType().describe("GitHub issue number"),
|
|
40584
|
+
body: stringType().describe("Comment body (markdown)"),
|
|
40585
|
+
type: enumType([
|
|
40586
|
+
"research",
|
|
40587
|
+
"context",
|
|
40588
|
+
"summary",
|
|
40589
|
+
"verification",
|
|
40590
|
+
"uat",
|
|
40591
|
+
"general"
|
|
40592
|
+
]).optional().describe("Comment type for structured header")
|
|
40593
|
+
}, async ({ issue_number, body, type }) => {
|
|
40594
|
+
try {
|
|
40595
|
+
try {
|
|
40596
|
+
requireAuth();
|
|
40597
|
+
} catch (e) {
|
|
40598
|
+
if (e instanceof AuthError) return mcpAuthError$1(e);
|
|
40599
|
+
throw e;
|
|
40600
|
+
}
|
|
40601
|
+
const result = await postComment(issue_number, type ? `<!-- maxsim:type=${type} -->\n${body}` : body);
|
|
40602
|
+
if (!result.ok) return mcpError(`Comment failed: ${result.error}`, "Comment failed");
|
|
40603
|
+
return mcpSuccess({
|
|
40604
|
+
issue_number,
|
|
40605
|
+
comment_id: result.data.commentId,
|
|
40606
|
+
type: type ?? "general"
|
|
40607
|
+
}, `Comment posted on issue #${issue_number}`);
|
|
40608
|
+
} catch (e) {
|
|
40609
|
+
return mcpError(e.message, "Operation failed");
|
|
40610
|
+
}
|
|
40611
|
+
});
|
|
40612
|
+
server.tool("mcp_batch_create_tasks", "Create multiple task sub-issues for a phase with automatic rollback on failure.", {
|
|
40613
|
+
phase_number: stringType().describe("Phase number (e.g. \"01\")"),
|
|
40614
|
+
parent_issue_number: numberType().describe("Parent phase issue number"),
|
|
40615
|
+
tasks: arrayType(objectType({
|
|
40616
|
+
task_id: stringType().describe("Task ID within the phase"),
|
|
40617
|
+
title: stringType().describe("Task title"),
|
|
40618
|
+
body: stringType().describe("Task body (markdown)")
|
|
40619
|
+
})).describe("List of tasks to create")
|
|
40620
|
+
}, async ({ phase_number, parent_issue_number, tasks }) => {
|
|
40621
|
+
try {
|
|
40622
|
+
try {
|
|
40623
|
+
requireAuth();
|
|
40624
|
+
} catch (e) {
|
|
40625
|
+
if (e instanceof AuthError) return mcpAuthError$1(e);
|
|
40626
|
+
throw e;
|
|
40627
|
+
}
|
|
40628
|
+
const succeeded = [];
|
|
40629
|
+
const failed = [];
|
|
40630
|
+
const created = [];
|
|
40631
|
+
for (const task of tasks) {
|
|
40632
|
+
const result = await createTaskSubIssue(phase_number, task.task_id, task.title, task.body, parent_issue_number);
|
|
40633
|
+
if (result.ok) {
|
|
40634
|
+
created.push({
|
|
40635
|
+
issueNumber: result.data.number,
|
|
40636
|
+
taskId: task.task_id
|
|
40637
|
+
});
|
|
40638
|
+
succeeded.push({
|
|
40639
|
+
task_id: task.task_id,
|
|
40640
|
+
issue_number: result.data.number
|
|
40641
|
+
});
|
|
40642
|
+
const cwd = detectProjectRoot();
|
|
40643
|
+
if (cwd) try {
|
|
40644
|
+
updateTaskMapping(cwd, phase_number, task.task_id, {
|
|
40645
|
+
number: result.data.number,
|
|
40646
|
+
id: result.data.id,
|
|
40647
|
+
node_id: "",
|
|
40648
|
+
item_id: "",
|
|
40649
|
+
status: "To Do"
|
|
40650
|
+
});
|
|
40651
|
+
} catch {}
|
|
40652
|
+
} else {
|
|
40653
|
+
failed.push({
|
|
40654
|
+
task_id: task.task_id,
|
|
40655
|
+
error: result.error
|
|
40656
|
+
});
|
|
40657
|
+
const rolledBack = [];
|
|
40658
|
+
for (const c of [...created].reverse()) if ((await closeIssue(c.issueNumber, "[MAXSIM-ROLLBACK] Partial batch failure", "not_planned")).ok) rolledBack.push(c.issueNumber);
|
|
40659
|
+
return mcpSuccess({
|
|
40660
|
+
succeeded,
|
|
40661
|
+
failed,
|
|
40662
|
+
rolled_back: rolledBack,
|
|
40663
|
+
partial: true
|
|
40664
|
+
}, `Batch failed at task "${task.task_id}". Rolled back ${rolledBack.length} issue(s).`);
|
|
40665
|
+
}
|
|
40666
|
+
}
|
|
40667
|
+
return mcpSuccess({
|
|
40668
|
+
succeeded,
|
|
40669
|
+
failed,
|
|
40670
|
+
rolled_back: [],
|
|
40671
|
+
partial: false
|
|
40672
|
+
}, `Batch created ${succeeded.length} task issue(s) for phase ${phase_number}`);
|
|
40673
|
+
} catch (e) {
|
|
40674
|
+
return mcpError(e.message, "Operation failed");
|
|
40675
|
+
}
|
|
40676
|
+
});
|
|
40677
|
+
server.tool("mcp_detect_external_edits", "Check if a phase issue body was modified outside MAXSIM.", { phase_number: stringType().describe("Phase number (e.g. \"01\")") }, async ({ phase_number }) => {
|
|
40678
|
+
try {
|
|
40679
|
+
try {
|
|
40680
|
+
requireAuth();
|
|
40681
|
+
} catch (e) {
|
|
40682
|
+
if (e instanceof AuthError) return mcpAuthError$1(e);
|
|
40683
|
+
throw e;
|
|
40684
|
+
}
|
|
40685
|
+
const cwd = detectProjectRoot();
|
|
40686
|
+
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
40687
|
+
const mapping = loadMapping(cwd);
|
|
40688
|
+
if (!mapping) return mcpError("github-issues.json not found. Run project setup first.", "Mapping missing");
|
|
40689
|
+
const phaseMapping = mapping.phases[phase_number];
|
|
40690
|
+
if (!phaseMapping) return mcpError(`Phase ${phase_number} not found in mapping`, "Phase not found");
|
|
40691
|
+
const storedHash = phaseMapping.body_hash;
|
|
40692
|
+
const issueNumber = phaseMapping.tracking_issue.number;
|
|
40693
|
+
const issueResult = await getPhaseIssue(issueNumber);
|
|
40694
|
+
if (!issueResult.ok) return mcpError(`Failed to fetch issue #${issueNumber}: ${issueResult.error}`, "Fetch failed");
|
|
40695
|
+
const liveHash = hashBody(issueResult.data.body);
|
|
40696
|
+
if (!storedHash) return mcpSuccess({
|
|
40697
|
+
modified: false,
|
|
40698
|
+
phase_number,
|
|
40699
|
+
issue_number: issueNumber,
|
|
40700
|
+
note: "No stored hash — baseline not yet established"
|
|
40701
|
+
}, `Phase ${phase_number}: no baseline hash stored`);
|
|
40702
|
+
const modified = liveHash !== storedHash;
|
|
40703
|
+
return mcpSuccess({
|
|
40704
|
+
modified,
|
|
40705
|
+
phase_number,
|
|
40706
|
+
issue_number: issueNumber
|
|
40707
|
+
}, modified ? `Phase ${phase_number} issue #${issueNumber} has been externally modified` : `Phase ${phase_number} issue #${issueNumber} matches stored hash`);
|
|
40708
|
+
} catch (e) {
|
|
40709
|
+
return mcpError(e.message, "Operation failed");
|
|
40710
|
+
}
|
|
40711
|
+
});
|
|
40504
40712
|
}
|
|
40505
40713
|
|
|
40506
40714
|
//#endregion
|
|
@@ -40586,6 +40794,57 @@ function registerBoardTools(server) {
|
|
|
40586
40794
|
return mcpError(e.message, "Operation failed");
|
|
40587
40795
|
}
|
|
40588
40796
|
});
|
|
40797
|
+
server.tool("mcp_sync_check", "Verify local github-issues.json mapping is in sync with live GitHub state.", {}, async () => {
|
|
40798
|
+
try {
|
|
40799
|
+
try {
|
|
40800
|
+
requireAuth();
|
|
40801
|
+
} catch (e) {
|
|
40802
|
+
if (e instanceof AuthError) return mcpAuthError(e);
|
|
40803
|
+
throw e;
|
|
40804
|
+
}
|
|
40805
|
+
const cwd = detectProjectRoot();
|
|
40806
|
+
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
40807
|
+
const mapping = loadMapping(cwd);
|
|
40808
|
+
if (!mapping) return mcpError("github-issues.json not found. Run project setup first.", "Mapping missing");
|
|
40809
|
+
const mismatches = [];
|
|
40810
|
+
const missing_remote = [];
|
|
40811
|
+
for (const [phaseNum, phaseMapping] of Object.entries(mapping.phases)) {
|
|
40812
|
+
const issueNumber = phaseMapping.tracking_issue.number;
|
|
40813
|
+
if (!issueNumber) continue;
|
|
40814
|
+
const issueResult = await getPhaseIssue(issueNumber);
|
|
40815
|
+
if (!issueResult.ok) {
|
|
40816
|
+
if (issueResult.error.includes("NOT_FOUND") || issueResult.error.includes("404")) missing_remote.push({
|
|
40817
|
+
phase_number: phaseNum,
|
|
40818
|
+
issue_number: issueNumber
|
|
40819
|
+
});
|
|
40820
|
+
continue;
|
|
40821
|
+
}
|
|
40822
|
+
const localStatus = phaseMapping.tracking_issue.status;
|
|
40823
|
+
const remoteState = issueResult.data.state;
|
|
40824
|
+
if (remoteState === "closed" && localStatus !== "Done") mismatches.push({
|
|
40825
|
+
phase_number: phaseNum,
|
|
40826
|
+
issue_number: issueNumber,
|
|
40827
|
+
local_state: localStatus,
|
|
40828
|
+
remote_state: remoteState
|
|
40829
|
+
});
|
|
40830
|
+
else if (remoteState === "open" && localStatus === "Done") mismatches.push({
|
|
40831
|
+
phase_number: phaseNum,
|
|
40832
|
+
issue_number: issueNumber,
|
|
40833
|
+
local_state: localStatus,
|
|
40834
|
+
remote_state: remoteState
|
|
40835
|
+
});
|
|
40836
|
+
}
|
|
40837
|
+
const in_sync = mismatches.length === 0 && missing_remote.length === 0;
|
|
40838
|
+
return mcpSuccess({
|
|
40839
|
+
in_sync,
|
|
40840
|
+
mismatches,
|
|
40841
|
+
missing_local: [],
|
|
40842
|
+
missing_remote
|
|
40843
|
+
}, in_sync ? "Local mapping is in sync with GitHub" : `Sync issues found: ${mismatches.length} mismatch(es), ${missing_remote.length} missing remote`);
|
|
40844
|
+
} catch (e) {
|
|
40845
|
+
return mcpError(e.message, "Operation failed");
|
|
40846
|
+
}
|
|
40847
|
+
});
|
|
40589
40848
|
server.tool("mcp_search_issues", "Search GitHub issues by label, state, or text query.", {
|
|
40590
40849
|
labels: arrayType(stringType()).optional().describe("Filter by label names"),
|
|
40591
40850
|
state: enumType([
|