arcane-cli 1.9.0 → 1.9.2
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/assets/.mcp.json +2 -2
- package/dist/index.js +84 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/assets/.mcp.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "Example MCP server config. Replace or remove the example server below with your own. Set a per-server \"timeout\" (milliseconds) so a hung server aborts on a predictable schedule instead of running to the client's own default idle limit (as long as 30 minutes) -- see .arcane/governance/git-conventions.md's MCP fail-fast / fallback rule.",
|
|
2
|
+
"$comment": "Example MCP server config. Replace or remove the example server below with your own: the package argument is a placeholder that npm rejects, so the example does not run until you name a real server package. Set a per-server \"timeout\" (milliseconds) so a hung server aborts on a predictable schedule instead of running to the client's own default idle limit (as long as 30 minutes) -- see .arcane/governance/git-conventions.md's MCP fail-fast / fallback rule.",
|
|
3
3
|
"mcpServers": {
|
|
4
4
|
"example-server": {
|
|
5
5
|
"command": "npx",
|
|
6
|
-
"args": ["-y", "
|
|
6
|
+
"args": ["-y", "<your-mcp-server-package>"],
|
|
7
7
|
"timeout": 30000
|
|
8
8
|
}
|
|
9
9
|
}
|
package/dist/index.js
CHANGED
|
@@ -5896,8 +5896,10 @@ async function scanRepository(repoRoot, rules, extensions, excludePrefixes = [])
|
|
|
5896
5896
|
|
|
5897
5897
|
// src/modules/exec.ts
|
|
5898
5898
|
import { execFile as execFile2 } from "child_process";
|
|
5899
|
+
import { existsSync, readFileSync } from "fs";
|
|
5900
|
+
import { win32 } from "path";
|
|
5899
5901
|
var EXTERNAL_CLI_TIMEOUT_MS = 1e4;
|
|
5900
|
-
function execFileWithTimeout(file, args, timeoutMs) {
|
|
5902
|
+
function execFileWithTimeout(file, args, timeoutMs, options = {}) {
|
|
5901
5903
|
return new Promise((resolve5, reject) => {
|
|
5902
5904
|
const child = execFile2(
|
|
5903
5905
|
file,
|
|
@@ -5906,7 +5908,8 @@ function execFileWithTimeout(file, args, timeoutMs) {
|
|
|
5906
5908
|
timeout: timeoutMs,
|
|
5907
5909
|
killSignal: "SIGTERM",
|
|
5908
5910
|
windowsHide: true,
|
|
5909
|
-
encoding: "utf8"
|
|
5911
|
+
encoding: "utf8",
|
|
5912
|
+
...options.env ? { env: { ...process.env, ...options.env } } : {}
|
|
5910
5913
|
},
|
|
5911
5914
|
(error, stdout, stderr) => {
|
|
5912
5915
|
if (error) {
|
|
@@ -5919,6 +5922,36 @@ function execFileWithTimeout(file, args, timeoutMs) {
|
|
|
5919
5922
|
child.stdin?.end();
|
|
5920
5923
|
});
|
|
5921
5924
|
}
|
|
5925
|
+
function defaultProbes() {
|
|
5926
|
+
return {
|
|
5927
|
+
platform: process.platform,
|
|
5928
|
+
pathEnv: process.env["PATH"] ?? process.env["Path"],
|
|
5929
|
+
readText: (path4) => {
|
|
5930
|
+
try {
|
|
5931
|
+
return readFileSync(path4, "utf8");
|
|
5932
|
+
} catch {
|
|
5933
|
+
return void 0;
|
|
5934
|
+
}
|
|
5935
|
+
},
|
|
5936
|
+
exists: (path4) => existsSync(path4)
|
|
5937
|
+
};
|
|
5938
|
+
}
|
|
5939
|
+
var MSI_LAUNCHER_LINE = /^\s*"%~dp0\\\.\.\\python\.exe"\s+-IBm\s+azure\.cli\s+%\*\s*$/im;
|
|
5940
|
+
function resolveAzureCli(probes = defaultProbes()) {
|
|
5941
|
+
const plain = { file: "az", prefixArgs: [] };
|
|
5942
|
+
if (probes.platform !== "win32") return plain;
|
|
5943
|
+
for (const raw of (probes.pathEnv ?? "").split(";")) {
|
|
5944
|
+
const dir = raw.trim().replace(/^"(.*)"$/, "$1");
|
|
5945
|
+
if (!dir || !win32.isAbsolute(dir)) continue;
|
|
5946
|
+
const launcher = probes.readText(win32.join(dir, "az.cmd"));
|
|
5947
|
+
if (launcher === void 0) continue;
|
|
5948
|
+
if (!MSI_LAUNCHER_LINE.test(launcher)) return plain;
|
|
5949
|
+
const interpreter = win32.resolve(dir, "..", "python.exe");
|
|
5950
|
+
if (!probes.exists(interpreter)) return plain;
|
|
5951
|
+
return { file: interpreter, prefixArgs: ["-IBm", "azure.cli"], env: { AZ_INSTALLER: "MSI" } };
|
|
5952
|
+
}
|
|
5953
|
+
return plain;
|
|
5954
|
+
}
|
|
5922
5955
|
|
|
5923
5956
|
// src/modules/platform-policy.ts
|
|
5924
5957
|
var SANCTIONED_MERGE_METHODS = ["merge", "rebase"];
|
|
@@ -6028,10 +6061,13 @@ function evaluateAdoMergePolicy(policies) {
|
|
|
6028
6061
|
}
|
|
6029
6062
|
return compareToLadder([...effective], []);
|
|
6030
6063
|
}
|
|
6031
|
-
async function fetchAdoMergeTypePolicies(org, project, repoName, branch) {
|
|
6064
|
+
async function fetchAdoMergeTypePolicies(org, project, repoName, branch, launch = resolveAzureCli()) {
|
|
6065
|
+
const az = (args) => execFileWithTimeout(launch.file, [...launch.prefixArgs, ...args], EXTERNAL_CLI_TIMEOUT_MS, {
|
|
6066
|
+
...launch.env ? { env: launch.env } : {}
|
|
6067
|
+
});
|
|
6032
6068
|
try {
|
|
6033
6069
|
const orgUrl = `https://dev.azure.com/${org}`;
|
|
6034
|
-
const { stdout: repoOut } = await
|
|
6070
|
+
const { stdout: repoOut } = await az([
|
|
6035
6071
|
"repos",
|
|
6036
6072
|
"show",
|
|
6037
6073
|
"--repository",
|
|
@@ -6044,10 +6080,10 @@ async function fetchAdoMergeTypePolicies(org, project, repoName, branch) {
|
|
|
6044
6080
|
"id",
|
|
6045
6081
|
"--output",
|
|
6046
6082
|
"tsv"
|
|
6047
|
-
]
|
|
6083
|
+
]);
|
|
6048
6084
|
const repositoryId = repoOut.trim();
|
|
6049
6085
|
if (!repositoryId) return null;
|
|
6050
|
-
const { stdout } = await
|
|
6086
|
+
const { stdout } = await az([
|
|
6051
6087
|
"repos",
|
|
6052
6088
|
"policy",
|
|
6053
6089
|
"list",
|
|
@@ -6061,7 +6097,7 @@ async function fetchAdoMergeTypePolicies(org, project, repoName, branch) {
|
|
|
6061
6097
|
project,
|
|
6062
6098
|
"--output",
|
|
6063
6099
|
"json"
|
|
6064
|
-
]
|
|
6100
|
+
]);
|
|
6065
6101
|
return JSON.parse(stdout);
|
|
6066
6102
|
} catch {
|
|
6067
6103
|
return null;
|
|
@@ -6074,18 +6110,26 @@ function parseGitHubRemote(url) {
|
|
|
6074
6110
|
if (!owner || !repo) return null;
|
|
6075
6111
|
return { owner, repo };
|
|
6076
6112
|
}
|
|
6077
|
-
function
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
const legacy = /([^./]+)\.visualstudio\.com\/([^/]+)\/_git\/([^/?]+)/.exec(url);
|
|
6084
|
-
if (legacy) {
|
|
6085
|
-
const [, org, project, repo] = legacy;
|
|
6086
|
-
if (org && project && repo) return { org, project, repo };
|
|
6113
|
+
function decodeAdoSegment(raw) {
|
|
6114
|
+
let decoded;
|
|
6115
|
+
try {
|
|
6116
|
+
decoded = decodeURIComponent(raw);
|
|
6117
|
+
} catch {
|
|
6118
|
+
return null;
|
|
6087
6119
|
}
|
|
6088
|
-
return null;
|
|
6120
|
+
if (!decoded || decoded.startsWith("-")) return null;
|
|
6121
|
+
return decoded;
|
|
6122
|
+
}
|
|
6123
|
+
function parseAdoRemote(url) {
|
|
6124
|
+
const match = /dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/?]+)/.exec(url) ?? /([^./]+)\.visualstudio\.com\/([^/]+)\/_git\/([^/?]+)/.exec(url);
|
|
6125
|
+
if (!match) return null;
|
|
6126
|
+
const [, rawOrg, rawProject, rawRepo] = match;
|
|
6127
|
+
if (!rawOrg || !rawProject || !rawRepo) return null;
|
|
6128
|
+
const org = decodeAdoSegment(rawOrg);
|
|
6129
|
+
const project = decodeAdoSegment(rawProject);
|
|
6130
|
+
const repo = decodeAdoSegment(rawRepo);
|
|
6131
|
+
if (org === null || project === null || repo === null) return null;
|
|
6132
|
+
return { org, project, repo };
|
|
6089
6133
|
}
|
|
6090
6134
|
|
|
6091
6135
|
// src/modules/placeholders.ts
|
|
@@ -6552,6 +6596,7 @@ async function checkGovernancePlaceholders(targetDir) {
|
|
|
6552
6596
|
message: `unknown placeholder(s) in status: active doc(s) \u2014 ${listed.join("; ")}. Fill each one in; or, if an agent resolves it at use time, add it to the runtime-placeholders list in .arcane/governance/${PLACEHOLDER_STANDARD_FILE}; or, if the document is meant to be filled in by hand, mark it \`status: template\`.`
|
|
6553
6597
|
};
|
|
6554
6598
|
}
|
|
6599
|
+
var UNOWNED_EXAMPLE_SCOPE = "@example/";
|
|
6555
6600
|
async function checkComponentRequires(targetDir) {
|
|
6556
6601
|
const name = "Component prerequisites";
|
|
6557
6602
|
let manifest;
|
|
@@ -6611,6 +6656,22 @@ async function checkMcpConfig(targetDir) {
|
|
|
6611
6656
|
if (serverNames.length === 0) {
|
|
6612
6657
|
return { name, passed: true, blocking: false, message: "no MCP servers configured" };
|
|
6613
6658
|
}
|
|
6659
|
+
const exampleHits = [];
|
|
6660
|
+
for (const n of serverNames) {
|
|
6661
|
+
const args = servers[n]?.args;
|
|
6662
|
+
if (!Array.isArray(args)) continue;
|
|
6663
|
+
for (const a of args) {
|
|
6664
|
+
if (typeof a === "string" && a.startsWith(UNOWNED_EXAMPLE_SCOPE)) exampleHits.push(`${n} (${a})`);
|
|
6665
|
+
}
|
|
6666
|
+
}
|
|
6667
|
+
if (exampleHits.length > 0) {
|
|
6668
|
+
return {
|
|
6669
|
+
name,
|
|
6670
|
+
passed: false,
|
|
6671
|
+
blocking: false,
|
|
6672
|
+
message: `.mcp.json runs a package under the ${UNOWNED_EXAMPLE_SCOPE} scope nobody here owns: ${exampleHits.join(", ")}. The old \`spell init\` scaffold shipped it as a placeholder, and \`npx -y\` would run whoever publishes it. Replace the entry with your own MCP server package, or remove it; the file is yours, so doctor does not edit it`
|
|
6673
|
+
};
|
|
6674
|
+
}
|
|
6614
6675
|
const missingTimeout = serverNames.filter((n) => typeof servers[n]?.timeout !== "number");
|
|
6615
6676
|
if (missingTimeout.length > 0) {
|
|
6616
6677
|
return {
|
|
@@ -7441,13 +7502,16 @@ async function getVersionAtRef(cwd, ref) {
|
|
|
7441
7502
|
return null;
|
|
7442
7503
|
}
|
|
7443
7504
|
}
|
|
7505
|
+
function excludeOutputs(generatedOutputs) {
|
|
7506
|
+
return generatedOutputs.length === 0 ? [] : ["--", ".", ...generatedOutputs.map((p) => `:(exclude)${p}`)];
|
|
7507
|
+
}
|
|
7444
7508
|
async function getCloseCommit(cwd, completedDate, generatedOutputs = []) {
|
|
7445
7509
|
if (!completedDate) {
|
|
7446
|
-
const args = generatedOutputs.length === 0 ? ["rev-parse", "HEAD"] : ["log", "-1", "--format=%H", "HEAD",
|
|
7510
|
+
const args = generatedOutputs.length === 0 ? ["rev-parse", "HEAD"] : ["log", "-1", "--format=%H", "HEAD", ...excludeOutputs(generatedOutputs)];
|
|
7447
7511
|
const head = await runGitTextOrNull(cwd, args);
|
|
7448
7512
|
return head === null || head === "" ? null : head;
|
|
7449
7513
|
}
|
|
7450
|
-
const log = await runGitTextOrNull(cwd, ["log", "--format=%H%x09%cs", "HEAD"]);
|
|
7514
|
+
const log = await runGitTextOrNull(cwd, ["log", "--format=%H%x09%cs", "HEAD", ...excludeOutputs(generatedOutputs)]);
|
|
7451
7515
|
if (log === null || log === "") return null;
|
|
7452
7516
|
for (const line of log.split("\n")) {
|
|
7453
7517
|
const [sha, landed] = line.split(" ");
|