arcane-cli 1.9.0 → 1.9.1

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.
@@ -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", "@example/mcp-server"],
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 execFileWithTimeout("az", [
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
- ], EXTERNAL_CLI_TIMEOUT_MS);
6083
+ ]);
6048
6084
  const repositoryId = repoOut.trim();
6049
6085
  if (!repositoryId) return null;
6050
- const { stdout } = await execFileWithTimeout("az", [
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
- ], EXTERNAL_CLI_TIMEOUT_MS);
6100
+ ]);
6065
6101
  return JSON.parse(stdout);
6066
6102
  } catch {
6067
6103
  return null;
@@ -6552,6 +6588,7 @@ async function checkGovernancePlaceholders(targetDir) {
6552
6588
  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
6589
  };
6554
6590
  }
6591
+ var UNOWNED_EXAMPLE_SCOPE = "@example/";
6555
6592
  async function checkComponentRequires(targetDir) {
6556
6593
  const name = "Component prerequisites";
6557
6594
  let manifest;
@@ -6611,6 +6648,22 @@ async function checkMcpConfig(targetDir) {
6611
6648
  if (serverNames.length === 0) {
6612
6649
  return { name, passed: true, blocking: false, message: "no MCP servers configured" };
6613
6650
  }
6651
+ const exampleHits = [];
6652
+ for (const n of serverNames) {
6653
+ const args = servers[n]?.args;
6654
+ if (!Array.isArray(args)) continue;
6655
+ for (const a of args) {
6656
+ if (typeof a === "string" && a.startsWith(UNOWNED_EXAMPLE_SCOPE)) exampleHits.push(`${n} (${a})`);
6657
+ }
6658
+ }
6659
+ if (exampleHits.length > 0) {
6660
+ return {
6661
+ name,
6662
+ passed: false,
6663
+ blocking: false,
6664
+ 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`
6665
+ };
6666
+ }
6614
6667
  const missingTimeout = serverNames.filter((n) => typeof servers[n]?.timeout !== "number");
6615
6668
  if (missingTimeout.length > 0) {
6616
6669
  return {
@@ -7441,13 +7494,16 @@ async function getVersionAtRef(cwd, ref) {
7441
7494
  return null;
7442
7495
  }
7443
7496
  }
7497
+ function excludeOutputs(generatedOutputs) {
7498
+ return generatedOutputs.length === 0 ? [] : ["--", ".", ...generatedOutputs.map((p) => `:(exclude)${p}`)];
7499
+ }
7444
7500
  async function getCloseCommit(cwd, completedDate, generatedOutputs = []) {
7445
7501
  if (!completedDate) {
7446
- const args = generatedOutputs.length === 0 ? ["rev-parse", "HEAD"] : ["log", "-1", "--format=%H", "HEAD", "--", ".", ...generatedOutputs.map((p) => `:(exclude)${p}`)];
7502
+ const args = generatedOutputs.length === 0 ? ["rev-parse", "HEAD"] : ["log", "-1", "--format=%H", "HEAD", ...excludeOutputs(generatedOutputs)];
7447
7503
  const head = await runGitTextOrNull(cwd, args);
7448
7504
  return head === null || head === "" ? null : head;
7449
7505
  }
7450
- const log = await runGitTextOrNull(cwd, ["log", "--format=%H%x09%cs", "HEAD"]);
7506
+ const log = await runGitTextOrNull(cwd, ["log", "--format=%H%x09%cs", "HEAD", ...excludeOutputs(generatedOutputs)]);
7451
7507
  if (log === null || log === "") return null;
7452
7508
  for (const line of log.split("\n")) {
7453
7509
  const [sha, landed] = line.split(" ");