arcane-cli 0.16.1 → 0.16.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.
@@ -220,6 +220,13 @@ Create the session branch as the **first action** of any session, before any fil
220
220
  1. `git branch -m <old> <new>`
221
221
  2. `git push -u origin <new>`
222
222
  3. `git push origin --delete <old>` (skip this if an active PR still depends on `<old>`).
223
+ - **EF-20 hazard:** on some Windows/Git-for-Windows filesystems, a rename or delete can trigger
224
+ an interactive retry prompt (e.g. a file lock from an editor or antivirus scan) that blocks
225
+ indefinitely on a terminal with no non-interactive fallback. When running these commands
226
+ directly through a tool that supports a per-call timeout (an agent's shell tool, a CI step),
227
+ set one rather than assuming the command will return. Arcane's own TypeScript Git helper
228
+ (`src/modules/git.ts`) applies this as a standing contract — closed stdin plus a
229
+ command-class timeout — for every `git` invocation the CLI itself makes.
223
230
 
224
231
  **Agents:**
225
232
 
package/dist/index.js CHANGED
@@ -1599,15 +1599,140 @@ async function runAgentsList(targetDir) {
1599
1599
 
1600
1600
  // src/modules/git.ts
1601
1601
  import { execFile } from "child_process";
1602
- import { promisify } from "util";
1603
- var execFileAsync = promisify(execFile);
1604
- async function inspectGitRepository(cwd) {
1605
- try {
1606
- const { stdout: stdout2 } = await execFileAsync(
1602
+ var GitTimeoutError = class extends Error {
1603
+ constructor(message, options) {
1604
+ super(message, options);
1605
+ this.name = "GitTimeoutError";
1606
+ }
1607
+ };
1608
+ var NETWORK_SUBCOMMANDS = /* @__PURE__ */ new Set(["fetch", "pull", "push", "clone", "ls-remote", "send-email"]);
1609
+ var WRITE_SUBCOMMANDS = /* @__PURE__ */ new Set([
1610
+ "init",
1611
+ "commit",
1612
+ "add",
1613
+ "checkout",
1614
+ "switch",
1615
+ "merge",
1616
+ "rebase",
1617
+ "reset",
1618
+ "tag",
1619
+ "mv",
1620
+ "rm",
1621
+ "cherry-pick",
1622
+ "revert",
1623
+ "stash",
1624
+ "apply",
1625
+ "am",
1626
+ "restore",
1627
+ "submodule",
1628
+ "sparse-checkout",
1629
+ "maintenance",
1630
+ "notes"
1631
+ ]);
1632
+ var AMBIGUOUS_AS_WRITE = /* @__PURE__ */ new Set(["branch", "config", "remote", "worktree"]);
1633
+ var READ_SUBCOMMANDS = /* @__PURE__ */ new Set([
1634
+ "status",
1635
+ "rev-parse",
1636
+ "log",
1637
+ "diff",
1638
+ "show",
1639
+ "describe",
1640
+ "cat-file",
1641
+ "ls-files",
1642
+ "ls-tree",
1643
+ "symbolic-ref",
1644
+ "merge-base",
1645
+ "blame",
1646
+ "shortlog",
1647
+ "grep",
1648
+ "reflog",
1649
+ "diff-tree",
1650
+ "rev-list",
1651
+ "name-rev",
1652
+ "count-objects",
1653
+ "verify-commit",
1654
+ "verify-tag",
1655
+ "help",
1656
+ "version"
1657
+ ]);
1658
+ var GLOBAL_FLAGS_WITH_SEPARATE_VALUE = /* @__PURE__ */ new Set(["-c", "-C"]);
1659
+ var DEFAULT_TIMEOUTS_MS = {
1660
+ read: 15e3,
1661
+ write: 3e4,
1662
+ network: 12e4
1663
+ };
1664
+ function classifyGitCommand(args) {
1665
+ let subcommand;
1666
+ for (let i = 0; i < args.length; i++) {
1667
+ const arg = args[i];
1668
+ if (GLOBAL_FLAGS_WITH_SEPARATE_VALUE.has(arg)) {
1669
+ i++;
1670
+ continue;
1671
+ }
1672
+ if (arg.startsWith("-")) continue;
1673
+ subcommand = arg;
1674
+ break;
1675
+ }
1676
+ if (!subcommand) return "read";
1677
+ if (NETWORK_SUBCOMMANDS.has(subcommand)) return "network";
1678
+ if (WRITE_SUBCOMMANDS.has(subcommand) || AMBIGUOUS_AS_WRITE.has(subcommand)) return "write";
1679
+ if (READ_SUBCOMMANDS.has(subcommand)) return "read";
1680
+ return "write";
1681
+ }
1682
+ function buildGitEnv() {
1683
+ return {
1684
+ ...process.env,
1685
+ GIT_TERMINAL_PROMPT: "0",
1686
+ GCM_INTERACTIVE: "Never",
1687
+ GIT_OPTIONAL_LOCKS: "0"
1688
+ };
1689
+ }
1690
+ function runGit(cwd, args, options = {}) {
1691
+ const commandClass = options.commandClass ?? classifyGitCommand(args);
1692
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUTS_MS[commandClass] ?? DEFAULT_TIMEOUTS_MS.write;
1693
+ return new Promise((resolve, reject) => {
1694
+ const child = execFile(
1607
1695
  "git",
1608
- ["rev-parse", "--is-inside-work-tree"],
1609
- { cwd }
1696
+ args,
1697
+ {
1698
+ cwd,
1699
+ env: buildGitEnv(),
1700
+ timeout: timeoutMs,
1701
+ // SIGTERM (Node's default) rather than SIGKILL: gives git a chance
1702
+ // to run its own cleanup on POSIX, relevant to EF-20's own concern
1703
+ // about a killed operation leaving refs/locks in an uncertain state.
1704
+ // Moot on Windows -- Node docs: both map to an abrupt TerminateProcess
1705
+ // there -- but this module isn't Windows-gated.
1706
+ killSignal: "SIGTERM",
1707
+ windowsHide: true,
1708
+ encoding: "utf8"
1709
+ },
1710
+ (error, stdout, stderr) => {
1711
+ if (error) {
1712
+ if (error.killed) {
1713
+ reject(
1714
+ new GitTimeoutError(
1715
+ // NOTE: embeds the raw args, including any secrets a future
1716
+ // network-class caller might pass (e.g. a credentialed
1717
+ // clone URL). No current caller does; redact before one does.
1718
+ `git ${args.join(" ")} timed out after ${timeoutMs}ms (class: ${commandClass})`,
1719
+ { cause: error }
1720
+ )
1721
+ );
1722
+ return;
1723
+ }
1724
+ reject(error);
1725
+ return;
1726
+ }
1727
+ resolve({ stdout, stderr });
1728
+ }
1610
1729
  );
1730
+ child.stdin?.end();
1731
+ });
1732
+ }
1733
+ async function inspectGitRepository(cwd) {
1734
+ try {
1735
+ const { stdout: stdout2 } = await runGit(cwd, ["rev-parse", "--is-inside-work-tree"]);
1611
1736
  if (stdout2.trim() !== "true") {
1612
1737
  return { status: "not-repository" };
1613
1738
  }
@@ -1615,15 +1740,11 @@ async function inspectGitRepository(cwd) {
1615
1740
  return { status: "not-repository" };
1616
1741
  }
1617
1742
  try {
1618
- await execFileAsync("git", ["rev-parse", "--verify", "HEAD"], { cwd });
1743
+ await runGit(cwd, ["rev-parse", "--verify", "HEAD"]);
1619
1744
  } catch {
1620
1745
  return { status: "no-commits" };
1621
1746
  }
1622
- const { stdout } = await execFileAsync(
1623
- "git",
1624
- ["status", "--porcelain", "-uall"],
1625
- { cwd }
1626
- );
1747
+ const { stdout } = await runGit(cwd, ["status", "--porcelain", "-uall"]);
1627
1748
  const lines = stdout.trim();
1628
1749
  return {
1629
1750
  status: "ready",
@@ -1632,7 +1753,7 @@ async function inspectGitRepository(cwd) {
1632
1753
  }
1633
1754
  async function countUncommittedChanges(cwd) {
1634
1755
  try {
1635
- const { stdout } = await execFileAsync("git", ["status", "--porcelain", "-uall"], { cwd });
1756
+ const { stdout } = await runGit(cwd, ["status", "--porcelain", "-uall"]);
1636
1757
  const lines = stdout.trim();
1637
1758
  return lines.length === 0 ? 0 : lines.split("\n").length;
1638
1759
  } catch {
@@ -2391,7 +2512,7 @@ async function runUninstall(options, targetDir) {
2391
2512
  import { access as access3, readFile as readFile5, mkdir as mkdir5, copyFile as fsCopyFile2 } from "fs/promises";
2392
2513
  import { join as join10, dirname as dirname2 } from "path";
2393
2514
  import { execFile as execFile2 } from "child_process";
2394
- import { promisify as promisify2 } from "util";
2515
+ import { promisify } from "util";
2395
2516
 
2396
2517
  // src/config/incidents.ts
2397
2518
  var INCIDENT_QUEUE = [
@@ -2459,7 +2580,7 @@ function evaluateIncidentGate(records) {
2459
2580
  }
2460
2581
 
2461
2582
  // src/commands/doctor.ts
2462
- var execFileAsync2 = promisify2(execFile2);
2583
+ var execFileAsync = promisify(execFile2);
2463
2584
  async function checkNodeVersion() {
2464
2585
  const name = "Node.js version";
2465
2586
  const raw = process.version;
@@ -2497,7 +2618,7 @@ async function checkVSCodeExtension(id, label) {
2497
2618
  let installBin = "code";
2498
2619
  for (const bin of ["code", "code-insiders"]) {
2499
2620
  try {
2500
- await execFileAsync2(bin, ["--version"]);
2621
+ await execFileAsync(bin, ["--version"]);
2501
2622
  installBin = bin;
2502
2623
  break;
2503
2624
  } catch {