@brainervirus/workit-cursor 8.12.2 → 8.13.0

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.
@@ -8881,6 +8881,14 @@ var PREFIXES, branchNames = (workspaceRoot) => {
8881
8881
  }, repoBranchPreset = (workspaceRoot) => {
8882
8882
  const detected = detectBranchPolicy(workspaceRoot);
8883
8883
  return detected.developBranch || detected.allowed.length ? detected.preset : null;
8884
+ }, releaseTagPrefixOf = (workspaceRoot, policyPrefix) => {
8885
+ if (typeof policyPrefix === "string")
8886
+ return policyPrefix.trim();
8887
+ const r = spawnSync2("git", ["config", "--get", "gitflow.prefix.versiontag"], {
8888
+ cwd: workspaceRoot,
8889
+ encoding: "utf8"
8890
+ });
8891
+ return r.status === 0 ? (r.stdout ?? "").trim() : "v";
8884
8892
  };
8885
8893
  var init_branch_policy = __esm(() => {
8886
8894
  init_config();
@@ -15650,11 +15658,11 @@ what and whether, the CLI does how and records what it observed.
15650
15658
  Native host allow/ask/deny, sandbox and org rules stay authoritative. Never
15651
15659
  switch paths to evade a denial, never claim enforcement a host cannot provide,
15652
15660
  and never fabricate an approval or verdict. A question answer is not
15653
- host permission, and imported decisions or handoffs never grant authority.
15661
+ host permission, and imported decisions or handoffs grant no authority.
15654
15662
 
15655
15663
  Autonomy contract:
15656
15664
  - Continue to the requested endpoint (commit, push, PR, green CI, verified,
15657
- merged) without asking "continue?", running checks and repairs on the way.
15665
+ merged) without asking "continue?", running checks and repairs.
15658
15666
  Stop only for a new consequential choice, a host denial, a conflicting edit
15659
15667
  or an unresolved blocker; report open gaps and uncertain workers.
15660
15668
  - No endpoint named: stop at a local commit on a policy-compliant branch and
@@ -15664,7 +15672,8 @@ Autonomy contract:
15664
15672
  never merging; \`merged\` also lands it with \`workit pr merge\`.
15665
15673
  Merge and release need a workspace grant only the user raises from their
15666
15674
  own terminal (a blocked verb names how); never edit workspaces.json or route
15667
- around a grant_required.
15675
+ around grant_required. Release: \`workit release start <v>\`, commits,
15676
+ \`finish\`; \`publish\` if asked.
15668
15677
  - Ask only for a product or preference choice, or for authority you lack, with
15669
15678
  your recommended answer. Facts are yours: read, run or prototype.
15670
15679
  - Label claims measured (run and seen this session), inferred, or guess.
@@ -15679,22 +15688,22 @@ Autonomy contract:
15679
15688
  reconcile every requested item and observe it (\`workit verify-delivery\`).
15680
15689
 
15681
15690
  CLI first: if a step has one right answer, use the \`workit\` verb instead of
15682
- hand-running git, gh or glab (\`workit git|pr|ci|check\`; \`workit help\` lists
15683
- the rest; lacking \`workit\`, \`npx -y @brainervirus/workit-cli\`).
15691
+ hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
15692
+ else \`npx -y @brainervirus/workit-cli\`).
15684
15693
  Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
15685
15694
  an amend.
15686
15695
  The task families (task, policy, evidence, finding, decision, worker, state)
15687
15696
  are optional continuity for tracked work; each branch has one implicit task,
15688
- and a solo edit needs no task. A decision
15689
- records a user choice; it never authorizes an effect. Judge work once with
15697
+ and a solo edit needs no task. A decision records a user choice; it never
15698
+ authorizes an effect. Judge work once with
15690
15699
  \`workit policy assess --judge risk= behavior= product-choice= plan=\`.
15691
- Across repositories, bind each item to its checkout, branch and endpoint,
15692
- resolve competing targets before a mutation, and reconcile an uncertain
15693
- external effect before retrying it.
15700
+ Across repositories, bind items to checkout, branch and endpoint, resolve
15701
+ competing targets before a mutation, and reconcile an uncertain
15702
+ external effect before a retry.
15694
15703
 
15695
15704
  Mid-task: never silently drop or resume an objective (workit-continue).
15696
15705
 
15697
- Skills: load the skill, never act from memory of it (slash aliases /wk-<name>).
15706
+ Skills: load the skill, never act from memory (aliases /wk-<name>).
15698
15707
  - brainstorm, plan, spec, grill, should we: workit-shape
15699
15708
  - implement, build, add a feature: workit-implement
15700
15709
  - review, blast radius: workit-review
@@ -17960,7 +17969,63 @@ var init_session_marker = __esm(() => {
17960
17969
  });
17961
17970
 
17962
17971
  // packages/workit-core/src/hooks/shell-words.ts
17963
- var closingQuote = (command, start, dialect) => {
17972
+ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "", MARKERS, literal2 = (word) => word.replaceAll(OPEN, "{").replaceAll(COMMA, ",").replaceAll(CLOSE, "}"), BRACE_LIMIT = 64, BRACE_PENDING = 512, BRACE_DEPTH = 16, braceGroup = (word) => {
17973
+ for (let start = 0;start < word.length; start++) {
17974
+ if (word[start] !== OPEN)
17975
+ continue;
17976
+ let depth = 0;
17977
+ let end = start;
17978
+ const commas = [];
17979
+ for (;end < word.length; end++) {
17980
+ if (word[end] === OPEN)
17981
+ depth++;
17982
+ else if (word[end] === CLOSE && --depth === 0)
17983
+ break;
17984
+ else if (word[end] === COMMA && depth === 1)
17985
+ commas.push(end);
17986
+ }
17987
+ if (end < word.length && commas.length)
17988
+ return { start, end, commas };
17989
+ }
17990
+ return null;
17991
+ }, expandBraces = (word) => {
17992
+ let depth = 0;
17993
+ for (const char of word) {
17994
+ if (char === OPEN && ++depth > BRACE_DEPTH)
17995
+ return [literal2(word)];
17996
+ if (char === CLOSE && depth > 0)
17997
+ depth--;
17998
+ }
17999
+ const done = new Set;
18000
+ const queue = [word];
18001
+ const queued = new Set(queue);
18002
+ for (let at = 0;at < queue.length; at++) {
18003
+ const current = queue[at];
18004
+ const group = braceGroup(current);
18005
+ if (!group) {
18006
+ const text = literal2(current);
18007
+ if (text)
18008
+ done.add(text);
18009
+ if (done.size > BRACE_LIMIT)
18010
+ return [literal2(word)];
18011
+ continue;
18012
+ }
18013
+ const pre = current.slice(0, group.start);
18014
+ const post = current.slice(group.end + 1);
18015
+ let from = group.start + 1;
18016
+ for (const comma of [...group.commas, group.end]) {
18017
+ const next = pre + current.slice(from, comma) + post;
18018
+ from = comma + 1;
18019
+ if (queued.has(next))
18020
+ continue;
18021
+ queued.add(next);
18022
+ queue.push(next);
18023
+ }
18024
+ if (queue.length - at > BRACE_PENDING)
18025
+ return [literal2(word)];
18026
+ }
18027
+ return [...done];
18028
+ }, closingQuote = (command, start, dialect, nesting = 0) => {
17964
18029
  const quote = command[start];
17965
18030
  for (let index = start + 1;index < command.length; index++) {
17966
18031
  const char = command[index];
@@ -17977,49 +18042,241 @@ var closingQuote = (command, start, dialect) => {
17977
18042
  index++;
17978
18043
  continue;
17979
18044
  }
18045
+ if (dialect === "posix" && char === "$" && command[index + 1] === "(") {
18046
+ index = substitutionEnd(command, index + 1, nesting + 1);
18047
+ continue;
18048
+ }
18049
+ if (dialect === "posix" && char === "`") {
18050
+ index = backtickEnd(command, index);
18051
+ continue;
18052
+ }
17980
18053
  if (char === '"')
17981
18054
  return index;
17982
18055
  }
17983
18056
  return -1;
