@brainervirus/workit-opencode 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 (2) hide show
  1. package/dist/plugin.js +1341 -194
  2. package/package.json +2 -2
package/dist/plugin.js CHANGED
@@ -16195,7 +16195,76 @@ var writeSessionMarker = (file, marker) => {
16195
16195
  };
16196
16196
 
16197
16197
  // packages/workit-core/src/hooks/shell-words.ts
16198
- var closingQuote = (command, start, dialect) => {
16198
+ class ShellAnalysisError extends Error {
16199
+ }
16200
+ var MAX_NESTING = 64;
16201
+ var OPEN = "";
16202
+ var COMMA = "";
16203
+ var CLOSE = "";
16204
+ var MARKERS = /[\uE000-\uE002]/u;
16205
+ var literal2 = (word) => word.replaceAll(OPEN, "{").replaceAll(COMMA, ",").replaceAll(CLOSE, "}");
16206
+ var BRACE_LIMIT = 64;
16207
+ var BRACE_PENDING = 512;
16208
+ var BRACE_DEPTH = 16;
16209
+ var braceGroup = (word) => {
16210
+ for (let start = 0;start < word.length; start++) {
16211
+ if (word[start] !== OPEN)
16212
+ continue;
16213
+ let depth = 0;
16214
+ let end = start;
16215
+ const commas = [];
16216
+ for (;end < word.length; end++) {
16217
+ if (word[end] === OPEN)
16218
+ depth++;
16219
+ else if (word[end] === CLOSE && --depth === 0)
16220
+ break;
16221
+ else if (word[end] === COMMA && depth === 1)
16222
+ commas.push(end);
16223
+ }
16224
+ if (end < word.length && commas.length)
16225
+ return { start, end, commas };
16226
+ }
16227
+ return null;
16228
+ };
16229
+ var expandBraces = (word) => {
16230
+ let depth = 0;
16231
+ for (const char of word) {
16232
+ if (char === OPEN && ++depth > BRACE_DEPTH)
16233
+ return [literal2(word)];
16234
+ if (char === CLOSE && depth > 0)
16235
+ depth--;
16236
+ }
16237
+ const done = new Set;
16238
+ const queue = [word];
16239
+ const queued = new Set(queue);
16240
+ for (let at = 0;at < queue.length; at++) {
16241
+ const current = queue[at];
16242
+ const group = braceGroup(current);
16243
+ if (!group) {
16244
+ const text = literal2(current);
16245
+ if (text)
16246
+ done.add(text);
16247
+ if (done.size > BRACE_LIMIT)
16248
+ return [literal2(word)];
16249
+ continue;
16250
+ }
16251
+ const pre = current.slice(0, group.start);
16252
+ const post = current.slice(group.end + 1);
16253
+ let from = group.start + 1;
16254
+ for (const comma of [...group.commas, group.end]) {
16255
+ const next = pre + current.slice(from, comma) + post;
16256
+ from = comma + 1;
16257
+ if (queued.has(next))
16258
+ continue;
16259
+ queued.add(next);
16260
+ queue.push(next);
16261
+ }
16262
+ if (queue.length - at > BRACE_PENDING)
16263
+ return [literal2(word)];
16264
+ }
16265
+ return [...done];
16266
+ };
16267
+ var closingQuote = (command, start, dialect, nesting = 0) => {
16199
16268
  const quote = command[start];
16200
16269
  for (let index = start + 1;index < command.length; index++) {
16201
16270
  const char = command[index];
@@ -16212,51 +16281,265 @@ var closingQuote = (command, start, dialect) => {
16212
16281
  index++;
16213
16282
  continue;
16214
16283
  }
16284
+ if (dialect === "posix" && char === "$" && command[index + 1] === "(") {
16285
+ index = substitutionEnd(command, index + 1, nesting + 1);
16286
+ continue;
16287
+ }
16288
+ if (dialect === "posix" && char === "`") {
16289
+ index = backtickEnd(command, index);
16290
+ continue;
16291
+ }
16215
16292
  if (char === '"')
16216
16293
  return index;
16217
16294
  }
16218
16295
  return -1;
16219
16296
  };
16297
+ var backtickEnd = (command, start) => {
16298
+ for (let index = start + 1;index < command.length; index++) {
16299
+ if (command[index] === "\\")
16300
+ index++;
16301
+ else if (command[index] === "`")
16302
+ return index;
16303
+ }
16304
+ return command.length;
16305
+ };
16306
+ var heredocDelimiter = (command, start) => {
16307
+ let index = start;
16308
+ if (command[index] === "-")
16309
+ index++;
16310
+ while (command[index] === " " || command[index] === "\t")
16311
+ index++;
16312
+ let delimiter = "";
16313
+ for (;index < command.length && !/[\s;&|<>()]/.test(command[index]); index++) {
16314
+ const char = command[index];
16315
+ if (char === "'" || char === '"') {
16316
+ const end = command.indexOf(char, index + 1);
16317
+ if (end < 0)
16318
+ break;
16319
+ delimiter += command.slice(index + 1, end);
16320
+ index = end;
16321
+ } else if (char === "\\")
16322
+ delimiter += command[++index] ?? "";
16323
+ else
16324
+ delimiter += char;
16325
+ }
16326
+ return { delimiter, end: index - 1 };
16327
+ };
16328
+ var skipHeredocs = (command, newline, delimiters) => {
16329
+ let index = newline;
16330
+ while (delimiters.length) {
16331
+ const delimiter = delimiters.shift();
16332
+ for (;; ) {
16333
+ const lineEnd = command.indexOf(`
16334
+ `, index + 1);
16335
+ const line = command.slice(index + 1, lineEnd < 0 ? undefined : lineEnd);
16336
+ index = lineEnd < 0 ? command.length : lineEnd;
16337
+ if (line.trim() === delimiter || lineEnd < 0)
16338
+ break;
16339
+ }
16340
+ }
16341
+ return index;
16342
+ };
16343
+ var substitutionEnd = (command, start, nesting = 0) => {
16344
+ if (nesting > MAX_NESTING)
16345
+ throw new ShellAnalysisError("substitutions nest too deep");
16346
+ let depth = 0;
16347
+ let wordStart = true;
16348
+ const heredocs = [];
16349
+ for (let index = start;index < command.length; index++) {
16350
+ const char = command[index];
16351
+ const atWordStart = wordStart;
16352
+ wordStart = /[\s;&|()]/.test(char);
16353
+ if (char === "\\")
16354
+ index++;
16355
+ else if (char === `
16356
+ ` && heredocs.length) {
16357
+ index = skipHeredocs(command, index, heredocs);
16358
+ wordStart = true;
16359
+ } else if (char === "'" || char === '"') {
16360
+ const end = closingQuote(command, index, "posix", nesting);
16361
+ if (end < 0)
16362
+ return command.length;
16363
+ index = end;
16364
+ } else if (char === "`")
16365
+ index = backtickEnd(command, index);
16366
+ else if (char === "#" && atWordStart) {
16367
+ const newline = command.indexOf(`
16368
+ `, index);
16369
+ index = newline < 0 ? command.length : newline - 1;
16370
+ } else if (char === "<" && command[index + 1] === "<" && command[index + 2] !== "<") {
16371
+ const { delimiter, end } = heredocDelimiter(command, index + 2);
16372
+ if (delimiter)
16373
+ heredocs.push(delimiter);
16374
+ index = end;
16375
+ } else if (char === "<" && command[index + 1] === "<")
16376
+ index += 2;
16377
+ else if (char === "$" && command[index + 1] === "(")
16378
+ index = substitutionEnd(command, index + 1, nesting + 1);
16379
+ else if (char === "(")
16380
+ depth++;
16381
+ else if (char === ")" && --depth === 0)
16382
+ return index;
16383
+ }
16384
+ return command.length;
16385
+ };
16220
16386
  var unquote = (body, quote, dialect) => {
16221
16387
  if (quote === "'")
16222
16388
  return dialect === "powershell" ? body.replaceAll("''", "'") : body;
16223
- return dialect === "posix" ? body.replace(/\\(["\\$`])/g, "$1") : body.replace(/`(.)/g, "$1");
16389
+ return dialect === "posix" ? body.replace(/\\\n/g, "").replace(/\\(["\\$`])/g, "$1") : body.replace(/`(.)/g, "$1");
16390
+ };
16391
+ var C_ESCAPES = {
16392
+ n: `
16393
+ `,
16394
+ t: "\t",
16395
+ r: "\r",
16396
+ a: "\x07",
16397
+ b: "\b",
16398
+ e: "\x1B",
16399
+ E: "\x1B",
16400
+ f: "\f",
16401
+ v: "\v"
16402
+ };
16403
+ var ansiC = (command, start) => {
16404
+ let text = "";
16405
+ let index = start + 1;
16406
+ for (;index < command.length && command[index] !== "'"; index++) {
16407
+ const char = command[index];
16408
+ if (char !== "\\" || index + 1 >= command.length) {
16409
+ text += char;
16410
+ continue;
16411
+ }
16412
+ const rest = command.slice(++index);
16413
+ const hex = /^x([0-9a-fA-F]{1,2})/.exec(rest);
16414
+ const octal = /^[0-7]{1,3}/.exec(rest);
16415
+ if (hex) {
16416
+ text += String.fromCharCode(Number.parseInt(hex[1], 16));
16417
+ index += hex[0].length - 1;
16418
+ } else if (octal) {
16419
+ text += String.fromCharCode(Number.parseInt(octal[0], 8));
16420
+ index += octal[0].length - 1;
16421
+ } else
16422
+ text += C_ESCAPES[rest[0]] ?? rest[0];
16423
+ }
16424
+ return { text, end: index };
16425
+ };
16426
+ var substitutions = (body) => {
16427
+ const found = [];
16428
+ for (let index = 0;index < body.length; index++) {
16429
+ const char = body[index];
16430
+ if (char === "\\")
16431
+ index++;
16432
+ else if (char === "`") {
16433
+ const end = backtickEnd(body, index);
16434
+ found.push(body.slice(index + 1, end).replace(/\\(.)/g, "$1"));
16435
+ index = end;
16436
+ } else if (char === "$" && body[index + 1] === "(") {
16437
+ const end = substitutionEnd(body, index + 1);
16438
+ found.push(body.slice(index + 2, end));
16439
+ index = end;
16440
+ }
16441
+ }
16442
+ return found;
16443
+ };
16444
+ var PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
16445
+ var PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
16446
+ var arithmeticEnd = (command, start) => {
16447
+ const end = substitutionEnd(command, start);
16448
+ return command[end - 1] === ")" && end > start + 2 ? end : 0;
16224
16449
  };
16225
16450
  var segmentsOf = (command, dialect = "posix") => {
16451
+ try {
16452
+ return tokenize(command, dialect);
16453
+ } catch (error) {
16454
+ const why = error instanceof ShellAnalysisError ? error.message : String(error);
16455
+ return [{ words: [], redirects: [], unanalyzable: why }];
16456
+ }
16457
+ };
16458
+ var tokenize = (command, dialect) => {
16459
+ const posix = dialect === "posix";
16226
16460
  const segments = [];
16227
16461
  let current = { words: [], redirects: [] };
16228
16462
  let word = "";
16229
16463
  let inWord = false;
16230
16464
  let pending = null;
16231
16465
  const heredocs = [];
16232
- const escape = dialect === "posix" ? "\\" : "`";
16466
+ const escape = posix ? "\\" : "`";
16233
16467
  const flushWord = () => {
16234
16468
  if (!inWord)
16235
16469
  return;
16470
+ const text = MARKERS.test(word) ? literal2(word) : word;
16236
16471
  if (pending === "redirect") {
16237
- if (!word.startsWith("&"))
16238
- current.redirects.push(word);
16472
+ if (!text.startsWith("&"))
16473
+ current.redirects.push(text);
16239
16474
  } else if (pending === "heredoc")
16240
- heredocs.push(word.replace(/^-/, ""));
16475
+ heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
16476
+ else if (pending === "herestring")
16477
+ (current.stdin ??= []).push(text);
16241
16478
  else if (pending !== "input")
16242
- current.words.push(word);
16479
+ current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
16243
16480
  pending = null;
16244
16481
  word = "";
16245
16482
  inWord = false;
16246
16483
  };
16247
16484
  const flushSegment = () => {
16248
16485
  flushWord();
16249
- if (current.words.length || current.redirects.length)
16486
+ if (current.words.length || current.redirects.length || current.stdin || current.subs)
16250
16487
  segments.push(current);
16251
16488
  current = { words: [], redirects: [] };
16252
16489
  };
16490
+ const group = (kind) => {
16491
+ flushSegment();
16492
+ pending = null;
16493
+ segments.push({ words: [], redirects: [], group: kind });
16494
+ };
16495
+ const substitution = (inner, text) => {
16496
+ (current.subs ??= []).push(inner);
16497
+ word += text;
16498
+ inWord = true;
16499
+ };
16500
+ const plain = posix ? PLAIN_POSIX : PLAIN_POWERSHELL;
16253
16501
  for (let index = 0;index < command.length; index++) {
16502
+ plain.lastIndex = index;
16503
+ const run = plain.exec(command);
16504
+ if (run) {
16505
+ word += run[0];
16506
+ inWord = true;
16507
+ index += run[0].length - 1;
16508
+ continue;
16509
+ }
16254
16510
  const char = command[index];
16255
- if (char === "'" || char === '"') {
16511
+ const next = command[index + 1];
16512
+ if (char === escape && (next === `
16513
+ ` || next === "\r" && command[index + 2] === `
16514
+ `))
16515
+ index += next === "\r" ? 2 : 1;
16516
+ else if (char === "#" && !inWord) {
16517
+ const newline = command.indexOf(`
16518
+ `, index);
16519
+ index = (newline < 0 ? command.length : newline) - 1;
16520
+ } else if (!posix && char === "<" && next === "#") {
16521
+ const end = command.indexOf("#>", index + 2);
16522
+ index = end < 0 ? command.length : end + 1;
16523
+ } else if (posix && char === "$" && next === "'") {
16524
+ const { text, end } = ansiC(command, index + 1);
16525
+ word += text;
16526
+ inWord = true;
16527
+ index = end;
16528
+ } else if (posix && char === "$" && next === '"')
16529
+ continue;
16530
+ else if (char === "'" || char === '"') {
16256
16531
  const end = closingQuote(command, index, dialect);
16257
- word += unquote(command.slice(index + 1, end < 0 ? undefined : end), char, dialect);
16532
+ const body = command.slice(index + 1, end < 0 ? undefined : end);
16533
+ if (posix && char === '"' && /\$\(|`/.test(body))
16534
+ (current.subs ??= []).push(...substitutions(body));
16535
+ word += unquote(body, char, dialect);
16258
16536
  inWord = true;
16259
16537
  index = end < 0 ? command.length : end;
16538
+ } else if (posix && char === "`") {
16539
+ const end = backtickEnd(command, index);
16540
+ const inner = command.slice(index + 1, end);
16541
+ substitution(inner.replace(/\\(.)/g, "$1"), `\`${inner}\``);
16542
+ index = end;
16260
16543
  } else if (char === escape && index + 1 < command.length) {
16261
16544
  word += command[++index];
16262
16545
  inWord = true;
@@ -16264,26 +16547,56 @@ var segmentsOf = (command, dialect = "posix") => {
16264
16547
  `) {
16265
16548
  flushSegment();
16266
16549
  while (heredocs.length) {
16267
- const delimiter = heredocs.shift();
16550
+ const { delimiter, owner } = heredocs.shift();
16268
16551
  const lines = command.slice(index + 1).split(`
16269
16552
  `);
16270
16553
  const at = lines.findIndex((line) => line.trim() === delimiter);
16271
- const skipped = (at < 0 ? lines : lines.slice(0, at + 1)).join(`
16554
+ (owner.stdin ??= []).push((at < 0 ? lines : lines.slice(0, at)).join(`
16555
+ `));
16556
+ index += (at < 0 ? lines : lines.slice(0, at + 1)).join(`
16272
16557
  `).length + 1;
16273
- index += skipped;
16274
16558
  }
16275
16559
  } else if (/\s/.test(char))
16276
16560
  flushWord();
16277
- else if (char === "(" || char === ")") {
16561
+ else if (posix && char === "(" && inWord && word.endsWith("$")) {
16562
+ const end = substitutionEnd(command, index);
16563
+ const inner = command.slice(index + 1, end);
16564
+ substitution(inner, `(${inner})`);
16565
+ index = end;
16566
+ } else if (posix && char === "(" && (pending === "input" || pending === "redirect")) {
16567
+ const sign = pending === "input" ? "<" : ">";
16568
+ pending = null;
16569
+ const end = substitutionEnd(command, index);
16570
+ const inner = command.slice(index + 1, end);
16571
+ substitution(inner, `${sign}(${inner})`);
16572
+ index = end;
16573
+ } else if (posix && char === "(" && next === "(" && !inWord && arithmeticEnd(command, index)) {
16574
+ flushSegment();
16575
+ index = arithmeticEnd(command, index);
16576
+ } else if (char === "(" || char === ")") {
16278
16577
  if (char === "(" && inWord && word.endsWith("$")) {
16279
16578
  word = word.slice(0, -1);
16280
16579
  inWord = word !== "";
16281
16580
  }
16581
+ group(char === "(" ? "open" : "close");
16582
+ } else if (!posix && (char === "{" || char === "}")) {
16583
+ if (char === "{" && inWord && word.endsWith("@")) {
16584
+ word += char;
16585
+ continue;
16586
+ }
16282
16587
  flushSegment();
16283
- segments.push({ words: [], redirects: [], group: char === "(" ? "open" : "close" });
16284
16588
  } else if (";&|".includes(char)) {
16285
- if (char === "&" && command[index + 1] === ">")
16589
+ if (char === "&" && next === ">")
16286
16590
  continue;
16591
+ if (char === "|" && next === "|")
16592
+ index++;
16593
+ else if (char === "|") {
16594
+ flushWord();
16595
+ if (current.words.length)
16596
+ current.pipe = true;
16597
+ if (next === "&")
16598
+ index++;
16599
+ }
16287
16600
  flushSegment();
16288
16601
  } else if (char === ">") {
16289
16602
  if (inWord && /^\d$/.test(word)) {
@@ -16299,16 +16612,24 @@ var segmentsOf = (command, dialect = "posix") => {
16299
16612
  inWord = true;
16300
16613
  }
16301
16614
  pending = "redirect";
16302
- } else if (char === "<" && dialect === "posix") {
16615
+ } else if (char === "<" && posix) {
16303
16616
  flushWord();
16304
- if (command[index + 1] === "<") {
16617
+ if (next === "<" && command[index + 2] === "<") {
16618
+ index += 2;
16619
+ pending = "herestring";
16620
+ } else if (next === "<") {
16305
16621
  index++;
16306
- if (command[index + 1] === "<")
16307
- index++;
16308
16622
  pending = "heredoc";
16309
16623
  } else
16310
16624
  pending = "input";
16311
- } else {
16625
+ } else if (posix && char === "{" && !word.endsWith("$")) {
16626
+ word += OPEN;
16627
+ inWord = true;
16628
+ } else if (posix && char === "," && word.includes(OPEN))
16629
+ word += COMMA;
16630
+ else if (posix && char === "}" && word.includes(OPEN))
16631
+ word += CLOSE;
16632
+ else {
16312
16633
  word += char;
16313
16634
  inWord = true;
16314
16635
  }
@@ -16319,8 +16640,35 @@ var segmentsOf = (command, dialect = "posix") => {
16319
16640
 
16320
16641
  // packages/workit-core/src/hooks/raw-git.ts
16321
16642
  var NONE2 = { kind: "none" };
16322
- var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
16323
- var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
16643
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh", "ash", "mksh", "yash", "fish"]);
16644
+ var POWERSHELLS = new Set(["pwsh", "powershell"]);
16645
+ var EVALS = new Set(["eval", "iex", "invoke-expression"]);
16646
+ var WRAPPERS = {
16647
+ sudo: { values: "-u -g -h -p -C -D -R -T -r -t -U --user --group --host --prompt --chdir" },
16648
+ doas: { values: "-u -C" },
16649
+ command: { values: "" },
16650
+ builtin: { values: "" },
16651
+ env: { values: "-u -C --unset --chdir" },
16652
+ nohup: { values: "" },
16653
+ setsid: { values: "" },
16654
+ time: { values: "-f -o --format --output" },
16655
+ exec: { values: "-a" },
16656
+ nice: { values: "-n --adjustment" },
16657
+ ionice: { values: "-c -n -p --class --classdata" },
16658
+ stdbuf: { values: "-i -o -e --input --output --error" },
16659
+ chronic: { values: "" },
16660
+ unbuffer: { values: "" },
16661
+ wsl: { values: "-d -u --cd --distribution --user" },
16662
+ timeout: { values: "-s -k --signal --kill-after", operands: 1 },
16663
+ flock: { values: "-w -E --timeout --conflict-exit-code", operands: 1 },
16664
+ coproc: { values: "" },
16665
+ taskset: { values: "", operands: 1 },
16666
+ watch: { values: "-n -d --interval --differences -q --equexit" },
16667
+ "&": { values: "" }
16668
+ };
16669
+ var WRAPPER_VALUES = new Map(Object.entries(WRAPPERS).map(([name, wrapper]) => [name, new Set(wrapper.values.split(" "))]));
16670
+ var KEYWORDS = new Set(["if", "then", "else", "elif", "while", "until", "do", "!", "{"]);
16671
+ var DECLARATIONS = new Set(["export", "local", "declare", "readonly", "typeset"]);
16324
16672
  var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
16325
16673
  var CD = new Set(["cd", "chdir", "set-location", "sl"]);
16326
16674
  var PUSHD = new Set(["pushd", "push-location"]);
@@ -16334,13 +16682,21 @@ var GIT_VALUE_OPTIONS = new Set([
16334
16682
  "--config-env",
16335
16683
  "--super-prefix"
16336
16684
  ]);
16685
+ var GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
16686
+ var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
16687
+ var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
16688
+ var MAX_DEPTH = 4;
16689
+ var MAX_EXPANSIONS = 16;
16337
16690
  var isAbsolute = (value) => path16.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
16338
- var joinDir = (base, next) => {
16691
+ var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
16692
+ var joinDir = (base, raw) => {
16693
+ const next = raw.replace(HERE, ".");
16339
16694
  if (base === undefined || /[$`*?]/.test(next) || next === "-")
16340
16695
  return;
16341
16696
  const expanded = next === "~" || next.startsWith("~/") ? path16.join(os4.homedir(), next.slice(1)) : next;
16342
16697
  return base === null || isAbsolute(expanded) ? expanded : path16.join(base, expanded);
16343
16698
  };
16699
+ var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
16344
16700
  var cdTarget = (words) => {
16345
16701
  const args = words.slice(1);
16346
16702
  const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
@@ -16350,88 +16706,433 @@ var cdTarget = (words) => {
16350
16706
  };
16351
16707
  var stripPrefixes = (input) => {
16352
16708
  let words = input;
16709
+ const env = {};
16353
16710
  for (;; ) {
16354
16711
  const first = words[0];
16355
16712
  if (first === undefined)
16356
- return words;
16357
- if (/^\w+=/.test(first))
16713
+ return { words, env };
16714
+ const assignment = ASSIGNMENT.exec(first);
16715
+ if (assignment) {
16716
+ if (GIT_CONFIG_ENV.test(assignment[1]))
16717
+ env[assignment[1]] = assignment[2];
16358
16718
  words = words.slice(1);
16359
- else if (PREFIXES2.has(first)) {
16719
+ } else if (KEYWORDS.has(first))
16360
16720
  words = words.slice(1);
16361
- while (words[0]?.startsWith("-"))
16362
- words = words.slice(words[0] === "-u" ? 2 : 1);
16363
- } else if (first === "timeout") {
16721
+ else if (first === "function")
16722
+ words = words.slice(2);
16723
+ else if (Object.hasOwn(WRAPPERS, commandName(first))) {
16724
+ const name = commandName(first);
16725
+ const values = WRAPPER_VALUES.get(name);
16364
16726
  words = words.slice(1);
16365
- while (words[0]?.startsWith("-"))
16727
+ const takesValue = (option) => values.has(option) || /^-[a-zA-Z]{2,}$/.test(option) && values.has(`-${option.at(-1)}`);
16728
+ while (words[0]?.startsWith("-") && words[0] !== "--")
16729
+ words = words.slice(takesValue(words[0]) ? 2 : 1);
16730
+ if (words[0] === "--")
16366
16731
  words = words.slice(1);
16367
- words = words.slice(1);
16732
+ words = words.slice(WRAPPERS[name].operands ?? 0);
16368
16733
  } else
16369
- return words;
16734
+ return { words, env };
16370
16735
  }
16371
16736
  };
16372
16737
  var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
16738
+ var VARIABLE = /\$(?:\{(\w+)\}|(\w+))/g;
16739
+ var expandWord = (word, vars) => {
16740
+ if (!word.includes("$"))
16741
+ return [word];
16742
+ let results = [""];
16743
+ let last = 0;
16744
+ for (const match of word.matchAll(VARIABLE)) {
16745
+ const values = vars.get(match[1] ?? match[2]) ?? [match[0]];
16746
+ const before = word.slice(last, match.index);
16747
+ results = results.flatMap((result) => values.map((value) => result + before + value)).slice(0, MAX_EXPANSIONS);
16748
+ last = match.index + match[0].length;
16749
+ }
16750
+ return results.map((result) => result + word.slice(last));
16751
+ };
16752
+ var substitute = (words, vars) => {
16753
+ if (vars.size === 0)
16754
+ return [words];
16755
+ let lines = [[]];
16756
+ for (const word of words) {
16757
+ const values = expandWord(word, vars);
16758
+ lines = lines.flatMap((line) => values.map((value) => [...line, value])).slice(0, MAX_EXPANSIONS);
16759
+ }
16760
+ return lines;
16761
+ };
16762
+ var assignments = (words, vars) => {
16763
+ if (words[0] === "for" && words[2] === "in" && /^\w+$/.test(words[1] ?? "")) {
16764
+ const values = words.slice(3).flatMap((word) => expandWord(word, vars));
16765
+ return [[words[1], values.slice(0, MAX_EXPANSIONS)]];
16766
+ }
16767
+ let rest = words;
16768
+ if (DECLARATIONS.has(rest[0] ?? "")) {
16769
+ rest = rest.slice(1);
16770
+ while (rest[0]?.startsWith("-"))
16771
+ rest = rest.slice(1);
16772
+ }
16773
+ if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
16774
+ return null;
16775
+ return rest.map((word) => {
16776
+ const [, name, value] = ASSIGNMENT.exec(word);
16777
+ return [name, expandWord(value, vars)];
16778
+ });
16779
+ };
16780
+ var literalOutput = (segment) => {
16781
+ if (!segment?.pipe)
16782
+ return;
16783
+ const { words } = stripPrefixes(segment.words);
16784
+ const name = commandName(words[0]);
16785
+ if (name === "echo") {
16786
+ let args = words.slice(1);
16787
+ while (/^-[neE]+$/.test(args[0] ?? ""))
16788
+ args = args.slice(1);
16789
+ return args.join(" ");
16790
+ }
16791
+ if (name === "printf") {
16792
+ const [format = "", ...args] = words.slice(1);
16793
+ return (args.length ? args.join(`
16794
+ `) : format).replace(/\\n/g, `
16795
+ `);
16796
+ }
16797
+ if (name === "cat" && words.length === 1 && segment.stdin)
16798
+ return segment.stdin.join(`
16799
+ `);
16800
+ return;
16801
+ };
16802
+ var stdinOf = (segment, previous) => segment.stdin ? segment.stdin.join(`
16803
+ `) : literalOutput(previous);
16804
+ var XARGS_VALUES = new Set(["-n", "-L", "-l", "-P", "-d", "-E", "-e", "-s", "-a", "--arg-file", "--delimiter"].concat([
16805
+ "--max-args",
16806
+ "--max-lines",
16807
+ "--max-procs",
16808
+ "--eof",
16809
+ "--max-chars",
16810
+ "--process-slot-var"
16811
+ ]));
16812
+ var xargsCommands = (words, input) => {
16813
+ let replace = null;
16814
+ let index = 1;
16815
+ for (;index < words.length; index++) {
16816
+ const word = words[index];
16817
+ if (word === "--") {
16818
+ index++;
16819
+ break;
16820
+ }
16821
+ if (!word.startsWith("-"))
16822
+ break;
16823
+ if (word === "-I")
16824
+ replace = words[++index] ?? "{}";
16825
+ else if (word === "--replace" || word === "-i")
16826
+ replace = "{}";
16827
+ else if (word.startsWith("--replace="))
16828
+ replace = word.slice("--replace=".length);
16829
+ else if (/^-[Ii]./.test(word))
16830
+ replace = word.slice(2);
16831
+ else if (XARGS_VALUES.has(word))
16832
+ index++;
16833
+ }
16834
+ const command = words.slice(index);
16835
+ if (command.length === 0)
16836
+ return [];
16837
+ if (input === undefined)
16838
+ return [command];
16839
+ if (replace === null)
16840
+ return [[...command, ...input.split(/\s+/).filter(Boolean)]];
16841
+ const token = replace;
16842
+ return input.split(`
16843
+ `).map((line) => line.trim()).filter(Boolean).slice(0, MAX_EXPANSIONS).map((line) => command.map((word) => word.replaceAll(token, line)));
16844
+ };
16845
+ var findCommands = (words) => {
16846
+ const found = [];
16847
+ for (let index = 1;index < words.length; index++) {
16848
+ if (!/^-(?:exec|execdir|ok|okdir)$/.test(words[index]))
16849
+ continue;
16850
+ const end = words.findIndex((word, at) => at > index && (word === ";" || word === "+"));
16851
+ found.push(words.slice(index + 1, end < 0 ? undefined : end));
16852
+ if (end < 0)
16853
+ break;
16854
+ index = end;
16855
+ }
16856
+ return found;
16857
+ };
16858
+ var POWERSHELL_VALUES = /^-(?:f|file|ex|executionpolicy|wd|workingdirectory|config|configurationname|o|outputformat|if|inputformat|inp|w|windowstyle|settingsfile|custompipename|psconsolefile)$/;
16859
+ var powershellScript = (words, stdin) => {
16860
+ for (let index = 1;index < words.length; index++) {
16861
+ const word = words[index];
16862
+ if (!/^[-/]/.test(word))
16863
+ return null;
16864
+ const flag = word.replace(/^[-/]+/, "").toLowerCase();
16865
+ if (!flag)
16866
+ return null;
16867
+ if (flag === "e" || flag === "ec" || flag.length >= 2 && "encodedcommand".startsWith(flag)) {
16868
+ try {
16869
+ return Buffer.from(words[index + 1] ?? "", "base64").toString("utf16le");
16870
+ } catch {
16871
+ return null;
16872
+ }
16873
+ }
16874
+ if (flag === "cwa" || flag.length >= 8 && "commandwithargs".startsWith(flag))
16875
+ return words[index + 1] ?? null;
16876
+ if ("command".startsWith(flag))
16877
+ return words[index + 1] === "-" ? stdin ?? null : words.slice(index + 1).join(" ");
16878
+ if (POWERSHELL_VALUES.test(`-${flag}`)) {
16879
+ if (flag === "f" || flag === "file")
16880
+ return null;
16881
+ index++;
16882
+ }
16883
+ }
16884
+ return null;
16885
+ };
16886
+ var cmdScript = (words) => {
16887
+ const at = words.findIndex((word, index) => index > 0 && /^\/[ck]$/i.test(word));
16888
+ if (at > 0)
16889
+ return words.slice(at + 1).join(" ");
16890
+ const attached = words.find((word, index) => index > 0 && /^\/[ck]./i.test(word));
16891
+ return attached ? [attached.slice(2), ...words.slice(words.indexOf(attached) + 1)].join(" ") : null;
16892
+ };
16893
+ var processSubstitutionText = (word) => {
16894
+ const inner = /^<\(([\s\S]*)\)$/.exec(word ?? "")?.[1];
16895
+ if (inner === undefined)
16896
+ return;
16897
+ const segments = segmentsOf(inner);
16898
+ return segments.length === 1 ? literalOutput({ ...segments[0], pipe: true }) : undefined;
16899
+ };
16900
+ var shellScript = (words, stdin) => {
16901
+ const flag = words.findIndex((word, index) => index > 0 && SCRIPT_FLAG.test(word));
16902
+ if (flag > 0)
16903
+ return words[flag + 1] === "--" ? words[flag + 2] : words[flag + 1];
16904
+ const operand = words.slice(1).find((word) => !word.startsWith("-"));
16905
+ if (operand !== undefined)
16906
+ return processSubstitutionText(operand);
16907
+ return stdin;
16908
+ };
16909
+ var scriptCommand = (words) => {
16910
+ for (let index = 1;index < words.length; index++) {
16911
+ const word = words[index];
16912
+ if (word === "--command" || /^-[a-zA-Z]*c$/.test(word))
16913
+ return words[index + 1];
16914
+ if (word.startsWith("--command="))
16915
+ return word.slice("--command=".length);
16916
+ }
16917
+ return;
16918
+ };
16919
+ var PARALLEL_VALUES = new Set(["-j", "--jobs", "-N", "-n", "-S", "--sshlogin", "-P", "--delay"]);
16920
+ var parallelCommands = (words) => {
16921
+ let index = 1;
16922
+ while (index < words.length && words[index].startsWith("-"))
16923
+ index += PARALLEL_VALUES.has(words[index]) ? 2 : 1;
16924
+ const separator = words.indexOf(":::", index);
16925
+ const command = words.slice(index, separator < 0 ? undefined : separator);
16926
+ if (separator < 0)
16927
+ return command.length ? [command] : [];
16928
+ 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]);
16929
+ };
16930
+ var START_PROCESS_VALUES = /^-(?:workingdirectory|wd|redirectstandard\w*|verb|windowstyle|credential|environment)$/i;
16931
+ var startProcessCommand = (words) => {
16932
+ let file;
16933
+ let args;
16934
+ const positionals = [];
16935
+ for (let index = 1;index < words.length; index++) {
16936
+ const word = words[index];
16937
+ const flag = word.startsWith("-") ? word.slice(1).toLowerCase() : null;
16938
+ if (flag === null)
16939
+ positionals.push(word);
16940
+ else if (flag && "filepath".startsWith(flag))
16941
+ file = words[++index];
16942
+ else if (flag && (flag === "args" || "argumentlist".startsWith(flag)))
16943
+ args = words[++index];
16944
+ else if (START_PROCESS_VALUES.test(word))
16945
+ index++;
16946
+ }
16947
+ file ??= positionals.shift();
16948
+ args ??= positionals.shift();
16949
+ if (!file)
16950
+ return null;
16951
+ return [file, ...(args ?? "").split(/[\s,]+/).filter(Boolean)];
16952
+ };
16953
+ var splitString = (line) => {
16954
+ if (commandName(line[0]) !== "env")
16955
+ return null;
16956
+ for (let index = 1;index < line.length; index++) {
16957
+ const word = line[index];
16958
+ if (word === "-S" || word === "--split-string")
16959
+ return line.slice(index + 1).join(" ");
16960
+ if (word.startsWith("--split-string="))
16961
+ return line.slice(index).join(" ").slice(15);
16962
+ if (/^-S./.test(word))
16963
+ return [word.slice(2), ...line.slice(index + 1)].join(" ");
16964
+ if (!word.startsWith("-") && !ASSIGNMENT.test(word))
16965
+ return null;
16966
+ }
16967
+ return null;
16968
+ };
16969
+ var CONFIG_READS = /^(?:--get\S*|-l|--list|--unset\S*|--remove-section|--rename-section|-e|--edit|--show-\S+|--name-only)$/;
16970
+ var configSetBy = (args) => {
16971
+ const positionals = args.filter((arg, index) => index > 0 && !arg.startsWith("-"));
16972
+ if (args[0] === "remote" && (positionals[0] === "add" || positionals[0] === "set-url")) {
16973
+ const [, name, url] = positionals;
16974
+ const key = args.includes("--push") ? "pushurl" : "url";
16975
+ return name && url ? ["-c", `remote.${name}.${key}=${url}`] : [];
16976
+ }
16977
+ if (args[0] !== "config" || args.some((arg) => CONFIG_READS.test(arg)))
16978
+ return [];
16979
+ const [key, value] = positionals[0] === "set" ? positionals.slice(1) : positionals;
16980
+ return key?.includes(".") && value !== undefined && positionals.length <= 3 ? ["-c", `${key}=${value}`] : [];
16981
+ };
16982
+ var gitInvocation = (words, dir, env = {}, stdin, inherited = []) => {
16983
+ let here = dir;
16984
+ const config = [...inherited];
16985
+ let gitDir = env.GIT_DIR;
16986
+ let index = 0;
16987
+ for (;index < words.length; index++) {
16988
+ const word = words[index];
16989
+ if (word === "-C")
16990
+ here = words[index + 1] ? joinDir(here, words[++index]) : here;
16991
+ else if (GIT_CONFIG_OPTIONS.has(word)) {
16992
+ if (word === "--git-dir")
16993
+ gitDir = words[index + 1];
16994
+ if (words[index + 1] !== undefined)
16995
+ config.push(word, words[++index]);
16996
+ } else if (/^--(?:config-env|git-dir)=/.test(word)) {
16997
+ if (word.startsWith("--git-dir="))
16998
+ gitDir = word.slice("--git-dir=".length);
16999
+ config.push(word);
17000
+ } else if (GIT_VALUE_OPTIONS.has(word))
17001
+ index++;
17002
+ else if (!word.startsWith("-"))
17003
+ break;
17004
+ }
17005
+ if (gitDir)
17006
+ here = joinDir(here, gitDir.replace(/(?:^|[\\/]+)\.git[\\/]*$/, "") || ".");
17007
+ return {
17008
+ tool: "git",
17009
+ dir: here,
17010
+ args: words.slice(index),
17011
+ ...config.length ? { config } : {},
17012
+ ...Object.keys(env).length ? { env } : {},
17013
+ ...stdin === undefined ? {} : { stdin }
17014
+ };
17015
+ };
16373
17016
  function rawInvocations(command, dialect = "posix", depth = 0) {
17017
+ if (depth > 0)
17018
+ return walk2(command, dialect, depth);
17019
+ const key = `${dialect}\x00${command}`;
17020
+ const cached = RECENT.get(key);
17021
+ if (cached)
17022
+ return cached;
17023
+ let found;
17024
+ try {
17025
+ found = walk2(command, dialect, depth);
17026
+ } catch (error) {
17027
+ throw error instanceof ShellAnalysisError ? error : new ShellAnalysisError(error instanceof Error ? error.message : String(error));
17028
+ }
17029
+ RECENT.set(key, found);
17030
+ if (RECENT.size > 8)
17031
+ RECENT.delete(RECENT.keys().next().value);
17032
+ return found;
17033
+ }
17034
+ var RECENT = new Map;
17035
+ function walk2(command, dialect, depth) {
16374
17036
  const found = [];
16375
17037
  let dir = null;
16376
17038
  const groups = [];
16377
17039
  let stack = [];
16378
- for (const segment of segmentsOf(command, dialect)) {
16379
- if (segment.group === "open") {
16380
- groups.push({ dir, stack: [...stack] });
16381
- continue;
16382
- }
16383
- if (segment.group === "close") {
16384
- const saved = groups.pop();
16385
- if (saved)
16386
- ({ dir, stack } = saved);
16387
- continue;
16388
- }
16389
- const words = stripPrefixes(segment.words);
17040
+ const vars = new Map;
17041
+ const exported = {};
17042
+ const sessionConfig = [];
17043
+ const nested = (script, inner) => {
17044
+ if (!script || depth >= MAX_DEPTH)
17045
+ return;
17046
+ for (const invocation of rawInvocations(script, inner, depth + 1))
17047
+ found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
17048
+ };
17049
+ const visit = (input, stdin, hops) => {
17050
+ const line = /^\$[\w:]+$/.test(input[0] ?? "") && input[1] === "=" ? input.slice(2) : input;
17051
+ const split = splitString(line);
17052
+ if (split !== null)
17053
+ return nested(split, "posix");
17054
+ const { words, env } = stripPrefixes(line);
16390
17055
  const name = commandName(words[0]);
17056
+ if (!name)
17057
+ return;
17058
+ const runs = (lines) => {
17059
+ if (hops < MAX_DEPTH)
17060
+ for (const next of lines)
17061
+ visit(next, undefined, hops + 1);
17062
+ };
16391
17063
  if (CD.has(name)) {
16392
17064
  const target = cdTarget(words);
16393
17065
  dir = target === null ? os4.homedir() : joinDir(dir, target);
16394
- continue;
16395
- }
16396
- if (PUSHD.has(name)) {
17066
+ } else if (PUSHD.has(name)) {
16397
17067
  const target = cdTarget(words);
16398
17068
  stack.push(dir);
16399
17069
  dir = target === null || target === "-" ? undefined : joinDir(dir, target);
16400
- continue;
16401
- }
16402
- if (POPD.has(name)) {
17070
+ } else if (POPD.has(name))
16403
17071
  dir = stack.length ? stack.pop() : undefined;
16404
- continue;
17072
+ else if (EVALS.has(name)) {
17073
+ const args = /^-c(?:o(?:m(?:m(?:a(?:n(?:d)?)?)?)?)?)?$/i.test(words[1] ?? "") ? words.slice(2) : words.slice(1);
17074
+ nested(args.join(" "), dialect);
17075
+ } else if (SHELLS.has(name))
17076
+ nested(shellScript(words, stdin), "posix");
17077
+ else if ((name === "source" || name === ".") && dialect === "posix")
17078
+ nested(processSubstitutionText(words[1]), "posix");
17079
+ else if (name === "trap" && dialect === "posix" && !/^-[lp]$/.test(words[1] ?? ""))
17080
+ nested(words[1], "posix");
17081
+ else if (name === "script")
17082
+ nested(scriptCommand(words), "posix");
17083
+ else if (name === "parallel")
17084
+ runs(parallelCommands(words));
17085
+ else if (dialect === "powershell" && /^(?:start-process|saps|start)$/.test(name)) {
17086
+ const started = startProcessCommand(words);
17087
+ if (started)
17088
+ runs([started]);
17089
+ } else if (POWERSHELLS.has(name))
17090
+ nested(powershellScript(words, stdin), "powershell");
17091
+ else if (name === "cmd")
17092
+ nested(cmdScript(words), "powershell");
17093
+ else if (name === "xargs" || name === "find")
17094
+ runs(name === "xargs" ? xargsCommands(words, stdin) : findCommands(words));
17095
+ else if (name === "gh" || name === "glab")
17096
+ found.push({
17097
+ tool: name,
17098
+ dir,
17099
+ args: words.slice(1),
17100
+ ...stdin === undefined ? {} : { stdin }
17101
+ });
17102
+ else if (name === "git") {
17103
+ const invocation = gitInvocation(words.slice(1), dir, { ...exported, ...env }, stdin, sessionConfig);
17104
+ found.push(invocation);
17105
+ sessionConfig.push(...configSetBy(invocation.args));
16405
17106
  }
16406
- if (SHELLS.has(name) && depth < 3) {
16407
- const flag = words.findIndex((word, index) => index > 0 && SCRIPT_FLAG.test(word));
16408
- const script = flag > 0 ? words[flag + 1] : undefined;
16409
- if (script)
16410
- for (const inner of rawInvocations(script, "posix", depth + 1))
16411
- found.push({
16412
- ...inner,
16413
- dir: inner.dir === null ? dir : inner.dir === undefined ? undefined : joinDir(dir, inner.dir)
16414
- });
17107
+ };
17108
+ const segments = segmentsOf(command, dialect);
17109
+ for (const [index, segment] of segments.entries()) {
17110
+ if (segment.group === "open") {
17111
+ groups.push({ dir, stack: [...stack] });
16415
17112
  continue;
16416
17113
  }
16417
- if (name === "gh" || name === "glab") {
16418
- found.push({ tool: name, dir, args: words.slice(1) });
17114
+ if (segment.group === "close") {
17115
+ const saved = groups.pop();
17116
+ if (saved)
17117
+ ({ dir, stack } = saved);
16419
17118
  continue;
16420
17119
  }
16421
- if (name !== "git")
17120
+ if (segment.unanalyzable)
17121
+ throw new ShellAnalysisError(segment.unanalyzable);
17122
+ for (const sub of segment.subs ?? [])
17123
+ nested(sub, "posix");
17124
+ const set = assignments(segment.words, vars);
17125
+ if (set) {
17126
+ for (const [name, values] of set) {
17127
+ vars.set(name, values);
17128
+ if (segment.words[0] === "export" && GIT_CONFIG_ENV.test(name) && values[0] !== undefined)
17129
+ exported[name] = values[0];
17130
+ }
16422
17131
  continue;
16423
- let here = dir;
16424
- let index = 1;
16425
- for (;index < words.length; index++) {
16426
- const word = words[index];
16427
- if (word === "-C")
16428
- here = words[index + 1] ? joinDir(here, words[++index]) : here;
16429
- else if (GIT_VALUE_OPTIONS.has(word))
16430
- index++;
16431
- else if (!word.startsWith("-"))
16432
- break;
16433
17132
  }
16434
- found.push({ tool: "git", dir: here, args: words.slice(index) });
17133
+ const stdin = stdinOf(segment, segments[index - 1]);
17134
+ for (const line of substitute(segment.words, vars))
17135
+ visit(line, stdin, 0);
16435
17136
  }
16436
17137
  return found;
16437
17138
  }
@@ -16467,6 +17168,55 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
16467
17168
  return found;
16468
17169
  }
16469
17170
  var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
17171
+ var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
17172
+ var pushDestination = (destination) => {
17173
+ if (destination === "HEAD" || destination === "@" || CURRENT_BRANCH.test(destination))
17174
+ return { name: null };
17175
+ if (destination.startsWith("refs/heads/"))
17176
+ return { name: destination.slice(11) };
17177
+ if (destination.startsWith("refs/tags/"))
17178
+ return { name: undefined, tag: destination.slice(10) };
17179
+ if (destination.startsWith("refs/"))
17180
+ return { name: undefined };
17181
+ return { name: destination };
17182
+ };
17183
+ var pushRefspecs = (specs) => {
17184
+ let force = false;
17185
+ let matching = false;
17186
+ const targets = [];
17187
+ const tags = [];
17188
+ const deleted = [];
17189
+ const globs = [];
17190
+ for (const raw of specs) {
17191
+ if (raw.startsWith("+"))
17192
+ force = true;
17193
+ const spec = raw.replace(/^\+/, "");
17194
+ if (spec.startsWith("^"))
17195
+ continue;
17196
+ if (spec === ":") {
17197
+ matching = true;
17198
+ continue;
17199
+ }
17200
+ const colon = spec.indexOf(":");
17201
+ const source = colon >= 0 ? spec.slice(0, colon) : spec;
17202
+ const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
17203
+ if (!destination)
17204
+ continue;
17205
+ if (destination.includes("*")) {
17206
+ globs.push({ source, destination });
17207
+ continue;
17208
+ }
17209
+ const { name, tag } = pushDestination(destination);
17210
+ if (tag !== undefined)
17211
+ tags.push(tag);
17212
+ if (name === undefined)
17213
+ continue;
17214
+ targets.push(name);
17215
+ if (colon === 0 && name !== null)
17216
+ deleted.push(name);
17217
+ }
17218
+ return { force, matching, targets, tags, deleted, globs };
17219
+ };
16470
17220
  var pushAction = (args) => {
16471
17221
  let force = false;
16472
17222
  let lease = false;
@@ -16476,6 +17226,7 @@ var pushAction = (args) => {
16476
17226
  let allBranches = false;
16477
17227
  let mirror = false;
16478
17228
  let remote = null;
17229
+ let dryRun = false;
16479
17230
  const positionals = [];
16480
17231
  for (let index = 1;index < args.length; index++) {
16481
17232
  const arg = args[index];
@@ -16483,9 +17234,11 @@ var pushAction = (args) => {
16483
17234
  positionals.push(...args.slice(index + 1));
16484
17235
  break;
16485
17236
  }
16486
- if (arg === "--dry-run" || /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg))
16487
- return null;
16488
- if (arg === "--repo")
17237
+ if (/^--dr(?:y(?:-(?:r(?:un?)?)?)?)?$/.test(arg) || /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg))
17238
+ dryRun = true;
17239
+ else if (/^--no-dr(?:y(?:-(?:r(?:un?)?)?)?)?$/.test(arg))
17240
+ dryRun = false;
17241
+ else if (arg === "--repo")
16489
17242
  remote = args[++index] ?? null;
16490
17243
  else if (arg.startsWith("--repo="))
16491
17244
  remote = arg.slice("--repo=".length);
@@ -16506,41 +17259,28 @@ var pushAction = (args) => {
16506
17259
  else if (!arg.startsWith("-"))
16507
17260
  positionals.push(arg);
16508
17261
  }
17262
+ if (dryRun)
17263
+ return null;
16509
17264
  const specs = remote === null ? positionals.slice(1) : positionals;
16510
17265
  remote ??= positionals[0] ?? null;
16511
- const targets = [];
16512
- const tags = [];
16513
- const deleted = [];
16514
- for (const raw of specs) {
16515
- if (raw.startsWith("+"))
16516
- force = true;
16517
- const spec = raw.replace(/^\+/, "");
16518
- const colon = spec.indexOf(":");
16519
- const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
16520
- if (!destination)
16521
- continue;
16522
- const name = destination === "HEAD" ? null : destination.startsWith("refs/heads/") ? destination.slice(11) : destination.startsWith("refs/") ? undefined : destination;
16523
- if (destination.startsWith("refs/tags/"))
16524
- tags.push(destination.slice(10));
16525
- if (name === undefined)
16526
- continue;
16527
- targets.push(name);
16528
- if (colon === 0 && name !== null)
16529
- deleted.push(name);
16530
- }
16531
- if (specs.length === 0 && !wholesale)
16532
- targets.push(null);
17266
+ const parsed = pushRefspecs(specs);
17267
+ const implicit = specs.length === 0 && !wholesale;
17268
+ if (implicit)
17269
+ parsed.targets.push(null);
16533
17270
  return {
16534
17271
  kind: "push",
16535
- targets,
16536
- blindForce: force && !lease,
17272
+ targets: parsed.targets,
17273
+ blindForce: (force || parsed.force) && !lease,
16537
17274
  remote,
16538
- ...tags.length ? { tags } : {},
17275
+ ...parsed.tags.length ? { tags: parsed.tags } : {},
16539
17276
  ...allTags ? { allTags: true } : {},
16540
17277
  ...deletes ? { deletes: true } : {},
16541
- ...deleted.length ? { deleted } : {},
17278
+ ...parsed.deleted.length ? { deleted: parsed.deleted } : {},
16542
17279
  ...allBranches ? { allBranches: true } : {},
16543
- ...mirror ? { mirror: true } : {}
17280
+ ...mirror ? { mirror: true } : {},
17281
+ ...implicit ? { implicit: true } : {},
17282
+ ...parsed.globs.length ? { globs: parsed.globs } : {},
17283
+ ...parsed.matching ? { matching: true } : {}
16544
17284
  };
16545
17285
  };
16546
17286
  var VALUE_OPTIONS = {
@@ -16586,22 +17326,110 @@ var asksHelp = (invocation) => {
16586
17326
  }
16587
17327
  return false;
16588
17328
  };
17329
+ var API_VALUES = new Set(["--method", "--header", "--raw-field", "--field", "--jq", "--template", "--input"].concat([
17330
+ "--hostname",
17331
+ "--preview",
17332
+ "--cache",
17333
+ "--output"
17334
+ ]));
17335
+ var API_SHORT_VALUES = "XHfFqtp";
17336
+ var API_FIELDS = new Set(["-f", "-F", "--raw-field", "--field"]);
17337
+ var API_MERGE = {
17338
+ gh: /(?:^|\/)(?:pulls\/[^/?#]+\/merge|merges)\/?(?:[?#]|$)/i,
17339
+ glab: /(?:^|\/)merge_requests\/[^/?#]+\/merge\/?(?:[?#]|$)/i
17340
+ };
17341
+ var GRAPHQL_MERGE = /\b(?:mergePullRequest|enablePullRequestAutoMerge|mergeBranch|mergeRequestAccept)\b/;
17342
+ var apiMerges = (tool, args, stdin) => {
17343
+ const request = { method: null, body: false };
17344
+ let endpoint = null;
17345
+ const values = [];
17346
+ const take = (name, value) => {
17347
+ if (name === "-X" || name === "--method")
17348
+ request.method = value.toUpperCase();
17349
+ if (API_FIELDS.has(name) || name === "--input")
17350
+ request.body = true;
17351
+ values.push(value);
17352
+ const fromStdin = name === "--input" ? value === "-" : /^(?:[^=]*=)?@-$/.test(value);
17353
+ if (fromStdin && stdin !== undefined)
17354
+ values.push(stdin);
17355
+ };
17356
+ for (let index = 1;index < args.length; index++) {
17357
+ const arg = args[index];
17358
+ if (arg === "--") {
17359
+ endpoint ??= args[index + 1] ?? null;
17360
+ break;
17361
+ }
17362
+ if (arg.startsWith("--")) {
17363
+ const eq = arg.indexOf("=");
17364
+ const name = eq > 0 ? arg.slice(0, eq) : arg;
17365
+ if (API_VALUES.has(name))
17366
+ take(name, eq > 0 ? arg.slice(eq + 1) : args[++index] ?? "");
17367
+ } else if (arg.startsWith("-") && arg.length > 1) {
17368
+ for (let at = 1;at < arg.length; at++) {
17369
+ if (!API_SHORT_VALUES.includes(arg[at]))
17370
+ continue;
17371
+ take(`-${arg[at]}`, at + 1 < arg.length ? arg.slice(at + 1) : args[++index] ?? "");
17372
+ break;
17373
+ }
17374
+ } else
17375
+ endpoint ??= arg;
17376
+ }
17377
+ if (!endpoint)
17378
+ return false;
17379
+ endpoint = endpoint.replace(/(?<!:)\/{2,}/g, "/");
17380
+ if (/(?:^|\/)graphql\/?$/i.test(endpoint))
17381
+ return values.some((value) => GRAPHQL_MERGE.test(value));
17382
+ const verb = request.method ?? (request.body ? "POST" : "GET");
17383
+ return verb !== "GET" && verb !== "HEAD" && API_MERGE[tool].test(endpoint);
17384
+ };
17385
+ var subtreePush = (args) => {
17386
+ const positionals = [];
17387
+ for (let index = 2;index < args.length; index++) {
17388
+ const arg = args[index];
17389
+ if (arg === "-P" || arg === "--prefix" || arg === "-m" || arg === "--message")
17390
+ index++;
17391
+ else if (!arg.startsWith("-"))
17392
+ positionals.push(arg);
17393
+ }
17394
+ const [remote, branch] = positionals;
17395
+ return remote && branch ? pushAction(["push", remote, `HEAD:refs/heads/${branch}`]) : null;
17396
+ };
16589
17397
  function rawAction(invocation) {
16590
17398
  const args = invocation.args;
17399
+ if (invocation.tool !== "git" && args[0] === "api")
17400
+ return apiMerges(invocation.tool, args, invocation.stdin) ? { kind: "merge", forge: invocation.tool, via: "api" } : null;
16591
17401
  if (asksHelp(invocation))
16592
17402
  return null;
16593
- const [first, second] = args;
17403
+ const [first] = args;
16594
17404
  if (invocation.tool === "git") {
17405
+ const second = args[1];
16595
17406
  if (first === "commit")
16596
17407
  return args.includes("--dry-run") ? null : { kind: "commit", amend: args.includes("--amend") };
16597
17408
  if (first === "push")
16598
17409
  return pushAction(args);
17410
+ if (first === "send-pack")
17411
+ return pushAction([
17412
+ "push",
17413
+ ...args.slice(1).filter((arg) => arg !== "--stdin"),
17414
+ ...args.includes("--stdin") ? (invocation.stdin ?? "").split(/\s+/).filter(Boolean) : []
17415
+ ]);
17416
+ if (first === "subtree" && second === "push")
17417
+ return subtreePush(args);
16599
17418
  return null;
16600
17419
  }
16601
17420
  const forge = invocation.tool;
16602
17421
  const noun = forge === "gh" ? "pr" : "mr";
16603
17422
  if (first !== noun)
16604
17423
  return null;
17424
+ const values = VALUE_OPTIONS[forge];
17425
+ let second;
17426
+ for (let index = 1;index < args.length && second === undefined; index++) {
17427
+ const arg = args[index];
17428
+ if (values.has(arg))
17429
+ index++;
17430
+ else if (!arg.startsWith("-"))
17431
+ second = arg;
17432
+ }
16605
17433
  if (second === "merge")
16606
17434
  return { kind: "merge", forge };
16607
17435
  if (second === "create")
@@ -16633,16 +17461,6 @@ var guardedBranch = (dir, branch) => {
16633
17461
  return false;
16634
17462
  }
16635
17463
  };
16636
- var toPushRemote = (repo, remote) => {
16637
- if (remote === null)
16638
- return true;
16639
- try {
16640
- const pushRemote = pushRemoteName(repo.dir, repo.branch);
16641
- return pushRemote === null || pushRemote === remote;
16642
- } catch {
16643
- return false;
16644
- }
16645
- };
16646
17464
  var headSha2 = (repo) => {
16647
17465
  const git = repo.location.git;
16648
17466
  if (!git || git.reftable)
@@ -16664,10 +17482,169 @@ var headSha2 = (repo) => {
16664
17482
  return null;
16665
17483
  }
16666
17484
  };
17485
+ var gitConfig = (dir, invocation, cache) => {
17486
+ const key = JSON.stringify([dir, invocation.config ?? [], invocation.env ?? {}]);
17487
+ const cached = cache.get(key);
17488
+ if (cached)
17489
+ return cached;
17490
+ const config = new Map;
17491
+ const listed = spawnSync11("git", [...invocation.config ?? [], "config", "-z", "--list"], {
17492
+ cwd: dir,
17493
+ encoding: "utf8",
17494
+ timeout: 3000,
17495
+ windowsHide: true,
17496
+ env: { ...process.env, ...invocation.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0" }
17497
+ });
17498
+ for (const entry of (listed.status === 0 ? listed.stdout : "").split("\x00")) {
17499
+ if (!entry)
17500
+ continue;
17501
+ const newline = entry.indexOf(`
17502
+ `);
17503
+ const name = newline < 0 ? entry : entry.slice(0, newline);
17504
+ config.set(name, [
17505
+ ...config.get(name) ?? [],
17506
+ newline < 0 ? "true" : entry.slice(newline + 1)
17507
+ ]);
17508
+ }
17509
+ cache.set(key, config);
17510
+ return config;
17511
+ };
17512
+ var configValue = (config, key) => config.get(key)?.at(-1);
17513
+ 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(" "));
17514
+ var JUDGED_COMMANDS = [
17515
+ "push",
17516
+ "commit",
17517
+ "switch",
17518
+ "checkout",
17519
+ "branch",
17520
+ "worktree",
17521
+ "stash",
17522
+ "update-ref",
17523
+ "symbolic-ref",
17524
+ "fetch",
17525
+ "pull",
17526
+ "subtree"
17527
+ ];
17528
+ var editDistance2 = (a, b) => {
17529
+ let before = [];
17530
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
17531
+ for (let i = 1;i <= a.length; i++) {
17532
+ const row = [i];
17533
+ for (let j = 1;j <= b.length; j++) {
17534
+ row[j] = Math.min(previous[j] + 1, row[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
17535
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1])
17536
+ row[j] = Math.min(row[j], before[j - 2] + 1);
17537
+ }
17538
+ [before, previous] = [previous, row];
17539
+ }
17540
+ return previous[b.length];
17541
+ };
17542
+ var autocorrected = (name, config) => {
17543
+ const setting = (configValue(config, "help.autocorrect") ?? "0").toLowerCase();
17544
+ if (setting !== "immediate" && !/^-?[1-9]\d*$/.test(setting))
17545
+ return null;
17546
+ let best = null;
17547
+ let bestDistance = 3;
17548
+ let tie = false;
17549
+ for (const command of JUDGED_COMMANDS) {
17550
+ const distance = editDistance2(name, command);
17551
+ if (distance < bestDistance)
17552
+ [best, bestDistance, tie] = [command, distance, false];
17553
+ else if (distance === bestDistance)
17554
+ tie = true;
17555
+ }
17556
+ return tie ? null : best;
17557
+ };
17558
+ var shellQuote = (word) => `'${word.replaceAll("'", "'\\''")}'`;
17559
+ var positional = (script, args) => {
17560
+ const word = (arg) => /^[\w./@+:=-]+$/.test(arg) ? arg : shellQuote(arg);
17561
+ return script.replace(/\$\{?([1-9])\}?/g, (_, n) => word(args[Number(n) - 1] ?? "")).replace(/\$\{?[@*]\}?/g, args.map(word).join(" "));
17562
+ };
17563
+ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
17564
+ const sub = invocation.args[0];
17565
+ if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
17566
+ return [invocation];
17567
+ const dir = path16.resolve(root, invocation.dir ?? ".");
17568
+ if (!fs14.existsSync(dir))
17569
+ return [invocation];
17570
+ const config = gitConfig(dir, invocation, cache);
17571
+ const alias = configValue(config, `alias.${sub.toLowerCase()}`);
17572
+ if (alias === undefined) {
17573
+ const corrected = autocorrected(sub, config);
17574
+ return corrected ? [{ ...invocation, args: [corrected, ...invocation.args.slice(1)] }] : [invocation];
17575
+ }
17576
+ const rest = invocation.args.slice(1);
17577
+ const expanded = alias.startsWith("!") ? rawInvocations([positional(alias.slice(1), rest), ...rest.map(shellQuote)].join(" ")).map((inner) => ({
17578
+ ...inner,
17579
+ dir: rebase(invocation.dir, inner.dir),
17580
+ ...inner.tool === "git" && (invocation.config || inner.config) ? { config: [...invocation.config ?? [], ...inner.config ?? []] } : {},
17581
+ ...inner.tool === "git" && (invocation.env || inner.env) ? { env: { ...invocation.env, ...inner.env } } : {}
17582
+ })) : [
17583
+ gitInvocation([...segmentsOf(alias)[0]?.words ?? [], ...rest], invocation.dir, invocation.env, invocation.stdin, invocation.config)
17584
+ ];
17585
+ return expanded.flatMap((inner) => expandGitAliases(root, inner, cache, depth + 1));
17586
+ };
17587
+ var normalizedUrl = (url, dir) => {
17588
+ const trimmed = url.trim();
17589
+ if (/^file:\/\//i.test(trimmed))
17590
+ return path16.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
17591
+ const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
17592
+ const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
17593
+ if (!scheme && !scp)
17594
+ return path16.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
17595
+ const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
17596
+ return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
17597
+ };
17598
+ var rewrittenUrl = (url, config) => {
17599
+ for (const kind of ["pushinsteadof", "insteadof"]) {
17600
+ let best = null;
17601
+ for (const [key, prefixes] of config) {
17602
+ if (!key.startsWith("url.") || !key.endsWith(`.${kind}`))
17603
+ continue;
17604
+ const base = key.slice(4, -(kind.length + 1));
17605
+ for (const prefix of prefixes)
17606
+ if (url.startsWith(prefix) && prefix.length > (best?.prefix.length ?? -1))
17607
+ best = { base, prefix };
17608
+ }
17609
+ if (best)
17610
+ return best.base + url.slice(best.prefix.length);
17611
+ }
17612
+ return url;
17613
+ };
17614
+ var pushUrl = (config, remote, dir) => {
17615
+ const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
17616
+ return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
17617
+ };
17618
+ var pushRemoteOf = (config, branch) => {
17619
+ const remotes = new Set;
17620
+ for (const key of config.keys()) {
17621
+ const name = /^remote\.(.+)\.url$/.exec(key)?.[1];
17622
+ if (name)
17623
+ remotes.add(name);
17624
+ }
17625
+ for (const candidate of [
17626
+ branch ? configValue(config, `branch.${branch}.pushremote`) : undefined,
17627
+ configValue(config, "remote.pushdefault"),
17628
+ branch ? configValue(config, `branch.${branch}.remote`) : undefined
17629
+ ])
17630
+ if (candidate && candidate !== "." && remotes.has(candidate))
17631
+ return candidate;
17632
+ if (remotes.has("origin"))
17633
+ return "origin";
17634
+ return remotes.size === 1 ? [...remotes][0] : null;
17635
+ };
17636
+ var toPushRemote = (repo, remote, config) => {
17637
+ if (remote === null || /[$`]/.test(remote))
17638
+ return true;
17639
+ const pushRemote = pushRemoteOf(config, repo.branch);
17640
+ if (pushRemote === null || pushRemote === remote)
17641
+ return true;
17642
+ return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
17643
+ };
16667
17644
  var quote = (text) => `\`${text}\``;
16668
17645
  var refusal = (action, branch, dir) => {
16669
17646
  if (action.kind === "merge")
16670
- 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")}).`;
17647
+ 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")}).`;
16671
17648
  const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
16672
17649
  if (dir && branch && plainPush && releaseBranch(dir, branch))
16673
17650
  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")}.`;
@@ -16688,6 +17665,74 @@ var refFacts = (repo, remote) => {
16688
17665
  }
16689
17666
  return facts;
16690
17667
  };
17668
+ var globBranches = (repo, glob) => {
17669
+ const source = glob.source.startsWith("refs/") ? glob.source : `refs/heads/${glob.source}`;
17670
+ const [sourcePrefix, sourceSuffix = ""] = source.split("*");
17671
+ const [destinationPrefix, destinationSuffix = ""] = glob.destination.split("*");
17672
+ const listed = spawnSync11("git", [
17673
+ "for-each-ref",
17674
+ "--format=%(refname)",
17675
+ sourcePrefix.slice(0, sourcePrefix.lastIndexOf("/") + 1)
17676
+ ], { cwd: repo.dir, encoding: "utf8", timeout: 5000, windowsHide: true });
17677
+ const names = [];
17678
+ for (const ref of (listed.stdout ?? "").split(`
17679
+ `)) {
17680
+ if (!ref.startsWith(sourcePrefix) || !ref.endsWith(sourceSuffix))
17681
+ continue;
17682
+ const middle = ref.slice(sourcePrefix.length, ref.length - sourceSuffix.length);
17683
+ const { name } = pushDestination(destinationPrefix + middle + destinationSuffix);
17684
+ if (name)
17685
+ names.push(name);
17686
+ }
17687
+ return names;
17688
+ };
17689
+ var pushedBranches = (action, repo, config, facts, remote) => {
17690
+ const targets = action.implicit ? [] : [...action.targets];
17691
+ const globs = [...action.globs ?? []];
17692
+ let matching = action.matching === true;
17693
+ const configured = action.implicit ? config.get(`remote.${remote}.push`) : undefined;
17694
+ if (configured?.length) {
17695
+ const parsed = pushRefspecs(configured);
17696
+ targets.push(...parsed.targets);
17697
+ globs.push(...parsed.globs);
17698
+ matching ||= parsed.matching;
17699
+ } else if (action.implicit) {
17700
+ const mode = (configValue(config, "push.default") ?? "simple").toLowerCase();
17701
+ const merge = repo.branch ? configValue(config, `branch.${repo.branch}.merge`) : undefined;
17702
+ if (mode === "matching")
17703
+ matching = true;
17704
+ else if ((mode === "upstream" || mode === "tracking") && merge?.startsWith("refs/heads/"))
17705
+ targets.push(merge.slice(11));
17706
+ else if (mode !== "nothing")
17707
+ targets.push(null);
17708
+ }
17709
+ if (action.allBranches)
17710
+ targets.push(...facts.heads);
17711
+ if (matching) {
17712
+ for (const head of facts.heads)
17713
+ if (facts.remoteHeads.has(head))
17714
+ targets.push(head);
17715
+ }
17716
+ for (const glob of globs)
17717
+ targets.push(...globBranches(repo, glob));
17718
+ return targets;
17719
+ };
17720
+ var literalReadings = (name) => {
17721
+ if (!/[$`]/.test(name))
17722
+ return [name];
17723
+ const withDefaults = name.replace(/\$\{\w+:?[-=]([^}$`]*)\}/g, "$1").replace(/\$\{\w+\}|\$\w+/g, "");
17724
+ return /[$`]/.test(withDefaults) || !withDefaults ? [] : [withDefaults];
17725
+ };
17726
+ var guardedTarget = (dir, target) => {
17727
+ for (const branch of literalReadings(target)) {
17728
+ if (guardedBranch(dir, branch))
17729
+ return branch;
17730
+ const bare = branch.startsWith("heads/") ? branch.slice(6) : null;
17731
+ if (bare && guardedBranch(dir, bare))
17732
+ return bare;
17733
+ }
17734
+ return null;
17735
+ };
16691
17736
  var isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name);
16692
17737
  var tagRefusal = (action, facts) => {
16693
17738
  const tags = [
@@ -16699,24 +17744,32 @@ var tagRefusal = (action, facts) => {
16699
17744
  const named = tags.length ? tags.join(" --release ") : "v<version>";
16700
17745
  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")}).`;
16701
17746
  };
16702
- var remoteHas = (repo, remote, branch) => {
16703
- const listed = spawnSync11("git", ["ls-remote", "--heads", remote, `refs/heads/${branch}`], {
17747
+ var remoteHeads = (repo, remote) => {
17748
+ const listed = spawnSync11("git", ["ls-remote", "--heads", remote], {
16704
17749
  cwd: repo.dir,
16705
17750
  encoding: "utf8",
16706
17751
  timeout: 3000,
17752
+ windowsHide: true,
16707
17753
  env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
16708
17754
  });
16709
- return listed.status !== 0 || (listed.stdout ?? "").trim() !== "";
17755
+ if (listed.status !== 0)
17756
+ return null;
17757
+ return new Set((listed.stdout ?? "").split(`
17758
+ `).map((line) => line.split("\t")[1] ?? "").filter((ref) => ref.startsWith("refs/heads/")).map((ref) => ref.slice(11)));
16710
17759
  };
16711
17760
  var offPolicyPush = (action, repo, facts, remote) => {
16712
17761
  const names = action.allBranches ? [...facts.heads] : action.targets.map((target) => target ?? repo.branch);
17762
+ let listed;
17763
+ const remoteHas = (branch) => (listed === undefined ? listed = remoteHeads(repo, remote) : listed)?.has(branch) ?? true;
16713
17764
  for (const branch of names) {
16714
- if (!branch || action.deleted?.includes(branch) || isTag(facts, branch))
17765
+ if (!branch || /[$`]/.test(branch))
17766
+ continue;
17767
+ if (action.deleted?.includes(branch) || isTag(facts, branch))
16715
17768
  continue;
16716
17769
  if (facts.remoteHeads.has(branch))
16717
17770
  continue;
16718
17771
  const check = validateBranchNameFor(repo.dir, branch);
16719
- if (check.ok || check.rule !== "allowed_pattern" || remoteHas(repo, remote, branch))
17772
+ if (check.ok || check.rule !== "allowed_pattern" || remoteHas(branch))
16720
17773
  continue;
16721
17774
  return `workit: raw ${quote("git push")} onto ${branch}: ${check.error}`;
16722
17775
  }
@@ -16738,23 +17791,50 @@ var nudgeFor = (action, host, session) => {
16738
17791
  return null;
16739
17792
  }
16740
17793
  };
16741
- var actionsIn = (cwd, command, dialect) => {
17794
+ var pushRefusal = (action, repo, config) => {
17795
+ if (!toPushRemote(repo, action.remote, config))
17796
+ return null;
17797
+ if (action.mirror)
17798
+ 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")}.`;
17799
+ const remote = action.remote ?? pushRemoteOf(config, repo.branch) ?? "origin";
17800
+ const facts = refFacts(repo, remote);
17801
+ const targets = pushedBranches(action, repo, config, facts, remote);
17802
+ const pushed = { ...action, targets };
17803
+ const offPolicy = action.allBranches ? offPolicyPush(pushed, repo, facts, remote) : null;
17804
+ if (offPolicy)
17805
+ return offPolicy;
17806
+ for (const target of targets) {
17807
+ const guarded = guardedTarget(repo.dir, target ?? repo.branch ?? "");
17808
+ if (guarded)
17809
+ return refusal(action, guarded, repo.dir);
17810
+ }
17811
+ return tagRefusal(pushed, facts) ?? (action.deletes ? null : offPolicyPush(pushed, repo, facts, remote));
17812
+ };
17813
+ var actionsIn = (cwd, command, dialect, cache = new Map) => {
16742
17814
  const out = [];
16743
17815
  const repos = new Map;
16744
- for (const invocation of rawInvocations(command, dialect)) {
16745
- if (invocation.dir === undefined)
16746
- continue;
16747
- const action = rawAction(invocation);
16748
- if (!action)
16749
- continue;
16750
- const dir = path16.resolve(cwd, invocation.dir ?? ".");
17816
+ const repoFor = (relative) => {
17817
+ const dir = path16.resolve(cwd, relative ?? ".");
16751
17818
  if (!repos.has(dir)) {
16752
17819
  const repo = repoAt(dir);
16753
17820
  repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
16754
17821
  }
16755
- const repo = repos.get(dir);
16756
- if (repo)
16757
- out.push({ action, repo });
17822
+ return repos.get(dir) ?? null;
17823
+ };
17824
+ for (const raw of rawInvocations(command, dialect)) {
17825
+ if (raw.dir === undefined)
17826
+ continue;
17827
+ const known = raw.tool !== "git" || GIT_COMMANDS.has(raw.args[0] ?? "");
17828
+ if (!known && !repoFor(raw.dir))
17829
+ continue;
17830
+ for (const invocation of known ? [raw] : expandGitAliases(cwd, raw, cache)) {
17831
+ if (invocation.dir === undefined)
17832
+ continue;
17833
+ const action = rawAction(invocation);
17834
+ const repo = action ? repoFor(invocation.dir) : null;
17835
+ if (action && repo)
17836
+ out.push({ action, repo, invocation });
17837
+ }
16758
17838
  }
16759
17839
  return out;
16760
17840
  };
@@ -16783,31 +17863,18 @@ var unseenNudges = (input, nudges, store) => {
16783
17863
  };
16784
17864
  function rawGitPre(input, command, options = {}) {
16785
17865
  try {
16786
- if (!mentionsRawTool(command))
16787
- return NONE2;
16788
17866
  const dialect = dialectOf(input);
16789
17867
  const nudges = new Map;
16790
17868
  let store = null;
16791
- for (const { action, repo } of actionsIn(input.cwd, command, dialect)) {
17869
+ const cache = new Map;
17870
+ for (const { action, repo, invocation } of actionsIn(input.cwd, command, dialect, cache)) {
16792
17871
  store ??= repo.location.dir;
16793
17872
  if (action.kind === "merge")
16794
17873
  return deny(refusal(action, null));
16795
- if (action.kind === "push" && toPushRemote(repo, action.remote)) {
16796
- if (action.mirror)
16797
- 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")}.`);
16798
- for (const target of action.targets) {
16799
- const branch = target ?? repo.branch;
16800
- if (branch && guardedBranch(repo.dir, branch))
16801
- return deny(refusal(action, branch, repo.dir));
16802
- }
16803
- const remote = action.remote ?? pushRemoteName(repo.dir, repo.branch) ?? "origin";
16804
- const facts = refFacts(repo, remote);
16805
- const tagDeny = tagRefusal(action, facts);
16806
- if (tagDeny)
16807
- return deny(tagDeny);
16808
- const nameDeny = action.deletes ? null : offPolicyPush(action, repo, facts, remote);
16809
- if (nameDeny)
16810
- return deny(nameDeny);
17874
+ if (action.kind === "push") {
17875
+ const refused = pushRefusal(action, repo, gitConfig(repo.dir, invocation, cache));
17876
+ if (refused)
17877
+ return deny(refused);
16811
17878
  }
16812
17879
  const nudge = nudgeFor(action, input.host, input.session.id);
16813
17880
  if (nudge && !nudges.has(nudgeKind(action)))
@@ -16820,11 +17887,17 @@ function rawGitPre(input, command, options = {}) {
16820
17887
  const shown = unseenNudges(input, nudges, store);
16821
17888
  return shown.length ? { kind: "context", text: shown.join(`
16822
17889
  `) } : NONE2;
16823
- } catch {
16824
- return NONE2;
17890
+ } catch (error) {
17891
+ return error instanceof ShellAnalysisError ? analysisDenial(error) : NONE2;
16825
17892
  }
16826
17893
  }
16827
17894
  var deny = (reason) => ({ kind: "deny", reason, unblock: null });
17895
+ var SPLIT_IT = "Split it into simpler commands (no deeply nested substitutions or huge brace lists) and run them one at a time.";
17896
+ var analysisDenial = (error) => ({
17897
+ kind: "deny",
17898
+ reason: `workit: the shell hook could not analyze this command (${error.message}), so it does not run. ${SPLIT_IT}`,
17899
+ unblock: SPLIT_IT
17900
+ });
16828
17901
  var MARKER_TTL_SECONDS = 3600;
16829
17902
  var markerFile = (repo, session, key) => path16.join(repo.location.dir, "hooks", `commit-${createHash12("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
16830
17903
  var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash12("sha256").update(command).digest("hex")}`;
@@ -16912,20 +17985,28 @@ var UNKNOWABLE = /[$`*?[\]{}~]/u;
16912
17985
  var SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
16913
17986
  var CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
16914
17987
  var WORKTREE_CREATE = new Set(["-b", "-B"]);
17988
+ var SUBTREE_CREATE = new Set(["-b", "--branch"]);
16915
17989
  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"]));
16916
17990
  var BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
17991
+ var longOption = (name, flags) => flags.has(name) || name.length >= 3 && [...flags].some((flag) => flag.startsWith("--") && flag.startsWith(name));
17992
+ var shortsOf = (flags) => [...flags].filter((flag) => /^-[^-]$/u.test(flag)).join("").replaceAll("-", "");
16917
17993
  var flagValue = (args, flags) => {
17994
+ const shorts = shortsOf(flags);
16918
17995
  for (let index = 1;index < args.length; index++) {
16919
17996
  const arg = args[index];
16920
17997
  if (arg === "--")
16921
17998
  return null;
16922
- if (flags.has(arg))
16923
- return args[index + 1] ?? null;
16924
- const eq = arg.indexOf("=");
16925
- if (arg.startsWith("--") && eq > 0 && flags.has(arg.slice(0, eq)))
16926
- return arg.slice(eq + 1);
16927
- if (!arg.startsWith("--") && arg.length > 2 && flags.has(arg.slice(0, 2)))
16928
- return arg.slice(2);
17999
+ if (arg.startsWith("--")) {
18000
+ const eq = arg.indexOf("=");
18001
+ if (!longOption(eq > 0 ? arg.slice(0, eq) : arg, flags))
18002
+ continue;
18003
+ return eq > 0 ? arg.slice(eq + 1) : args[index + 1] ?? null;
18004
+ }
18005
+ if (!arg.startsWith("-") || arg.length < 2)
18006
+ continue;
18007
+ for (let at = 1;at < arg.length; at++)
18008
+ if (shorts.includes(arg[at]))
18009
+ return at + 1 < arg.length ? arg.slice(at + 1) : args[index + 1] ?? null;
16929
18010
  }
16930
18011
  return null;
16931
18012
  };
@@ -16958,55 +18039,114 @@ var branchCommandTarget = (args) => {
16958
18039
  positionals.push(...args.slice(index + 1));
16959
18040
  break;
16960
18041
  }
16961
- const flag = arg.split("=", 1)[0];
16962
- if (BRANCH_NO_CREATE.has(flag))
16963
- return null;
16964
- if (BRANCH_RENAME.has(flag))
16965
- rename = true;
16966
- else if (!arg.startsWith("-"))
18042
+ if (arg.startsWith("--")) {
18043
+ const flag = arg.split("=", 1)[0];
18044
+ if (longOption(flag, BRANCH_NO_CREATE))
18045
+ return null;
18046
+ if (longOption(flag, BRANCH_RENAME))
18047
+ rename = true;
18048
+ } else if (arg.startsWith("-") && arg.length > 1) {
18049
+ const shorts = arg.slice(1).split("");
18050
+ if (shorts.some((short) => BRANCH_NO_CREATE.has(`-${short}`)))
18051
+ return null;
18052
+ if (shorts.some((short) => BRANCH_RENAME.has(`-${short}`)))
18053
+ rename = true;
18054
+ } else
16967
18055
  positionals.push(arg);
16968
18056
  }
16969
18057
  if (positionals.length < 1 || positionals.length > 2)
16970
18058
  return null;
16971
18059
  return rename ? positionals.at(-1) ?? null : positionals[0];
16972
18060
  };
16973
- var gitBranchCreation = (args) => {
16974
- const [sub, second] = args;
16975
- 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;
16976
- return target && !target.startsWith("-") && !UNKNOWABLE.test(target) ? target : null;
18061
+ var positionalsOf = (args, skip, values = new Set) => {
18062
+ const found = [];
18063
+ for (let index = skip;index < args.length; index++) {
18064
+ const arg = args[index];
18065
+ if (arg === "--")
18066
+ return [...found, ...args.slice(index + 1)];
18067
+ if (values.has(arg))
18068
+ index++;
18069
+ else if (!arg.startsWith("-") || arg === "-")
18070
+ found.push(arg);
18071
+ }
18072
+ return found;
16977
18073
  };
16978
- var shellBranchTargets = (command, dialect = "posix") => {
16979
- if (!/\bgit\b/u.test(command))
18074
+ var localHead = (ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : null;
18075
+ var updateRefTargets = (args, stdin) => {
18076
+ if (args.some((arg) => arg === "-d" || arg === "--delete"))
16980
18077
  return [];
16981
- const found = [];
16982
- for (const invocation of rawInvocations(command, dialect)) {
16983
- if (invocation.tool !== "git")
18078
+ if (args.includes("--stdin"))
18079
+ return (stdin ?? "").split(/[\n\0]/u).map((line) => /^\s*(?:create|update)\s+(\S+)/u.exec(line)?.[1] ?? "").map(localHead).filter((name) => name !== null);
18080
+ const [ref] = positionalsOf(args, 1, new Set(["-m"]));
18081
+ const name = ref ? localHead(ref) : null;
18082
+ return name ? [name] : [];
18083
+ };
18084
+ var symbolicRefTarget = (args) => {
18085
+ if (args.some((arg) => arg === "-d" || arg === "--delete"))
18086
+ return null;
18087
+ const [, ref] = positionalsOf(args, 1, new Set(["-m"]));
18088
+ return ref ? localHead(ref) : null;
18089
+ };
18090
+ 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"]));
18091
+ var fetchTargets = (args, stdin) => {
18092
+ if (args.includes("--all") || args.includes("--multiple"))
18093
+ return [];
18094
+ const specs = args.includes("--stdin") ? (stdin ?? "").split(/\s+/u).filter(Boolean) : positionalsOf(args, 1, FETCH_VALUES).slice(1);
18095
+ const names = [];
18096
+ for (const raw of specs) {
18097
+ const colon = raw.indexOf(":");
18098
+ const destination = colon >= 0 ? raw.slice(colon + 1) : "";
18099
+ if (!destination || destination.includes("*"))
16984
18100
  continue;
16985
- const branch = gitBranchCreation(invocation.args);
16986
- if (branch)
16987
- found.push({ branch, dir: invocation.dir });
18101
+ const name = destination.startsWith("refs/") ? localHead(destination) : destination;
18102
+ if (name)
18103
+ names.push(name);
16988
18104
  }
16989
- return found;
18105
+ return names;
18106
+ };
18107
+ var localPushTargets = (args) => {
18108
+ const action = pushAction(args);
18109
+ if (action?.kind !== "push" || action.remote !== "." || action.deletes)
18110
+ return [];
18111
+ return action.targets.filter((name) => name !== null);
18112
+ };
18113
+ var gitBranchCreations = (args, stdin) => {
18114
+ const [sub, second] = args;
18115
+ 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)] : [];
18116
+ return targets.filter((target) => target !== null && target !== "" && !target.startsWith("-") && !UNKNOWABLE.test(target));
16990
18117
  };
18118
+ var targetsOf = (invocations) => invocations.flatMap((invocation) => invocation.tool === "git" ? gitBranchCreations(invocation.args, invocation.stdin).map((branch) => ({
18119
+ branch,
18120
+ dir: invocation.dir
18121
+ })) : []);
16991
18122
  var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
16992
18123
  cwd: dir,
16993
18124
  stdio: "ignore",
16994
18125
  timeout: 5000
16995
18126
  }).status === 0;
18127
+ var judgedDir = (root, dir) => {
18128
+ if (dir === undefined)
18129
+ return null;
18130
+ if (dir === null)
18131
+ return root;
18132
+ const resolved = path17.resolve(root, dir);
18133
+ return existsSync6(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
18134
+ };
16996
18135
  var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
16997
18136
  let judged = false;
16998
- for (const target of shellBranchTargets(command, dialect)) {
16999
- if (target.dir === undefined)
17000
- continue;
17001
- const dir = target.dir === null ? root : path17.resolve(root, target.dir);
17002
- if (target.dir !== null && (!existsSync6(dir) || !inWorkitWorkspace(dir)))
18137
+ const cache = new Map;
18138
+ for (const raw of rawInvocations(command, dialect)) {
18139
+ if (raw.tool !== "git" || judgedDir(root, raw.dir) === null)
17003
18140
  continue;
17004
- if (localBranchExists(dir, target.branch))
17005
- continue;
17006
- judged = true;
17007
- const check = validateBranchNameFor(dir, target.branch);
17008
- if (!check.ok)
17009
- return check;
18141
+ for (const target of targetsOf(expandGitAliases(root, raw, cache))) {
18142
+ const dir = judgedDir(root, target.dir);
18143
+ if (dir === null || localBranchExists(dir, target.branch))
18144
+ continue;
18145
+ judged = true;
18146
+ const check = validateBranchNameFor(dir, target.branch);
18147
+ if (!check.ok)
18148
+ return check;
18149
+ }
17010
18150
  }
17011
18151
  return judged ? { ok: true } : null;
17012
18152
  };
@@ -20281,7 +21421,14 @@ function entryCompactContext(store, entry, session, context) {
20281
21421
  var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
20282
21422
  // packages/workit-core/src/hooks/policy.ts
20283
21423
  var shellPolicy = (cwd, command, dialect = "posix") => {
20284
- const policy = shellBranchPolicyViolation(cwd, command, dialect);
21424
+ let policy;
21425
+ try {
21426
+ policy = shellBranchPolicyViolation(cwd, command, dialect);
21427
+ } catch (error) {
21428
+ if (error instanceof ShellAnalysisError)
21429
+ return analysisDenial(error);
21430
+ throw error;
21431
+ }
20285
21432
  return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
20286
21433
  };
20287
21434
  // packages/workit-core/src/hooks/write-gate.ts
@@ -20310,11 +21457,11 @@ var exempt = (cwd, root, file, plans) => {
20310
21457
  const segments = posix.split("/");
20311
21458
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20312
21459
  };
20313
- var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
21460
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
20314
21461
  var operands = (words) => words.filter((item) => !item.startsWith("-"));
20315
21462
  var editsOf = (input) => {
20316
21463
  let words = input;
20317
- while (words.length && (PREFIXES3.has(words[0]) || /^\w+=/.test(words[0])))
21464
+ while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
20318
21465
  words = words.slice(1);
20319
21466
  const [verb, ...args] = words;
20320
21467
  const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();