@brainervirus/workit-pi 8.12.1 → 8.12.3

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.
Files changed (3) hide show
  1. package/dist/worker.js +50 -2
  2. package/dist/workit.js +1341 -194
  3. package/package.json +1 -1
package/dist/workit.js CHANGED
@@ -16298,7 +16298,76 @@ var writeSessionMarker = (file, marker) => {
16298
16298
  };
16299
16299
 
16300
16300
  // packages/workit-core/src/hooks/shell-words.ts
16301
- var closingQuote = (command, start, dialect) => {
16301
+ class ShellAnalysisError extends Error {
16302
+ }
16303
+ var MAX_NESTING = 64;
16304
+ var OPEN = "";
16305
+ var COMMA = "";
16306
+ var CLOSE = "";
16307
+ var MARKERS = /[\uE000-\uE002]/u;
16308
+ var literal2 = (word) => word.replaceAll(OPEN, "{").replaceAll(COMMA, ",").replaceAll(CLOSE, "}");
16309
+ var BRACE_LIMIT = 64;
16310
+ var BRACE_PENDING = 512;
16311
+ var BRACE_DEPTH = 16;
16312
+ var braceGroup = (word) => {
16313
+ for (let start = 0;start < word.length; start++) {
16314
+ if (word[start] !== OPEN)
16315
+ continue;
16316
+ let depth = 0;
16317
+ let end = start;
16318
+ const commas = [];
16319
+ for (;end < word.length; end++) {
16320
+ if (word[end] === OPEN)
16321
+ depth++;
16322
+ else if (word[end] === CLOSE && --depth === 0)
16323
+ break;
16324
+ else if (word[end] === COMMA && depth === 1)
16325
+ commas.push(end);
16326
+ }
16327
+ if (end < word.length && commas.length)
16328
+ return { start, end, commas };
16329
+ }
16330
+ return null;
16331
+ };
16332
+ var expandBraces = (word) => {
16333
+ let depth = 0;
16334
+ for (const char of word) {
16335
+ if (char === OPEN && ++depth > BRACE_DEPTH)
16336
+ return [literal2(word)];
16337
+ if (char === CLOSE && depth > 0)
16338
+ depth--;
16339
+ }
16340
+ const done = new Set;
16341
+ const queue = [word];
16342
+ const queued = new Set(queue);
16343
+ for (let at = 0;at < queue.length; at++) {
16344
+ const current = queue[at];
16345
+ const group = braceGroup(current);
16346
+ if (!group) {
16347
+ const text = literal2(current);
16348
+ if (text)
16349
+ done.add(text);
16350
+ if (done.size > BRACE_LIMIT)
16351
+ return [literal2(word)];
16352
+ continue;
16353
+ }
16354
+ const pre = current.slice(0, group.start);
16355
+ const post = current.slice(group.end + 1);
16356
+ let from = group.start + 1;
16357
+ for (const comma of [...group.commas, group.end]) {
16358
+ const next = pre + current.slice(from, comma) + post;
16359
+ from = comma + 1;
16360
+ if (queued.has(next))
16361
+ continue;
16362
+ queued.add(next);
16363
+ queue.push(next);
16364
+ }
16365
+ if (queue.length - at > BRACE_PENDING)
16366
+ return [literal2(word)];
16367
+ }
16368
+ return [...done];
16369
+ };
16370
+ var closingQuote = (command, start, dialect, nesting = 0) => {
16302
16371
  const quote = command[start];
16303
16372
  for (let index = start + 1;index < command.length; index++) {
16304
16373
  const char = command[index];
@@ -16315,51 +16384,265 @@ var closingQuote = (command, start, dialect) => {
16315
16384
  index++;
16316
16385
  continue;
16317
16386
  }
16387
+ if (dialect === "posix" && char === "$" && command[index + 1] === "(") {
16388
+ index = substitutionEnd(command, index + 1, nesting + 1);
16389
+ continue;
16390
+ }
16391
+ if (dialect === "posix" && char === "`") {
16392
+ index = backtickEnd(command, index);
16393
+ continue;
16394
+ }
16318
16395
  if (char === '"')
16319
16396
  return index;
16320
16397
  }
16321
16398
  return -1;
16322
16399
  };
16400
+ var backtickEnd = (command, start) => {
16401
+ for (let index = start + 1;index < command.length; index++) {
16402
+ if (command[index] === "\\")
16403
+ index++;
16404
+ else if (command[index] === "`")
16405
+ return index;
16406
+ }
16407
+ return command.length;
16408
+ };
16409
+ var heredocDelimiter = (command, start) => {
16410
+ let index = start;
16411
+ if (command[index] === "-")
16412
+ index++;
16413
+ while (command[index] === " " || command[index] === "\t")
16414
+ index++;
16415
+ let delimiter = "";
16416
+ for (;index < command.length && !/[\s;&|<>()]/.test(command[index]); index++) {
16417
+ const char = command[index];
16418
+ if (char === "'" || char === '"') {
16419
+ const end = command.indexOf(char, index + 1);
16420
+ if (end < 0)
16421
+ break;
16422
+ delimiter += command.slice(index + 1, end);
16423
+ index = end;
16424
+ } else if (char === "\\")
16425
+ delimiter += command[++index] ?? "";
16426
+ else
16427
+ delimiter += char;
16428
+ }
16429
+ return { delimiter, end: index - 1 };
16430
+ };
16431
+ var skipHeredocs = (command, newline, delimiters) => {
16432
+ let index = newline;
16433
+ while (delimiters.length) {
16434
+ const delimiter = delimiters.shift();
16435
+ for (;; ) {
16436
+ const lineEnd = command.indexOf(`
16437
+ `, index + 1);
16438
+ const line = command.slice(index + 1, lineEnd < 0 ? undefined : lineEnd);
16439
+ index = lineEnd < 0 ? command.length : lineEnd;
16440
+ if (line.trim() === delimiter || lineEnd < 0)
16441
+ break;
16442
+ }
16443
+ }
16444
+ return index;
16445
+ };
16446
+ var substitutionEnd = (command, start, nesting = 0) => {
16447
+ if (nesting > MAX_NESTING)
16448
+ throw new ShellAnalysisError("substitutions nest too deep");
16449
+ let depth = 0;
16450
+ let wordStart = true;
16451
+ const heredocs = [];
16452
+ for (let index = start;index < command.length; index++) {
16453
+ const char = command[index];
16454
+ const atWordStart = wordStart;
16455
+ wordStart = /[\s;&|()]/.test(char);
16456
+ if (char === "\\")
16457
+ index++;
16458
+ else if (char === `
16459
+ ` && heredocs.length) {
16460
+ index = skipHeredocs(command, index, heredocs);
16461
+ wordStart = true;
16462
+ } else if (char === "'" || char === '"') {
16463
+ const end = closingQuote(command, index, "posix", nesting);
16464
+ if (end < 0)
16465
+ return command.length;
16466
+ index = end;
16467
+ } else if (char === "`")
16468
+ index = backtickEnd(command, index);
16469
+ else if (char === "#" && atWordStart) {
16470
+ const newline = command.indexOf(`
16471
+ `, index);
16472
+ index = newline < 0 ? command.length : newline - 1;
16473
+ } else if (char === "<" && command[index + 1] === "<" && command[index + 2] !== "<") {
16474
+ const { delimiter, end } = heredocDelimiter(command, index + 2);
16475
+ if (delimiter)
16476
+ heredocs.push(delimiter);
16477
+ index = end;
16478
+ } else if (char === "<" && command[index + 1] === "<")
16479
+ index += 2;
16480
+ else if (char === "$" && command[index + 1] === "(")
16481
+ index = substitutionEnd(command, index + 1, nesting + 1);
16482
+ else if (char === "(")
16483
+ depth++;
16484
+ else if (char === ")" && --depth === 0)
16485
+ return index;
16486
+ }
16487
+ return command.length;
16488
+ };
16323
16489
  var unquote = (body, quote, dialect) => {
16324
16490
  if (quote === "'")
16325
16491
  return dialect === "powershell" ? body.replaceAll("''", "'") : body;
16326
- return dialect === "posix" ? body.replace(/\\(["\\$`])/g, "$1") : body.replace(/`(.)/g, "$1");
16492
+ return dialect === "posix" ? body.replace(/\\\n/g, "").replace(/\\(["\\$`])/g, "$1") : body.replace(/`(.)/g, "$1");
16493
+ };
16494
+ var C_ESCAPES = {
16495
+ n: `
16496
+ `,
16497
+ t: "\t",
16498
+ r: "\r",
16499
+ a: "\x07",
16500
+ b: "\b",
16501
+ e: "\x1B",
16502
+ E: "\x1B",
16503
+ f: "\f",
16504
+ v: "\v"
16505
+ };
16506
+ var ansiC = (command, start) => {
16507
+ let text = "";
16508
+ let index = start + 1;
16509
+ for (;index < command.length && command[index] !== "'"; index++) {
16510
+ const char = command[index];
16511
+ if (char !== "\\" || index + 1 >= command.length) {
16512
+ text += char;
16513
+ continue;
16514
+ }
16515
+ const rest = command.slice(++index);
16516
+ const hex = /^x([0-9a-fA-F]{1,2})/.exec(rest);
16517
+ const octal = /^[0-7]{1,3}/.exec(rest);
16518
+ if (hex) {
16519
+ text += String.fromCharCode(Number.parseInt(hex[1], 16));
16520
+ index += hex[0].length - 1;
16521
+ } else if (octal) {
16522
+ text += String.fromCharCode(Number.parseInt(octal[0], 8));
16523
+ index += octal[0].length - 1;
16524
+ } else
16525
+ text += C_ESCAPES[rest[0]] ?? rest[0];
16526
+ }
16527
+ return { text, end: index };
16528
+ };
16529
+ var substitutions = (body) => {
16530
+ const found = [];
16531
+ for (let index = 0;index < body.length; index++) {
16532
+ const char = body[index];
16533
+ if (char === "\\")
16534
+ index++;
16535
+ else if (char === "`") {
16536
+ const end = backtickEnd(body, index);
16537
+ found.push(body.slice(index + 1, end).replace(/\\(.)/g, "$1"));
16538
+ index = end;
16539
+ } else if (char === "$" && body[index + 1] === "(") {
16540
+ const end = substitutionEnd(body, index + 1);
16541
+ found.push(body.slice(index + 2, end));
16542
+ index = end;
16543
+ }
16544
+ }
16545
+ return found;
16546
+ };
16547
+ var PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
16548
+ var PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
16549
+ var arithmeticEnd = (command, start) => {
16550
+ const end = substitutionEnd(command, start);
16551
+ return command[end - 1] === ")" && end > start + 2 ? end : 0;
16327
16552
  };
16328
16553
  var segmentsOf = (command, dialect = "posix") => {
16554
+ try {
16555
+ return tokenize(command, dialect);
16556
+ } catch (error) {
16557
+ const why = error instanceof ShellAnalysisError ? error.message : String(error);
16558
+ return [{ words: [], redirects: [], unanalyzable: why }];
16559
+ }
16560
+ };
16561
+ var tokenize = (command, dialect) => {
16562
+ const posix = dialect === "posix";
16329
16563
  const segments = [];
16330
16564
  let current = { words: [], redirects: [] };
16331
16565
  let word = "";
16332
16566
  let inWord = false;
16333
16567
  let pending = null;
16334
16568
  const heredocs = [];
16335
- const escape = dialect === "posix" ? "\\" : "`";
16569
+ const escape = posix ? "\\" : "`";
16336
16570
  const flushWord = () => {
16337
16571
  if (!inWord)
16338
16572
  return;
16573
+ const text = MARKERS.test(word) ? literal2(word) : word;
16339
16574
  if (pending === "redirect") {
16340
- if (!word.startsWith("&"))
16341
- current.redirects.push(word);
16575
+ if (!text.startsWith("&"))
16576
+ current.redirects.push(text);
16342
16577
  } else if (pending === "heredoc")
16343
- heredocs.push(word.replace(/^-/, ""));
16578
+ heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
16579
+ else if (pending === "herestring")
16580
+ (current.stdin ??= []).push(text);
16344
16581
  else if (pending !== "input")
16345
- current.words.push(word);
16582
+ current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
16346
16583
  pending = null;
16347
16584
  word = "";
16348
16585
  inWord = false;
16349
16586
  };
16350
16587
  const flushSegment = () => {
16351
16588
  flushWord();
16352
- if (current.words.length || current.redirects.length)
16589
+ if (current.words.length || current.redirects.length || current.stdin || current.subs)
16353
16590
  segments.push(current);
16354
16591
  current = { words: [], redirects: [] };
16355
16592
  };
16593
+ const group = (kind) => {
16594
+ flushSegment();
16595
+ pending = null;
16596
+ segments.push({ words: [], redirects: [], group: kind });
16597
+ };
16598
+ const substitution = (inner, text) => {
16599
+ (current.subs ??= []).push(inner);
16600
+ word += text;
16601
+ inWord = true;
16602
+ };
16603
+ const plain = posix ? PLAIN_POSIX : PLAIN_POWERSHELL;
16356
16604
  for (let index = 0;index < command.length; index++) {
16605
+ plain.lastIndex = index;
16606
+ const run = plain.exec(command);
16607
+ if (run) {
16608
+ word += run[0];
16609
+ inWord = true;
16610
+ index += run[0].length - 1;
16611
+ continue;
16612
+ }
16357
16613
  const char = command[index];
16358
- if (char === "'" || char === '"') {
16614
+ const next = command[index + 1];
16615
+ if (char === escape && (next === `
16616
+ ` || next === "\r" && command[index + 2] === `
16617
+ `))
16618
+ index += next === "\r" ? 2 : 1;
16619
+ else if (char === "#" && !inWord) {
16620
+ const newline = command.indexOf(`
16621
+ `, index);
16622
+ index = (newline < 0 ? command.length : newline) - 1;
16623
+ } else if (!posix && char === "<" && next === "#") {
16624
+ const end = command.indexOf("#>", index + 2);
16625
+ index = end < 0 ? command.length : end + 1;
16626
+ } else if (posix && char === "$" && next === "'") {
16627
+ const { text, end } = ansiC(command, index + 1);
16628
+ word += text;
16629
+ inWord = true;
16630
+ index = end;
16631
+ } else if (posix && char === "$" && next === '"')
16632
+ continue;
16633
+ else if (char === "'" || char === '"') {
16359
16634
  const end = closingQuote(command, index, dialect);
16360
- word += unquote(command.slice(index + 1, end < 0 ? undefined : end), char, dialect);
16635
+ const body = command.slice(index + 1, end < 0 ? undefined : end);
16636
+ if (posix && char === '"' && /\$\(|`/.test(body))
16637
+ (current.subs ??= []).push(...substitutions(body));
16638
+ word += unquote(body, char, dialect);
16361
16639
  inWord = true;
16362
16640
  index = end < 0 ? command.length : end;
16641
+ } else if (posix && char === "`") {
16642
+ const end = backtickEnd(command, index);
16643
+ const inner = command.slice(index + 1, end);
16644
+ substitution(inner.replace(/\\(.)/g, "$1"), `\`${inner}\``);
16645
+ index = end;
16363
16646
  } else if (char === escape && index + 1 < command.length) {
16364
16647
  word += command[++index];
16365
16648
  inWord = true;
@@ -16367,26 +16650,56 @@ var segmentsOf = (command, dialect = "posix") => {
16367
16650
  `) {
16368
16651
  flushSegment();
16369
16652
  while (heredocs.length) {
16370
- const delimiter = heredocs.shift();
16653
+ const { delimiter, owner } = heredocs.shift();
16371
16654
  const lines = command.slice(index + 1).split(`
16372
16655
  `);
16373
16656
  const at = lines.findIndex((line) => line.trim() === delimiter);
16374
- const skipped = (at < 0 ? lines : lines.slice(0, at + 1)).join(`
16657
+ (owner.stdin ??= []).push((at < 0 ? lines : lines.slice(0, at)).join(`
16658
+ `));
16659
+ index += (at < 0 ? lines : lines.slice(0, at + 1)).join(`
16375
16660
  `).length + 1;
16376
- index += skipped;
16377
16661
  }
16378
16662
  } else if (/\s/.test(char))
16379
16663
  flushWord();
16380
- else if (char === "(" || char === ")") {
16664
+ else if (posix && char === "(" && inWord && word.endsWith("$")) {
16665
+ const end = substitutionEnd(command, index);
16666
+ const inner = command.slice(index + 1, end);
16667
+ substitution(inner, `(${inner})`);
16668
+ index = end;
16669
+ } else if (posix && char === "(" && (pending === "input" || pending === "redirect")) {
16670
+ const sign = pending === "input" ? "<" : ">";
16671
+ pending = null;
16672
+ const end = substitutionEnd(command, index);
16673
+ const inner = command.slice(index + 1, end);
16674
+ substitution(inner, `${sign}(${inner})`);
16675
+ index = end;
16676
+ } else if (posix && char === "(" && next === "(" && !inWord && arithmeticEnd(command, index)) {
16677
+ flushSegment();
16678
+ index = arithmeticEnd(command, index);
16679
+ } else if (char === "(" || char === ")") {
16381
16680
  if (char === "(" && inWord && word.endsWith("$")) {
16382
16681
  word = word.slice(0, -1);
16383
16682
  inWord = word !== "";
16384
16683
  }
16684
+ group(char === "(" ? "open" : "close");
16685
+ } else if (!posix && (char === "{" || char === "}")) {
16686
+ if (char === "{" && inWord && word.endsWith("@")) {
16687
+ word += char;
16688
+ continue;
16689
+ }
16385
16690
  flushSegment();
16386
- segments.push({ words: [], redirects: [], group: char === "(" ? "open" : "close" });
16387
16691
  } else if (";&|".includes(char)) {
16388
- if (char === "&" && command[index + 1] === ">")
16692
+ if (char === "&" && next === ">")
16389
16693
  continue;
16694
+ if (char === "|" && next === "|")
16695
+ index++;
16696
+ else if (char === "|") {
16697
+ flushWord();
16698
+ if (current.words.length)
16699
+ current.pipe = true;
16700
+ if (next === "&")
16701
+ index++;
16702
+ }
16390
16703
  flushSegment();
16391
16704
  } else if (char === ">") {
16392
16705
  if (inWord && /^\d$/.test(word)) {
@@ -16402,16 +16715,24 @@ var segmentsOf = (command, dialect = "posix") => {
16402
16715
  inWord = true;
16403
16716
  }
16404
16717
  pending = "redirect";
16405
- } else if (char === "<" && dialect === "posix") {
16718
+ } else if (char === "<" && posix) {
16406
16719
  flushWord();
16407
- if (command[index + 1] === "<") {
16720
+ if (next === "<" && command[index + 2] === "<") {
16721
+ index += 2;
16722
+ pending = "herestring";
16723
+ } else if (next === "<") {
16408
16724
  index++;
16409
- if (command[index + 1] === "<")
16410
- index++;
16411
16725
  pending = "heredoc";
16412
16726
  } else
16413
16727
  pending = "input";
16414
- } else {
16728
+ } else if (posix && char === "{" && !word.endsWith("$")) {
16729
+ word += OPEN;
16730
+ inWord = true;
16731
+ } else if (posix && char === "," && word.includes(OPEN))
16732
+ word += COMMA;
16733
+ else if (posix && char === "}" && word.includes(OPEN))
16734
+ word += CLOSE;
16735
+ else {
16415
16736
  word += char;
16416
16737
  inWord = true;
16417
16738
  }
@@ -16422,8 +16743,35 @@ var segmentsOf = (command, dialect = "posix") => {
16422
16743
 
16423
16744
  // packages/workit-core/src/hooks/raw-git.ts
16424
16745
  var NONE2 = { kind: "none" };
16425
- var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
16426
- var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
16746
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh", "ash", "mksh", "yash", "fish"]);
16747
+ var POWERSHELLS = new Set(["pwsh", "powershell"]);
16748
+ var EVALS = new Set(["eval", "iex", "invoke-expression"]);
16749
+ var WRAPPERS = {
16750
+ sudo: { values: "-u -g -h -p -C -D -R -T -r -t -U --user --group --host --prompt --chdir" },
16751
+ doas: { values: "-u -C" },
16752
+ command: { values: "" },
16753
+ builtin: { values: "" },
16754
+ env: { values: "-u -C --unset --chdir" },
16755
+ nohup: { values: "" },
16756
+ setsid: { values: "" },
16757
+ time: { values: "-f -o --format --output" },
16758
+ exec: { values: "-a" },
16759
+ nice: { values: "-n --adjustment" },
16760
+ ionice: { values: "-c -n -p --class --classdata" },
16761
+ stdbuf: { values: "-i -o -e --input --output --error" },
16762
+ chronic: { values: "" },
16763
+ unbuffer: { values: "" },
16764
+ wsl: { values: "-d -u --cd --distribution --user" },
16765
+ timeout: { values: "-s -k --signal --kill-after", operands: 1 },
16766
+ flock: { values: "-w -E --timeout --conflict-exit-code", operands: 1 },
16767
+ coproc: { values: "" },
16768
+ taskset: { values: "", operands: 1 },
16769
+ watch: { values: "-n -d --interval --differences -q --equexit" },
16770
+ "&": { values: "" }
16771
+ };
16772
+ var WRAPPER_VALUES = new Map(Object.entries(WRAPPERS).map(([name, wrapper]) => [name, new Set(wrapper.values.split(" "))]));
16773
+ var KEYWORDS = new Set(["if", "then", "else", "elif", "while", "until", "do", "!", "{"]);
16774
+ var DECLARATIONS = new Set(["export", "local", "declare", "readonly", "typeset"]);
16427
16775
  var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
16428
16776
  var CD = new Set(["cd", "chdir", "set-location", "sl"]);
16429
16777
  var PUSHD = new Set(["pushd", "push-location"]);
@@ -16437,13 +16785,21 @@ var GIT_VALUE_OPTIONS = new Set([
16437
16785
  "--config-env",
16438
16786
  "--super-prefix"
16439
16787
  ]);
16788
+ var GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
16789
+ var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
16790
+ var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
16791
+ var MAX_DEPTH = 4;
16792
+ var MAX_EXPANSIONS = 16;
16440
16793
  var isAbsolute = (value) => path18.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
16441
- var joinDir = (base, next) => {
16794
+ var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
16795
+ var joinDir = (base, raw) => {
16796
+ const next = raw.replace(HERE, ".");
16442
16797
  if (base === undefined || /[$`*?]/.test(next) || next === "-")
16443
16798
  return;
16444
16799
  const expanded = next === "~" || next.startsWith("~/") ? path18.join(os4.homedir(), next.slice(1)) : next;
16445
16800
  return base === null || isAbsolute(expanded) ? expanded : path18.join(base, expanded);
16446
16801
  };
16802
+ var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
16447
16803
  var cdTarget = (words) => {
16448
16804
  const args = words.slice(1);
16449
16805
  const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
@@ -16453,88 +16809,433 @@ var cdTarget = (words) => {
16453
16809
  };
16454
16810
  var stripPrefixes = (input) => {
16455
16811
  let words = input;
16812
+ const env = {};
16456
16813
  for (;; ) {
16457
16814
  const first = words[0];
16458
16815
  if (first === undefined)
16459
- return words;
16460
- if (/^\w+=/.test(first))
16816
+ return { words, env };
16817
+ const assignment = ASSIGNMENT.exec(first);
16818
+ if (assignment) {
16819
+ if (GIT_CONFIG_ENV.test(assignment[1]))
16820
+ env[assignment[1]] = assignment[2];
16461
16821
  words = words.slice(1);
16462
- else if (PREFIXES2.has(first)) {
16822
+ } else if (KEYWORDS.has(first))
16463
16823
  words = words.slice(1);
16464
- while (words[0]?.startsWith("-"))
16465
- words = words.slice(words[0] === "-u" ? 2 : 1);
16466
- } else if (first === "timeout") {
16824
+ else if (first === "function")
16825
+ words = words.slice(2);
16826
+ else if (Object.hasOwn(WRAPPERS, commandName(first))) {
16827
+ const name = commandName(first);
16828
+ const values = WRAPPER_VALUES.get(name);
16467
16829
  words = words.slice(1);
16468
- while (words[0]?.startsWith("-"))
16830
+ const takesValue = (option) => values.has(option) || /^-[a-zA-Z]{2,}$/.test(option) && values.has(`-${option.at(-1)}`);
16831
+ while (words[0]?.startsWith("-") && words[0] !== "--")
16832
+ words = words.slice(takesValue(words[0]) ? 2 : 1);
16833
+ if (words[0] === "--")
16469
16834
  words = words.slice(1);
16470
- words = words.slice(1);
16835
+ words = words.slice(WRAPPERS[name].operands ?? 0);
16471
16836
  } else
16472
- return words;
16837
+ return { words, env };
16473
16838
  }
16474
16839
  };
16475
16840
  var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
16841
+ var VARIABLE = /\$(?:\{(\w+)\}|(\w+))/g;
16842
+ var expandWord = (word, vars) => {
16843
+ if (!word.includes("$"))
16844
+ return [word];
16845
+ let results = [""];
16846
+ let last = 0;
16847
+ for (const match of word.matchAll(VARIABLE)) {
16848
+ const values = vars.get(match[1] ?? match[2]) ?? [match[0]];
16849
+ const before = word.slice(last, match.index);
16850
+ results = results.flatMap((result) => values.map((value) => result + before + value)).slice(0, MAX_EXPANSIONS);
16851
+ last = match.index + match[0].length;
16852
+ }
16853
+ return results.map((result) => result + word.slice(last));
16854
+ };
16855
+ var substitute = (words, vars) => {
16856
+ if (vars.size === 0)
16857
+ return [words];
16858
+ let lines = [[]];
16859
+ for (const word of words) {
16860
+ const values = expandWord(word, vars);
16861
+ lines = lines.flatMap((line) => values.map((value) => [...line, value])).slice(0, MAX_EXPANSIONS);
16862
+ }
16863
+ return lines;
16864
+ };
16865
+ var assignments = (words, vars) => {
16866
+ if (words[0] === "for" && words[2] === "in" && /^\w+$/.test(words[1] ?? "")) {
16867
+ const values = words.slice(3).flatMap((word) => expandWord(word, vars));
16868
+ return [[words[1], values.slice(0, MAX_EXPANSIONS)]];
16869
+ }
16870
+ let rest = words;
16871
+ if (DECLARATIONS.has(rest[0] ?? "")) {
16872
+ rest = rest.slice(1);
16873
+ while (rest[0]?.startsWith("-"))
16874
+ rest = rest.slice(1);
16875
+ }
16876
+ if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
16877
+ return null;
16878
+ return rest.map((word) => {
16879
+ const [, name, value] = ASSIGNMENT.exec(word);
16880
+ return [name, expandWord(value, vars)];
16881
+ });
16882
+ };
16883
+ var literalOutput = (segment) => {
16884
+ if (!segment?.pipe)
16885
+ return;
16886
+ const { words } = stripPrefixes(segment.words);
16887
+ const name = commandName(words[0]);
16888
+ if (name === "echo") {
16889
+ let args = words.slice(1);
16890
+ while (/^-[neE]+$/.test(args[0] ?? ""))
16891
+ args = args.slice(1);
16892
+ return args.join(" ");
16893
+ }
16894
+ if (name === "printf") {
16895
+ const [format = "", ...args] = words.slice(1);
16896
+ return (args.length ? args.join(`
16897
+ `) : format).replace(/\\n/g, `
16898
+ `);
16899
+ }
16900
+ if (name === "cat" && words.length === 1 && segment.stdin)
16901
+ return segment.stdin.join(`
16902
+ `);
16903
+ return;
16904
+ };
16905
+ var stdinOf = (segment, previous) => segment.stdin ? segment.stdin.join(`
16906
+ `) : literalOutput(previous);
16907
+ var XARGS_VALUES = new Set(["-n", "-L", "-l", "-P", "-d", "-E", "-e", "-s", "-a", "--arg-file", "--delimiter"].concat([
16908
+ "--max-args",
16909
+ "--max-lines",
16910
+ "--max-procs",
16911
+ "--eof",
16912
+ "--max-chars",
16913
+ "--process-slot-var"
16914
+ ]));
16915
+ var xargsCommands = (words, input) => {
16916
+ let replace = null;
16917
+ let index = 1;
16918
+ for (;index < words.length; index++) {
16919
+ const word = words[index];
16920
+ if (word === "--") {
16921
+ index++;
16922
+ break;
16923
+ }
16924
+ if (!word.startsWith("-"))
16925
+ break;
16926
+ if (word === "-I")
16927
+ replace = words[++index] ?? "{}";
16928
+ else if (word === "--replace" || word === "-i")
16929
+ replace = "{}";
16930
+ else if (word.startsWith("--replace="))
16931
+ replace = word.slice("--replace=".length);
16932
+ else if (/^-[Ii]./.test(word))
16933
+ replace = word.slice(2);
16934
+ else if (XARGS_VALUES.has(word))
16935
+ index++;
16936
+ }
16937
+ const command = words.slice(index);
16938
+ if (command.length === 0)
16939
+ return [];
16940
+ if (input === undefined)
16941
+ return [command];
16942
+ if (replace === null)
16943
+ return [[...command, ...input.split(/\s+/).filter(Boolean)]];
16944
+ const token = replace;
16945
+ return input.split(`
16946
+ `).map((line) => line.trim()).filter(Boolean).slice(0, MAX_EXPANSIONS).map((line) => command.map((word) => word.replaceAll(token, line)));
16947
+ };
16948
+ var findCommands = (words) => {
16949
+ const found = [];
16950
+ for (let index = 1;index < words.length; index++) {
16951
+ if (!/^-(?:exec|execdir|ok|okdir)$/.test(words[index]))
16952
+ continue;
16953
+ const end = words.findIndex((word, at) => at > index && (word === ";" || word === "+"));
16954
+ found.push(words.slice(index + 1, end < 0 ? undefined : end));
16955
+ if (end < 0)
16956
+ break;
16957
+ index = end;
16958
+ }
16959
+ return found;
16960
+ };
16961
+ var POWERSHELL_VALUES = /^-(?:f|file|ex|executionpolicy|wd|workingdirectory|config|configurationname|o|outputformat|if|inputformat|inp|w|windowstyle|settingsfile|custompipename|psconsolefile)$/;
16962
+ var powershellScript = (words, stdin) => {
16963
+ for (let index = 1;index < words.length; index++) {
16964
+ const word = words[index];
16965
+ if (!/^[-/]/.test(word))
16966
+ return null;
16967
+ const flag = word.replace(/^[-/]+/, "").toLowerCase();
16968
+ if (!flag)
16969
+ return null;
16970
+ if (flag === "e" || flag === "ec" || flag.length >= 2 && "encodedcommand".startsWith(flag)) {
16971
+ try {
16972
+ return Buffer.from(words[index + 1] ?? "", "base64").toString("utf16le");
16973
+ } catch {
16974
+ return null;
16975
+ }
16976
+ }
16977
+ if (flag === "cwa" || flag.length >= 8 && "commandwithargs".startsWith(flag))
16978
+ return words[index + 1] ?? null;
16979
+ if ("command".startsWith(flag))
16980
+ return words[index + 1] === "-" ? stdin ?? null : words.slice(index + 1).join(" ");
16981
+ if (POWERSHELL_VALUES.test(`-${flag}`)) {
16982
+ if (flag === "f" || flag === "file")
16983
+ return null;
16984
+ index++;
16985
+ }
16986
+ }
16987
+ return null;
16988
+ };
16989
+ var cmdScript = (words) => {
16990
+ const at = words.findIndex((word, index) => index > 0 && /^\/[ck]$/i.test(word));
16991
+ if (at > 0)
16992
+ return words.slice(at + 1).join(" ");
16993
+ const attached = words.find((word, index) => index > 0 && /^\/[ck]./i.test(word));
16994
+ return attached ? [attached.slice(2), ...words.slice(words.indexOf(attached) + 1)].join(" ") : null;
16995
+ };
16996
+ var processSubstitutionText = (word) => {
16997
+ const inner = /^<\(([\s\S]*)\)$/.exec(word ?? "")?.[1];
16998
+ if (inner === undefined)
16999
+ return;
17000
+ const segments = segmentsOf(inner);
17001
+ return segments.length === 1 ? literalOutput({ ...segments[0], pipe: true }) : undefined;
17002
+ };
17003
+ var shellScript = (words, stdin) => {
17004
+ const flag = words.findIndex((word, index) => index > 0 && SCRIPT_FLAG.test(word));
17005
+ if (flag > 0)
17006
+ return words[flag + 1] === "--" ? words[flag + 2] : words[flag + 1];
17007
+ const operand = words.slice(1).find((word) => !word.startsWith("-"));
17008
+ if (operand !== undefined)
17009
+ return processSubstitutionText(operand);
17010
+ return stdin;
17011
+ };
17012
+ var scriptCommand = (words) => {
17013
+ for (let index = 1;index < words.length; index++) {
17014
+ const word = words[index];
17015
+ if (word === "--command" || /^-[a-zA-Z]*c$/.test(word))
17016
+ return words[index + 1];
17017
+ if (word.startsWith("--command="))
17018
+ return word.slice("--command=".length);
17019
+ }
17020
+ return;
17021
+ };
17022
+ var PARALLEL_VALUES = new Set(["-j", "--jobs", "-N", "-n", "-S", "--sshlogin", "-P", "--delay"]);
17023
+ var parallelCommands = (words) => {
17024
+ let index = 1;
17025
+ while (index < words.length && words[index].startsWith("-"))
17026
+ index += PARALLEL_VALUES.has(words[index]) ? 2 : 1;
17027
+ const separator = words.indexOf(":::", index);
17028
+ const command = words.slice(index, separator < 0 ? undefined : separator);
17029
+ if (separator < 0)
17030
+ return command.length ? [command] : [];
17031
+ return words.slice(separator + 1).filter((arg) => !arg.startsWith(":::")).slice(0, MAX_EXPANSIONS).map((arg) => command.some((word) => word.includes("{}")) ? command.map((word) => word.replaceAll("{}", arg)) : [...command, arg]);
17032
+ };
17033
+ var START_PROCESS_VALUES = /^-(?:workingdirectory|wd|redirectstandard\w*|verb|windowstyle|credential|environment)$/i;
17034
+ var startProcessCommand = (words) => {
17035
+ let file;
17036
+ let args;
17037
+ const positionals = [];
17038
+ for (let index = 1;index < words.length; index++) {
17039
+ const word = words[index];
17040
+ const flag = word.startsWith("-") ? word.slice(1).toLowerCase() : null;
17041
+ if (flag === null)
17042
+ positionals.push(word);
17043
+ else if (flag && "filepath".startsWith(flag))
17044
+ file = words[++index];
17045
+ else if (flag && (flag === "args" || "argumentlist".startsWith(flag)))
17046
+ args = words[++index];
17047
+ else if (START_PROCESS_VALUES.test(word))
17048
+ index++;
17049
+ }
17050
+ file ??= positionals.shift();
17051
+ args ??= positionals.shift();
17052
+ if (!file)
17053
+ return null;
17054
+ return [file, ...(args ?? "").split(/[\s,]+/).filter(Boolean)];
17055
+ };
17056
+ var splitString = (line) => {
17057
+ if (commandName(line[0]) !== "env")
17058
+ return null;
17059
+ for (let index = 1;index < line.length; index++) {
17060
+ const word = line[index];
17061
+ if (word === "-S" || word === "--split-string")
17062
+ return line.slice(index + 1).join(" ");
17063
+ if (word.startsWith("--split-string="))
17064
+ return line.slice(index).join(" ").slice(15);
17065
+ if (/^-S./.test(word))
17066
+ return [word.slice(2), ...line.slice(index + 1)].join(" ");
17067
+ if (!word.startsWith("-") && !ASSIGNMENT.test(word))
17068
+ return null;
17069
+ }
17070
+ return null;
17071
+ };
17072
+ var CONFIG_READS = /^(?:--get\S*|-l|--list|--unset\S*|--remove-section|--rename-section|-e|--edit|--show-\S+|--name-only)$/;
17073
+ var configSetBy = (args) => {
17074
+ const positionals = args.filter((arg, index) => index > 0 && !arg.startsWith("-"));
17075
+ if (args[0] === "remote" && (positionals[0] === "add" || positionals[0] === "set-url")) {
17076
+ const [, name, url] = positionals;
17077
+ const key = args.includes("--push") ? "pushurl" : "url";
17078
+ return name && url ? ["-c", `remote.${name}.${key}=${url}`] : [];
17079
+ }
17080
+ if (args[0] !== "config" || args.some((arg) => CONFIG_READS.test(arg)))
17081
+ return [];
17082
+ const [key, value] = positionals[0] === "set" ? positionals.slice(1) : positionals;
17083
+ return key?.includes(".") && value !== undefined && positionals.length <= 3 ? ["-c", `${key}=${value}`] : [];
17084
+ };
17085
+ var gitInvocation = (words, dir, env = {}, stdin, inherited = []) => {
17086
+ let here = dir;
17087
+ const config = [...inherited];
17088
+ let gitDir = env.GIT_DIR;
17089
+ let index = 0;
17090
+ for (;index < words.length; index++) {
17091
+ const word = words[index];
17092
+ if (word === "-C")
17093
+ here = words[index + 1] ? joinDir(here, words[++index]) : here;
17094
+ else if (GIT_CONFIG_OPTIONS.has(word)) {
17095
+ if (word === "--git-dir")
17096
+ gitDir = words[index + 1];
17097
+ if (words[index + 1] !== undefined)
17098
+ config.push(word, words[++index]);
17099
+ } else if (/^--(?:config-env|git-dir)=/.test(word)) {
17100
+ if (word.startsWith("--git-dir="))
17101
+ gitDir = word.slice("--git-dir=".length);
17102
+ config.push(word);
17103
+ } else if (GIT_VALUE_OPTIONS.has(word))
17104
+ index++;
17105
+ else if (!word.startsWith("-"))
17106
+ break;
17107
+ }
17108
+ if (gitDir)
17109
+ here = joinDir(here, gitDir.replace(/(?:^|[\\/]+)\.git[\\/]*$/, "") || ".");
17110
+ return {
17111
+ tool: "git",
17112
+ dir: here,
17113
+ args: words.slice(index),
17114
+ ...config.length ? { config } : {},
17115
+ ...Object.keys(env).length ? { env } : {},
17116
+ ...stdin === undefined ? {} : { stdin }
17117
+ };
17118
+ };
16476
17119
  function rawInvocations(command, dialect = "posix", depth = 0) {
17120
+ if (depth > 0)
17121
+ return walk2(command, dialect, depth);
17122
+ const key = `${dialect}\x00${command}`;
17123
+ const cached = RECENT.get(key);
17124
+ if (cached)
17125
+ return cached;
17126
+ let found;
17127
+ try {
17128
+ found = walk2(command, dialect, depth);
17129
+ } catch (error) {
17130
+ throw error instanceof ShellAnalysisError ? error : new ShellAnalysisError(error instanceof Error ? error.message : String(error));
17131
+ }
17132
+ RECENT.set(key, found);
17133
+ if (RECENT.size > 8)
17134
+ RECENT.delete(RECENT.keys().next().value);
17135
+ return found;
17136
+ }
17137
+ var RECENT = new Map;
17138
+ function walk2(command, dialect, depth) {
16477
17139
  const found = [];
16478
17140
  let dir = null;
16479
17141
  const groups = [];
16480
17142
  let stack = [];
16481
- for (const segment of segmentsOf(command, dialect)) {
16482
- if (segment.group === "open") {
16483
- groups.push({ dir, stack: [...stack] });
16484
- continue;
16485
- }
16486
- if (segment.group === "close") {
16487
- const saved = groups.pop();
16488
- if (saved)
16489
- ({ dir, stack } = saved);
16490
- continue;
16491
- }
16492
- const words = stripPrefixes(segment.words);
17143
+ const vars = new Map;
17144
+ const exported = {};
17145
+ const sessionConfig = [];
17146
+ const nested = (script, inner) => {
17147
+ if (!script || depth >= MAX_DEPTH)
17148
+ return;
17149
+ for (const invocation of rawInvocations(script, inner, depth + 1))
17150
+ found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
17151
+ };
17152
+ const visit = (input, stdin, hops) => {
17153
+ const line = /^\$[\w:]+$/.test(input[0] ?? "") && input[1] === "=" ? input.slice(2) : input;
17154
+ const split = splitString(line);
17155
+ if (split !== null)
17156
+ return nested(split, "posix");
17157
+ const { words, env } = stripPrefixes(line);
16493
17158
  const name = commandName(words[0]);
17159
+ if (!name)
17160
+ return;
17161
+ const runs = (lines) => {
17162
+ if (hops < MAX_DEPTH)
17163
+ for (const next of lines)
17164
+ visit(next, undefined, hops + 1);
17165
+ };
16494
17166
  if (CD.has(name)) {
16495
17167
  const target = cdTarget(words);
16496
17168
  dir = target === null ? os4.homedir() : joinDir(dir, target);
16497
- continue;
16498
- }
16499
- if (PUSHD.has(name)) {
17169
+ } else if (PUSHD.has(name)) {
16500
17170
  const target = cdTarget(words);
16501
17171
  stack.push(dir);
16502
17172
  dir = target === null || target === "-" ? undefined : joinDir(dir, target);
16503
- continue;
16504
- }
16505
- if (POPD.has(name)) {
17173
+ } else if (POPD.has(name))
16506
17174
  dir = stack.length ? stack.pop() : undefined;
16507
- continue;
17175
+ else if (EVALS.has(name)) {
17176
+ const args = /^-c(?:o(?:m(?:m(?:a(?:n(?:d)?)?)?)?)?)?$/i.test(words[1] ?? "") ? words.slice(2) : words.slice(1);
17177
+ nested(args.join(" "), dialect);
17178
+ } else if (SHELLS.has(name))
17179
+ nested(shellScript(words, stdin), "posix");
17180
+ else if ((name === "source" || name === ".") && dialect === "posix")
17181
+ nested(processSubstitutionText(words[1]), "posix");
17182
+ else if (name === "trap" && dialect === "posix" && !/^-[lp]$/.test(words[1] ?? ""))
17183
+ nested(words[1], "posix");
17184
+ else if (name === "script")
17185
+ nested(scriptCommand(words), "posix");
17186
+ else if (name === "parallel")
17187
+ runs(parallelCommands(words));
17188
+ else if (dialect === "powershell" && /^(?:start-process|saps|start)$/.test(name)) {
17189
+ const started = startProcessCommand(words);
17190
+ if (started)
17191
+ runs([started]);
17192
+ } else if (POWERSHELLS.has(name))
17193
+ nested(powershellScript(words, stdin), "powershell");
17194
+ else if (name === "cmd")
17195
+ nested(cmdScript(words), "powershell");
17196
+ else if (name === "xargs" || name === "find")
17197
+ runs(name === "xargs" ? xargsCommands(words, stdin) : findCommands(words));
17198
+ else if (name === "gh" || name === "glab")
17199
+ found.push({
17200
+ tool: name,
17201
+ dir,
17202
+ args: words.slice(1),
17203
+ ...stdin === undefined ? {} : { stdin }
17204
+ });
17205
+ else if (name === "git") {
17206
+ const invocation = gitInvocation(words.slice(1), dir, { ...exported, ...env }, stdin, sessionConfig);
17207
+ found.push(invocation);
17208
+ sessionConfig.push(...configSetBy(invocation.args));
16508
17209
  }
16509
- if (SHELLS.has(name) && depth < 3) {
16510
- const flag = words.findIndex((word, index) => index > 0 && SCRIPT_FLAG.test(word));
16511
- const script = flag > 0 ? words[flag + 1] : undefined;
16512
- if (script)
16513
- for (const inner of rawInvocations(script, "posix", depth + 1))
16514
- found.push({
16515
- ...inner,
16516
- dir: inner.dir === null ? dir : inner.dir === undefined ? undefined : joinDir(dir, inner.dir)
16517
- });
17210
+ };
17211
+ const segments = segmentsOf(command, dialect);
17212
+ for (const [index, segment] of segments.entries()) {
17213
+ if (segment.group === "open") {
17214
+ groups.push({ dir, stack: [...stack] });
16518
17215
  continue;
16519
17216
  }
16520
- if (name === "gh" || name === "glab") {
16521
- found.push({ tool: name, dir, args: words.slice(1) });
17217
+ if (segment.group === "close") {
17218
+ const saved = groups.pop();
17219
+ if (saved)
17220
+ ({ dir, stack } = saved);
16522
17221
  continue;
16523
17222
  }
16524
- if (name !== "git")
17223
+ if (segment.unanalyzable)
17224
+ throw new ShellAnalysisError(segment.unanalyzable);
17225
+ for (const sub of segment.subs ?? [])
17226
+ nested(sub, "posix");
17227
+ const set = assignments(segment.words, vars);
17228
+ if (set) {
17229
+ for (const [name, values] of set) {
17230
+ vars.set(name, values);
17231
+ if (segment.words[0] === "export" && GIT_CONFIG_ENV.test(name) && values[0] !== undefined)
17232
+ exported[name] = values[0];
17233
+ }
16525
17234
  continue;
16526
- let here = dir;
16527
- let index = 1;
16528
- for (;index < words.length; index++) {
16529
- const word = words[index];
16530
- if (word === "-C")
16531
- here = words[index + 1] ? joinDir(here, words[++index]) : here;
16532
- else if (GIT_VALUE_OPTIONS.has(word))
16533
- index++;
16534
- else if (!word.startsWith("-"))
16535
- break;
16536
17235
  }
16537
- found.push({ tool: "git", dir: here, args: words.slice(index) });
17236
+ const stdin = stdinOf(segment, segments[index - 1]);
17237
+ for (const line of substitute(segment.words, vars))
17238
+ visit(line, stdin, 0);
16538
17239
  }
16539
17240
  return found;
16540
17241
  }
@@ -16570,6 +17271,55 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
16570
17271
  return found;
16571
17272
  }
16572
17273
  var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
17274
+ var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
17275
+ var pushDestination = (destination) => {
17276
+ if (destination === "HEAD" || destination === "@" || CURRENT_BRANCH.test(destination))
17277
+ return { name: null };
17278
+ if (destination.startsWith("refs/heads/"))
17279
+ return { name: destination.slice(11) };
17280
+ if (destination.startsWith("refs/tags/"))
17281
+ return { name: undefined, tag: destination.slice(10) };
17282
+ if (destination.startsWith("refs/"))
17283
+ return { name: undefined };
17284
+ return { name: destination };
17285
+ };
17286
+ var pushRefspecs = (specs) => {
17287
+ let force = false;
17288
+ let matching = false;
17289
+ const targets = [];
17290
+ const tags = [];
17291
+ const deleted = [];
17292
+ const globs = [];
17293
+ for (const raw of specs) {
17294
+ if (raw.startsWith("+"))
17295
+ force = true;
17296
+ const spec = raw.replace(/^\+/, "");
17297
+ if (spec.startsWith("^"))
17298
+ continue;
17299
+ if (spec === ":") {
17300
+ matching = true;
17301
+ continue;
17302
+ }
17303
+ const colon = spec.indexOf(":");
17304
+ const source = colon >= 0 ? spec.slice(0, colon) : spec;
17305
+ const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
17306
+ if (!destination)
17307
+ continue;
17308
+ if (destination.includes("*")) {
17309
+ globs.push({ source, destination });
17310
+ continue;
17311
+ }
17312
+ const { name, tag } = pushDestination(destination);
17313
+ if (tag !== undefined)
17314
+ tags.push(tag);
17315
+ if (name === undefined)
17316
+ continue;
17317
+ targets.push(name);
17318
+ if (colon === 0 && name !== null)
17319
+ deleted.push(name);
17320
+ }
17321
+ return { force, matching, targets, tags, deleted, globs };
17322
+ };
16573
17323
  var pushAction = (args) => {
16574
17324
  let force = false;
16575
17325
  let lease = false;
@@ -16579,6 +17329,7 @@ var pushAction = (args) => {
16579
17329
  let allBranches = false;
16580
17330
  let mirror = false;
16581
17331
  let remote = null;
17332
+ let dryRun = false;
16582
17333
  const positionals = [];
16583
17334
  for (let index = 1;index < args.length; index++) {
16584
17335
  const arg = args[index];
@@ -16586,9 +17337,11 @@ var pushAction = (args) => {
16586
17337
  positionals.push(...args.slice(index + 1));
16587
17338
  break;
16588
17339
  }
16589
- if (arg === "--dry-run" || /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg))
16590
- return null;
16591
- if (arg === "--repo")
17340
+ if (/^--dr(?:y(?:-(?:r(?:un?)?)?)?)?$/.test(arg) || /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg))
17341
+ dryRun = true;
17342
+ else if (/^--no-dr(?:y(?:-(?:r(?:un?)?)?)?)?$/.test(arg))
17343
+ dryRun = false;
17344
+ else if (arg === "--repo")
16592
17345
  remote = args[++index] ?? null;
16593
17346
  else if (arg.startsWith("--repo="))
16594
17347
  remote = arg.slice("--repo=".length);
@@ -16609,41 +17362,28 @@ var pushAction = (args) => {
16609
17362
  else if (!arg.startsWith("-"))
16610
17363
  positionals.push(arg);
16611
17364
  }
17365
+ if (dryRun)
17366
+ return null;
16612
17367
  const specs = remote === null ? positionals.slice(1) : positionals;
16613
17368
  remote ??= positionals[0] ?? null;
16614
- const targets = [];
16615
- const tags = [];
16616
- const deleted = [];
16617
- for (const raw of specs) {
16618
- if (raw.startsWith("+"))
16619
- force = true;
16620
- const spec = raw.replace(/^\+/, "");
16621
- const colon = spec.indexOf(":");
16622
- const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
16623
- if (!destination)
16624
- continue;
16625
- const name = destination === "HEAD" ? null : destination.startsWith("refs/heads/") ? destination.slice(11) : destination.startsWith("refs/") ? undefined : destination;
16626
- if (destination.startsWith("refs/tags/"))
16627
- tags.push(destination.slice(10));
16628
- if (name === undefined)
16629
- continue;
16630
- targets.push(name);
16631
- if (colon === 0 && name !== null)
16632
- deleted.push(name);
16633
- }
16634
- if (specs.length === 0 && !wholesale)
16635
- targets.push(null);
17369
+ const parsed = pushRefspecs(specs);
17370
+ const implicit = specs.length === 0 && !wholesale;
17371
+ if (implicit)
17372
+ parsed.targets.push(null);
16636
17373
  return {
16637
17374
  kind: "push",
16638
- targets,
16639
- blindForce: force && !lease,
17375
+ targets: parsed.targets,
17376
+ blindForce: (force || parsed.force) && !lease,
16640
17377
  remote,
16641
- ...tags.length ? { tags } : {},
17378
+ ...parsed.tags.length ? { tags: parsed.tags } : {},
16642
17379
  ...allTags ? { allTags: true } : {},
16643
17380
  ...deletes ? { deletes: true } : {},
16644
- ...deleted.length ? { deleted } : {},
17381
+ ...parsed.deleted.length ? { deleted: parsed.deleted } : {},
16645
17382
  ...allBranches ? { allBranches: true } : {},
16646
- ...mirror ? { mirror: true } : {}
17383
+ ...mirror ? { mirror: true } : {},
17384
+ ...implicit ? { implicit: true } : {},
17385
+ ...parsed.globs.length ? { globs: parsed.globs } : {},
17386
+ ...parsed.matching ? { matching: true } : {}
16647
17387
  };
16648
17388
  };
16649
17389
  var VALUE_OPTIONS = {
@@ -16689,22 +17429,110 @@ var asksHelp = (invocation) => {
16689
17429
  }
16690
17430
  return false;
16691
17431
  };
17432
+ var API_VALUES = new Set(["--method", "--header", "--raw-field", "--field", "--jq", "--template", "--input"].concat([
17433
+ "--hostname",
17434
+ "--preview",
17435
+ "--cache",
17436
+ "--output"
17437
+ ]));
17438
+ var API_SHORT_VALUES = "XHfFqtp";
17439
+ var API_FIELDS = new Set(["-f", "-F", "--raw-field", "--field"]);
17440
+ var API_MERGE = {
17441
+ gh: /(?:^|\/)(?:pulls\/[^/?#]+\/merge|merges)\/?(?:[?#]|$)/i,
17442
+ glab: /(?:^|\/)merge_requests\/[^/?#]+\/merge\/?(?:[?#]|$)/i
17443
+ };
17444
+ var GRAPHQL_MERGE = /\b(?:mergePullRequest|enablePullRequestAutoMerge|mergeBranch|mergeRequestAccept)\b/;
17445
+ var apiMerges = (tool, args, stdin) => {
17446
+ const request = { method: null, body: false };
17447
+ let endpoint = null;
17448
+ const values = [];
17449
+ const take = (name, value) => {
17450
+ if (name === "-X" || name === "--method")
17451
+ request.method = value.toUpperCase();
17452
+ if (API_FIELDS.has(name) || name === "--input")
17453
+ request.body = true;
17454
+ values.push(value);
17455
+ const fromStdin = name === "--input" ? value === "-" : /^(?:[^=]*=)?@-$/.test(value);
17456
+ if (fromStdin && stdin !== undefined)
17457
+ values.push(stdin);
17458
+ };
17459
+ for (let index = 1;index < args.length; index++) {
17460
+ const arg = args[index];
17461
+ if (arg === "--") {
17462
+ endpoint ??= args[index + 1] ?? null;
17463
+ break;
17464
+ }
17465
+ if (arg.startsWith("--")) {
17466
+ const eq = arg.indexOf("=");
17467
+ const name = eq > 0 ? arg.slice(0, eq) : arg;
17468
+ if (API_VALUES.has(name))
17469
+ take(name, eq > 0 ? arg.slice(eq + 1) : args[++index] ?? "");
17470
+ } else if (arg.startsWith("-") && arg.length > 1) {
17471
+ for (let at = 1;at < arg.length; at++) {
17472
+ if (!API_SHORT_VALUES.includes(arg[at]))
17473
+ continue;
17474
+ take(`-${arg[at]}`, at + 1 < arg.length ? arg.slice(at + 1) : args[++index] ?? "");
17475
+ break;
17476
+ }
17477
+ } else
17478
+ endpoint ??= arg;
17479
+ }
17480
+ if (!endpoint)
17481
+ return false;
17482
+ endpoint = endpoint.replace(/(?<!:)\/{2,}/g, "/");
17483
+ if (/(?:^|\/)graphql\/?$/i.test(endpoint))
17484
+ return values.some((value) => GRAPHQL_MERGE.test(value));
17485
+ const verb = request.method ?? (request.body ? "POST" : "GET");
17486
+ return verb !== "GET" && verb !== "HEAD" && API_MERGE[tool].test(endpoint);
17487
+ };
17488
+ var subtreePush = (args) => {
17489
+ const positionals = [];
17490
+ for (let index = 2;index < args.length; index++) {
17491
+ const arg = args[index];
17492
+ if (arg === "-P" || arg === "--prefix" || arg === "-m" || arg === "--message")
17493
+ index++;
17494
+ else if (!arg.startsWith("-"))
17495
+ positionals.push(arg);
17496
+ }
17497
+ const [remote, branch] = positionals;
17498
+ return remote && branch ? pushAction(["push", remote, `HEAD:refs/heads/${branch}`]) : null;
17499
+ };
16692
17500
  function rawAction(invocation) {
16693
17501
  const args = invocation.args;
17502
+ if (invocation.tool !== "git" && args[0] === "api")
17503
+ return apiMerges(invocation.tool, args, invocation.stdin) ? { kind: "merge", forge: invocation.tool, via: "api" } : null;
16694
17504
  if (asksHelp(invocation))
16695
17505
  return null;
16696
- const [first, second] = args;
17506
+ const [first] = args;
16697
17507
  if (invocation.tool === "git") {
17508
+ const second = args[1];
16698
17509
  if (first === "commit")
16699
17510
  return args.includes("--dry-run") ? null : { kind: "commit", amend: args.includes("--amend") };
16700
17511
  if (first === "push")
16701
17512
  return pushAction(args);
17513
+ if (first === "send-pack")
17514
+ return pushAction([
17515
+ "push",
17516
+ ...args.slice(1).filter((arg) => arg !== "--stdin"),
17517
+ ...args.includes("--stdin") ? (invocation.stdin ?? "").split(/\s+/).filter(Boolean) : []
17518
+ ]);
17519
+ if (first === "subtree" && second === "push")
17520
+ return subtreePush(args);
16702
17521
  return null;
16703
17522
  }
16704
17523
  const forge = invocation.tool;
16705
17524
  const noun = forge === "gh" ? "pr" : "mr";
16706
17525
  if (first !== noun)
16707
17526
  return null;
17527
+ const values = VALUE_OPTIONS[forge];
17528
+ let second;
17529
+ for (let index = 1;index < args.length && second === undefined; index++) {
17530
+ const arg = args[index];
17531
+ if (values.has(arg))
17532
+ index++;
17533
+ else if (!arg.startsWith("-"))
17534
+ second = arg;
17535
+ }
16708
17536
  if (second === "merge")
16709
17537
  return { kind: "merge", forge };
16710
17538
  if (second === "create")
@@ -16736,16 +17564,6 @@ var guardedBranch = (dir, branch) => {
16736
17564
  return false;
16737
17565
  }
16738
17566
  };
16739
- var toPushRemote = (repo, remote) => {
16740
- if (remote === null)
16741
- return true;
16742
- try {
16743
- const pushRemote = pushRemoteName(repo.dir, repo.branch);
16744
- return pushRemote === null || pushRemote === remote;
16745
- } catch {
16746
- return false;
16747
- }
16748
- };
16749
17567
  var headSha2 = (repo) => {
16750
17568
  const git = repo.location.git;
16751
17569
  if (!git || git.reftable)
@@ -16767,10 +17585,169 @@ var headSha2 = (repo) => {
16767
17585
  return null;
16768
17586
  }
16769
17587
  };
17588
+ var gitConfig = (dir, invocation, cache) => {
17589
+ const key = JSON.stringify([dir, invocation.config ?? [], invocation.env ?? {}]);
17590
+ const cached = cache.get(key);
17591
+ if (cached)
17592
+ return cached;
17593
+ const config = new Map;
17594
+ const listed = spawnSync11("git", [...invocation.config ?? [], "config", "-z", "--list"], {
17595
+ cwd: dir,
17596
+ encoding: "utf8",
17597
+ timeout: 3000,
17598
+ windowsHide: true,
17599
+ env: { ...process.env, ...invocation.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0" }
17600
+ });
17601
+ for (const entry of (listed.status === 0 ? listed.stdout : "").split("\x00")) {
17602
+ if (!entry)
17603
+ continue;
17604
+ const newline = entry.indexOf(`
17605
+ `);
17606
+ const name = newline < 0 ? entry : entry.slice(0, newline);
17607
+ config.set(name, [
17608
+ ...config.get(name) ?? [],
17609
+ newline < 0 ? "true" : entry.slice(newline + 1)
17610
+ ]);
17611
+ }
17612
+ cache.set(key, config);
17613
+ return config;
17614
+ };
17615
+ var configValue = (config, key) => config.get(key)?.at(-1);
17616
+ var GIT_COMMANDS = new Set(("add am annotate apply archive bisect blame branch bugreport bundle cat-file check-attr " + "check-ignore check-mailmap check-ref-format checkout checkout-index cherry cherry-pick " + "citool clean clone column commit commit-graph commit-tree config count-objects credential " + "daemon describe diagnose diff diff-files diff-index diff-tree difftool fast-export " + "fast-import fetch fetch-pack filter-branch fmt-merge-msg for-each-ref for-each-repo " + "format-patch fsck gc get-tar-commit-id grep gui hash-object help hook index-pack init " + "instaweb interpret-trailers lfs log ls-files ls-remote ls-tree mailinfo mailsplit " + "maintenance merge merge-base merge-file merge-index merge-tree mergetool mktag mktree " + "multi-pack-index mv name-rev notes pack-objects pack-refs patch-id prune pull push " + "range-diff read-tree rebase reflog remote repack replace request-pull rerere reset " + "restore rev-list rev-parse revert rm send-email shortlog show show-branch show-ref " + "sparse-checkout stage stash status stripspace submodule subtree switch symbolic-ref tag " + "update-index update-ref var verify-commit verify-pack verify-tag version whatchanged " + "worktree write-tree").split(" "));
17617
+ var JUDGED_COMMANDS = [
17618
+ "push",
17619
+ "commit",
17620
+ "switch",
17621
+ "checkout",
17622
+ "branch",
17623
+ "worktree",
17624
+ "stash",
17625
+ "update-ref",
17626
+ "symbolic-ref",
17627
+ "fetch",
17628
+ "pull",
17629
+ "subtree"
17630
+ ];
17631
+ var editDistance2 = (a, b) => {
17632
+ let before = [];
17633
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
17634
+ for (let i = 1;i <= a.length; i++) {
17635
+ const row = [i];
17636
+ for (let j = 1;j <= b.length; j++) {
17637
+ row[j] = Math.min(previous[j] + 1, row[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
17638
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1])
17639
+ row[j] = Math.min(row[j], before[j - 2] + 1);
17640
+ }
17641
+ [before, previous] = [previous, row];
17642
+ }
17643
+ return previous[b.length];
17644
+ };
17645
+ var autocorrected = (name, config) => {
17646
+ const setting = (configValue(config, "help.autocorrect") ?? "0").toLowerCase();
17647
+ if (setting !== "immediate" && !/^-?[1-9]\d*$/.test(setting))
17648
+ return null;
17649
+ let best = null;
17650
+ let bestDistance = 3;
17651
+ let tie = false;
17652
+ for (const command of JUDGED_COMMANDS) {
17653
+ const distance = editDistance2(name, command);
17654
+ if (distance < bestDistance)
17655
+ [best, bestDistance, tie] = [command, distance, false];
17656
+ else if (distance === bestDistance)
17657
+ tie = true;
17658
+ }
17659
+ return tie ? null : best;
17660
+ };
17661
+ var shellQuote = (word) => `'${word.replaceAll("'", "'\\''")}'`;
17662
+ var positional = (script, args) => {
17663
+ const word = (arg) => /^[\w./@+:=-]+$/.test(arg) ? arg : shellQuote(arg);
17664
+ return script.replace(/\$\{?([1-9])\}?/g, (_, n) => word(args[Number(n) - 1] ?? "")).replace(/\$\{?[@*]\}?/g, args.map(word).join(" "));
17665
+ };
17666
+ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
17667
+ const sub = invocation.args[0];
17668
+ if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
17669
+ return [invocation];
17670
+ const dir = path18.resolve(root, invocation.dir ?? ".");
17671
+ if (!fs15.existsSync(dir))
17672
+ return [invocation];
17673
+ const config = gitConfig(dir, invocation, cache);
17674
+ const alias = configValue(config, `alias.${sub.toLowerCase()}`);
17675
+ if (alias === undefined) {
17676
+ const corrected = autocorrected(sub, config);
17677
+ return corrected ? [{ ...invocation, args: [corrected, ...invocation.args.slice(1)] }] : [invocation];
17678
+ }
17679
+ const rest = invocation.args.slice(1);
17680
+ const expanded = alias.startsWith("!") ? rawInvocations([positional(alias.slice(1), rest), ...rest.map(shellQuote)].join(" ")).map((inner) => ({
17681
+ ...inner,
17682
+ dir: rebase(invocation.dir, inner.dir),
17683
+ ...inner.tool === "git" && (invocation.config || inner.config) ? { config: [...invocation.config ?? [], ...inner.config ?? []] } : {},
17684
+ ...inner.tool === "git" && (invocation.env || inner.env) ? { env: { ...invocation.env, ...inner.env } } : {}
17685
+ })) : [
17686
+ gitInvocation([...segmentsOf(alias)[0]?.words ?? [], ...rest], invocation.dir, invocation.env, invocation.stdin, invocation.config)
17687
+ ];
17688
+ return expanded.flatMap((inner) => expandGitAliases(root, inner, cache, depth + 1));
17689
+ };
17690
+ var normalizedUrl = (url, dir) => {
17691
+ const trimmed = url.trim();
17692
+ if (/^file:\/\//i.test(trimmed))
17693
+ return path18.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
17694
+ const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
17695
+ const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
17696
+ if (!scheme && !scp)
17697
+ return path18.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
17698
+ const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
17699
+ return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
17700
+ };
17701
+ var rewrittenUrl = (url, config) => {
17702
+ for (const kind of ["pushinsteadof", "insteadof"]) {
17703
+ let best = null;
17704
+ for (const [key, prefixes] of config) {
17705
+ if (!key.startsWith("url.") || !key.endsWith(`.${kind}`))
17706
+ continue;
17707
+ const base = key.slice(4, -(kind.length + 1));
17708
+ for (const prefix of prefixes)
17709
+ if (url.startsWith(prefix) && prefix.length > (best?.prefix.length ?? -1))
17710
+ best = { base, prefix };
17711
+ }
17712
+ if (best)
17713
+ return best.base + url.slice(best.prefix.length);
17714
+ }
17715
+ return url;
17716
+ };
17717
+ var pushUrl = (config, remote, dir) => {
17718
+ const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
17719
+ return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
17720
+ };
17721
+ var pushRemoteOf = (config, branch) => {
17722
+ const remotes = new Set;
17723
+ for (const key of config.keys()) {
17724
+ const name = /^remote\.(.+)\.url$/.exec(key)?.[1];
17725
+ if (name)
17726
+ remotes.add(name);
17727
+ }
17728
+ for (const candidate of [
17729
+ branch ? configValue(config, `branch.${branch}.pushremote`) : undefined,
17730
+ configValue(config, "remote.pushdefault"),
17731
+ branch ? configValue(config, `branch.${branch}.remote`) : undefined
17732
+ ])
17733
+ if (candidate && candidate !== "." && remotes.has(candidate))
17734
+ return candidate;
17735
+ if (remotes.has("origin"))
17736
+ return "origin";
17737
+ return remotes.size === 1 ? [...remotes][0] : null;
17738
+ };
17739
+ var toPushRemote = (repo, remote, config) => {
17740
+ if (remote === null || /[$`]/.test(remote))
17741
+ return true;
17742
+ const pushRemote = pushRemoteOf(config, repo.branch);
17743
+ if (pushRemote === null || pushRemote === remote)
17744
+ return true;
17745
+ return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
17746
+ };
16770
17747
  var quote = (text) => `\`${text}\``;
16771
17748
  var refusal = (action, branch, dir) => {
16772
17749
  if (action.kind === "merge")
16773
- return `workit: raw ${quote(action.forge === "gh" ? "gh pr merge" : "glab mr merge")} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
17750
+ return `workit: raw ${quote(action.via === "api" ? `${action.forge} api` : action.forge === "gh" ? "gh pr merge" : "glab mr merge")}${action.via === "api" ? " merge" : ""} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
16774
17751
  const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
16775
17752
  if (dir && branch && plainPush && releaseBranch(dir, branch))
16776
17753
  return `workit: raw ${quote("git push")} onto ${branch}, a protected release branch, bypasses workit's protected-branch check. To finish a release or hotfix, push it with ${quote(`workit git push --release ${branch} [--release v<version>]`)}: it pushes ${branch} (fast-forward only) and the release tag, verifies the remote and records it, under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}). Feature work goes through ${quote("workit git push")} and ${quote("workit pr merge")}.`;
@@ -16791,6 +17768,74 @@ var refFacts = (repo, remote) => {
16791
17768
  }
16792
17769
  return facts;
16793
17770
  };
17771
+ var globBranches = (repo, glob) => {
17772
+ const source = glob.source.startsWith("refs/") ? glob.source : `refs/heads/${glob.source}`;
17773
+ const [sourcePrefix, sourceSuffix = ""] = source.split("*");
17774
+ const [destinationPrefix, destinationSuffix = ""] = glob.destination.split("*");
17775
+ const listed = spawnSync11("git", [
17776
+ "for-each-ref",
17777
+ "--format=%(refname)",
17778
+ sourcePrefix.slice(0, sourcePrefix.lastIndexOf("/") + 1)
17779
+ ], { cwd: repo.dir, encoding: "utf8", timeout: 5000, windowsHide: true });
17780
+ const names = [];
17781
+ for (const ref of (listed.stdout ?? "").split(`
17782
+ `)) {
17783
+ if (!ref.startsWith(sourcePrefix) || !ref.endsWith(sourceSuffix))
17784
+ continue;
17785
+ const middle = ref.slice(sourcePrefix.length, ref.length - sourceSuffix.length);
17786
+ const { name } = pushDestination(destinationPrefix + middle + destinationSuffix);
17787
+ if (name)
17788
+ names.push(name);
17789
+ }
17790
+ return names;
17791
+ };
17792
+ var pushedBranches = (action, repo, config, facts, remote) => {
17793
+ const targets = action.implicit ? [] : [...action.targets];
17794
+ const globs = [...action.globs ?? []];
17795
+ let matching = action.matching === true;
17796
+ const configured = action.implicit ? config.get(`remote.${remote}.push`) : undefined;
17797
+ if (configured?.length) {
17798
+ const parsed = pushRefspecs(configured);
17799
+ targets.push(...parsed.targets);
17800
+ globs.push(...parsed.globs);
17801
+ matching ||= parsed.matching;
17802
+ } else if (action.implicit) {
17803
+ const mode = (configValue(config, "push.default") ?? "simple").toLowerCase();
17804
+ const merge = repo.branch ? configValue(config, `branch.${repo.branch}.merge`) : undefined;
17805
+ if (mode === "matching")
17806
+ matching = true;
17807
+ else if ((mode === "upstream" || mode === "tracking") && merge?.startsWith("refs/heads/"))
17808
+ targets.push(merge.slice(11));
17809
+ else if (mode !== "nothing")
17810
+ targets.push(null);
17811
+ }
17812
+ if (action.allBranches)
17813
+ targets.push(...facts.heads);
17814
+ if (matching) {
17815
+ for (const head of facts.heads)
17816
+ if (facts.remoteHeads.has(head))
17817
+ targets.push(head);
17818
+ }
17819
+ for (const glob of globs)
17820
+ targets.push(...globBranches(repo, glob));
17821
+ return targets;
17822
+ };
17823
+ var literalReadings = (name) => {
17824
+ if (!/[$`]/.test(name))
17825
+ return [name];
17826
+ const withDefaults = name.replace(/\$\{\w+:?[-=]([^}$`]*)\}/g, "$1").replace(/\$\{\w+\}|\$\w+/g, "");
17827
+ return /[$`]/.test(withDefaults) || !withDefaults ? [] : [withDefaults];
17828
+ };
17829
+ var guardedTarget = (dir, target) => {
17830
+ for (const branch of literalReadings(target)) {
17831
+ if (guardedBranch(dir, branch))
17832
+ return branch;
17833
+ const bare = branch.startsWith("heads/") ? branch.slice(6) : null;
17834
+ if (bare && guardedBranch(dir, bare))
17835
+ return bare;
17836
+ }
17837
+ return null;
17838
+ };
16794
17839
  var isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name);
16795
17840
  var tagRefusal = (action, facts) => {
16796
17841
  const tags = [
@@ -16802,24 +17847,32 @@ var tagRefusal = (action, facts) => {
16802
17847
  const named = tags.length ? tags.join(" --release ") : "v<version>";
16803
17848
  return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (v<version> tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
16804
17849
  };
16805
- var remoteHas = (repo, remote, branch) => {
16806
- const listed = spawnSync11("git", ["ls-remote", "--heads", remote, `refs/heads/${branch}`], {
17850
+ var remoteHeads = (repo, remote) => {
17851
+ const listed = spawnSync11("git", ["ls-remote", "--heads", remote], {
16807
17852
  cwd: repo.dir,
16808
17853
  encoding: "utf8",
16809
17854
  timeout: 3000,
17855
+ windowsHide: true,
16810
17856
  env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
16811
17857
  });
16812
- return listed.status !== 0 || (listed.stdout ?? "").trim() !== "";
17858
+ if (listed.status !== 0)
17859
+ return null;
17860
+ return new Set((listed.stdout ?? "").split(`
17861
+ `).map((line) => line.split("\t")[1] ?? "").filter((ref) => ref.startsWith("refs/heads/")).map((ref) => ref.slice(11)));
16813
17862
  };
16814
17863
  var offPolicyPush = (action, repo, facts, remote) => {
16815
17864
  const names = action.allBranches ? [...facts.heads] : action.targets.map((target) => target ?? repo.branch);
17865
+ let listed;
17866
+ const remoteHas = (branch) => (listed === undefined ? listed = remoteHeads(repo, remote) : listed)?.has(branch) ?? true;
16816
17867
  for (const branch of names) {
16817
- if (!branch || action.deleted?.includes(branch) || isTag(facts, branch))
17868
+ if (!branch || /[$`]/.test(branch))
17869
+ continue;
17870
+ if (action.deleted?.includes(branch) || isTag(facts, branch))
16818
17871
  continue;
16819
17872
  if (facts.remoteHeads.has(branch))
16820
17873
  continue;
16821
17874
  const check = validateBranchNameFor(repo.dir, branch);
16822
- if (check.ok || check.rule !== "allowed_pattern" || remoteHas(repo, remote, branch))
17875
+ if (check.ok || check.rule !== "allowed_pattern" || remoteHas(branch))
16823
17876
  continue;
16824
17877
  return `workit: raw ${quote("git push")} onto ${branch}: ${check.error}`;
16825
17878
  }
@@ -16841,23 +17894,50 @@ var nudgeFor = (action, host, session) => {
16841
17894
  return null;
16842
17895
  }
16843
17896
  };
16844
- var actionsIn = (cwd, command, dialect) => {
17897
+ var pushRefusal = (action, repo, config) => {
17898
+ if (!toPushRemote(repo, action.remote, config))
17899
+ return null;
17900
+ if (action.mirror)
17901
+ return `workit: raw ${quote("git push --mirror")} rewrites and deletes every remote ref, protected branches and tags included. Push one branch with ${quote("workit git push")}.`;
17902
+ const remote = action.remote ?? pushRemoteOf(config, repo.branch) ?? "origin";
17903
+ const facts = refFacts(repo, remote);
17904
+ const targets = pushedBranches(action, repo, config, facts, remote);
17905
+ const pushed = { ...action, targets };
17906
+ const offPolicy = action.allBranches ? offPolicyPush(pushed, repo, facts, remote) : null;
17907
+ if (offPolicy)
17908
+ return offPolicy;
17909
+ for (const target of targets) {
17910
+ const guarded = guardedTarget(repo.dir, target ?? repo.branch ?? "");
17911
+ if (guarded)
17912
+ return refusal(action, guarded, repo.dir);
17913
+ }
17914
+ return tagRefusal(pushed, facts) ?? (action.deletes ? null : offPolicyPush(pushed, repo, facts, remote));
17915
+ };
17916
+ var actionsIn = (cwd, command, dialect, cache = new Map) => {
16845
17917
  const out = [];
16846
17918
  const repos = new Map;
16847
- for (const invocation of rawInvocations(command, dialect)) {
16848
- if (invocation.dir === undefined)
16849
- continue;
16850
- const action = rawAction(invocation);
16851
- if (!action)
16852
- continue;
16853
- const dir = path18.resolve(cwd, invocation.dir ?? ".");
17919
+ const repoFor = (relative) => {
17920
+ const dir = path18.resolve(cwd, relative ?? ".");
16854
17921
  if (!repos.has(dir)) {
16855
17922
  const repo = repoAt(dir);
16856
17923
  repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
16857
17924
  }
16858
- const repo = repos.get(dir);
16859
- if (repo)
16860
- out.push({ action, repo });
17925
+ return repos.get(dir) ?? null;
17926
+ };
17927
+ for (const raw of rawInvocations(command, dialect)) {
17928
+ if (raw.dir === undefined)
17929
+ continue;
17930
+ const known = raw.tool !== "git" || GIT_COMMANDS.has(raw.args[0] ?? "");
17931
+ if (!known && !repoFor(raw.dir))
17932
+ continue;
17933
+ for (const invocation of known ? [raw] : expandGitAliases(cwd, raw, cache)) {
17934
+ if (invocation.dir === undefined)
17935
+ continue;
17936
+ const action = rawAction(invocation);
17937
+ const repo = action ? repoFor(invocation.dir) : null;
17938
+ if (action && repo)
17939
+ out.push({ action, repo, invocation });
17940
+ }
16861
17941
  }
16862
17942
  return out;
16863
17943
  };
@@ -16886,31 +17966,18 @@ var unseenNudges = (input, nudges, store) => {
16886
17966
  };
16887
17967
  function rawGitPre(input, command, options = {}) {
16888
17968
  try {
16889
- if (!mentionsRawTool(command))
16890
- return NONE2;
16891
17969
  const dialect = dialectOf(input);
16892
17970
  const nudges = new Map;
16893
17971
  let store = null;
16894
- for (const { action, repo } of actionsIn(input.cwd, command, dialect)) {
17972
+ const cache = new Map;
17973
+ for (const { action, repo, invocation } of actionsIn(input.cwd, command, dialect, cache)) {
16895
17974
  store ??= repo.location.dir;
16896
17975
  if (action.kind === "merge")
16897
17976
  return deny(refusal(action, null));
16898
- if (action.kind === "push" && toPushRemote(repo, action.remote)) {
16899
- if (action.mirror)
16900
- return deny(`workit: raw ${quote("git push --mirror")} rewrites and deletes every remote ref, protected branches and tags included. Push one branch with ${quote("workit git push")}.`);
16901
- for (const target of action.targets) {
16902
- const branch = target ?? repo.branch;
16903
- if (branch && guardedBranch(repo.dir, branch))
16904
- return deny(refusal(action, branch, repo.dir));
16905
- }
16906
- const remote = action.remote ?? pushRemoteName(repo.dir, repo.branch) ?? "origin";
16907
- const facts = refFacts(repo, remote);
16908
- const tagDeny = tagRefusal(action, facts);
16909
- if (tagDeny)
16910
- return deny(tagDeny);
16911
- const nameDeny = action.deletes ? null : offPolicyPush(action, repo, facts, remote);
16912
- if (nameDeny)
16913
- return deny(nameDeny);
17977
+ if (action.kind === "push") {
17978
+ const refused = pushRefusal(action, repo, gitConfig(repo.dir, invocation, cache));
17979
+ if (refused)
17980
+ return deny(refused);
16914
17981
  }
16915
17982
  const nudge = nudgeFor(action, input.host, input.session.id);
16916
17983
  if (nudge && !nudges.has(nudgeKind(action)))
@@ -16923,11 +17990,17 @@ function rawGitPre(input, command, options = {}) {
16923
17990
  const shown = unseenNudges(input, nudges, store);
16924
17991
  return shown.length ? { kind: "context", text: shown.join(`
16925
17992
  `) } : NONE2;
16926
- } catch {
16927
- return NONE2;
17993
+ } catch (error) {
17994
+ return error instanceof ShellAnalysisError ? analysisDenial(error) : NONE2;
16928
17995
  }
16929
17996
  }
16930
17997
  var deny = (reason) => ({ kind: "deny", reason, unblock: null });
17998
+ var SPLIT_IT = "Split it into simpler commands (no deeply nested substitutions or huge brace lists) and run them one at a time.";
17999
+ var analysisDenial = (error) => ({
18000
+ kind: "deny",
18001
+ reason: `workit: the shell hook could not analyze this command (${error.message}), so it does not run. ${SPLIT_IT}`,
18002
+ unblock: SPLIT_IT
18003
+ });
16931
18004
  var NEXT_COMMAND = "next";
16932
18005
  var MARKER_TTL_SECONDS = 3600;
16933
18006
  var markerFile = (repo, session, key) => path18.join(repo.location.dir, "hooks", `commit-${createHash13("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
@@ -17028,20 +18101,28 @@ var UNKNOWABLE = /[$`*?[\]{}~]/u;
17028
18101
  var SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
17029
18102
  var CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
17030
18103
  var WORKTREE_CREATE = new Set(["-b", "-B"]);
18104
+ var SUBTREE_CREATE = new Set(["-b", "--branch"]);
17031
18105
  var BRANCH_NO_CREATE = new Set(["-d", "-D", "--delete", "-l", "--list", "-a", "--all", "-r", "--remotes", "--show-current"].concat(["-v", "-vv", "--verbose", "--contains", "--no-contains", "--merged", "--no-merged"]).concat(["--points-at", "--sort", "--format", "--column", "--no-column", "-u"]).concat(["--set-upstream-to", "--unset-upstream", "--edit-description", "-i", "--ignore-case"]));
17032
18106
  var BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
18107
+ var longOption = (name, flags) => flags.has(name) || name.length >= 3 && [...flags].some((flag) => flag.startsWith("--") && flag.startsWith(name));
18108
+ var shortsOf = (flags) => [...flags].filter((flag) => /^-[^-]$/u.test(flag)).join("").replaceAll("-", "");
17033
18109
  var flagValue = (args, flags) => {
18110
+ const shorts = shortsOf(flags);
17034
18111
  for (let index = 1;index < args.length; index++) {
17035
18112
  const arg = args[index];
17036
18113
  if (arg === "--")
17037
18114
  return null;
17038
- if (flags.has(arg))
17039
- return args[index + 1] ?? null;
17040
- const eq = arg.indexOf("=");
17041
- if (arg.startsWith("--") && eq > 0 && flags.has(arg.slice(0, eq)))
17042
- return arg.slice(eq + 1);
17043
- if (!arg.startsWith("--") && arg.length > 2 && flags.has(arg.slice(0, 2)))
17044
- return arg.slice(2);
18115
+ if (arg.startsWith("--")) {
18116
+ const eq = arg.indexOf("=");
18117
+ if (!longOption(eq > 0 ? arg.slice(0, eq) : arg, flags))
18118
+ continue;
18119
+ return eq > 0 ? arg.slice(eq + 1) : args[index + 1] ?? null;
18120
+ }
18121
+ if (!arg.startsWith("-") || arg.length < 2)
18122
+ continue;
18123
+ for (let at = 1;at < arg.length; at++)
18124
+ if (shorts.includes(arg[at]))
18125
+ return at + 1 < arg.length ? arg.slice(at + 1) : args[index + 1] ?? null;
17045
18126
  }
17046
18127
  return null;
17047
18128
  };
@@ -17074,55 +18155,114 @@ var branchCommandTarget = (args) => {
17074
18155
  positionals.push(...args.slice(index + 1));
17075
18156
  break;
17076
18157
  }
17077
- const flag = arg.split("=", 1)[0];
17078
- if (BRANCH_NO_CREATE.has(flag))
17079
- return null;
17080
- if (BRANCH_RENAME.has(flag))
17081
- rename = true;
17082
- else if (!arg.startsWith("-"))
18158
+ if (arg.startsWith("--")) {
18159
+ const flag = arg.split("=", 1)[0];
18160
+ if (longOption(flag, BRANCH_NO_CREATE))
18161
+ return null;
18162
+ if (longOption(flag, BRANCH_RENAME))
18163
+ rename = true;
18164
+ } else if (arg.startsWith("-") && arg.length > 1) {
18165
+ const shorts = arg.slice(1).split("");
18166
+ if (shorts.some((short) => BRANCH_NO_CREATE.has(`-${short}`)))
18167
+ return null;
18168
+ if (shorts.some((short) => BRANCH_RENAME.has(`-${short}`)))
18169
+ rename = true;
18170
+ } else
17083
18171
  positionals.push(arg);
17084
18172
  }
17085
18173
  if (positionals.length < 1 || positionals.length > 2)
17086
18174
  return null;
17087
18175
  return rename ? positionals.at(-1) ?? null : positionals[0];
17088
18176
  };
17089
- var gitBranchCreation = (args) => {
17090
- const [sub, second] = args;
17091
- const target = sub === "switch" ? flagValue(args, SWITCH_CREATE) : sub === "checkout" ? flagValue(args, CHECKOUT_CREATE) : sub === "branch" ? branchCommandTarget(args) : sub === "worktree" && second === "add" ? worktreeTarget(args.slice(1)) : null;
17092
- return target && !target.startsWith("-") && !UNKNOWABLE.test(target) ? target : null;
18177
+ var positionalsOf = (args, skip, values = new Set) => {
18178
+ const found = [];
18179
+ for (let index = skip;index < args.length; index++) {
18180
+ const arg = args[index];
18181
+ if (arg === "--")
18182
+ return [...found, ...args.slice(index + 1)];
18183
+ if (values.has(arg))
18184
+ index++;
18185
+ else if (!arg.startsWith("-") || arg === "-")
18186
+ found.push(arg);
18187
+ }
18188
+ return found;
17093
18189
  };
17094
- var shellBranchTargets = (command, dialect = "posix") => {
17095
- if (!/\bgit\b/u.test(command))
18190
+ var localHead = (ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : null;
18191
+ var updateRefTargets = (args, stdin) => {
18192
+ if (args.some((arg) => arg === "-d" || arg === "--delete"))
17096
18193
  return [];
17097
- const found = [];
17098
- for (const invocation of rawInvocations(command, dialect)) {
17099
- if (invocation.tool !== "git")
18194
+ if (args.includes("--stdin"))
18195
+ return (stdin ?? "").split(/[\n\0]/u).map((line) => /^\s*(?:create|update)\s+(\S+)/u.exec(line)?.[1] ?? "").map(localHead).filter((name) => name !== null);
18196
+ const [ref] = positionalsOf(args, 1, new Set(["-m"]));
18197
+ const name = ref ? localHead(ref) : null;
18198
+ return name ? [name] : [];
18199
+ };
18200
+ var symbolicRefTarget = (args) => {
18201
+ if (args.some((arg) => arg === "-d" || arg === "--delete"))
18202
+ return null;
18203
+ const [, ref] = positionalsOf(args, 1, new Set(["-m"]));
18204
+ return ref ? localHead(ref) : null;
18205
+ };
18206
+ var FETCH_VALUES = new Set(["--depth", "--deepen", "--shallow-since", "--shallow-exclude", "--refmap", "-j", "--jobs"].concat(["--upload-pack", "-o", "--server-option", "--negotiation-tip", "--filter"]).concat(["--recurse-submodules-default", "--submodule-prefix", "-s", "--strategy", "-X"]).concat(["--strategy-option"]));
18207
+ var fetchTargets = (args, stdin) => {
18208
+ if (args.includes("--all") || args.includes("--multiple"))
18209
+ return [];
18210
+ const specs = args.includes("--stdin") ? (stdin ?? "").split(/\s+/u).filter(Boolean) : positionalsOf(args, 1, FETCH_VALUES).slice(1);
18211
+ const names = [];
18212
+ for (const raw of specs) {
18213
+ const colon = raw.indexOf(":");
18214
+ const destination = colon >= 0 ? raw.slice(colon + 1) : "";
18215
+ if (!destination || destination.includes("*"))
17100
18216
  continue;
17101
- const branch = gitBranchCreation(invocation.args);
17102
- if (branch)
17103
- found.push({ branch, dir: invocation.dir });
18217
+ const name = destination.startsWith("refs/") ? localHead(destination) : destination;
18218
+ if (name)
18219
+ names.push(name);
17104
18220
  }
17105
- return found;
18221
+ return names;
18222
+ };
18223
+ var localPushTargets = (args) => {
18224
+ const action = pushAction(args);
18225
+ if (action?.kind !== "push" || action.remote !== "." || action.deletes)
18226
+ return [];
18227
+ return action.targets.filter((name) => name !== null);
18228
+ };
18229
+ var gitBranchCreations = (args, stdin) => {
18230
+ const [sub, second] = args;
18231
+ const targets = sub === "switch" ? [flagValue(args, SWITCH_CREATE)] : sub === "checkout" ? [flagValue(args, CHECKOUT_CREATE)] : sub === "branch" ? [branchCommandTarget(args)] : sub === "worktree" && second === "add" ? [worktreeTarget(args.slice(1))] : sub === "stash" && second === "branch" ? positionalsOf(args, 2).slice(0, 1) : sub === "update-ref" ? updateRefTargets(args, stdin) : sub === "symbolic-ref" ? [symbolicRefTarget(args)] : sub === "fetch" || sub === "pull" ? fetchTargets(args, stdin) : sub === "push" ? localPushTargets(args) : sub === "subtree" && second === "split" ? [flagValue(args.slice(1), SUBTREE_CREATE)] : [];
18232
+ return targets.filter((target) => target !== null && target !== "" && !target.startsWith("-") && !UNKNOWABLE.test(target));
17106
18233
  };
18234
+ var targetsOf = (invocations) => invocations.flatMap((invocation) => invocation.tool === "git" ? gitBranchCreations(invocation.args, invocation.stdin).map((branch) => ({
18235
+ branch,
18236
+ dir: invocation.dir
18237
+ })) : []);
17107
18238
  var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
17108
18239
  cwd: dir,
17109
18240
  stdio: "ignore",
17110
18241
  timeout: 5000
17111
18242
  }).status === 0;
18243
+ var judgedDir = (root, dir) => {
18244
+ if (dir === undefined)
18245
+ return null;
18246
+ if (dir === null)
18247
+ return root;
18248
+ const resolved = path19.resolve(root, dir);
18249
+ return existsSync6(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
18250
+ };
17112
18251
  var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
17113
18252
  let judged = false;
17114
- for (const target of shellBranchTargets(command, dialect)) {
17115
- if (target.dir === undefined)
17116
- continue;
17117
- const dir = target.dir === null ? root : path19.resolve(root, target.dir);
17118
- if (target.dir !== null && (!existsSync6(dir) || !inWorkitWorkspace(dir)))
18253
+ const cache = new Map;
18254
+ for (const raw of rawInvocations(command, dialect)) {
18255
+ if (raw.tool !== "git" || judgedDir(root, raw.dir) === null)
17119
18256
  continue;
17120
- if (localBranchExists(dir, target.branch))
17121
- continue;
17122
- judged = true;
17123
- const check = validateBranchNameFor(dir, target.branch);
17124
- if (!check.ok)
17125
- return check;
18257
+ for (const target of targetsOf(expandGitAliases(root, raw, cache))) {
18258
+ const dir = judgedDir(root, target.dir);
18259
+ if (dir === null || localBranchExists(dir, target.branch))
18260
+ continue;
18261
+ judged = true;
18262
+ const check = validateBranchNameFor(dir, target.branch);
18263
+ if (!check.ok)
18264
+ return check;
18265
+ }
17126
18266
  }
17127
18267
  return judged ? { ok: true } : null;
17128
18268
  };
@@ -20449,7 +21589,14 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
20449
21589
  };
20450
21590
  // packages/workit-core/src/hooks/policy.ts
20451
21591
  var shellPolicy = (cwd, command, dialect = "posix") => {
20452
- const policy = shellBranchPolicyViolation(cwd, command, dialect);
21592
+ let policy;
21593
+ try {
21594
+ policy = shellBranchPolicyViolation(cwd, command, dialect);
21595
+ } catch (error) {
21596
+ if (error instanceof ShellAnalysisError)
21597
+ return analysisDenial(error);
21598
+ throw error;
21599
+ }
20453
21600
  return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
20454
21601
  };
20455
21602
  // packages/workit-core/src/hooks/write-gate.ts
@@ -20478,11 +21625,11 @@ var exempt = (cwd, root, file, plans) => {
20478
21625
  const segments = posix.split("/");
20479
21626
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20480
21627
  };
20481
- var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
21628
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
20482
21629
  var operands = (words) => words.filter((item) => !item.startsWith("-"));
20483
21630
  var editsOf = (input) => {
20484
21631
  let words = input;
20485
- while (words.length && (PREFIXES3.has(words[0]) || /^\w+=/.test(words[0])))
21632
+ while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
20486
21633
  words = words.slice(1);
20487
21634
  const [verb, ...args] = words;
20488
21635
  const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();