18057
+ }, backtickEnd = (command, start) => {
18058
+ for (let index = start + 1;index < command.length; index++) {
18059
+ if (command[index] === "\\")
18060
+ index++;
18061
+ else if (command[index] === "`")
18062
+ return index;
18063
+ }
18064
+ return command.length;
18065
+ }, heredocDelimiter = (command, start) => {
18066
+ let index = start;
18067
+ if (command[index] === "-")
18068
+ index++;
18069
+ while (command[index] === " " || command[index] === "\t")
18070
+ index++;
18071
+ let delimiter = "";
18072
+ for (;index < command.length && !/[\s;&|<>()]/.test(command[index]); index++) {
18073
+ const char = command[index];
18074
+ if (char === "'" || char === '"') {
18075
+ const end = command.indexOf(char, index + 1);
18076
+ if (end < 0)
18077
+ break;
18078
+ delimiter += command.slice(index + 1, end);
18079
+ index = end;
18080
+ } else if (char === "\\")
18081
+ delimiter += command[++index] ?? "";
18082
+ else
18083
+ delimiter += char;
18084
+ }
18085
+ return { delimiter, end: index - 1 };
18086
+ }, skipHeredocs = (command, newline, delimiters) => {
18087
+ let index = newline;
18088
+ while (delimiters.length) {
18089
+ const delimiter = delimiters.shift();
18090
+ for (;; ) {
18091
+ const lineEnd = command.indexOf(`
18092
+ `, index + 1);
18093
+ const line = command.slice(index + 1, lineEnd < 0 ? undefined : lineEnd);
18094
+ index = lineEnd < 0 ? command.length : lineEnd;
18095
+ if (line.trim() === delimiter || lineEnd < 0)
18096
+ break;
18097
+ }
18098
+ }
18099
+ return index;
18100
+ }, substitutionEnd = (command, start, nesting = 0) => {
18101
+ if (nesting > MAX_NESTING)
18102
+ throw new ShellAnalysisError("substitutions nest too deep");
18103
+ let depth = 0;
18104
+ let wordStart = true;
18105
+ const heredocs = [];
18106
+ for (let index = start;index < command.length; index++) {
18107
+ const char = command[index];
18108
+ const atWordStart = wordStart;
18109
+ wordStart = /[\s;&|()]/.test(char);
18110
+ if (char === "\\")
18111
+ index++;
18112
+ else if (char === `
18113
+ ` && heredocs.length) {
18114
+ index = skipHeredocs(command, index, heredocs);
18115
+ wordStart = true;
18116
+ } else if (char === "'" || char === '"') {
18117
+ const end = closingQuote(command, index, "posix", nesting);
18118
+ if (end < 0)
18119
+ return command.length;
18120
+ index = end;
18121
+ } else if (char === "`")
18122
+ index = backtickEnd(command, index);
18123
+ else if (char === "#" && atWordStart) {
18124
+ const newline = command.indexOf(`
18125
+ `, index);
18126
+ index = newline < 0 ? command.length : newline - 1;
18127
+ } else if (char === "<" && command[index + 1] === "<" && command[index + 2] !== "<") {
18128
+ const { delimiter, end } = heredocDelimiter(command, index + 2);
18129
+ if (delimiter)
18130
+ heredocs.push(delimiter);
18131
+ index = end;
18132
+ } else if (char === "<" && command[index + 1] === "<")
18133
+ index += 2;
18134
+ else if (char === "$" && command[index + 1] === "(")
18135
+ index = substitutionEnd(command, index + 1, nesting + 1);
18136
+ else if (char === "(")
18137
+ depth++;
18138
+ else if (char === ")" && --depth === 0)
18139
+ return index;
18140
+ }
18141
+ return command.length;
17984
18142
  }, unquote = (body, quote, dialect) => {
17985
18143
  if (quote === "'")
17986
18144
  return dialect === "powershell" ? body.replaceAll("''", "'") : body;
17987
- return dialect === "posix" ? body.replace(/\\(["\\$`])/g, "$1") : body.replace(/`(.)/g, "$1");
18145
+ return dialect === "posix" ? body.replace(/\\\n/g, "").replace(/\\(["\\$`])/g, "$1") : body.replace(/`(.)/g, "$1");
18146
+ }, C_ESCAPES, ansiC = (command, start) => {
18147
+ let text = "";
18148
+ let index = start + 1;
18149
+ for (;index < command.length && command[index] !== "'"; index++) {
18150
+ const char = command[index];
18151
+ if (char !== "\\" || index + 1 >= command.length) {
18152
+ text += char;
18153
+ continue;
18154
+ }
18155
+ const rest = command.slice(++index);
18156
+ const hex = /^x([0-9a-fA-F]{1,2})/.exec(rest);
18157
+ const octal = /^[0-7]{1,3}/.exec(rest);
18158
+ if (hex) {
18159
+ text += String.fromCharCode(Number.parseInt(hex[1], 16));
18160
+ index += hex[0].length - 1;
18161
+ } else if (octal) {
18162
+ text += String.fromCharCode(Number.parseInt(octal[0], 8));
18163
+ index += octal[0].length - 1;
18164
+ } else
18165
+ text += C_ESCAPES[rest[0]] ?? rest[0];
18166
+ }
18167
+ return { text, end: index };
18168
+ }, substitutions = (body) => {
18169
+ const found = [];
18170
+ for (let index = 0;index < body.length; index++) {
18171
+ const char = body[index];
18172
+ if (char === "\\")
18173
+ index++;
18174
+ else if (char === "`") {
18175
+ const end = backtickEnd(body, index);
18176
+ found.push(body.slice(index + 1, end).replace(/\\(.)/g, "$1"));
18177
+ index = end;
18178
+ } else if (char === "$" && body[index + 1] === "(") {
18179
+ const end = substitutionEnd(body, index + 1);
18180
+ found.push(body.slice(index + 2, end));
18181
+ index = end;
18182
+ }
18183
+ }
18184
+ return found;
18185
+ }, PLAIN_POSIX, PLAIN_POWERSHELL, arithmeticEnd = (command, start) => {
18186
+ const end = substitutionEnd(command, start);
18187
+ return command[end - 1] === ")" && end > start + 2 ? end : 0;
17988
18188
  }, segmentsOf = (command, dialect = "posix") => {
18189
+ try {
18190
+ return tokenize(command, dialect);
18191
+ } catch (error) {
18192
+ const why = error instanceof ShellAnalysisError ? error.message : String(error);
18193
+ return [{ words: [], redirects: [], unanalyzable: why }];
18194
+ }
18195
+ }, tokenize = (command, dialect) => {
18196
+ const posix = dialect === "posix";
17989
18197
  const segments = [];
17990
18198
  let current = { words: [], redirects: [] };
17991
18199
  let word = "";
17992
18200
  let inWord = false;
17993
18201
  let pending = null;
17994
18202
  const heredocs = [];
17995
- const escape = dialect === "posix" ? "\\" : "`";
18203
+ const escape = posix ? "\\" : "`";
17996
18204
  const flushWord = () => {
17997
18205
  if (!inWord)
17998
18206
  return;
18207
+ const text = MARKERS.test(word) ? literal2(word) : word;
17999
18208
  if (pending === "redirect") {
18000
- if (!word.startsWith("&"))
18001
- current.redirects.push(word);
18209
+ if (!text.startsWith("&"))
18210
+ current.redirects.push(text);
18002
18211
  } else if (pending === "heredoc")
18003
- heredocs.push(word.replace(/^-/, ""));
18212
+ heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
18213
+ else if (pending === "herestring")
18214
+ (current.stdin ??= []).push(text);
18004
18215
  else if (pending !== "input")
18005
- current.words.push(word);
18216
+ current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
18006
18217
  pending = null;
18007
18218
  word = "";
18008
18219
  inWord = false;
18009
18220
  };
18010
18221
  const flushSegment = () => {
18011
18222
  flushWord();
18012
- if (current.words.length || current.redirects.length)
18223
+ if (current.words.length || current.redirects.length || current.stdin || current.subs)
18013
18224
  segments.push(current);
18014
18225
  current = { words: [], redirects: [] };
18015
18226
  };
18227
+ const group = (kind) => {
18228
+ flushSegment();
18229
+ pending = null;
18230
+ segments.push({ words: [], redirects: [], group: kind });
18231
+ };
18232
+ const substitution = (inner, text) => {
18233
+ (current.subs ??= []).push(inner);
18234
+ word += text;
18235
+ inWord = true;
18236
+ };
18237
+ const plain = posix ? PLAIN_POSIX : PLAIN_POWERSHELL;
18016
18238
  for (let index = 0;index < command.length; index++) {
18239
+ plain.lastIndex = index;
18240
+ const run = plain.exec(command);
18241
+ if (run) {
18242
+ word += run[0];
18243
+ inWord = true;
18244
+ index += run[0].length - 1;
18245
+ continue;
18246
+ }
18017
18247
  const char = command[index];
18018
- if (char === "'" || char === '"') {
18248
+ const next = command[index + 1];
18249
+ if (char === escape && (next === `
18250
+ ` || next === "\r" && command[index + 2] === `
18251
+ `))
18252
+ index += next === "\r" ? 2 : 1;
18253
+ else if (char === "#" && !inWord) {
18254
+ const newline = command.indexOf(`
18255
+ `, index);
18256
+ index = (newline < 0 ? command.length : newline) - 1;
18257
+ } else if (!posix && char === "<" && next === "#") {
18258
+ const end = command.indexOf("#>", index + 2);
18259
+ index = end < 0 ? command.length : end + 1;
18260
+ } else if (posix && char === "$" && next === "'") {
18261
+ const { text, end } = ansiC(command, index + 1);
18262
+ word += text;
18263
+ inWord = true;
18264
+ index = end;
18265
+ } else if (posix && char === "$" && next === '"')
18266
+ continue;
18267
+ else if (char === "'" || char === '"') {
18019
18268
  const end = closingQuote(command, index, dialect);
18020
- word += unquote(command.slice(index + 1, end < 0 ? undefined : end), char, dialect);
18269
+ const body = command.slice(index + 1, end < 0 ? undefined : end);
18270
+ if (posix && char === '"' && /\$\(|`/.test(body))
18271
+ (current.subs ??= []).push(...substitutions(body));
18272
+ word += unquote(body, char, dialect);
18021
18273
  inWord = true;
18022
18274
  index = end < 0 ? command.length : end;
18275
+ } else if (posix && char === "`") {
18276
+ const end = backtickEnd(command, index);
18277
+ const inner = command.slice(index + 1, end);
18278
+ substitution(inner.replace(/\\(.)/g, "$1"), `\`${inner}\``);
18279
+ index = end;
18023
18280
  } else if (char === escape && index + 1 < command.length) {
18024
18281
  word += command[++index];
18025
18282
  inWord = true;
@@ -18027,26 +18284,56 @@ var closingQuote = (command, start, dialect) => {
18027
18284
  `) {
18028
18285
  flushSegment();
18029
18286
  while (heredocs.length) {
18030
- const delimiter = heredocs.shift();
18287
+ const { delimiter, owner } = heredocs.shift();
18031
18288
  const lines = command.slice(index + 1).split(`
18032
18289
  `);
18033
18290
  const at = lines.findIndex((line) => line.trim() === delimiter);
18034
- const skipped = (at < 0 ? lines : lines.slice(0, at + 1)).join(`
18291
+ (owner.stdin ??= []).push((at < 0 ? lines : lines.slice(0, at)).join(`
18292
+ `));
18293
+ index += (at < 0 ? lines : lines.slice(0, at + 1)).join(`
18035
18294
  `).length + 1;
18036
- index += skipped;
18037
18295
  }
18038
18296
  } else if (/\s/.test(char))
18039
18297
  flushWord();
18040
- else if (char === "(" || char === ")") {
18298
+ else if (posix && char === "(" && inWord && word.endsWith("$")) {
18299
+ const end = substitutionEnd(command, index);
18300
+ const inner = command.slice(index + 1, end);
18301
+ substitution(inner, `(${inner})`);
18302
+ index = end;
18303
+ } else if (posix && char === "(" && (pending === "input" || pending === "redirect")) {
18304
+ const sign = pending === "input" ? "<" : ">";
18305
+ pending = null;
18306
+ const end = substitutionEnd(command, index);
18307
+ const inner = command.slice(index + 1, end);
18308
+ substitution(inner, `${sign}(${inner})`);
18309
+ index = end;
18310
+ } else if (posix && char === "(" && next === "(" && !inWord && arithmeticEnd(command, index)) {
18311
+ flushSegment();
18312
+ index = arithmeticEnd(command, index);
18313
+ } else if (char === "(" || char === ")") {
18041
18314
  if (char === "(" && inWord && word.endsWith("$")) {
18042
18315
  word = word.slice(0, -1);
18043
18316
  inWord = word !== "";
18044
18317
  }
18318
+ group(char === "(" ? "open" : "close");
18319
+ } else if (!posix && (char === "{" || char === "}")) {
18320
+ if (char === "{" && inWord && word.endsWith("@")) {
18321
+ word += char;
18322
+ continue;
18323
+ }
18045
18324
  flushSegment();
18046
- segments.push({ words: [], redirects: [], group: char === "(" ? "open" : "close" });
18047
18325
  } else if (";&|".includes(char)) {
18048
- if (char === "&" && command[index + 1] === ">")
18326
+ if (char === "&" && next === ">")
18049
18327
  continue;
18328
+ if (char === "|" && next === "|")
18329
+ index++;
18330
+ else if (char === "|") {
18331
+ flushWord();
18332
+ if (current.words.length)
18333
+ current.pipe = true;
18334
+ if (next === "&")
18335
+ index++;
18336
+ }
18050
18337
  flushSegment();
18051
18338
  } else if (char === ">") {
18052
18339
  if (inWord && /^\d$/.test(word)) {
@@ -18062,16 +18349,24 @@ var closingQuote = (command, start, dialect) => {
18062
18349
  inWord = true;
18063
18350
  }
18064
18351
  pending = "redirect";
18065
- } else if (char === "<" && dialect === "posix") {
18352
+ } else if (char === "<" && posix) {
18066
18353
  flushWord();
18067
- if (command[index + 1] === "<") {
18354
+ if (next === "<" && command[index + 2] === "<") {
18355
+ index += 2;
18356
+ pending = "herestring";
18357
+ } else if (next === "<") {
18068
18358
  index++;
18069
- if (command[index + 1] === "<")
18070
- index++;
18071
18359
  pending = "heredoc";
18072
18360
  } else
18073
18361
  pending = "input";
18074
- } else {
18362
+ } else if (posix && char === "{" && !word.endsWith("$")) {
18363
+ word += OPEN;
18364
+ inWord = true;
18365
+ } else if (posix && char === "," && word.includes(OPEN))
18366
+ word += COMMA;
18367
+ else if (posix && char === "}" && word.includes(OPEN))
18368
+ word += CLOSE;
18369
+ else {
18075
18370
  word += char;
18076
18371
  inWord = true;
18077
18372
  }
@@ -18079,6 +18374,25 @@ var closingQuote = (command, start, dialect) => {
18079
18374
  flushSegment();
18080
18375
  return segments;
18081
18376
  };
18377
+ var init_shell_words = __esm(() => {
18378
+ ShellAnalysisError = class ShellAnalysisError extends Error {
18379
+ };
18380
+ MARKERS = /[\uE000-\uE002]/u;
18381
+ C_ESCAPES = {
18382
+ n: `
18383
+ `,
18384
+ t: "\t",
18385
+ r: "\r",
18386
+ a: "\x07",
18387
+ b: "\b",
18388
+ e: "\x1B",
18389
+ E: "\x1B",
18390
+ f: "\f",
18391
+ v: "\v"
18392
+ };
18393
+ PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
18394
+ PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
18395
+ });
18082
18396
 
18083
18397
  // packages/workit-core/src/hooks/raw-git.ts
18084
18398
  import { spawnSync as spawnSync11 } from "node:child_process";
@@ -18087,67 +18401,124 @@ import fs16 from "node:fs";
18087
18401
  import os5 from "node:os";
18088
18402
  import path20 from "node:path";
18089
18403
  function rawInvocations(command, dialect = "posix", depth = 0) {
18404
+ if (depth > 0)
18405
+ return walk2(command, dialect, depth);
18406
+ const key = `${dialect}\x00${command}`;
18407
+ const cached = RECENT.get(key);
18408
+ if (cached)
18409
+ return cached;
18410
+ let found;
18411
+ try {
18412
+ found = walk2(command, dialect, depth);
18413
+ } catch (error) {
18414
+ throw error instanceof ShellAnalysisError ? error : new ShellAnalysisError(error instanceof Error ? error.message : String(error));
18415
+ }
18416
+ RECENT.set(key, found);
18417
+ if (RECENT.size > 8)
18418
+ RECENT.delete(RECENT.keys().next().value);
18419
+ return found;
18420
+ }
18421
+ function walk2(command, dialect, depth) {
18090
18422
  const found = [];
18091
18423
  let dir = null;
18092
18424
  const groups = [];
18093
18425
  let stack = [];
18094
- for (const segment of segmentsOf(command, dialect)) {
18095
- if (segment.group === "open") {
18096
- groups.push({ dir, stack: [...stack] });
18097
- continue;
18098
- }
18099
- if (segment.group === "close") {
18100
- const saved = groups.pop();
18101
- if (saved)
18102
- ({ dir, stack } = saved);
18103
- continue;
18104
- }
18105
- const words = stripPrefixes(segment.words);
18426
+ const vars = new Map;
18427
+ const exported = {};
18428
+ const sessionConfig = [];
18429
+ const nested = (script, inner) => {
18430
+ if (!script || depth >= MAX_DEPTH)
18431
+ return;
18432
+ for (const invocation of rawInvocations(script, inner, depth + 1))
18433
+ found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
18434
+ };
18435
+ const visit = (input, stdin, hops) => {
18436
+ const line = /^\$[\w:]+$/.test(input[0] ?? "") && input[1] === "=" ? input.slice(2) : input;
18437
+ const split = splitString(line);
18438
+ if (split !== null)
18439
+ return nested(split, "posix");
18440
+ const { words, env } = stripPrefixes(line);
18106
18441
  const name = commandName(words[0]);
18442
+ if (!name)
18443
+ return;
18444
+ const runs = (lines) => {
18445
+ if (hops < MAX_DEPTH)
18446
+ for (const next of lines)
18447
+ visit(next, undefined, hops + 1);
18448
+ };
18107
18449
  if (CD.has(name)) {
18108
18450
  const target = cdTarget(words);
18109
18451
  dir = target === null ? os5.homedir() : joinDir(dir, target);
18110
- continue;
18111
- }
18112
- if (PUSHD.has(name)) {
18452
+ } else if (PUSHD.has(name)) {
18113
18453
  const target = cdTarget(words);
18114
18454
  stack.push(dir);
18115
18455
  dir = target === null || target === "-" ? undefined : joinDir(dir, target);
18116
- continue;
18117
- }
18118
- if (POPD.has(name)) {
18456
+ } else if (POPD.has(name))
18119
18457
  dir = stack.length ? stack.pop() : undefined;
18120
- continue;
18458
+ else if (EVALS.has(name)) {
18459
+ const args = /^-c(?:o(?:m(?:m(?:a(?:n(?:d)?)?)?)?)?)?$/i.test(words[1] ?? "") ? words.slice(2) : words.slice(1);
18460
+ nested(args.join(" "), dialect);
18461
+ } else if (SHELLS.has(name))
18462
+ nested(shellScript(words, stdin), "posix");
18463
+ else if ((name === "source" || name === ".") && dialect === "posix")
18464
+ nested(processSubstitutionText(words[1]), "posix");
18465
+ else if (name === "trap" && dialect === "posix" && !/^-[lp]$/.test(words[1] ?? ""))
18466
+ nested(words[1], "posix");
18467
+ else if (name === "script")
18468
+ nested(scriptCommand(words), "posix");
18469
+ else if (name === "parallel")
18470
+ runs(parallelCommands(words));
18471
+ else if (dialect === "powershell" && /^(?:start-process|saps|start)$/.test(name)) {
18472
+ const started = startProcessCommand(words);
18473
+ if (started)
18474
+ runs([started]);
18475
+ } else if (POWERSHELLS.has(name))
18476
+ nested(powershellScript(words, stdin), "powershell");
18477
+ else if (name === "cmd")
18478
+ nested(cmdScript(words), "powershell");
18479
+ else if (name === "xargs" || name === "find")
18480
+ runs(name === "xargs" ? xargsCommands(words, stdin) : findCommands(words));
18481
+ else if (name === "gh" || name === "glab")
18482
+ found.push({
18483
+ tool: name,
18484
+ dir,
18485
+ args: words.slice(1),
18486
+ ...stdin === undefined ? {} : { stdin }
18487
+ });
18488
+ else if (name === "git") {
18489
+ const invocation = gitInvocation(words.slice(1), dir, { ...exported, ...env }, stdin, sessionConfig);
18490
+ found.push(invocation);
18491
+ sessionConfig.push(...configSetBy(invocation.args));
18121
18492
  }
18122
- if (SHELLS.has(name) && depth < 3) {
18123
- const flag = words.findIndex((word, index) => index > 0 && SCRIPT_FLAG.test(word));
18124
- const script = flag > 0 ? words[flag + 1] : undefined;
18125
- if (script)
18126
- for (const inner of rawInvocations(script, "posix", depth + 1))
18127
- found.push({
18128
- ...inner,
18129
- dir: inner.dir === null ? dir : inner.dir === undefined ? undefined : joinDir(dir, inner.dir)
18130
- });
18493
+ };
18494
+ const segments = segmentsOf(command, dialect);
18495
+ for (const [index, segment] of segments.entries()) {
18496
+ if (segment.group === "open") {
18497
+ groups.push({ dir, stack: [...stack] });
18131
18498
  continue;
18132
18499
  }
18133
- if (name === "gh" || name === "glab") {
18134
- found.push({ tool: name, dir, args: words.slice(1) });
18500
+ if (segment.group === "close") {
18501
+ const saved = groups.pop();
18502
+ if (saved)
18503
+ ({ dir, stack } = saved);
18135
18504
  continue;
18136
18505
  }
18137
- if (name !== "git")
18506
+ if (segment.unanalyzable)
18507
+ throw new ShellAnalysisError(segment.unanalyzable);
18508
+ for (const sub of segment.subs ?? [])
18509
+ nested(sub, "posix");
18510
+ const set = assignments(segment.words, vars);
18511
+ if (set) {
18512
+ for (const [name, values] of set) {
18513
+ vars.set(name, values);
18514
+ if (segment.words[0] === "export" && GIT_CONFIG_ENV.test(name) && values[0] !== undefined)
18515
+ exported[name] = values[0];
18516
+ }
18138
18517
  continue;
18139
- let here = dir;
18140
- let index = 1;
18141
- for (;index < words.length; index++) {
18142
- const word = words[index];
18143
- if (word === "-C")
18144
- here = words[index + 1] ? joinDir(here, words[++index]) : here;
18145
- else if (GIT_VALUE_OPTIONS.has(word))
18146
- index++;
18147
- else if (!word.startsWith("-"))
18148
- break;
18149
18518
  }
18150
- found.push({ tool: "git", dir: here, args: words.slice(index) });
18519
+ const stdin = stdinOf(segment, segments[index - 1]);
18520
+ for (const line of substitute(segment.words, vars))
18521
+ visit(line, stdin, 0);
18151
18522
  }
18152
18523
  return found;
18153
18524
  }
@@ -18170,20 +18541,40 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
18170
18541
  }
18171
18542
  function rawAction(invocation) {
18172
18543
  const args = invocation.args;
18544
+ if (invocation.tool !== "git" && args[0] === "api")
18545
+ return apiMerges(invocation.tool, args, invocation.stdin) ? { kind: "merge", forge: invocation.tool, via: "api" } : null;
18173
18546
  if (asksHelp(invocation))
18174
18547
  return null;
18175
- const [first, second] = args;
18548
+ const [first] = args;
18176
18549
  if (invocation.tool === "git") {
18550
+ const second = args[1];
18177
18551
  if (first === "commit")
18178
18552
  return args.includes("--dry-run") ? null : { kind: "commit", amend: args.includes("--amend") };
18179
18553
  if (first === "push")
18180
18554
  return pushAction(args);
18555
+ if (first === "send-pack")
18556
+ return pushAction([
18557
+ "push",
18558
+ ...args.slice(1).filter((arg) => arg !== "--stdin"),
18559
+ ...args.includes("--stdin") ? (invocation.stdin ?? "").split(/\s+/).filter(Boolean) : []
18560
+ ]);
18561
+ if (first === "subtree" && second === "push")
18562
+ return subtreePush(args);
18181
18563
  return null;
18182
18564
  }
18183
18565
  const forge = invocation.tool;
18184
18566
  const noun = forge === "gh" ? "pr" : "mr";
18185
18567
  if (first !== noun)
18186
18568
  return null;
18569
+ const values = VALUE_OPTIONS[forge];
18570
+ let second;
18571
+ for (let index = 1;index < args.length && second === undefined; index++) {
18572
+ const arg = args[index];
18573
+ if (values.has(arg))
18574
+ index++;
18575
+ else if (!arg.startsWith("-"))
18576
+ second = arg;
18577
+ }
18187
18578
  if (second === "merge")
18188
18579
  return { kind: "merge", forge };
18189
18580
  if (second === "create")
@@ -18194,31 +18585,18 @@ function rawAction(invocation) {
18194
18585
  }
18195
18586
  function rawGitPre(input, command, options = {}) {
18196
18587
  try {
18197
- if (!mentionsRawTool(command))
18198
- return NONE2;
18199
18588
  const dialect = dialectOf(input);
18200
18589
  const nudges = new Map;
18201
18590
  let store = null;
18202
- for (const { action, repo } of actionsIn(input.cwd, command, dialect)) {
18591
+ const cache = new Map;
18592
+ for (const { action, repo, invocation } of actionsIn(input.cwd, command, dialect, cache)) {
18203
18593
  store ??= repo.location.dir;
18204
18594
  if (action.kind === "merge")
18205
18595
  return deny(refusal(action, null));
18206
- if (action.kind === "push" && toPushRemote(repo, action.remote)) {
18207
- if (action.mirror)
18208
- 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")}.`);
18209
- for (const target of action.targets) {
18210
- const branch = target ?? repo.branch;
18211
- if (branch && guardedBranch(repo.dir, branch))
18212
- return deny(refusal(action, branch, repo.dir));
18213
- }
18214
- const remote = action.remote ?? pushRemoteName(repo.dir, repo.branch) ?? "origin";
18215
- const facts = refFacts(repo, remote);
18216
- const tagDeny = tagRefusal(action, facts);
18217
- if (tagDeny)
18218
- return deny(tagDeny);
18219
- const nameDeny = action.deletes ? null : offPolicyPush(action, repo, facts, remote);
18220
- if (nameDeny)
18221
- return deny(nameDeny);
18596
+ if (action.kind === "push") {
18597
+ const refused = pushRefusal(action, repo, gitConfig(repo.dir, invocation, cache));
18598
+ if (refused)
18599
+ return deny(refused);
18222
18600
  }
18223
18601
  const nudge = nudgeFor(action, input.host, input.session.id);
18224
18602
  if (nudge && !nudges.has(nudgeKind(action)))
@@ -18231,8 +18609,8 @@ function rawGitPre(input, command, options = {}) {
18231
18609
  const shown = unseenNudges(input, nudges, store);
18232
18610
  return shown.length ? { kind: "context", text: shown.join(`
18233
18611
  `) } : NONE2;
18234
- } catch {
18235
- return NONE2;
18612
+ } catch (error) {
18613
+ return error instanceof ShellAnalysisError ? analysisDenial(error) : NONE2;
18236
18614
  }
18237
18615
  }
18238
18616
  function noteRawCommit(input, command, key) {
@@ -18271,12 +18649,13 @@ function settlePendingCommit(input) {
18271
18649
  settle2(input, repo, file);
18272
18650
  } catch {}
18273
18651
  }
18274
- var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\"), joinDir = (base, next) => {
18652
+ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLARATIONS, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, GIT_CONFIG_OPTIONS, GIT_CONFIG_ENV, ASSIGNMENT, MAX_DEPTH = 4, MAX_EXPANSIONS = 16, isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\"), HERE, joinDir = (base, raw) => {
18653
+ const next = raw.replace(HERE, ".");
18275
18654
  if (base === undefined || /[$`*?]/.test(next) || next === "-")
18276
18655
  return;
18277
18656
  const expanded = next === "~" || next.startsWith("~/") ? path20.join(os5.homedir(), next.slice(1)) : next;
18278
18657
  return base === null || isAbsolute(expanded) ? expanded : path20.join(base, expanded);
18279
- }, cdTarget = (words) => {
18658
+ }, rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner), cdTarget = (words) => {
18280
18659
  const args = words.slice(1);
18281
18660
  const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
18282
18661
  if (flag >= 0)
@@ -18284,25 +18663,328 @@ var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
18284
18663
  return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
18285
18664
  }, stripPrefixes = (input) => {
18286
18665
  let words = input;
18666
+ const env = {};
18287
18667
  for (;; ) {
18288
18668
  const first = words[0];
18289
18669
  if (first === undefined)
18290
- return words;
18291
- if (/^\w+=/.test(first))
18670
+ return { words, env };
18671
+ const assignment = ASSIGNMENT.exec(first);
18672
+ if (assignment) {
18673
+ if (GIT_CONFIG_ENV.test(assignment[1]))
18674
+ env[assignment[1]] = assignment[2];
18292
18675
  words = words.slice(1);
18293
- else if (PREFIXES2.has(first)) {
18676
+ } else if (KEYWORDS.has(first))
18294
18677
  words = words.slice(1);
18295
- while (words[0]?.startsWith("-"))
18296
- words = words.slice(words[0] === "-u" ? 2 : 1);
18297
- } else if (first === "timeout") {
18678
+ else if (first === "function")
18679
+ words = words.slice(2);
18680
+ else if (Object.hasOwn(WRAPPERS, commandName(first))) {
18681
+ const name = commandName(first);
18682
+ const values = WRAPPER_VALUES.get(name);
18298
18683
  words = words.slice(1);
18299
- while (words[0]?.startsWith("-"))
18684
+ const takesValue = (option) => values.has(option) || /^-[a-zA-Z]{2,}$/.test(option) && values.has(`-${option.at(-1)}`);
18685
+ while (words[0]?.startsWith("-") && words[0] !== "--")
18686
+ words = words.slice(takesValue(words[0]) ? 2 : 1);
18687
+ if (words[0] === "--")
18300
18688
  words = words.slice(1);
18301
- words = words.slice(1);
18689
+ words = words.slice(WRAPPERS[name].operands ?? 0);
18302
18690
  } else
18303
- return words;
18691
+ return { words, env };
18692
+ }
18693
+ }, commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, ""), VARIABLE, expandWord = (word, vars) => {
18694
+ if (!word.includes("$"))
18695
+ return [word];
18696
+ let results = [""];
18697
+ let last = 0;
18698
+ for (const match of word.matchAll(VARIABLE)) {
18699
+ const values = vars.get(match[1] ?? match[2]) ?? [match[0]];
18700
+ const before = word.slice(last, match.index);
18701
+ results = results.flatMap((result) => values.map((value) => result + before + value)).slice(0, MAX_EXPANSIONS);
18702
+ last = match.index + match[0].length;
18703
+ }
18704
+ return results.map((result) => result + word.slice(last));
18705
+ }, substitute = (words, vars) => {
18706
+ if (vars.size === 0)
18707
+ return [words];
18708
+ let lines = [[]];
18709
+ for (const word of words) {
18710
+ const values = expandWord(word, vars);
18711
+ lines = lines.flatMap((line) => values.map((value) => [...line, value])).slice(0, MAX_EXPANSIONS);
18712
+ }
18713
+ return lines;
18714
+ }, assignments = (words, vars) => {
18715
+ if (words[0] === "for" && words[2] === "in" && /^\w+$/.test(words[1] ?? "")) {
18716
+ const values = words.slice(3).flatMap((word) => expandWord(word, vars));
18717
+ return [[words[1], values.slice(0, MAX_EXPANSIONS)]];
18718
+ }
18719
+ let rest = words;
18720
+ if (DECLARATIONS.has(rest[0] ?? "")) {
18721
+ rest = rest.slice(1);
18722
+ while (rest[0]?.startsWith("-"))
18723
+ rest = rest.slice(1);
18724
+ }
18725
+ if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
18726
+ return null;
18727
+ return rest.map((word) => {
18728
+ const [, name, value] = ASSIGNMENT.exec(word);
18729
+ return [name, expandWord(value, vars)];
18730
+ });
18731
+ }, literalOutput = (segment) => {
18732
+ if (!segment?.pipe)
18733
+ return;
18734
+ const { words } = stripPrefixes(segment.words);
18735
+ const name = commandName(words[0]);
18736
+ if (name === "echo") {
18737
+ let args = words.slice(1);
18738
+ while (/^-[neE]+$/.test(args[0] ?? ""))
18739
+ args = args.slice(1);
18740
+ return args.join(" ");
18741
+ }
18742
+ if (name === "printf") {
18743
+ const [format = "", ...args] = words.slice(1);
18744
+ return (args.length ? args.join(`
18745
+ `) : format).replace(/\\n/g, `
18746
+ `);
18747
+ }
18748
+ if (name === "cat" && words.length === 1 && segment.stdin)
18749
+ return segment.stdin.join(`
18750
+ `);
18751
+ return;
18752
+ }, stdinOf = (segment, previous) => segment.stdin ? segment.stdin.join(`
18753
+ `) : literalOutput(previous), XARGS_VALUES, xargsCommands = (words, input) => {
18754
+ let replace = null;
18755
+ let index = 1;
18756
+ for (;index < words.length; index++) {
18757
+ const word = words[index];
18758
+ if (word === "--") {
18759
+ index++;
18760
+ break;
18761
+ }
18762
+ if (!word.startsWith("-"))
18763
+ break;
18764
+ if (word === "-I")
18765
+ replace = words[++index] ?? "{}";
18766
+ else if (word === "--replace" || word === "-i")
18767
+ replace = "{}";
18768
+ else if (word.startsWith("--replace="))
18769
+ replace = word.slice("--replace=".length);
18770
+ else if (/^-[Ii]./.test(word))
18771
+ replace = word.slice(2);
18772
+ else if (XARGS_VALUES.has(word))
18773
+ index++;
18774
+ }
18775
+ const command = words.slice(index);
18776
+ if (command.length === 0)
18777
+ return [];
18778
+ if (input === undefined)
18779
+ return [command];
18780
+ if (replace === null)
18781
+ return [[...command, ...input.split(/\s+/).filter(Boolean)]];
18782
+ const token = replace;
18783
+ return input.split(`
18784
+ `).map((line) => line.trim()).filter(Boolean).slice(0, MAX_EXPANSIONS).map((line) => command.map((word) => word.replaceAll(token, line)));
18785
+ }, findCommands = (words) => {
18786
+ const found = [];
18787
+ for (let index = 1;index < words.length; index++) {
18788
+ if (!/^-(?:exec|execdir|ok|okdir)$/.test(words[index]))
18789
+ continue;
18790
+ const end = words.findIndex((word, at) => at > index && (word === ";" || word === "+"));
18791
+ found.push(words.slice(index + 1, end < 0 ? undefined : end));
18792
+ if (end < 0)
18793
+ break;
18794
+ index = end;
18795
+ }
18796
+ return found;
18797
+ }, POWERSHELL_VALUES, powershellScript = (words, stdin) => {
18798
+ for (let index = 1;index < words.length; index++) {
18799
+ const word = words[index];
18800
+ if (!/^[-/]/.test(word))
18801
+ return null;
18802
+ const flag = word.replace(/^[-/]+/, "").toLowerCase();
18803
+ if (!flag)
18804
+ return null;
18805
+ if (flag === "e" || flag === "ec" || flag.length >= 2 && "encodedcommand".startsWith(flag)) {
18806
+ try {
18807
+ return Buffer.from(words[index + 1] ?? "", "base64").toString("utf16le");
18808
+ } catch {
18809
+ return null;
18810
+ }
18811
+ }
18812
+ if (flag === "cwa" || flag.length >= 8 && "commandwithargs".startsWith(flag))
18813
+ return words[index + 1] ?? null;
18814
+ if ("command".startsWith(flag))
18815
+ return words[index + 1] === "-" ? stdin ?? null : words.slice(index + 1).join(" ");
18816
+ if (POWERSHELL_VALUES.test(`-${flag}`)) {
18817
+ if (flag === "f" || flag === "file")
18818
+ return null;
18819
+ index++;
18820
+ }
18821
+ }
18822
+ return null;
18823
+ }, cmdScript = (words) => {
18824
+ const at = words.findIndex((word, index) => index > 0 && /^\/[ck]$/i.test(word));
18825
+ if (at > 0)
18826
+ return words.slice(at + 1).join(" ");
18827
+ const attached = words.find((word, index) => index > 0 && /^\/[ck]./i.test(word));
18828
+ return attached ? [attached.slice(2), ...words.slice(words.indexOf(attached) + 1)].join(" ") : null;
18829
+ }, processSubstitutionText = (word) => {
18830
+ const inner = /^<\(([\s\S]*)\)$/.exec(word ?? "")?.[1];
18831
+ if (inner === undefined)
18832
+ return;
18833
+ const segments = segmentsOf(inner);
18834
+ return segments.length === 1 ? literalOutput({ ...segments[0], pipe: true }) : undefined;
18835
+ }, shellScript = (words, stdin) => {
18836
+ const flag = words.findIndex((word, index) => index > 0 && SCRIPT_FLAG.test(word));
18837
+ if (flag > 0)
18838
+ return words[flag + 1] === "--" ? words[flag + 2] : words[flag + 1];
18839
+ const operand = words.slice(1).find((word) => !word.startsWith("-"));
18840
+ if (operand !== undefined)
18841
+ return processSubstitutionText(operand);
18842
+ return stdin;
18843
+ }, scriptCommand = (words) => {
18844
+ for (let index = 1;index < words.length; index++) {
18845
+ const word = words[index];
18846
+ if (word === "--command" || /^-[a-zA-Z]*c$/.test(word))
18847
+ return words[index + 1];
18848
+ if (word.startsWith("--command="))
18849
+ return word.slice("--command=".length);
18850
+ }
18851
+ return;
18852
+ }, PARALLEL_VALUES, parallelCommands = (words) => {
18853
+ let index = 1;
18854
+ while (index < words.length && words[index].startsWith("-"))
18855
+ index += PARALLEL_VALUES.has(words[index]) ? 2 : 1;
18856
+ const separator = words.indexOf(":::", index);
18857
+ const command = words.slice(index, separator < 0 ? undefined : separator);
18858
+ if (separator < 0)
18859
+ return command.length ? [command] : [];
18860
+ 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]);
18861
+ }, START_PROCESS_VALUES, startProcessCommand = (words) => {
18862
+ let file;
18863
+ let args;
18864
+ const positionals = [];
18865
+ for (let index = 1;index < words.length; index++) {
18866
+ const word = words[index];
18867
+ const flag = word.startsWith("-") ? word.slice(1).toLowerCase() : null;
18868
+ if (flag === null)
18869
+ positionals.push(word);
18870
+ else if (flag && "filepath".startsWith(flag))
18871
+ file = words[++index];
18872
+ else if (flag && (flag === "args" || "argumentlist".startsWith(flag)))
18873
+ args = words[++index];
18874
+ else if (START_PROCESS_VALUES.test(word))
18875
+ index++;
18876
+ }
18877
+ file ??= positionals.shift();
18878
+ args ??= positionals.shift();
18879
+ if (!file)
18880
+ return null;
18881
+ return [file, ...(args ?? "").split(/[\s,]+/).filter(Boolean)];
18882
+ }, splitString = (line) => {
18883
+ if (commandName(line[0]) !== "env")
18884
+ return null;
18885
+ for (let index = 1;index < line.length; index++) {
18886
+ const word = line[index];
18887
+ if (word === "-S" || word === "--split-string")
18888
+ return line.slice(index + 1).join(" ");
18889
+ if (word.startsWith("--split-string="))
18890
+ return line.slice(index).join(" ").slice(15);
18891
+ if (/^-S./.test(word))
18892
+ return [word.slice(2), ...line.slice(index + 1)].join(" ");
18893
+ if (!word.startsWith("-") && !ASSIGNMENT.test(word))
18894
+ return null;
18304
18895
  }
18305
- }, commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, ""), LAUNCHERS, WORKIT_BIN, PUSH_VALUE_OPTIONS, pushAction = (args) => {
18896
+ return null;
18897
+ }, CONFIG_READS, configSetBy = (args) => {
18898
+ const positionals = args.filter((arg, index) => index > 0 && !arg.startsWith("-"));
18899
+ if (args[0] === "remote" && (positionals[0] === "add" || positionals[0] === "set-url")) {
18900
+ const [, name, url] = positionals;
18901
+ const key = args.includes("--push") ? "pushurl" : "url";
18902
+ return name && url ? ["-c", `remote.${name}.${key}=${url}`] : [];
18903
+ }
18904
+ if (args[0] !== "config" || args.some((arg) => CONFIG_READS.test(arg)))
18905
+ return [];
18906
+ const [key, value] = positionals[0] === "set" ? positionals.slice(1) : positionals;
18907
+ return key?.includes(".") && value !== undefined && positionals.length <= 3 ? ["-c", `${key}=${value}`] : [];
18908
+ }, gitInvocation = (words, dir, env = {}, stdin, inherited = []) => {
18909
+ let here = dir;
18910
+ const config = [...inherited];
18911
+ let gitDir = env.GIT_DIR;
18912
+ let index = 0;
18913
+ for (;index < words.length; index++) {
18914
+ const word = words[index];
18915
+ if (word === "-C")
18916
+ here = words[index + 1] ? joinDir(here, words[++index]) : here;
18917
+ else if (GIT_CONFIG_OPTIONS.has(word)) {
18918
+ if (word === "--git-dir")
18919
+ gitDir = words[index + 1];
18920
+ if (words[index + 1] !== undefined)
18921
+ config.push(word, words[++index]);
18922
+ } else if (/^--(?:config-env|git-dir)=/.test(word)) {
18923
+ if (word.startsWith("--git-dir="))
18924
+ gitDir = word.slice("--git-dir=".length);
18925
+ config.push(word);
18926
+ } else if (GIT_VALUE_OPTIONS.has(word))
18927
+ index++;
18928
+ else if (!word.startsWith("-"))
18929
+ break;
18930
+ }
18931
+ if (gitDir)
18932
+ here = joinDir(here, gitDir.replace(/(?:^|[\\/]+)\.git[\\/]*$/, "") || ".");
18933
+ return {
18934
+ tool: "git",
18935
+ dir: here,
18936
+ args: words.slice(index),
18937
+ ...config.length ? { config } : {},
18938
+ ...Object.keys(env).length ? { env } : {},
18939
+ ...stdin === undefined ? {} : { stdin }
18940
+ };
18941
+ }, RECENT, LAUNCHERS, WORKIT_BIN, PUSH_VALUE_OPTIONS, CURRENT_BRANCH, pushDestination = (destination) => {
18942
+ if (destination === "HEAD" || destination === "@" || CURRENT_BRANCH.test(destination))
18943
+ return { name: null };
18944
+ if (destination.startsWith("refs/heads/"))
18945
+ return { name: destination.slice(11) };
18946
+ if (destination.startsWith("refs/tags/"))
18947
+ return { name: undefined, tag: destination.slice(10) };
18948
+ if (destination.startsWith("refs/"))
18949
+ return { name: undefined };
18950
+ return { name: destination };
18951
+ }, pushRefspecs = (specs) => {
18952
+ let force = false;
18953
+ let matching = false;
18954
+ const targets = [];
18955
+ const tags = [];
18956
+ const deleted = [];
18957
+ const globs = [];
18958
+ for (const raw of specs) {
18959
+ if (raw.startsWith("+"))
18960
+ force = true;
18961
+ const spec = raw.replace(/^\+/, "");
18962
+ if (spec.startsWith("^"))
18963
+ continue;
18964
+ if (spec === ":") {
18965
+ matching = true;
18966
+ continue;
18967
+ }
18968
+ const colon = spec.indexOf(":");
18969
+ const source = colon >= 0 ? spec.slice(0, colon) : spec;
18970
+ const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
18971
+ if (!destination)
18972
+ continue;
18973
+ if (destination.includes("*")) {
18974
+ globs.push({ source, destination });
18975
+ continue;
18976
+ }
18977
+ const { name, tag } = pushDestination(destination);
18978
+ if (tag !== undefined)
18979
+ tags.push(tag);
18980
+ if (name === undefined)
18981
+ continue;
18982
+ targets.push(name);
18983
+ if (colon === 0 && name !== null)
18984
+ deleted.push(name);
18985
+ }
18986
+ return { force, matching, targets, tags, deleted, globs };
18987
+ }, pushAction = (args) => {
18306
18988
  let force = false;
18307
18989
  let lease = false;
18308
18990
  let wholesale = false;
@@ -18311,6 +18993,7 @@ var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
18311
18993
  let allBranches = false;
18312
18994
  let mirror = false;
18313
18995
  let remote = null;
18996
+ let dryRun = false;
18314
18997
  const positionals = [];
18315
18998
  for (let index = 1;index < args.length; index++) {
18316
18999
  const arg = args[index];
@@ -18318,9 +19001,11 @@ var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
18318
19001
  positionals.push(...args.slice(index + 1));
18319
19002
  break;
18320
19003
  }
18321
- if (arg === "--dry-run" || /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg))
18322
- return null;
18323
- if (arg === "--repo")
19004
+ if (/^--dr(?:y(?:-(?:r(?:un?)?)?)?)?$/.test(arg) || /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg))
19005
+ dryRun = true;
19006
+ else if (/^--no-dr(?:y(?:-(?:r(?:un?)?)?)?)?$/.test(arg))
19007
+ dryRun = false;
19008
+ else if (arg === "--repo")
18324
19009
  remote = args[++index] ?? null;
18325
19010
  else if (arg.startsWith("--repo="))
18326
19011
  remote = arg.slice("--repo=".length);
@@ -18341,41 +19026,28 @@ var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
18341
19026
  else if (!arg.startsWith("-"))
18342
19027
  positionals.push(arg);
18343
19028
  }
19029
+ if (dryRun)
19030
+ return null;
18344
19031
  const specs = remote === null ? positionals.slice(1) : positionals;
18345
19032
  remote ??= positionals[0] ?? null;
18346
- const targets = [];
18347
- const tags = [];
18348
- const deleted = [];
18349
- for (const raw of specs) {
18350
- if (raw.startsWith("+"))
18351
- force = true;
18352
- const spec = raw.replace(/^\+/, "");
18353
- const colon = spec.indexOf(":");
18354
- const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
18355
- if (!destination)
18356
- continue;
18357
- const name = destination === "HEAD" ? null : destination.startsWith("refs/heads/") ? destination.slice(11) : destination.startsWith("refs/") ? undefined : destination;
18358
- if (destination.startsWith("refs/tags/"))
18359
- tags.push(destination.slice(10));
18360
- if (name === undefined)
18361
- continue;
18362
- targets.push(name);
18363
- if (colon === 0 && name !== null)
18364
- deleted.push(name);
18365
- }
18366
- if (specs.length === 0 && !wholesale)
18367
- targets.push(null);
19033
+ const parsed = pushRefspecs(specs);
19034
+ const implicit = specs.length === 0 && !wholesale;
19035
+ if (implicit)
19036
+ parsed.targets.push(null);
18368
19037
  return {
18369
19038
  kind: "push",
18370
- targets,
18371
- blindForce: force && !lease,
19039
+ targets: parsed.targets,
19040
+ blindForce: (force || parsed.force) && !lease,
18372
19041
  remote,
18373
- ...tags.length ? { tags } : {},
19042
+ ...parsed.tags.length ? { tags: parsed.tags } : {},
18374
19043
  ...allTags ? { allTags: true } : {},
18375
19044
  ...deletes ? { deletes: true } : {},
18376
- ...deleted.length ? { deleted } : {},
19045
+ ...parsed.deleted.length ? { deleted: parsed.deleted } : {},
18377
19046
  ...allBranches ? { allBranches: true } : {},
18378
- ...mirror ? { mirror: true } : {}
19047
+ ...mirror ? { mirror: true } : {},
19048
+ ...implicit ? { implicit: true } : {},
19049
+ ...parsed.globs.length ? { globs: parsed.globs } : {},
19050
+ ...parsed.matching ? { matching: true } : {}
18379
19051
  };
18380
19052
  }, VALUE_OPTIONS, asksHelp = (invocation) => {
18381
19053
  const { tool, args } = invocation;
@@ -18392,6 +19064,59 @@ var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
18392
19064
  index++;
18393
19065
  }
18394
19066
  return false;
19067
+ }, API_VALUES, API_SHORT_VALUES = "XHfFqtp", API_FIELDS, API_MERGE, GRAPHQL_MERGE, apiMerges = (tool, args, stdin) => {
19068
+ const request = { method: null, body: false };
19069
+ let endpoint = null;
19070
+ const values = [];
19071
+ const take = (name, value) => {
19072
+ if (name === "-X" || name === "--method")
19073
+ request.method = value.toUpperCase();
19074
+ if (API_FIELDS.has(name) || name === "--input")
19075
+ request.body = true;
19076
+ values.push(value);
19077
+ const fromStdin = name === "--input" ? value === "-" : /^(?:[^=]*=)?@-$/.test(value);
19078
+ if (fromStdin && stdin !== undefined)
19079
+ values.push(stdin);
19080
+ };
19081
+ for (let index = 1;index < args.length; index++) {
19082
+ const arg = args[index];
19083
+ if (arg === "--") {
19084
+ endpoint ??= args[index + 1] ?? null;
19085
+ break;
19086
+ }
19087
+ if (arg.startsWith("--")) {
19088
+ const eq = arg.indexOf("=");
19089
+ const name = eq > 0 ? arg.slice(0, eq) : arg;
19090
+ if (API_VALUES.has(name))
19091
+ take(name, eq > 0 ? arg.slice(eq + 1) : args[++index] ?? "");
19092
+ } else if (arg.startsWith("-") && arg.length > 1) {
19093
+ for (let at = 1;at < arg.length; at++) {
19094
+ if (!API_SHORT_VALUES.includes(arg[at]))
19095
+ continue;
19096
+ take(`-${arg[at]}`, at + 1 < arg.length ? arg.slice(at + 1) : args[++index] ?? "");
19097
+ break;
19098
+ }
19099
+ } else
19100
+ endpoint ??= arg;
19101
+ }
19102
+ if (!endpoint)
19103
+ return false;
19104
+ endpoint = endpoint.replace(/(?<!:)\/{2,}/g, "/");
19105
+ if (/(?:^|\/)graphql\/?$/i.test(endpoint))
19106
+ return values.some((value) => GRAPHQL_MERGE.test(value));
19107
+ const verb = request.method ?? (request.body ? "POST" : "GET");
19108
+ return verb !== "GET" && verb !== "HEAD" && API_MERGE[tool].test(endpoint);
19109
+ }, subtreePush = (args) => {
19110
+ const positionals = [];
19111
+ for (let index = 2;index < args.length; index++) {
19112
+ const arg = args[index];
19113
+ if (arg === "-P" || arg === "--prefix" || arg === "-m" || arg === "--message")
19114
+ index++;
19115
+ else if (!arg.startsWith("-"))
19116
+ positionals.push(arg);
19117
+ }
19118
+ const [remote, branch] = positionals;
19119
+ return remote && branch ? pushAction(["push", remote, `HEAD:refs/heads/${branch}`]) : null;
18395
19120
  }, repoAt = (dir) => {
18396
19121
  const location = resolveStore(dir);
18397
19122
  if (location instanceof Error || !location.shared)
@@ -18412,15 +19137,6 @@ var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
18412
19137
  } catch {
18413
19138
  return false;
18414
19139
  }
18415
- }, toPushRemote = (repo, remote) => {
18416
- if (remote === null)
18417
- return true;
18418
- try {
18419
- const pushRemote = pushRemoteName(repo.dir, repo.branch);
18420
- return pushRemote === null || pushRemote === remote;
18421
- } catch {
18422
- return false;
18423
- }
18424
19140
  }, headSha2 = (repo) => {
18425
19141
  const git = repo.location.git;
18426
19142
  if (!git || git.reftable)
@@ -18441,12 +19157,144 @@ var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
18441
19157
  } catch {
18442
19158
  return null;
18443
19159
  }
19160
+ }, gitConfig = (dir, invocation, cache) => {
19161
+ const key = JSON.stringify([dir, invocation.config ?? [], invocation.env ?? {}]);
19162
+ const cached = cache.get(key);
19163
+ if (cached)
19164
+ return cached;
19165
+ const config = new Map;
19166
+ const listed = spawnSync11("git", [...invocation.config ?? [], "config", "-z", "--list"], {
19167
+ cwd: dir,
19168
+ encoding: "utf8",
19169
+ timeout: 3000,
19170
+ windowsHide: true,
19171
+ env: { ...process.env, ...invocation.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0" }
19172
+ });
19173
+ for (const entry of (listed.status === 0 ? listed.stdout : "").split("\x00")) {
19174
+ if (!entry)
19175
+ continue;
19176
+ const newline = entry.indexOf(`
19177
+ `);
19178
+ const name = newline < 0 ? entry : entry.slice(0, newline);
19179
+ config.set(name, [
19180
+ ...config.get(name) ?? [],
19181
+ newline < 0 ? "true" : entry.slice(newline + 1)
19182
+ ]);
19183
+ }
19184
+ cache.set(key, config);
19185
+ return config;
19186
+ }, configValue = (config, key) => config.get(key)?.at(-1), GIT_COMMANDS, JUDGED_COMMANDS, editDistance2 = (a, b) => {
19187
+ let before = [];
19188
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
19189
+ for (let i = 1;i <= a.length; i++) {
19190
+ const row = [i];
19191
+ for (let j = 1;j <= b.length; j++) {
19192
+ row[j] = Math.min(previous[j] + 1, row[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
19193
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1])
19194
+ row[j] = Math.min(row[j], before[j - 2] + 1);
19195
+ }
19196
+ [before, previous] = [previous, row];
19197
+ }
19198
+ return previous[b.length];
19199
+ }, autocorrected = (name, config) => {
19200
+ const setting = (configValue(config, "help.autocorrect") ?? "0").toLowerCase();
19201
+ if (setting !== "immediate" && !/^-?[1-9]\d*$/.test(setting))
19202
+ return null;
19203
+ let best = null;
19204
+ let bestDistance = 3;
19205
+ let tie = false;
19206
+ for (const command of JUDGED_COMMANDS) {
19207
+ const distance = editDistance2(name, command);
19208
+ if (distance < bestDistance)
19209
+ [best, bestDistance, tie] = [command, distance, false];
19210
+ else if (distance === bestDistance)
19211
+ tie = true;
19212
+ }
19213
+ return tie ? null : best;
19214
+ }, shellQuote = (word) => `'${word.replaceAll("'", "'\\''")}'`, positional = (script, args) => {
19215
+ const word = (arg) => /^[\w./@+:=-]+$/.test(arg) ? arg : shellQuote(arg);
19216
+ return script.replace(/\$\{?([1-9])\}?/g, (_, n) => word(args[Number(n) - 1] ?? "")).replace(/\$\{?[@*]\}?/g, args.map(word).join(" "));
19217
+ }, expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
19218
+ const sub = invocation.args[0];
19219
+ if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
19220
+ return [invocation];
19221
+ const dir = path20.resolve(root, invocation.dir ?? ".");
19222
+ if (!fs16.existsSync(dir))
19223
+ return [invocation];
19224
+ const config = gitConfig(dir, invocation, cache);
19225
+ const alias = configValue(config, `alias.${sub.toLowerCase()}`);
19226
+ if (alias === undefined) {
19227
+ const corrected = autocorrected(sub, config);
19228
+ return corrected ? [{ ...invocation, args: [corrected, ...invocation.args.slice(1)] }] : [invocation];
19229
+ }
19230
+ const rest = invocation.args.slice(1);
19231
+ const expanded = alias.startsWith("!") ? rawInvocations([positional(alias.slice(1), rest), ...rest.map(shellQuote)].join(" ")).map((inner) => ({
19232
+ ...inner,
19233
+ dir: rebase(invocation.dir, inner.dir),
19234
+ ...inner.tool === "git" && (invocation.config || inner.config) ? { config: [...invocation.config ?? [], ...inner.config ?? []] } : {},
19235
+ ...inner.tool === "git" && (invocation.env || inner.env) ? { env: { ...invocation.env, ...inner.env } } : {}
19236
+ })) : [
19237
+ gitInvocation([...segmentsOf(alias)[0]?.words ?? [], ...rest], invocation.dir, invocation.env, invocation.stdin, invocation.config)
19238
+ ];
19239
+ return expanded.flatMap((inner) => expandGitAliases(root, inner, cache, depth + 1));
19240
+ }, normalizedUrl = (url, dir) => {
19241
+ const trimmed = url.trim();
19242
+ if (/^file:\/\//i.test(trimmed))
19243
+ return path20.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
19244
+ const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
19245
+ const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
19246
+ if (!scheme && !scp)
19247
+ return path20.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
19248
+ const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
19249
+ return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
19250
+ }, rewrittenUrl = (url, config) => {
19251
+ for (const kind of ["pushinsteadof", "insteadof"]) {
19252
+ let best = null;
19253
+ for (const [key, prefixes] of config) {
19254
+ if (!key.startsWith("url.") || !key.endsWith(`.${kind}`))
19255
+ continue;
19256
+ const base = key.slice(4, -(kind.length + 1));
19257
+ for (const prefix of prefixes)
19258
+ if (url.startsWith(prefix) && prefix.length > (best?.prefix.length ?? -1))
19259
+ best = { base, prefix };
19260
+ }
19261
+ if (best)
19262
+ return best.base + url.slice(best.prefix.length);
19263
+ }
19264
+ return url;
19265
+ }, pushUrl = (config, remote, dir) => {
19266
+ const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
19267
+ return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
19268
+ }, pushRemoteOf = (config, branch) => {
19269
+ const remotes = new Set;
19270
+ for (const key of config.keys()) {
19271
+ const name = /^remote\.(.+)\.url$/.exec(key)?.[1];
19272
+ if (name)
19273
+ remotes.add(name);
19274
+ }
19275
+ for (const candidate of [
19276
+ branch ? configValue(config, `branch.${branch}.pushremote`) : undefined,
19277
+ configValue(config, "remote.pushdefault"),
19278
+ branch ? configValue(config, `branch.${branch}.remote`) : undefined
19279
+ ])
19280
+ if (candidate && candidate !== "." && remotes.has(candidate))
19281
+ return candidate;
19282
+ if (remotes.has("origin"))
19283
+ return "origin";
19284
+ return remotes.size === 1 ? [...remotes][0] : null;
19285
+ }, toPushRemote = (repo, remote, config) => {
19286
+ if (remote === null || /[$`]/.test(remote))
19287
+ return true;
19288
+ const pushRemote = pushRemoteOf(config, repo.branch);
19289
+ if (pushRemote === null || pushRemote === remote)
19290
+ return true;
19291
+ return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
18444
19292
  }, quote = (text) => `\`${text}\``, refusal = (action, branch, dir) => {
18445
19293
  if (action.kind === "merge")
18446
- 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")}).`;
19294
+ 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")}).`;
18447
19295
  const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
18448
19296
  if (dir && branch && plainPush && releaseBranch(dir, branch))
18449
- 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")}.`;
19297
+ 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 ${releaseTagPrefixOf(dir)}<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")}.`;
18450
19298
  return `workit: raw ${quote("git push")}${action.kind === "push" && action.blindForce ? " --force" : ""} onto ${branch}, a protected branch or the default target, bypasses workit's protected-branch check. Push your feature branch with ${quote("workit git push")} (${quote("workit git push --force-with-lease")} to rewrite it) and land it with ${quote("workit pr merge")}.`;
18451
19299
  }, refFacts = (repo, remote) => {
18452
19300
  const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
@@ -18462,32 +19310,105 @@ var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
18462
19310
  facts.remoteHeads.add(ref.slice(remotePrefix.length));
18463
19311
  }
18464
19312
  return facts;
18465
- }, isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name), tagRefusal = (action, facts) => {
19313
+ }, globBranches = (repo, glob) => {
19314
+ const source = glob.source.startsWith("refs/") ? glob.source : `refs/heads/${glob.source}`;
19315
+ const [sourcePrefix, sourceSuffix = ""] = source.split("*");
19316
+ const [destinationPrefix, destinationSuffix = ""] = glob.destination.split("*");
19317
+ const listed = spawnSync11("git", [
19318
+ "for-each-ref",
19319
+ "--format=%(refname)",
19320
+ sourcePrefix.slice(0, sourcePrefix.lastIndexOf("/") + 1)
19321
+ ], { cwd: repo.dir, encoding: "utf8", timeout: 5000, windowsHide: true });
19322
+ const names = [];
19323
+ for (const ref of (listed.stdout ?? "").split(`
19324
+ `)) {
19325
+ if (!ref.startsWith(sourcePrefix) || !ref.endsWith(sourceSuffix))
19326
+ continue;
19327
+ const middle = ref.slice(sourcePrefix.length, ref.length - sourceSuffix.length);
19328
+ const { name } = pushDestination(destinationPrefix + middle + destinationSuffix);
19329
+ if (name)
19330
+ names.push(name);
19331
+ }
19332
+ return names;
19333
+ }, pushedBranches = (action, repo, config, facts, remote) => {
19334
+ const targets = action.implicit ? [] : [...action.targets];
19335
+ const globs = [...action.globs ?? []];
19336
+ let matching = action.matching === true;
19337
+ const configured = action.implicit ? config.get(`remote.${remote}.push`) : undefined;
19338
+ if (configured?.length) {
19339
+ const parsed = pushRefspecs(configured);
19340
+ targets.push(...parsed.targets);
19341
+ globs.push(...parsed.globs);
19342
+ matching ||= parsed.matching;
19343
+ } else if (action.implicit) {
19344
+ const mode = (configValue(config, "push.default") ?? "simple").toLowerCase();
19345
+ const merge = repo.branch ? configValue(config, `branch.${repo.branch}.merge`) : undefined;
19346
+ if (mode === "matching")
19347
+ matching = true;
19348
+ else if ((mode === "upstream" || mode === "tracking") && merge?.startsWith("refs/heads/"))
19349
+ targets.push(merge.slice(11));
19350
+ else if (mode !== "nothing")
19351
+ targets.push(null);
19352
+ }
19353
+ if (action.allBranches)
19354
+ targets.push(...facts.heads);
19355
+ if (matching) {
19356
+ for (const head of facts.heads)
19357
+ if (facts.remoteHeads.has(head))
19358
+ targets.push(head);
19359
+ }
19360
+ for (const glob of globs)
19361
+ targets.push(...globBranches(repo, glob));
19362
+ return targets;
19363
+ }, literalReadings = (name) => {
19364
+ if (!/[$`]/.test(name))
19365
+ return [name];
19366
+ const withDefaults = name.replace(/\$\{\w+:?[-=]([^}$`]*)\}/g, "$1").replace(/\$\{\w+\}|\$\w+/g, "");
19367
+ return /[$`]/.test(withDefaults) || !withDefaults ? [] : [withDefaults];
19368
+ }, guardedTarget = (dir, target) => {
19369
+ for (const branch of literalReadings(target)) {
19370
+ if (guardedBranch(dir, branch))
19371
+ return branch;
19372
+ const bare = branch.startsWith("heads/") ? branch.slice(6) : null;
19373
+ if (bare && guardedBranch(dir, bare))
19374
+ return bare;
19375
+ }
19376
+ return null;
19377
+ }, isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name), tagRefusal = (action, facts, dir) => {
18466
19378
  const tags = [
18467
19379
  ...action.tags ?? [],
18468
19380
  ...action.targets.filter((name) => name !== null && isTag(facts, name))
18469
19381
  ];
18470
19382
  if (!tags.length && !action.allTags)
18471
19383
  return null;
18472
- const named = tags.length ? tags.join(" --release ") : "v<version>";
18473
- 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")}).`;
18474
- }, remoteHas = (repo, remote, branch) => {
18475
- const listed = spawnSync11("git", ["ls-remote", "--heads", remote, `refs/heads/${branch}`], {
19384
+ const shape = `${releaseTagPrefixOf(dir)}<version>`;
19385
+ const named = tags.length ? tags.join(" --release ") : shape;
19386
+ 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}`)} (${shape} 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")}).`;
19387
+ }, remoteHeads = (repo, remote) => {
19388
+ const listed = spawnSync11("git", ["ls-remote", "--heads", remote], {
18476
19389
  cwd: repo.dir,
18477
19390
  encoding: "utf8",
18478
19391
  timeout: 3000,
19392
+ windowsHide: true,
18479
19393
  env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
18480
19394
  });
18481
- return listed.status !== 0 || (listed.stdout ?? "").trim() !== "";
19395
+ if (listed.status !== 0)
19396
+ return null;
19397
+ return new Set((listed.stdout ?? "").split(`
19398
+ `).map((line) => line.split("\t")[1] ?? "").filter((ref) => ref.startsWith("refs/heads/")).map((ref) => ref.slice(11)));
18482
19399
  }, offPolicyPush = (action, repo, facts, remote) => {
18483
19400
  const names = action.allBranches ? [...facts.heads] : action.targets.map((target) => target ?? repo.branch);
19401
+ let listed;
19402
+ const remoteHas = (branch) => (listed === undefined ? listed = remoteHeads(repo, remote) : listed)?.has(branch) ?? true;
18484
19403
  for (const branch of names) {
18485
- if (!branch || action.deleted?.includes(branch) || isTag(facts, branch))
19404
+ if (!branch || /[$`]/.test(branch))
19405
+ continue;
19406
+ if (action.deleted?.includes(branch) || isTag(facts, branch))
18486
19407
  continue;
18487
19408
  if (facts.remoteHeads.has(branch))
18488
19409
  continue;
18489
19410
  const check = validateBranchNameFor(repo.dir, branch);
18490
- if (check.ok || check.rule !== "allowed_pattern" || remoteHas(repo, remote, branch))
19411
+ if (check.ok || check.rule !== "allowed_pattern" || remoteHas(branch))
18491
19412
  continue;
18492
19413
  return `workit: raw ${quote("git push")} onto ${branch}: ${check.error}`;
18493
19414
  }
@@ -18507,23 +19428,49 @@ var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
18507
19428
  case "merge":
18508
19429
  return null;
18509
19430
  }
18510
- }, actionsIn = (cwd, command, dialect) => {
19431
+ }, pushRefusal = (action, repo, config) => {
19432
+ if (!toPushRemote(repo, action.remote, config))
19433
+ return null;
19434
+ if (action.mirror)
19435
+ 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")}.`;
19436
+ const remote = action.remote ?? pushRemoteOf(config, repo.branch) ?? "origin";
19437
+ const facts = refFacts(repo, remote);
19438
+ const targets = pushedBranches(action, repo, config, facts, remote);
19439
+ const pushed = { ...action, targets };
19440
+ const offPolicy = action.allBranches ? offPolicyPush(pushed, repo, facts, remote) : null;
19441
+ if (offPolicy)
19442
+ return offPolicy;
19443
+ for (const target of targets) {
19444
+ const guarded = guardedTarget(repo.dir, target ?? repo.branch ?? "");
19445
+ if (guarded)
19446
+ return refusal(action, guarded, repo.dir);
19447
+ }
19448
+ return tagRefusal(pushed, facts, repo.dir) ?? (action.deletes ? null : offPolicyPush(pushed, repo, facts, remote));
19449
+ }, actionsIn = (cwd, command, dialect, cache = new Map) => {
18511
19450
  const out = [];
18512
19451
  const repos = new Map;
18513
- for (const invocation of rawInvocations(command, dialect)) {
18514
- if (invocation.dir === undefined)
18515
- continue;
18516
- const action = rawAction(invocation);
18517
- if (!action)
18518
- continue;
18519
- const dir = path20.resolve(cwd, invocation.dir ?? ".");
19452
+ const repoFor = (relative) => {
19453
+ const dir = path20.resolve(cwd, relative ?? ".");
18520
19454
  if (!repos.has(dir)) {
18521
19455
  const repo = repoAt(dir);
18522
19456
  repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
18523
19457
  }
18524
- const repo = repos.get(dir);
18525
- if (repo)
18526
- out.push({ action, repo });
19458
+ return repos.get(dir) ?? null;
19459
+ };
19460
+ for (const raw of rawInvocations(command, dialect)) {
19461
+ if (raw.dir === undefined)
19462
+ continue;
19463
+ const known = raw.tool !== "git" || GIT_COMMANDS.has(raw.args[0] ?? "");
19464
+ if (!known && !repoFor(raw.dir))
19465
+ continue;
19466
+ for (const invocation of known ? [raw] : expandGitAliases(cwd, raw, cache)) {
19467
+ if (invocation.dir === undefined)
19468
+ continue;
19469
+ const action = rawAction(invocation);
19470
+ const repo = action ? repoFor(invocation.dir) : null;
19471
+ if (action && repo)
19472
+ out.push({ action, repo, invocation });
19473
+ }
18527
19474
  }
18528
19475
  return out;
18529
19476
  }, inWorkitWorkspace = (dir) => {
@@ -18543,7 +19490,11 @@ var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
18543
19490
  raw: [...marker.raw, ...fresh.map((kind) => agent + kind)]
18544
19491
  });
18545
19492
  return fresh.map((kind) => nudges.get(kind));
18546
- }, deny = (reason) => ({ kind: "deny", reason, unblock: null }), NEXT_COMMAND = "next", MARKER_TTL_SECONDS = 3600, markerFile = (repo, session, key) => path20.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`), callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`, headCommit = (dir) => {
19493
+ }, deny = (reason) => ({ kind: "deny", reason, unblock: null }), SPLIT_IT = "Split it into simpler commands (no deeply nested substitutions or huge brace lists) and run them one at a time.", analysisDenial = (error) => ({
19494
+ kind: "deny",
19495
+ reason: `workit: the shell hook could not analyze this command (${error.message}), so it does not run. ${SPLIT_IT}`,
19496
+ unblock: SPLIT_IT
19497
+ }), NEXT_COMMAND = "next", MARKER_TTL_SECONDS = 3600, markerFile = (repo, session, key) => path20.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`), callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`, headCommit = (dir) => {
18547
19498
  const run = spawnSync11("git", [
18548
19499
  "log",
18549
19500
  "-1",
@@ -18598,14 +19549,42 @@ var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
18598
19549
  };
18599
19550
  var init_raw_git = __esm(() => {
18600
19551
  init_branch();
19552
+ init_branch_policy();
18601
19553
  init_workspaces();
18602
- init_rev();
18603
19554
  init_ledger();
18604
19555
  init_paths();
18605
19556
  init_session_marker();
19557
+ init_shell_words();
18606
19558
  NONE2 = { kind: "none" };
18607
- SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
18608
- PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
19559
+ SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh", "ash", "mksh", "yash", "fish"]);
19560
+ POWERSHELLS = new Set(["pwsh", "powershell"]);
19561
+ EVALS = new Set(["eval", "iex", "invoke-expression"]);
19562
+ WRAPPERS = {
19563
+ sudo: { values: "-u -g -h -p -C -D -R -T -r -t -U --user --group --host --prompt --chdir" },
19564
+ doas: { values: "-u -C" },
19565
+ command: { values: "" },
19566
+ builtin: { values: "" },
19567
+ env: { values: "-u -C --unset --chdir" },
19568
+ nohup: { values: "" },
19569
+ setsid: { values: "" },
19570
+ time: { values: "-f -o --format --output" },
19571
+ exec: { values: "-a" },
19572
+ nice: { values: "-n --adjustment" },
19573
+ ionice: { values: "-c -n -p --class --classdata" },
19574
+ stdbuf: { values: "-i -o -e --input --output --error" },
19575
+ chronic: { values: "" },
19576
+ unbuffer: { values: "" },
19577
+ wsl: { values: "-d -u --cd --distribution --user" },
19578
+ timeout: { values: "-s -k --signal --kill-after", operands: 1 },
19579
+ flock: { values: "-w -E --timeout --conflict-exit-code", operands: 1 },
19580
+ coproc: { values: "" },
19581
+ taskset: { values: "", operands: 1 },
19582
+ watch: { values: "-n -d --interval --differences -q --equexit" },
19583
+ "&": { values: "" }
19584
+ };
19585
+ WRAPPER_VALUES = new Map(Object.entries(WRAPPERS).map(([name, wrapper]) => [name, new Set(wrapper.values.split(" "))]));
19586
+ KEYWORDS = new Set(["if", "then", "else", "elif", "while", "until", "do", "!", "{"]);
19587
+ DECLARATIONS = new Set(["export", "local", "declare", "readonly", "typeset"]);
18609
19588
  SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
18610
19589
  CD = new Set(["cd", "chdir", "set-location", "sl"]);
18611
19590
  PUSHD = new Set(["pushd", "push-location"]);
@@ -18619,6 +19598,24 @@ var init_raw_git = __esm(() => {
18619
19598
  "--config-env",
18620
19599
  "--super-prefix"
18621
19600
  ]);
19601
+ GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
19602
+ GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
19603
+ ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
19604
+ HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
19605
+ VARIABLE = /\$(?:\{(\w+)\}|(\w+))/g;
19606
+ XARGS_VALUES = new Set(["-n", "-L", "-l", "-P", "-d", "-E", "-e", "-s", "-a", "--arg-file", "--delimiter"].concat([
19607
+ "--max-args",
19608
+ "--max-lines",
19609
+ "--max-procs",
19610
+ "--eof",
19611
+ "--max-chars",
19612
+ "--process-slot-var"
19613
+ ]));
19614
+ POWERSHELL_VALUES = /^-(?:f|file|ex|executionpolicy|wd|workingdirectory|config|configurationname|o|outputformat|if|inputformat|inp|w|windowstyle|settingsfile|custompipename|psconsolefile)$/;
19615
+ PARALLEL_VALUES = new Set(["-j", "--jobs", "-N", "-n", "-S", "--sshlogin", "-P", "--delay"]);
19616
+ START_PROCESS_VALUES = /^-(?:workingdirectory|wd|redirectstandard\w*|verb|windowstyle|credential|environment)$/i;
19617
+ CONFIG_READS = /^(?:--get\S*|-l|--list|--unset\S*|--remove-section|--rename-section|-e|--edit|--show-\S+|--name-only)$/;
19618
+ RECENT = new Map;
18622
19619
  LAUNCHERS = new Set([
18623
19620
  "npx",
18624
19621
  "bunx",
@@ -18634,6 +19631,7 @@ var init_raw_git = __esm(() => {
18634
19631
  ]);
18635
19632
  WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
18636
19633
  PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
19634
+ CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
18637
19635
  VALUE_OPTIONS = {
18638
19636
  git: new Set([
18639
19637
  "-m",
@@ -18661,24 +19659,56 @@ var init_raw_git = __esm(() => {
18661
19659
  gh: new Set(["-b", "--body", "-F", "--body-file", "-t", "--title", "--subject", "-A", "--author-email"].concat(["-B", "--base", "-H", "--head", "-l", "--label", "-a", "--assignee"]).concat(["-r", "--reviewer", "-m", "--milestone", "-p", "--project", "-T", "--template"]).concat(["--match-head-commit", "-R", "--repo"])),
18662
19660
  glab: new Set(["-m", "--message", "--squash-message", "--sha", "-t", "--title", "-d", "--description"].concat(["-b", "--target-branch", "-s", "--source-branch", "-l", "--label"]).concat(["-a", "--assignee", "--reviewer", "-R", "--repo"]))
18663
19661
  };
19662
+ API_VALUES = new Set(["--method", "--header", "--raw-field", "--field", "--jq", "--template", "--input"].concat([
19663
+ "--hostname",
19664
+ "--preview",
19665
+ "--cache",
19666
+ "--output"
19667
+ ]));
19668
+ API_FIELDS = new Set(["-f", "-F", "--raw-field", "--field"]);
19669
+ API_MERGE = {
19670
+ gh: /(?:^|\/)(?:pulls\/[^/?#]+\/merge|merges)\/?(?:[?#]|$)/i,
19671
+ glab: /(?:^|\/)merge_requests\/[^/?#]+\/merge\/?(?:[?#]|$)/i
19672
+ };
19673
+ GRAPHQL_MERGE = /\b(?:mergePullRequest|enablePullRequestAutoMerge|mergeBranch|mergeRequestAccept)\b/;
19674
+ 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(" "));
19675
+ JUDGED_COMMANDS = [
19676
+ "push",
19677
+ "commit",
19678
+ "switch",
19679
+ "checkout",
19680
+ "branch",
19681
+ "worktree",
19682
+ "stash",
19683
+ "update-ref",
19684
+ "symbolic-ref",
19685
+ "fetch",
19686
+ "pull",
19687
+ "subtree"
19688
+ ];
18664
19689
  });
18665
19690
 
18666
19691
  // packages/workit-core/src/core/route-intent.ts
18667
19692
  import { spawnSync as spawnSync12 } from "node:child_process";
18668
19693
  import { existsSync as existsSync6 } from "node:fs";
18669
19694
  import path21 from "node:path";
18670
- var UNKNOWABLE, SWITCH_CREATE, CHECKOUT_CREATE, WORKTREE_CREATE, BRANCH_NO_CREATE, BRANCH_RENAME, flagValue = (args, flags) => {
19695
+ var UNKNOWABLE, SWITCH_CREATE, CHECKOUT_CREATE, WORKTREE_CREATE, SUBTREE_CREATE, BRANCH_NO_CREATE, BRANCH_RENAME, longOption = (name, flags) => flags.has(name) || name.length >= 3 && [...flags].some((flag) => flag.startsWith("--") && flag.startsWith(name)), shortsOf = (flags) => [...flags].filter((flag) => /^-[^-]$/u.test(flag)).join("").replaceAll("-", ""), flagValue = (args, flags) => {
19696
+ const shorts = shortsOf(flags);
18671
19697
  for (let index = 1;index < args.length; index++) {
18672
19698
  const arg = args[index];
18673
19699
  if (arg === "--")
18674
19700
  return null;
18675
- if (flags.has(arg))
18676
- return args[index + 1] ?? null;
18677
- const eq = arg.indexOf("=");
18678
- if (arg.startsWith("--") && eq > 0 && flags.has(arg.slice(0, eq)))
18679
- return arg.slice(eq + 1);
18680
- if (!arg.startsWith("--") && arg.length > 2 && flags.has(arg.slice(0, 2)))
18681
- return arg.slice(2);
19701
+ if (arg.startsWith("--")) {
19702
+ const eq = arg.indexOf("=");
19703
+ if (!longOption(eq > 0 ? arg.slice(0, eq) : arg, flags))
19704
+ continue;
19705
+ return eq > 0 ? arg.slice(eq + 1) : args[index + 1] ?? null;
19706
+ }
19707
+ if (!arg.startsWith("-") || arg.length < 2)
19708
+ continue;
19709
+ for (let at = 1;at < arg.length; at++)
19710
+ if (shorts.includes(arg[at]))
19711
+ return at + 1 < arg.length ? arg.slice(at + 1) : args[index + 1] ?? null;
18682
19712
  }
18683
19713
  return null;
18684
19714
  }, WORKTREE_VALUES, worktreeTarget = (args) => {
@@ -18708,51 +19738,102 @@ var UNKNOWABLE, SWITCH_CREATE, CHECKOUT_CREATE, WORKTREE_CREATE, BRANCH_NO_CREAT
18708
19738
  positionals.push(...args.slice(index + 1));
18709
19739
  break;
18710
19740
  }
18711
- const flag = arg.split("=", 1)[0];
18712
- if (BRANCH_NO_CREATE.has(flag))
18713
- return null;
18714
- if (BRANCH_RENAME.has(flag))
18715
- rename = true;
18716
- else if (!arg.startsWith("-"))
19741
+ if (arg.startsWith("--")) {
19742
+ const flag = arg.split("=", 1)[0];
19743
+ if (longOption(flag, BRANCH_NO_CREATE))
19744
+ return null;
19745
+ if (longOption(flag, BRANCH_RENAME))
19746
+ rename = true;
19747
+ } else if (arg.startsWith("-") && arg.length > 1) {
19748
+ const shorts = arg.slice(1).split("");
19749
+ if (shorts.some((short) => BRANCH_NO_CREATE.has(`-${short}`)))
19750
+ return null;
19751
+ if (shorts.some((short) => BRANCH_RENAME.has(`-${short}`)))
19752
+ rename = true;
19753
+ } else
18717
19754
  positionals.push(arg);
18718
19755
  }
18719
19756
  if (positionals.length < 1 || positionals.length > 2)
18720
19757
  return null;
18721
19758
  return rename ? positionals.at(-1) ?? null : positionals[0];
18722
- }, gitBranchCreation = (args) => {
18723
- const [sub, second] = args;
18724
- 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;
18725
- return target && !target.startsWith("-") && !UNKNOWABLE.test(target) ? target : null;
18726
- }, shellBranchTargets = (command, dialect = "posix") => {
18727
- if (!/\bgit\b/u.test(command))
18728
- return [];
19759
+ }, positionalsOf = (args, skip, values = new Set) => {
18729
19760
  const found = [];
18730
- for (const invocation of rawInvocations(command, dialect)) {
18731
- if (invocation.tool !== "git")
18732
- continue;
18733
- const branch = gitBranchCreation(invocation.args);
18734
- if (branch)
18735
- found.push({ branch, dir: invocation.dir });
19761
+ for (let index = skip;index < args.length; index++) {
19762
+ const arg = args[index];
19763
+ if (arg === "--")
19764
+ return [...found, ...args.slice(index + 1)];
19765
+ if (values.has(arg))
19766
+ index++;
19767
+ else if (!arg.startsWith("-") || arg === "-")
19768
+ found.push(arg);
18736
19769
  }
18737
19770
  return found;
18738
- }, localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
19771
+ }, localHead = (ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : null, updateRefTargets = (args, stdin) => {
19772
+ if (args.some((arg) => arg === "-d" || arg === "--delete"))
19773
+ return [];
19774
+ if (args.includes("--stdin"))
19775
+ return (stdin ?? "").split(/[\n\0]/u).map((line) => /^\s*(?:create|update)\s+(\S+)/u.exec(line)?.[1] ?? "").map(localHead).filter((name) => name !== null);
19776
+ const [ref] = positionalsOf(args, 1, new Set(["-m"]));
19777
+ const name = ref ? localHead(ref) : null;
19778
+ return name ? [name] : [];
19779
+ }, symbolicRefTarget = (args) => {
19780
+ if (args.some((arg) => arg === "-d" || arg === "--delete"))
19781
+ return null;
19782
+ const [, ref] = positionalsOf(args, 1, new Set(["-m"]));
19783
+ return ref ? localHead(ref) : null;
19784
+ }, FETCH_VALUES, fetchTargets = (args, stdin) => {
19785
+ if (args.includes("--all") || args.includes("--multiple"))
19786
+ return [];
19787
+ const specs = args.includes("--stdin") ? (stdin ?? "").split(/\s+/u).filter(Boolean) : positionalsOf(args, 1, FETCH_VALUES).slice(1);
19788
+ const names = [];
19789
+ for (const raw of specs) {
19790
+ const colon = raw.indexOf(":");
19791
+ const destination = colon >= 0 ? raw.slice(colon + 1) : "";
19792
+ if (!destination || destination.includes("*"))
19793
+ continue;
19794
+ const name = destination.startsWith("refs/") ? localHead(destination) : destination;
19795
+ if (name)
19796
+ names.push(name);
19797
+ }
19798
+ return names;
19799
+ }, localPushTargets = (args) => {
19800
+ const action = pushAction(args);
19801
+ if (action?.kind !== "push" || action.remote !== "." || action.deletes)
19802
+ return [];
19803
+ return action.targets.filter((name) => name !== null);
19804
+ }, gitBranchCreations = (args, stdin) => {
19805
+ const [sub, second] = args;
19806
+ 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)] : [];
19807
+ return targets.filter((target) => target !== null && target !== "" && !target.startsWith("-") && !UNKNOWABLE.test(target));
19808
+ }, targetsOf = (invocations) => invocations.flatMap((invocation) => invocation.tool === "git" ? gitBranchCreations(invocation.args, invocation.stdin).map((branch) => ({
19809
+ branch,
19810
+ dir: invocation.dir
19811
+ })) : []), localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
18739
19812
  cwd: dir,
18740
19813
  stdio: "ignore",
18741
19814
  timeout: 5000
18742
- }).status === 0, shellBranchPolicyViolation = (root, command, dialect = "posix") => {
19815
+ }).status === 0, judgedDir = (root, dir) => {
19816
+ if (dir === undefined)
19817
+ return null;
19818
+ if (dir === null)
19819
+ return root;
19820
+ const resolved = path21.resolve(root, dir);
19821
+ return existsSync6(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
19822
+ }, shellBranchPolicyViolation = (root, command, dialect = "posix") => {
18743
19823
  let judged = false;
18744
- for (const target of shellBranchTargets(command, dialect)) {
18745
- if (target.dir === undefined)
19824
+ const cache = new Map;
19825
+ for (const raw of rawInvocations(command, dialect)) {
19826
+ if (raw.tool !== "git" || judgedDir(root, raw.dir) === null)
18746
19827
  continue;
18747
- const dir = target.dir === null ? root : path21.resolve(root, target.dir);
18748
- if (target.dir !== null && (!existsSync6(dir) || !inWorkitWorkspace(dir)))
18749
- continue;
18750
- if (localBranchExists(dir, target.branch))
18751
- continue;
18752
- judged = true;
18753
- const check = validateBranchNameFor(dir, target.branch);
18754
- if (!check.ok)
18755
- return check;
19828
+ for (const target of targetsOf(expandGitAliases(root, raw, cache))) {
19829
+ const dir = judgedDir(root, target.dir);
19830
+ if (dir === null || localBranchExists(dir, target.branch))
19831
+ continue;
19832
+ judged = true;
19833
+ const check = validateBranchNameFor(dir, target.branch);
19834
+ if (!check.ok)
19835
+ return check;
19836
+ }
18756
19837
  }
18757
19838
  return judged ? { ok: true } : null;
18758
19839
  };
@@ -18763,18 +19844,29 @@ var init_route_intent = __esm(() => {
18763
19844
  SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
18764
19845
  CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
18765
19846
  WORKTREE_CREATE = new Set(["-b", "-B"]);
19847
+ SUBTREE_CREATE = new Set(["-b", "--branch"]);
18766
19848
  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"]));
18767
19849
  BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
18768
19850
  WORKTREE_VALUES = new Set(["--reason"]);
19851
+ 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"]));
18769
19852
  });
18770
19853
 
18771
19854
  // packages/workit-core/src/hooks/policy.ts
18772
19855
  var shellPolicy = (cwd, command, dialect = "posix") => {
18773
- const policy = shellBranchPolicyViolation(cwd, command, dialect);
19856
+ let policy;
19857
+ try {
19858
+ policy = shellBranchPolicyViolation(cwd, command, dialect);
19859
+ } catch (error) {
19860
+ if (error instanceof ShellAnalysisError)
19861
+ return analysisDenial(error);
19862
+ throw error;
19863
+ }
18774
19864
  return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
18775
19865
  };
18776
19866
  var init_policy = __esm(() => {
18777
19867
  init_route_intent();
19868
+ init_raw_git();
19869
+ init_shell_words();
18778
19870
  });
18779
19871
 
18780
19872
  // packages/workit-core/src/hooks/write-gate.ts
@@ -18816,9 +19908,9 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
18816
19908
  const posix = relative.split(path22.sep).join("/");
18817
19909
  const segments = posix.split("/");
18818
19910
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
18819
- }, PREFIXES3, operands = (words) => words.filter((item) => !item.startsWith("-")), editsOf = (input) => {
19911
+ }, PREFIXES2, operands = (words) => words.filter((item) => !item.startsWith("-")), editsOf = (input) => {
18820
19912
  let words = input;
18821
- while (words.length && (PREFIXES3.has(words[0]) || /^\w+=/.test(words[0])))
19913
+ while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
18822
19914
  words = words.slice(1);
18823
19915
  const [verb, ...args] = words;
18824
19916
  const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
@@ -18947,9 +20039,10 @@ var init_write_gate = __esm(() => {
18947
20039
  init_derive();
18948
20040
  init_task_evaluation();
18949
20041
  init_ledger();
20042
+ init_shell_words();
18950
20043
  NONE3 = { kind: "none" };
18951
20044
  CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
18952
- PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
20045
+ PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
18953
20046
  cache2 = new Map;
18954
20047
  });
18955
20048
 
@@ -19265,6 +20358,7 @@ var init_skill_nudge = __esm(() => {
19265
20358
  init_ledger();
19266
20359
  init_raw_git();
19267
20360
  init_session_marker();
20361
+ init_shell_words();
19268
20362
  USER_INVOKED = new Set(["workit-retro", "workit-architecture"]);
19269
20363
  SKILL_HOST = {
19270
20364
  claude_code: "claude-code",
@@ -20243,7 +21337,7 @@ var init_cursor = __esm(() => {
20243
21337
  "compact.pre": { support: "partial", native: "preCompact" },
20244
21338
  stop: { support: "native", native: "stop" }
20245
21339
  },
20246
- shellPolicy: { deny: "native", channel: "exit2+json", failClosed: true },
21340
+ shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false },
20247
21341
  context: {
20248
21342
  sessionStart: "native",
20249
21343
  perTurn: "none",