maxsimcli 4.0.2 → 4.1.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/dist/.tsbuildinfo +1 -1
- package/dist/assets/CHANGELOG.md +8 -0
- package/dist/backend/index.d.ts +4 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +12 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/lifecycle.d.ts +13 -0
- package/dist/backend/lifecycle.d.ts.map +1 -0
- package/dist/backend/lifecycle.js +168 -0
- package/dist/backend/lifecycle.js.map +1 -0
- package/dist/backend/server.d.ts +13 -0
- package/dist/backend/server.d.ts.map +1 -0
- package/dist/backend/server.js +1013 -0
- package/dist/backend/server.js.map +1 -0
- package/dist/backend/terminal.d.ts +49 -0
- package/dist/backend/terminal.d.ts.map +1 -0
- package/dist/backend/terminal.js +209 -0
- package/dist/backend/terminal.js.map +1 -0
- package/dist/backend/types.d.ts +77 -0
- package/dist/backend/types.d.ts.map +1 -0
- package/dist/backend/types.js +6 -0
- package/dist/backend/types.js.map +1 -0
- package/dist/backend-server.cjs +80513 -0
- package/dist/backend-server.cjs.map +1 -0
- package/dist/backend-server.d.cts +2 -0
- package/dist/backend-server.d.ts +11 -0
- package/dist/backend-server.d.ts.map +1 -0
- package/dist/backend-server.js +43 -0
- package/dist/backend-server.js.map +1 -0
- package/dist/cli.cjs +71 -29
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +23 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/skills.d.ts +4 -3
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +14 -15
- package/dist/core/skills.js.map +1 -1
- package/dist/{core-TFSlUjV1.cjs → core-RRjCSt0G.cjs} +1 -9
- package/dist/{core-TFSlUjV1.cjs.map → core-RRjCSt0G.cjs.map} +1 -1
- package/dist/esm-iIOBzmdz.cjs +1561 -0
- package/dist/esm-iIOBzmdz.cjs.map +1 -0
- package/dist/install.cjs +2 -2
- package/dist/lifecycle-D4E9yP6E.cjs +136 -0
- package/dist/lifecycle-D4E9yP6E.cjs.map +1 -0
- package/dist/mcp/config-tools.d.ts +13 -0
- package/dist/mcp/config-tools.d.ts.map +1 -0
- package/dist/mcp/config-tools.js +66 -0
- package/dist/mcp/config-tools.js.map +1 -0
- package/dist/mcp/context-tools.d.ts +13 -0
- package/dist/mcp/context-tools.d.ts.map +1 -0
- package/dist/mcp/context-tools.js +172 -0
- package/dist/mcp/context-tools.js.map +1 -0
- package/dist/mcp/index.d.ts +0 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +6 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/roadmap-tools.d.ts +13 -0
- package/dist/mcp/roadmap-tools.d.ts.map +1 -0
- package/dist/mcp/roadmap-tools.js +79 -0
- package/dist/mcp/roadmap-tools.js.map +1 -0
- package/dist/mcp-server.cjs +641 -3
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/server-pvY2WbKj.cjs +2980 -0
- package/dist/server-pvY2WbKj.cjs.map +1 -0
- package/dist/{skills-BOSxYUzf.cjs → skills-MYlMkYNt.cjs} +41 -29
- package/dist/skills-MYlMkYNt.cjs.map +1 -0
- package/package.json +7 -1
- package/dist/skills-BOSxYUzf.cjs.map +0 -1
package/dist/mcp-server.cjs
CHANGED
|
@@ -23028,7 +23028,7 @@ var require_has_flag = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
23028
23028
|
//#endregion
|
|
23029
23029
|
//#region ../../node_modules/supports-color/index.js
|
|
23030
23030
|
var require_supports_color = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
23031
|
-
const os$
|
|
23031
|
+
const os$2 = require("os");
|
|
23032
23032
|
const tty$1 = require("tty");
|
|
23033
23033
|
const hasFlag = require_has_flag();
|
|
23034
23034
|
const { env } = process;
|
|
@@ -23055,7 +23055,7 @@ var require_supports_color = /* @__PURE__ */ __commonJSMin(((exports, module) =>
|
|
|
23055
23055
|
const min = forceColor || 0;
|
|
23056
23056
|
if (env.TERM === "dumb") return min;
|
|
23057
23057
|
if (process.platform === "win32") {
|
|
23058
|
-
const osRelease = os$
|
|
23058
|
+
const osRelease = os$2.release().split(".");
|
|
23059
23059
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
23060
23060
|
return 1;
|
|
23061
23061
|
}
|
|
@@ -26756,6 +26756,9 @@ function statePath(cwd) {
|
|
|
26756
26756
|
function roadmapPath(cwd) {
|
|
26757
26757
|
return planningPath(cwd, "ROADMAP.md");
|
|
26758
26758
|
}
|
|
26759
|
+
function configPath(cwd) {
|
|
26760
|
+
return planningPath(cwd, "config.json");
|
|
26761
|
+
}
|
|
26759
26762
|
function phasesPath(cwd) {
|
|
26760
26763
|
return planningPath(cwd, "phases");
|
|
26761
26764
|
}
|
|
@@ -26770,6 +26773,19 @@ function listSubDirs(dir, sortByPhase = false) {
|
|
|
26770
26773
|
const dirs = node_fs.default.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
26771
26774
|
return sortByPhase ? dirs.sort((a, b) => comparePhaseNum(a, b)) : dirs;
|
|
26772
26775
|
}
|
|
26776
|
+
/** Async version of listSubDirs using fs.promises. */
|
|
26777
|
+
async function listSubDirsAsync(dir, sortByPhase = false) {
|
|
26778
|
+
const dirs = (await node_fs.promises.readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
26779
|
+
return sortByPhase ? dirs.sort((a, b) => comparePhaseNum(a, b)) : dirs;
|
|
26780
|
+
}
|
|
26781
|
+
/** Async version of safeReadFile using fs.promises. */
|
|
26782
|
+
async function safeReadFileAsync(filePath) {
|
|
26783
|
+
try {
|
|
26784
|
+
return await node_fs.promises.readFile(filePath, "utf-8");
|
|
26785
|
+
} catch {
|
|
26786
|
+
return null;
|
|
26787
|
+
}
|
|
26788
|
+
}
|
|
26773
26789
|
/** Extract a human-readable message from an unknown thrown value. */
|
|
26774
26790
|
function errorMsg(e) {
|
|
26775
26791
|
return e instanceof Error ? e.message : String(e);
|
|
@@ -26784,6 +26800,104 @@ function debugLog(contextOrError, error) {
|
|
|
26784
26800
|
function escapePhaseNum(phase) {
|
|
26785
26801
|
return String(phase).replace(/\./g, "\\.");
|
|
26786
26802
|
}
|
|
26803
|
+
function safeReadFile(filePath) {
|
|
26804
|
+
try {
|
|
26805
|
+
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
26806
|
+
} catch {
|
|
26807
|
+
return null;
|
|
26808
|
+
}
|
|
26809
|
+
}
|
|
26810
|
+
let _configCache = null;
|
|
26811
|
+
function loadConfig(cwd) {
|
|
26812
|
+
if (_configCache && _configCache.cwd === cwd) return _configCache.config;
|
|
26813
|
+
const cfgPath = configPath(cwd);
|
|
26814
|
+
const defaults = {
|
|
26815
|
+
model_profile: "balanced",
|
|
26816
|
+
commit_docs: true,
|
|
26817
|
+
search_gitignored: false,
|
|
26818
|
+
branching_strategy: "none",
|
|
26819
|
+
phase_branch_template: "maxsim/phase-{phase}-{slug}",
|
|
26820
|
+
milestone_branch_template: "maxsim/{milestone}-{slug}",
|
|
26821
|
+
research: true,
|
|
26822
|
+
plan_checker: true,
|
|
26823
|
+
verifier: true,
|
|
26824
|
+
parallelization: true,
|
|
26825
|
+
brave_search: false
|
|
26826
|
+
};
|
|
26827
|
+
try {
|
|
26828
|
+
const raw = node_fs.default.readFileSync(cfgPath, "utf-8");
|
|
26829
|
+
const parsed = JSON.parse(raw);
|
|
26830
|
+
const get = (key, nested) => {
|
|
26831
|
+
if (parsed[key] !== void 0) return parsed[key];
|
|
26832
|
+
if (nested) {
|
|
26833
|
+
const section = parsed[nested.section];
|
|
26834
|
+
if (section && typeof section === "object" && section !== null && nested.field in section) return section[nested.field];
|
|
26835
|
+
}
|
|
26836
|
+
};
|
|
26837
|
+
const parallelization = (() => {
|
|
26838
|
+
const val = get("parallelization");
|
|
26839
|
+
if (typeof val === "boolean") return val;
|
|
26840
|
+
if (typeof val === "object" && val !== null && "enabled" in val) return val.enabled;
|
|
26841
|
+
return defaults.parallelization;
|
|
26842
|
+
})();
|
|
26843
|
+
const result = {
|
|
26844
|
+
model_profile: get("model_profile") ?? defaults.model_profile,
|
|
26845
|
+
commit_docs: get("commit_docs", {
|
|
26846
|
+
section: "planning",
|
|
26847
|
+
field: "commit_docs"
|
|
26848
|
+
}) ?? defaults.commit_docs,
|
|
26849
|
+
search_gitignored: get("search_gitignored", {
|
|
26850
|
+
section: "planning",
|
|
26851
|
+
field: "search_gitignored"
|
|
26852
|
+
}) ?? defaults.search_gitignored,
|
|
26853
|
+
branching_strategy: get("branching_strategy", {
|
|
26854
|
+
section: "git",
|
|
26855
|
+
field: "branching_strategy"
|
|
26856
|
+
}) ?? defaults.branching_strategy,
|
|
26857
|
+
phase_branch_template: get("phase_branch_template", {
|
|
26858
|
+
section: "git",
|
|
26859
|
+
field: "phase_branch_template"
|
|
26860
|
+
}) ?? defaults.phase_branch_template,
|
|
26861
|
+
milestone_branch_template: get("milestone_branch_template", {
|
|
26862
|
+
section: "git",
|
|
26863
|
+
field: "milestone_branch_template"
|
|
26864
|
+
}) ?? defaults.milestone_branch_template,
|
|
26865
|
+
research: get("research", {
|
|
26866
|
+
section: "workflow",
|
|
26867
|
+
field: "research"
|
|
26868
|
+
}) ?? defaults.research,
|
|
26869
|
+
plan_checker: get("plan_checker", {
|
|
26870
|
+
section: "workflow",
|
|
26871
|
+
field: "plan_checker"
|
|
26872
|
+
}) ?? get("plan_checker", {
|
|
26873
|
+
section: "workflow",
|
|
26874
|
+
field: "plan_check"
|
|
26875
|
+
}) ?? defaults.plan_checker,
|
|
26876
|
+
verifier: get("verifier", {
|
|
26877
|
+
section: "workflow",
|
|
26878
|
+
field: "verifier"
|
|
26879
|
+
}) ?? defaults.verifier,
|
|
26880
|
+
parallelization,
|
|
26881
|
+
brave_search: get("brave_search") ?? defaults.brave_search,
|
|
26882
|
+
model_overrides: parsed["model_overrides"]
|
|
26883
|
+
};
|
|
26884
|
+
_configCache = {
|
|
26885
|
+
cwd,
|
|
26886
|
+
config: result
|
|
26887
|
+
};
|
|
26888
|
+
return result;
|
|
26889
|
+
} catch (e) {
|
|
26890
|
+
if (node_fs.default.existsSync(cfgPath)) {
|
|
26891
|
+
console.warn(`[maxsim] Warning: config.json exists but could not be parsed — using defaults.`);
|
|
26892
|
+
debugLog("config-load-failed", e);
|
|
26893
|
+
}
|
|
26894
|
+
_configCache = {
|
|
26895
|
+
cwd,
|
|
26896
|
+
config: defaults
|
|
26897
|
+
};
|
|
26898
|
+
return defaults;
|
|
26899
|
+
}
|
|
26900
|
+
}
|
|
26787
26901
|
function normalizePhaseName(phase) {
|
|
26788
26902
|
const match = phase.match(/^(\d+)([A-Z])?(\.\d+)?/i);
|
|
26789
26903
|
if (!match) return phase;
|
|
@@ -33574,6 +33688,23 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
33574
33688
|
exports.visitAsync = visit.visitAsync;
|
|
33575
33689
|
}));
|
|
33576
33690
|
|
|
33691
|
+
//#endregion
|
|
33692
|
+
//#region src/core/types.ts
|
|
33693
|
+
var import_dist = /* @__PURE__ */ __toESM(require_dist());
|
|
33694
|
+
function cmdOk(result, rawValue) {
|
|
33695
|
+
return {
|
|
33696
|
+
ok: true,
|
|
33697
|
+
result,
|
|
33698
|
+
rawValue
|
|
33699
|
+
};
|
|
33700
|
+
}
|
|
33701
|
+
function cmdErr(error) {
|
|
33702
|
+
return {
|
|
33703
|
+
ok: false,
|
|
33704
|
+
error
|
|
33705
|
+
};
|
|
33706
|
+
}
|
|
33707
|
+
|
|
33577
33708
|
//#endregion
|
|
33578
33709
|
//#region src/core/frontmatter.ts
|
|
33579
33710
|
/**
|
|
@@ -33581,7 +33712,6 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
33581
33712
|
*
|
|
33582
33713
|
* Uses the `yaml` npm package instead of a hand-rolled parser.
|
|
33583
33714
|
*/
|
|
33584
|
-
var import_dist = /* @__PURE__ */ __toESM(require_dist());
|
|
33585
33715
|
|
|
33586
33716
|
//#endregion
|
|
33587
33717
|
//#region src/core/phase.ts
|
|
@@ -34245,6 +34375,511 @@ function registerStateTools(server) {
|
|
|
34245
34375
|
});
|
|
34246
34376
|
}
|
|
34247
34377
|
|
|
34378
|
+
//#endregion
|
|
34379
|
+
//#region src/core/roadmap.ts
|
|
34380
|
+
/**
|
|
34381
|
+
* Roadmap — Roadmap parsing and update operations
|
|
34382
|
+
*
|
|
34383
|
+
* Ported from maxsim/bin/lib/roadmap.cjs
|
|
34384
|
+
*/
|
|
34385
|
+
async function cmdRoadmapAnalyze(cwd) {
|
|
34386
|
+
const content = await safeReadFileAsync(roadmapPath(cwd));
|
|
34387
|
+
if (!content) return cmdOk({
|
|
34388
|
+
error: "ROADMAP.md not found",
|
|
34389
|
+
milestones: [],
|
|
34390
|
+
phases: [],
|
|
34391
|
+
current_phase: null
|
|
34392
|
+
});
|
|
34393
|
+
const phasesDir = phasesPath(cwd);
|
|
34394
|
+
const phasePattern = getPhasePattern();
|
|
34395
|
+
const parsedPhases = [];
|
|
34396
|
+
let match;
|
|
34397
|
+
while ((match = phasePattern.exec(content)) !== null) {
|
|
34398
|
+
const phaseNum = match[1];
|
|
34399
|
+
const phaseName = match[2].replace(/\(INSERTED\)/i, "").trim();
|
|
34400
|
+
const sectionStart = match.index;
|
|
34401
|
+
const nextHeader = content.slice(sectionStart).match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
34402
|
+
const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
|
|
34403
|
+
const section = content.slice(sectionStart, sectionEnd);
|
|
34404
|
+
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
34405
|
+
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
34406
|
+
const dependsMatch = section.match(/\*\*Depends on:\*\*\s*([^\n]+)/i);
|
|
34407
|
+
const depends_on = dependsMatch ? dependsMatch[1].trim() : null;
|
|
34408
|
+
parsedPhases.push({
|
|
34409
|
+
phaseNum,
|
|
34410
|
+
phaseName,
|
|
34411
|
+
goal,
|
|
34412
|
+
depends_on,
|
|
34413
|
+
normalized: normalizePhaseName(phaseNum),
|
|
34414
|
+
checkboxPattern: new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${phaseNum.replace(".", "\\.")}`, "i")
|
|
34415
|
+
});
|
|
34416
|
+
}
|
|
34417
|
+
let allDirs = [];
|
|
34418
|
+
try {
|
|
34419
|
+
allDirs = await listSubDirsAsync(phasesDir);
|
|
34420
|
+
} catch {}
|
|
34421
|
+
const phases = await Promise.all(parsedPhases.map(async (p) => {
|
|
34422
|
+
let diskStatus = "no_directory";
|
|
34423
|
+
let planCount = 0;
|
|
34424
|
+
let summaryCount = 0;
|
|
34425
|
+
let hasContext = false;
|
|
34426
|
+
let hasResearch = false;
|
|
34427
|
+
try {
|
|
34428
|
+
const dirMatch = allDirs.find((d) => d.startsWith(p.normalized + "-") || d === p.normalized);
|
|
34429
|
+
if (dirMatch) {
|
|
34430
|
+
const phaseFiles = await node_fs.default.promises.readdir(node_path.default.join(phasesDir, dirMatch));
|
|
34431
|
+
planCount = phaseFiles.filter((f) => isPlanFile(f)).length;
|
|
34432
|
+
summaryCount = phaseFiles.filter((f) => isSummaryFile(f)).length;
|
|
34433
|
+
hasContext = phaseFiles.some((f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md");
|
|
34434
|
+
hasResearch = phaseFiles.some((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
|
|
34435
|
+
if (summaryCount >= planCount && planCount > 0) diskStatus = "complete";
|
|
34436
|
+
else if (summaryCount > 0) diskStatus = "partial";
|
|
34437
|
+
else if (planCount > 0) diskStatus = "planned";
|
|
34438
|
+
else if (hasResearch) diskStatus = "researched";
|
|
34439
|
+
else if (hasContext) diskStatus = "discussed";
|
|
34440
|
+
else diskStatus = "empty";
|
|
34441
|
+
}
|
|
34442
|
+
} catch (e) {
|
|
34443
|
+
debugLog(e);
|
|
34444
|
+
}
|
|
34445
|
+
const checkboxMatch = content.match(p.checkboxPattern);
|
|
34446
|
+
const roadmapComplete = checkboxMatch ? checkboxMatch[1] === "x" : false;
|
|
34447
|
+
return {
|
|
34448
|
+
number: p.phaseNum,
|
|
34449
|
+
name: p.phaseName,
|
|
34450
|
+
goal: p.goal,
|
|
34451
|
+
depends_on: p.depends_on,
|
|
34452
|
+
plan_count: planCount,
|
|
34453
|
+
summary_count: summaryCount,
|
|
34454
|
+
has_context: hasContext,
|
|
34455
|
+
has_research: hasResearch,
|
|
34456
|
+
disk_status: diskStatus,
|
|
34457
|
+
roadmap_complete: roadmapComplete
|
|
34458
|
+
};
|
|
34459
|
+
}));
|
|
34460
|
+
const milestones = [];
|
|
34461
|
+
const milestonePattern = /##\s*(.*v(\d+\.\d+)[^(\n]*)/gi;
|
|
34462
|
+
let mMatch;
|
|
34463
|
+
while ((mMatch = milestonePattern.exec(content)) !== null) milestones.push({
|
|
34464
|
+
heading: mMatch[1].trim(),
|
|
34465
|
+
version: "v" + mMatch[2]
|
|
34466
|
+
});
|
|
34467
|
+
const currentPhase = phases.find((p) => p.disk_status === "planned" || p.disk_status === "partial") || null;
|
|
34468
|
+
const nextPhase = phases.find((p) => p.disk_status === "empty" || p.disk_status === "no_directory" || p.disk_status === "discussed" || p.disk_status === "researched") || null;
|
|
34469
|
+
const totalPlans = phases.reduce((sum, p) => sum + p.plan_count, 0);
|
|
34470
|
+
const totalSummaries = phases.reduce((sum, p) => sum + p.summary_count, 0);
|
|
34471
|
+
const completedPhases = phases.filter((p) => p.disk_status === "complete").length;
|
|
34472
|
+
const checklistPattern = /-\s*\[[ x]\]\s*\*\*Phase\s+(\d+[A-Z]?(?:\.\d+)?)/gi;
|
|
34473
|
+
const checklistPhases = /* @__PURE__ */ new Set();
|
|
34474
|
+
let checklistMatch;
|
|
34475
|
+
while ((checklistMatch = checklistPattern.exec(content)) !== null) checklistPhases.add(checklistMatch[1]);
|
|
34476
|
+
const detailPhases = new Set(phases.map((p) => p.number));
|
|
34477
|
+
const missingDetails = [...checklistPhases].filter((p) => !detailPhases.has(p));
|
|
34478
|
+
return cmdOk({
|
|
34479
|
+
milestones,
|
|
34480
|
+
phases,
|
|
34481
|
+
phase_count: phases.length,
|
|
34482
|
+
completed_phases: completedPhases,
|
|
34483
|
+
total_plans: totalPlans,
|
|
34484
|
+
total_summaries: totalSummaries,
|
|
34485
|
+
progress_percent: totalPlans > 0 ? Math.min(100, Math.round(totalSummaries / totalPlans * 100)) : 0,
|
|
34486
|
+
current_phase: currentPhase ? currentPhase.number : null,
|
|
34487
|
+
next_phase: nextPhase ? nextPhase.number : null,
|
|
34488
|
+
missing_phase_details: missingDetails.length > 0 ? missingDetails : null
|
|
34489
|
+
});
|
|
34490
|
+
}
|
|
34491
|
+
|
|
34492
|
+
//#endregion
|
|
34493
|
+
//#region src/core/context-loader.ts
|
|
34494
|
+
/**
|
|
34495
|
+
* Context Loader — Intelligent file selection for workflow context assembly
|
|
34496
|
+
*
|
|
34497
|
+
* Selects relevant planning files based on the current task/phase domain,
|
|
34498
|
+
* preventing context overload by loading only what matters.
|
|
34499
|
+
*/
|
|
34500
|
+
function fileEntry(cwd, relPath, role) {
|
|
34501
|
+
const fullPath = node_path.default.join(cwd, relPath);
|
|
34502
|
+
try {
|
|
34503
|
+
return {
|
|
34504
|
+
path: relPath,
|
|
34505
|
+
role,
|
|
34506
|
+
size: node_fs.default.statSync(fullPath).size
|
|
34507
|
+
};
|
|
34508
|
+
} catch {
|
|
34509
|
+
return null;
|
|
34510
|
+
}
|
|
34511
|
+
}
|
|
34512
|
+
function addIfExists(files, cwd, relPath, role) {
|
|
34513
|
+
const entry = fileEntry(cwd, relPath, role);
|
|
34514
|
+
if (entry) files.push(entry);
|
|
34515
|
+
}
|
|
34516
|
+
function loadProjectContext(cwd) {
|
|
34517
|
+
const files = [];
|
|
34518
|
+
addIfExists(files, cwd, ".planning/PROJECT.md", "project-vision");
|
|
34519
|
+
addIfExists(files, cwd, ".planning/REQUIREMENTS.md", "requirements");
|
|
34520
|
+
addIfExists(files, cwd, ".planning/STATE.md", "state");
|
|
34521
|
+
addIfExists(files, cwd, ".planning/config.json", "config");
|
|
34522
|
+
return files;
|
|
34523
|
+
}
|
|
34524
|
+
function loadRoadmapContext(cwd) {
|
|
34525
|
+
const files = [];
|
|
34526
|
+
addIfExists(files, cwd, ".planning/ROADMAP.md", "roadmap");
|
|
34527
|
+
return files;
|
|
34528
|
+
}
|
|
34529
|
+
function loadPhaseContext(cwd, phase) {
|
|
34530
|
+
const files = [];
|
|
34531
|
+
const phaseInfo = findPhaseInternal(cwd, phase);
|
|
34532
|
+
if (!phaseInfo?.directory) return files;
|
|
34533
|
+
const phaseDir = phaseInfo.directory;
|
|
34534
|
+
try {
|
|
34535
|
+
const phaseFiles = node_fs.default.readdirSync(node_path.default.join(cwd, phaseDir));
|
|
34536
|
+
for (const f of phaseFiles) {
|
|
34537
|
+
const relPath = node_path.default.join(phaseDir, f);
|
|
34538
|
+
if (f.endsWith("-CONTEXT.md") || f === "CONTEXT.md") addIfExists(files, cwd, relPath, "phase-context");
|
|
34539
|
+
else if (f.endsWith("-RESEARCH.md") || f === "RESEARCH.md") addIfExists(files, cwd, relPath, "phase-research");
|
|
34540
|
+
else if (f.endsWith("-PLAN.md")) addIfExists(files, cwd, relPath, "phase-plan");
|
|
34541
|
+
else if (f.endsWith("-SUMMARY.md")) addIfExists(files, cwd, relPath, "phase-summary");
|
|
34542
|
+
else if (f.endsWith("-VERIFICATION.md") || f === "VERIFICATION.md") addIfExists(files, cwd, relPath, "phase-verification");
|
|
34543
|
+
}
|
|
34544
|
+
} catch (e) {
|
|
34545
|
+
debugLog("context-loader-phase-files-failed", e);
|
|
34546
|
+
}
|
|
34547
|
+
return files;
|
|
34548
|
+
}
|
|
34549
|
+
function loadArtefakteContext(cwd, phase) {
|
|
34550
|
+
const files = [];
|
|
34551
|
+
for (const filename of [
|
|
34552
|
+
"DECISIONS.md",
|
|
34553
|
+
"ACCEPTANCE-CRITERIA.md",
|
|
34554
|
+
"NO-GOS.md"
|
|
34555
|
+
]) {
|
|
34556
|
+
if (phase) {
|
|
34557
|
+
const phaseInfo = findPhaseInternal(cwd, phase);
|
|
34558
|
+
if (phaseInfo?.directory) addIfExists(files, cwd, node_path.default.join(phaseInfo.directory, filename), `artefakt-${filename.toLowerCase()}`);
|
|
34559
|
+
}
|
|
34560
|
+
addIfExists(files, cwd, `.planning/${filename}`, `artefakt-${filename.toLowerCase()}`);
|
|
34561
|
+
}
|
|
34562
|
+
return files;
|
|
34563
|
+
}
|
|
34564
|
+
function loadHistoryContext(cwd, currentPhase) {
|
|
34565
|
+
const files = [];
|
|
34566
|
+
const pd = phasesPath(cwd);
|
|
34567
|
+
try {
|
|
34568
|
+
const dirs = listSubDirs(pd, true);
|
|
34569
|
+
for (const dir of dirs) {
|
|
34570
|
+
if (currentPhase) {
|
|
34571
|
+
if (dir.match(/^(\d+[A-Z]?(?:\.\d+)?)/i)?.[1] === currentPhase) continue;
|
|
34572
|
+
}
|
|
34573
|
+
const dirPath = node_path.default.join(pd, dir);
|
|
34574
|
+
const summaries = node_fs.default.readdirSync(dirPath).filter((f) => isSummaryFile(f));
|
|
34575
|
+
for (const s of summaries) addIfExists(files, cwd, node_path.default.join(".planning", "phases", dir, s), "history-summary");
|
|
34576
|
+
}
|
|
34577
|
+
} catch (e) {
|
|
34578
|
+
debugLog("context-loader-history-failed", e);
|
|
34579
|
+
}
|
|
34580
|
+
return files;
|
|
34581
|
+
}
|
|
34582
|
+
function cmdContextLoad(cwd, phase, topic, includeHistory) {
|
|
34583
|
+
const allFiles = [];
|
|
34584
|
+
allFiles.push(...loadProjectContext(cwd));
|
|
34585
|
+
allFiles.push(...loadRoadmapContext(cwd));
|
|
34586
|
+
allFiles.push(...loadArtefakteContext(cwd, phase));
|
|
34587
|
+
if (phase) allFiles.push(...loadPhaseContext(cwd, phase));
|
|
34588
|
+
if (includeHistory) allFiles.push(...loadHistoryContext(cwd, phase));
|
|
34589
|
+
const seen = /* @__PURE__ */ new Set();
|
|
34590
|
+
const deduped = allFiles.filter((f) => {
|
|
34591
|
+
if (seen.has(f.path)) return false;
|
|
34592
|
+
seen.add(f.path);
|
|
34593
|
+
return true;
|
|
34594
|
+
});
|
|
34595
|
+
return cmdOk({
|
|
34596
|
+
files: deduped,
|
|
34597
|
+
total_size: deduped.reduce((sum, f) => sum + f.size, 0),
|
|
34598
|
+
phase: phase ?? null,
|
|
34599
|
+
topic: topic ?? null
|
|
34600
|
+
});
|
|
34601
|
+
}
|
|
34602
|
+
|
|
34603
|
+
//#endregion
|
|
34604
|
+
//#region src/mcp/context-tools.ts
|
|
34605
|
+
/**
|
|
34606
|
+
* Context Query MCP Tools — Project context exposed as MCP tools
|
|
34607
|
+
*
|
|
34608
|
+
* CRITICAL: Never import output() or error() from core — they call process.exit().
|
|
34609
|
+
* CRITICAL: Never write to stdout — it is reserved for MCP JSON-RPC protocol.
|
|
34610
|
+
* CRITICAL: Never call process.exit() — the server must stay alive after every tool call.
|
|
34611
|
+
*/
|
|
34612
|
+
/**
|
|
34613
|
+
* Register all context query tools on the MCP server.
|
|
34614
|
+
*/
|
|
34615
|
+
function registerContextTools(server) {
|
|
34616
|
+
server.tool("mcp_get_active_phase", "Get the currently active phase and next phase from roadmap analysis and STATE.md.", {}, async () => {
|
|
34617
|
+
try {
|
|
34618
|
+
const cwd = detectProjectRoot();
|
|
34619
|
+
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
34620
|
+
const roadmapResult = await cmdRoadmapAnalyze(cwd);
|
|
34621
|
+
let current_phase = null;
|
|
34622
|
+
let next_phase = null;
|
|
34623
|
+
let phase_name = null;
|
|
34624
|
+
let status = null;
|
|
34625
|
+
if (roadmapResult.ok) {
|
|
34626
|
+
const data = roadmapResult.result;
|
|
34627
|
+
current_phase = data.current_phase ?? null;
|
|
34628
|
+
next_phase = data.next_phase ?? null;
|
|
34629
|
+
}
|
|
34630
|
+
const stateContent = safeReadFile(planningPath(cwd, "STATE.md"));
|
|
34631
|
+
if (stateContent) {
|
|
34632
|
+
const statePhase = stateExtractField(stateContent, "Current Phase");
|
|
34633
|
+
if (statePhase) phase_name = statePhase;
|
|
34634
|
+
const stateStatus = stateExtractField(stateContent, "Status");
|
|
34635
|
+
if (stateStatus) status = stateStatus;
|
|
34636
|
+
}
|
|
34637
|
+
return mcpSuccess({
|
|
34638
|
+
current_phase,
|
|
34639
|
+
next_phase,
|
|
34640
|
+
phase_name,
|
|
34641
|
+
status
|
|
34642
|
+
}, `Active phase: ${phase_name ?? current_phase ?? "unknown"}`);
|
|
34643
|
+
} catch (e) {
|
|
34644
|
+
return mcpError("Failed: " + e.message, "Error occurred");
|
|
34645
|
+
}
|
|
34646
|
+
});
|
|
34647
|
+
server.tool("mcp_get_guidelines", "Get project guidelines: PROJECT.md vision, config, and optionally phase-specific context.", { phase: stringType().optional().describe("Optional phase number to include phase-specific context") }, async ({ phase }) => {
|
|
34648
|
+
try {
|
|
34649
|
+
const cwd = detectProjectRoot();
|
|
34650
|
+
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
34651
|
+
const project_vision = safeReadFile(planningPath(cwd, "PROJECT.md"));
|
|
34652
|
+
const config = loadConfig(cwd);
|
|
34653
|
+
let phase_context = null;
|
|
34654
|
+
if (phase) {
|
|
34655
|
+
const phaseInfo = findPhaseInternal(cwd, phase);
|
|
34656
|
+
if (phaseInfo) phase_context = safeReadFile(node_path.default.join(phaseInfo.directory, `${phaseInfo.phase_number}-CONTEXT.md`));
|
|
34657
|
+
}
|
|
34658
|
+
return mcpSuccess({
|
|
34659
|
+
project_vision,
|
|
34660
|
+
config,
|
|
34661
|
+
phase_context
|
|
34662
|
+
}, `Guidelines loaded${phase ? ` with phase ${phase} context` : ""}`);
|
|
34663
|
+
} catch (e) {
|
|
34664
|
+
return mcpError("Failed: " + e.message, "Error occurred");
|
|
34665
|
+
}
|
|
34666
|
+
});
|
|
34667
|
+
server.tool("mcp_get_context_for_task", "Load context files for a task, including project context, roadmap, and optionally phase-specific and topic-specific files.", {
|
|
34668
|
+
phase: stringType().optional().describe("Optional phase number to scope context to"),
|
|
34669
|
+
topic: stringType().optional().describe("Optional topic to filter context by")
|
|
34670
|
+
}, async ({ phase, topic }) => {
|
|
34671
|
+
try {
|
|
34672
|
+
const cwd = detectProjectRoot();
|
|
34673
|
+
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
34674
|
+
const result = cmdContextLoad(cwd, phase, topic, true);
|
|
34675
|
+
if (!result.ok) return mcpError(result.error, "Context load failed");
|
|
34676
|
+
return mcpSuccess({ context: result.result }, `Context loaded${phase ? ` for phase ${phase}` : ""}${topic ? ` topic "${topic}"` : ""}`);
|
|
34677
|
+
} catch (e) {
|
|
34678
|
+
return mcpError("Failed: " + e.message, "Error occurred");
|
|
34679
|
+
}
|
|
34680
|
+
});
|
|
34681
|
+
server.tool("mcp_get_project_overview", "Get a high-level project overview: PROJECT.md, REQUIREMENTS.md, and STATE.md contents.", {}, async () => {
|
|
34682
|
+
try {
|
|
34683
|
+
const cwd = detectProjectRoot();
|
|
34684
|
+
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
34685
|
+
return mcpSuccess({
|
|
34686
|
+
project: safeReadFile(planningPath(cwd, "PROJECT.md")),
|
|
34687
|
+
requirements: safeReadFile(planningPath(cwd, "REQUIREMENTS.md")),
|
|
34688
|
+
state: safeReadFile(planningPath(cwd, "STATE.md"))
|
|
34689
|
+
}, "Project overview loaded");
|
|
34690
|
+
} catch (e) {
|
|
34691
|
+
return mcpError("Failed: " + e.message, "Error occurred");
|
|
34692
|
+
}
|
|
34693
|
+
});
|
|
34694
|
+
server.tool("mcp_get_phase_detail", "Get detailed information about a specific phase including all its files (plans, summaries, context, research, verification).", { phase: stringType().describe("Phase number or name (e.g. \"01\", \"1\", \"01A\")") }, async ({ phase }) => {
|
|
34695
|
+
try {
|
|
34696
|
+
const cwd = detectProjectRoot();
|
|
34697
|
+
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
34698
|
+
const phaseInfo = findPhaseInternal(cwd, phase);
|
|
34699
|
+
if (!phaseInfo) return mcpError(`Phase ${phase} not found`, "Phase not found");
|
|
34700
|
+
const files = [];
|
|
34701
|
+
try {
|
|
34702
|
+
const entries = node_fs.default.readdirSync(phaseInfo.directory);
|
|
34703
|
+
for (const entry of entries) {
|
|
34704
|
+
const fullPath = node_path.default.join(phaseInfo.directory, entry);
|
|
34705
|
+
if (node_fs.default.statSync(fullPath).isFile()) files.push({
|
|
34706
|
+
name: entry,
|
|
34707
|
+
content: safeReadFile(fullPath)
|
|
34708
|
+
});
|
|
34709
|
+
}
|
|
34710
|
+
} catch {}
|
|
34711
|
+
return mcpSuccess({
|
|
34712
|
+
phase_number: phaseInfo.phase_number,
|
|
34713
|
+
phase_name: phaseInfo.phase_name,
|
|
34714
|
+
directory: phaseInfo.directory,
|
|
34715
|
+
files
|
|
34716
|
+
}, `Phase ${phaseInfo.phase_number} detail: ${files.length} file(s)`);
|
|
34717
|
+
} catch (e) {
|
|
34718
|
+
return mcpError("Failed: " + e.message, "Error occurred");
|
|
34719
|
+
}
|
|
34720
|
+
});
|
|
34721
|
+
}
|
|
34722
|
+
|
|
34723
|
+
//#endregion
|
|
34724
|
+
//#region src/mcp/roadmap-tools.ts
|
|
34725
|
+
/**
|
|
34726
|
+
* Register all roadmap query tools on the MCP server.
|
|
34727
|
+
*/
|
|
34728
|
+
function registerRoadmapTools(server) {
|
|
34729
|
+
server.tool("mcp_get_roadmap", "Get the full roadmap analysis including all phases, their status, and progress.", {}, async () => {
|
|
34730
|
+
try {
|
|
34731
|
+
const cwd = detectProjectRoot();
|
|
34732
|
+
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
34733
|
+
const result = await cmdRoadmapAnalyze(cwd);
|
|
34734
|
+
if (!result.ok) return mcpError(result.error, "Roadmap analysis failed");
|
|
34735
|
+
return mcpSuccess({ roadmap: result.result }, "Roadmap analysis complete");
|
|
34736
|
+
} catch (e) {
|
|
34737
|
+
return mcpError("Failed: " + e.message, "Error occurred");
|
|
34738
|
+
}
|
|
34739
|
+
});
|
|
34740
|
+
server.tool("mcp_get_roadmap_progress", "Get a focused progress summary: total phases, completed, in-progress, not started, and progress percentage.", {}, async () => {
|
|
34741
|
+
try {
|
|
34742
|
+
const cwd = detectProjectRoot();
|
|
34743
|
+
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
34744
|
+
const result = await cmdRoadmapAnalyze(cwd);
|
|
34745
|
+
if (!result.ok) return mcpError(result.error, "Roadmap analysis failed");
|
|
34746
|
+
const data = result.result;
|
|
34747
|
+
const phases = data.phases ?? [];
|
|
34748
|
+
const total_phases = phases.length;
|
|
34749
|
+
let completed = 0;
|
|
34750
|
+
let in_progress = 0;
|
|
34751
|
+
let not_started = 0;
|
|
34752
|
+
for (const p of phases) {
|
|
34753
|
+
const status = String(p.status ?? "").toLowerCase();
|
|
34754
|
+
if (status === "completed" || status === "done") completed++;
|
|
34755
|
+
else if (status === "in-progress" || status === "in_progress" || status === "active") in_progress++;
|
|
34756
|
+
else not_started++;
|
|
34757
|
+
}
|
|
34758
|
+
const progress_percent = total_phases > 0 ? Math.round(completed / total_phases * 100) : 0;
|
|
34759
|
+
return mcpSuccess({
|
|
34760
|
+
total_phases,
|
|
34761
|
+
completed,
|
|
34762
|
+
in_progress,
|
|
34763
|
+
not_started,
|
|
34764
|
+
progress_percent,
|
|
34765
|
+
current_phase: data.current_phase ?? null,
|
|
34766
|
+
next_phase: data.next_phase ?? null
|
|
34767
|
+
}, `Progress: ${completed}/${total_phases} phases complete (${progress_percent}%)`);
|
|
34768
|
+
} catch (e) {
|
|
34769
|
+
return mcpError("Failed: " + e.message, "Error occurred");
|
|
34770
|
+
}
|
|
34771
|
+
});
|
|
34772
|
+
}
|
|
34773
|
+
|
|
34774
|
+
//#endregion
|
|
34775
|
+
//#region src/core/config.ts
|
|
34776
|
+
/**
|
|
34777
|
+
* Config — Planning config CRUD operations
|
|
34778
|
+
*
|
|
34779
|
+
* Ported from maxsim/bin/lib/config.cjs
|
|
34780
|
+
*/
|
|
34781
|
+
function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
34782
|
+
const configPath = node_path.default.join(cwd, ".planning", "config.json");
|
|
34783
|
+
if (!keyPath) return cmdErr("Usage: config-set <key.path> <value>");
|
|
34784
|
+
let parsedValue = value;
|
|
34785
|
+
if (value === "true") parsedValue = true;
|
|
34786
|
+
else if (value === "false") parsedValue = false;
|
|
34787
|
+
else if (value !== void 0 && !isNaN(Number(value)) && value !== "") parsedValue = Number(value);
|
|
34788
|
+
let config = {};
|
|
34789
|
+
try {
|
|
34790
|
+
if (node_fs.default.existsSync(configPath)) config = JSON.parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
34791
|
+
} catch (err) {
|
|
34792
|
+
return cmdErr("Failed to read config.json: " + err.message);
|
|
34793
|
+
}
|
|
34794
|
+
const keys = keyPath.split(".");
|
|
34795
|
+
let current = config;
|
|
34796
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
34797
|
+
const key = keys[i];
|
|
34798
|
+
if (current[key] === void 0 || typeof current[key] !== "object") current[key] = {};
|
|
34799
|
+
current = current[key];
|
|
34800
|
+
}
|
|
34801
|
+
current[keys[keys.length - 1]] = parsedValue;
|
|
34802
|
+
try {
|
|
34803
|
+
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
34804
|
+
return cmdOk({
|
|
34805
|
+
updated: true,
|
|
34806
|
+
key: keyPath,
|
|
34807
|
+
value: parsedValue
|
|
34808
|
+
}, raw ? `${keyPath}=${parsedValue}` : void 0);
|
|
34809
|
+
} catch (err) {
|
|
34810
|
+
return cmdErr("Failed to write config.json: " + err.message);
|
|
34811
|
+
}
|
|
34812
|
+
}
|
|
34813
|
+
function cmdConfigGet(cwd, keyPath, raw) {
|
|
34814
|
+
const configPath = node_path.default.join(cwd, ".planning", "config.json");
|
|
34815
|
+
if (!keyPath) return cmdErr("Usage: config-get <key.path>");
|
|
34816
|
+
let config = {};
|
|
34817
|
+
try {
|
|
34818
|
+
if (node_fs.default.existsSync(configPath)) config = JSON.parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
34819
|
+
else return cmdErr("No config.json found at " + configPath);
|
|
34820
|
+
} catch (err) {
|
|
34821
|
+
return cmdErr("Failed to read config.json: " + err.message);
|
|
34822
|
+
}
|
|
34823
|
+
const keys = keyPath.split(".");
|
|
34824
|
+
let current = config;
|
|
34825
|
+
for (const key of keys) {
|
|
34826
|
+
if (current === void 0 || current === null || typeof current !== "object") return cmdErr(`Key not found: ${keyPath}`);
|
|
34827
|
+
current = current[key];
|
|
34828
|
+
}
|
|
34829
|
+
if (current === void 0) return cmdErr(`Key not found: ${keyPath}`);
|
|
34830
|
+
return cmdOk(current, raw ? String(current) : void 0);
|
|
34831
|
+
}
|
|
34832
|
+
|
|
34833
|
+
//#endregion
|
|
34834
|
+
//#region src/mcp/config-tools.ts
|
|
34835
|
+
/**
|
|
34836
|
+
* Config Query MCP Tools — Project configuration exposed as MCP tools
|
|
34837
|
+
*
|
|
34838
|
+
* CRITICAL: Never import output() or error() from core — they call process.exit().
|
|
34839
|
+
* CRITICAL: Never write to stdout — it is reserved for MCP JSON-RPC protocol.
|
|
34840
|
+
* CRITICAL: Never call process.exit() — the server must stay alive after every tool call.
|
|
34841
|
+
*/
|
|
34842
|
+
/**
|
|
34843
|
+
* Register all config query tools on the MCP server.
|
|
34844
|
+
*/
|
|
34845
|
+
function registerConfigTools(server) {
|
|
34846
|
+
server.tool("mcp_get_config", "Get project configuration. Optionally provide a key path to get a specific value.", { key: stringType().optional().describe("Optional dot-separated key path (e.g. \"model_profile\", \"branching.strategy\")") }, async ({ key }) => {
|
|
34847
|
+
try {
|
|
34848
|
+
const cwd = detectProjectRoot();
|
|
34849
|
+
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
34850
|
+
if (key) {
|
|
34851
|
+
const result = cmdConfigGet(cwd, key, true);
|
|
34852
|
+
if (!result.ok) return mcpError(result.error, "Config get failed");
|
|
34853
|
+
return mcpSuccess({
|
|
34854
|
+
key,
|
|
34855
|
+
value: result.rawValue ?? result.result
|
|
34856
|
+
}, `Config value for "${key}"`);
|
|
34857
|
+
}
|
|
34858
|
+
return mcpSuccess({ config: loadConfig(cwd) }, "Full configuration loaded");
|
|
34859
|
+
} catch (e) {
|
|
34860
|
+
return mcpError("Failed: " + e.message, "Error occurred");
|
|
34861
|
+
}
|
|
34862
|
+
});
|
|
34863
|
+
server.tool("mcp_update_config", "Update a project configuration value by key path.", {
|
|
34864
|
+
key: stringType().describe("Dot-separated key path (e.g. \"model_profile\", \"branching.strategy\")"),
|
|
34865
|
+
value: stringType().describe("New value to set")
|
|
34866
|
+
}, async ({ key, value }) => {
|
|
34867
|
+
try {
|
|
34868
|
+
const cwd = detectProjectRoot();
|
|
34869
|
+
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
34870
|
+
const result = cmdConfigSet(cwd, key, value, true);
|
|
34871
|
+
if (!result.ok) return mcpError(result.error, "Config update failed");
|
|
34872
|
+
return mcpSuccess({
|
|
34873
|
+
updated: true,
|
|
34874
|
+
key,
|
|
34875
|
+
value
|
|
34876
|
+
}, `Config "${key}" updated to "${value}"`);
|
|
34877
|
+
} catch (e) {
|
|
34878
|
+
return mcpError("Failed: " + e.message, "Error occurred");
|
|
34879
|
+
}
|
|
34880
|
+
});
|
|
34881
|
+
}
|
|
34882
|
+
|
|
34248
34883
|
//#endregion
|
|
34249
34884
|
//#region src/mcp/index.ts
|
|
34250
34885
|
/**
|
|
@@ -34254,6 +34889,9 @@ function registerAllTools(server) {
|
|
|
34254
34889
|
registerPhaseTools(server);
|
|
34255
34890
|
registerTodoTools(server);
|
|
34256
34891
|
registerStateTools(server);
|
|
34892
|
+
registerContextTools(server);
|
|
34893
|
+
registerRoadmapTools(server);
|
|
34894
|
+
registerConfigTools(server);
|
|
34257
34895
|
}
|
|
34258
34896
|
|
|
34259
34897
|
//#endregion
|