@staff0rd/assist 0.615.3 → 0.616.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.615.3",
9
+ version: "0.616.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -16541,6 +16541,84 @@ function stripEnvPrefix(parts) {
16541
16541
  return i > 0 ? parts.slice(i) : parts;
16542
16542
  }
16543
16543
 
16544
+ // src/shared/extractGhApiMethod.ts
16545
+ function extractGhApiMethod(args) {
16546
+ for (let i = 0; i < args.length; i++) {
16547
+ const arg = args[i];
16548
+ if (arg.startsWith("--method=")) return arg.slice("--method=".length);
16549
+ if (arg.startsWith("-X=")) return arg.slice("-X=".length);
16550
+ if (arg.startsWith("-X") && arg.length > 2) return arg.slice(2);
16551
+ if ((arg === "--method" || arg === "-X") && i + 1 < args.length) {
16552
+ return args[i + 1];
16553
+ }
16554
+ }
16555
+ return void 0;
16556
+ }
16557
+
16558
+ // src/shared/tokenize.ts
16559
+ function tokenize(command) {
16560
+ const tokens = [];
16561
+ let current = "";
16562
+ let inSingle = false;
16563
+ let inDouble = false;
16564
+ for (let i = 0; i < command.length; i++) {
16565
+ const ch = command[i];
16566
+ if (ch === "'" && !inDouble) {
16567
+ inSingle = !inSingle;
16568
+ current += ch;
16569
+ } else if (ch === '"' && !inSingle) {
16570
+ inDouble = !inDouble;
16571
+ current += ch;
16572
+ } else if (ch === "\\" && inDouble && i + 1 < command.length) {
16573
+ current += ch + command[++i];
16574
+ } else if (/\s/.test(ch) && !inSingle && !inDouble) {
16575
+ if (current) {
16576
+ tokens.push(current);
16577
+ current = "";
16578
+ }
16579
+ } else {
16580
+ current += ch;
16581
+ }
16582
+ }
16583
+ if (current) tokens.push(current);
16584
+ return tokens;
16585
+ }
16586
+
16587
+ // src/commands/cliHook/isGhIssueApiWrite.ts
16588
+ var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PATCH", "PUT", "DELETE"]);
16589
+ var BODY_FLAGS = ["-f", "-F", "--field", "--raw-field", "--input"];
16590
+ var COMMAND_OPERATORS = /* @__PURE__ */ new Set(["&&", "||", ";", "|", "&"]);
16591
+ var ISSUE_ENDPOINT = /(?:^|[/"'`])repos\/[^/\s"'`]+\/[^/\s"'`]+\/issues(?:$|[/?"'`])/;
16592
+ function isGhIssueApiWrite(command) {
16593
+ const tokens = tokenize(command);
16594
+ for (let i = 0; i + 1 < tokens.length; i++) {
16595
+ if (tokens[i] !== "gh" || tokens[i + 1] !== "api") continue;
16596
+ const args = argsUntilNextCommand(tokens.slice(i + 2));
16597
+ if (targetsIssueEndpoint(args) && isWrite(args)) return true;
16598
+ }
16599
+ return false;
16600
+ }
16601
+ function argsUntilNextCommand(tokens) {
16602
+ const end = tokens.findIndex((token) => COMMAND_OPERATORS.has(token));
16603
+ return end === -1 ? tokens : tokens.slice(0, end);
16604
+ }
16605
+ function targetsIssueEndpoint(args) {
16606
+ return args.some((arg) => ISSUE_ENDPOINT.test(arg));
16607
+ }
16608
+ function isWrite(args) {
16609
+ const method = extractGhApiMethod(args);
16610
+ if (method) return WRITE_METHODS.has(unquote(method).toUpperCase());
16611
+ return args.some(isBodyFlag);
16612
+ }
16613
+ function isBodyFlag(arg) {
16614
+ return BODY_FLAGS.some(
16615
+ (flag) => arg === flag || arg.startsWith(`${flag}=`) || flag.length === 2 && arg.length > 2 && arg.startsWith(flag)
16616
+ );
16617
+ }
16618
+ function unquote(value) {
16619
+ return value.replace(/^['"]|['"]$/g, "");
16620
+ }
16621
+
16544
16622
  // src/commands/cliHook/findBuiltinDeny.ts
16545
16623
  var BUILTIN_DENIES = [
16546
16624
  {
@@ -16555,6 +16633,10 @@ var BUILTIN_DENIES = [
16555
16633
  pattern: "gh issue create",
16556
16634
  message: "Do not run 'gh issue create' directly. Use 'assist github issue create --title <title> --body <body> [-R <owner>/<repo>]' instead \u2014 it validates the title and body before delegating to gh, and gates the issue on the user's approval itself. Run 'assist github issue create --help' and follow its guidance to compose the body; it is authoritative on how approval is handled in this environment."
16557
16635
  },
16636
+ {
16637
+ pattern: "gh issue edit",
16638
+ message: "Do not run 'gh issue edit' directly. Use 'assist github issue edit <number> [-R <owner>/<repo>]' instead \u2014 it fetches the issue's current body, opens it in the assist web preview pane, and pushes nothing back until the user approves it. Run 'assist github issue edit --help' for how the working file and the preview pane fit together; it is authoritative on how approval is handled in this environment."
16639
+ },
16558
16640
  {
16559
16641
  pattern: "gh issue comment",
16560
16642
  message: "Do not run 'gh issue comment' directly. Use 'assist github issue comment <number> --body <body> [-R <owner>/<repo>]' instead \u2014 it validates the body before delegating to gh, and gates the comment on the user's approval itself. Run 'assist github issue comment --help' and follow its guidance to compose the body; it is authoritative on how approval is handled in this environment."
@@ -16564,6 +16646,7 @@ var BUILTIN_DENIES = [
16564
16646
  message: `Do not run 'git commit' directly. Use 'assist commit "<message>"' instead.`
16565
16647
  }
16566
16648
  ];
16649
+ var GH_ISSUE_API_MESSAGE = "Do not write to GitHub issue endpoints with 'gh api' \u2014 it bypasses the content validation and the approval pane that every outward-facing issue write goes through. Use 'assist github issue create --title <title> --body <body>' to open an issue, 'assist github issue edit <number>' to rework one's body, 'assist github issue comment <number> --body <body>' to post a comment, or 'assist github issue edit-comment <comment-id> --body <body>' to amend a comment that is already posted. Each validates the content and gates it on the user's approval before anything reaches GitHub; run the command's --help for how to compose it.";
16567
16650
  var BRANCH_CREATION_MESSAGE = "Do not create branches with raw git. Use the /branch command, or 'assist branch <slug> [--jira <KEY>]' \u2014 it branches off the fresh remote default and enforces the team naming convention.";
16568
16651
  var COMMAND_BOUNDARY = String.raw`(?<=(?:^|[;&|(\n])\s*)`;
16569
16652
  var BRANCH_CREATION_REGEXES = [
@@ -16580,17 +16663,24 @@ function rawDenyRegex(pattern2) {
16580
16663
  return new RegExp(`(?<=^|\\s)${tokens}(?=\\s|$)`);
16581
16664
  }
16582
16665
  var RAW_BUILTIN_DENIES = [
16583
- ...BUILTIN_DENIES.map((rule) => ({
16584
- message: rule.message,
16585
- regex: rawDenyRegex(rule.pattern)
16586
- })),
16666
+ ...BUILTIN_DENIES.map((rule) => {
16667
+ const regex = rawDenyRegex(rule.pattern);
16668
+ return {
16669
+ message: rule.message,
16670
+ matches: (text18) => regex.test(text18)
16671
+ };
16672
+ }),
16587
16673
  ...BRANCH_CREATION_REGEXES.map((regex) => ({
16588
16674
  message: BRANCH_CREATION_MESSAGE,
16589
- regex
16590
- }))
16675
+ matches: (text18) => regex.test(text18)
16676
+ })),
16677
+ {
16678
+ message: GH_ISSUE_API_MESSAGE,
16679
+ matches: isGhIssueApiWrite
16680
+ }
16591
16681
  ];
16592
16682
  function matchBuiltinDeny(text18) {
16593
- return RAW_BUILTIN_DENIES.find((rule) => rule.regex.test(text18));
16683
+ return RAW_BUILTIN_DENIES.find((rule) => rule.matches(text18));
16594
16684
  }
16595
16685
  function toDecision(rule) {
16596
16686
  if (!rule) return void 0;
@@ -16610,6 +16700,64 @@ function findBuiltinDenyRaw(rawCommand) {
16610
16700
  return toDecision(matchBuiltinDeny(rawCommand));
16611
16701
  }
16612
16702
 
16703
+ // src/commands/cliHook/findTruncatedReadDeny.ts
16704
+ var UNTRUNCATABLE_READS = ["assist backlog show", "assist backlog view"];
16705
+ var APPROVAL_GATED_COMMANDS = [
16706
+ "assist backlog propose",
16707
+ "assist backlog comment",
16708
+ "assist backlog update-plan",
16709
+ "assist backlog add-phase",
16710
+ "assist github issue create",
16711
+ "assist github issue edit",
16712
+ "assist github issue comment",
16713
+ "assist slack post",
16714
+ "assist prs raise",
16715
+ "assist prs edit",
16716
+ "assist prs comment",
16717
+ "assist prs reply",
16718
+ "assist prs wontfix",
16719
+ "assist miro extract"
16720
+ ];
16721
+ var TRUNCATOR_BINARIES = ["head", "tail"];
16722
+ var PIPED_TRUNCATOR_RE = /\|\s*(?:\S*\/)?(?:head|tail)\b/;
16723
+ function matchPrefix(prefixes, part) {
16724
+ return prefixes.find(
16725
+ (prefix2) => part === prefix2 || part.startsWith(`${prefix2} `)
16726
+ );
16727
+ }
16728
+ function startsWithTruncator(part) {
16729
+ const binary = part.split(/\s+/)[0]?.split("/").pop() ?? "";
16730
+ return TRUNCATOR_BINARIES.includes(binary);
16731
+ }
16732
+ function toDecision2(read3, gated) {
16733
+ if (read3)
16734
+ return {
16735
+ permissionDecision: "deny",
16736
+ permissionDecisionReason: `Do not pipe '${read3}' through head or tail. Plan, Activity and Comments print at the end of the output, so a truncated read drops them and leaves you assuming the item has none. Run '${read3} <id>' bare and read all of it, or use a focused view: 'assist backlog comments <id>' for comments only.`
16737
+ };
16738
+ if (gated)
16739
+ return {
16740
+ permissionDecision: "deny",
16741
+ permissionDecisionReason: `Do not pipe '${gated}' through head or tail. It gates on a preview the reviewer can reject with inline comments, and those comments print at the end of the output. Nothing persists them, so a truncated read discards the reviewer's feedback for good and they have to retype it. Run '${gated}' bare and read all of it.`
16742
+ };
16743
+ return void 0;
16744
+ }
16745
+ function findTruncatedReadDeny(parts) {
16746
+ if (!parts.some(startsWithTruncator)) return void 0;
16747
+ return toDecision2(
16748
+ parts.map((part) => matchPrefix(UNTRUNCATABLE_READS, part)).find(Boolean),
16749
+ parts.map((part) => matchPrefix(APPROVAL_GATED_COMMANDS, part)).find(Boolean)
16750
+ );
16751
+ }
16752
+ function findTruncatedReadDenyRaw(rawCommand) {
16753
+ if (!startsWithTruncator(rawCommand) && !PIPED_TRUNCATOR_RE.test(rawCommand))
16754
+ return void 0;
16755
+ return toDecision2(
16756
+ matchPrefix(UNTRUNCATABLE_READS, rawCommand),
16757
+ matchPrefix(APPROVAL_GATED_COMMANDS, rawCommand)
16758
+ );
16759
+ }
16760
+
16613
16761
  // src/shared/isApprovedRead.ts
16614
16762
  import { resolve as resolve13, sep } from "path";
16615
16763
 
@@ -16619,38 +16767,9 @@ function appliesFixStructure(command) {
16619
16767
  return APPLY_RE.test(command);
16620
16768
  }
16621
16769
 
16622
- // src/shared/tokenize.ts
16623
- function tokenize(command) {
16624
- const tokens = [];
16625
- let current = "";
16626
- let inSingle = false;
16627
- let inDouble = false;
16628
- for (let i = 0; i < command.length; i++) {
16629
- const ch = command[i];
16630
- if (ch === "'" && !inDouble) {
16631
- inSingle = !inSingle;
16632
- current += ch;
16633
- } else if (ch === '"' && !inSingle) {
16634
- inDouble = !inDouble;
16635
- current += ch;
16636
- } else if (ch === "\\" && inDouble && i + 1 < command.length) {
16637
- current += ch + command[++i];
16638
- } else if (/\s/.test(ch) && !inSingle && !inDouble) {
16639
- if (current) {
16640
- tokens.push(current);
16641
- current = "";
16642
- }
16643
- } else {
16644
- current += ch;
16645
- }
16646
- }
16647
- if (current) tokens.push(current);
16648
- return tokens;
16649
- }
16650
-
16651
16770
  // src/shared/isGhApiRead.ts
16652
16771
  var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
16653
- var BODY_FLAGS = /* @__PURE__ */ new Set(["-f", "-F", "--field", "--raw-field", "--input"]);
16772
+ var BODY_FLAGS2 = /* @__PURE__ */ new Set(["-f", "-F", "--field", "--raw-field", "--input"]);
16654
16773
  var FIELD_FLAGS = /* @__PURE__ */ new Set(["-f", "--field", "-F"]);
16655
16774
  function isGhApiRead(command) {
16656
16775
  const tokens = tokenize(command);
@@ -16661,24 +16780,13 @@ function isGhApiRead(command) {
16661
16780
  if (args.includes("graphql")) {
16662
16781
  return isGraphqlRead(args);
16663
16782
  }
16664
- const method = extractMethod(args);
16783
+ const method = extractGhApiMethod(args);
16665
16784
  if (method) return READ_METHODS.has(method.toUpperCase());
16666
- if (args.some((t) => BODY_FLAGS.has(t))) return false;
16785
+ if (args.some((t) => BODY_FLAGS2.has(t))) return false;
16667
16786
  return true;
16668
16787
  }
16669
- function extractMethod(args) {
16670
- for (let i = 0; i < args.length; i++) {
16671
- const arg = args[i];
16672
- if (arg.startsWith("--method=")) return arg.slice("--method=".length);
16673
- if (arg.startsWith("-X=")) return arg.slice("-X=".length);
16674
- if ((arg === "--method" || arg === "-X") && i + 1 < args.length) {
16675
- return args[i + 1];
16676
- }
16677
- }
16678
- return void 0;
16679
- }
16680
16788
  function isGraphqlRead(args) {
16681
- const method = extractMethod(args);
16789
+ const method = extractGhApiMethod(args);
16682
16790
  if (method) return READ_METHODS.has(method.toUpperCase());
16683
16791
  const queryBody = extractGraphqlQuery(args);
16684
16792
  if (!queryBody) return false;
@@ -16896,51 +17004,6 @@ function matchesConfigDeny(command) {
16896
17004
  );
16897
17005
  }
16898
17006
 
16899
- // src/commands/cliHook/findTruncatedReadDeny.ts
16900
- var UNTRUNCATABLE_READS = ["assist backlog show", "assist backlog view"];
16901
- var APPROVAL_GATED_COMMANDS = [
16902
- "assist backlog propose",
16903
- "assist backlog comment",
16904
- "assist backlog update-plan",
16905
- "assist backlog add-phase",
16906
- "assist github issue create",
16907
- "assist github issue edit",
16908
- "assist github issue comment",
16909
- "assist slack post",
16910
- "assist prs raise",
16911
- "assist prs edit",
16912
- "assist prs comment",
16913
- "assist prs reply",
16914
- "assist prs wontfix",
16915
- "assist miro extract"
16916
- ];
16917
- var TRUNCATOR_BINARIES = ["head", "tail"];
16918
- var PIPED_TRUNCATOR_RE = /\|\s*(?:\S*\/)?(?:head|tail)\b/;
16919
- function matchPrefix(prefixes, part) {
16920
- return prefixes.find(
16921
- (prefix2) => part === prefix2 || part.startsWith(`${prefix2} `)
16922
- );
16923
- }
16924
- function isTruncator(part) {
16925
- const binary = part.split(/\s+/)[0]?.split("/").pop() ?? "";
16926
- return TRUNCATOR_BINARIES.includes(binary) || PIPED_TRUNCATOR_RE.test(part);
16927
- }
16928
- function findTruncatedReadDeny(parts) {
16929
- const read3 = parts.map((part) => matchPrefix(UNTRUNCATABLE_READS, part)).find(Boolean);
16930
- const gated = parts.map((part) => matchPrefix(APPROVAL_GATED_COMMANDS, part)).find(Boolean);
16931
- if (!read3 && !gated) return void 0;
16932
- if (!parts.some(isTruncator)) return void 0;
16933
- if (read3)
16934
- return {
16935
- permissionDecision: "deny",
16936
- permissionDecisionReason: `Do not pipe '${read3}' through head or tail. Plan, Activity and Comments print at the end of the output, so a truncated read drops them and leaves you assuming the item has none. Run '${read3} <id>' bare and read all of it, or use a focused view: 'assist backlog comments <id>' for comments only.`
16937
- };
16938
- return {
16939
- permissionDecision: "deny",
16940
- permissionDecisionReason: `Do not pipe '${gated}' through head or tail. It gates on a preview the reviewer can reject with inline comments, and those comments print at the end of the output. Nothing persists them, so a truncated read discards the reviewer's feedback for good and they have to retype it. Run '${gated}' bare and read all of it.`
16941
- };
16942
- }
16943
-
16944
17007
  // src/commands/cliHook/findSubcommandAdvice.ts
16945
17008
  var SUBCOMMAND_READS = [
16946
17009
  {
@@ -17014,7 +17077,7 @@ function resolvePermission(toolName, parts) {
17014
17077
  function decideCommand(toolName, rawCommand) {
17015
17078
  const result = splitCompound(rawCommand);
17016
17079
  if (result.ok) return resolvePermission(toolName, result.parts);
17017
- return findBuiltinDenyRaw(rawCommand) ?? findDeny(toolName, [rawCommand]);
17080
+ return findBuiltinDenyRaw(rawCommand) ?? findTruncatedReadDenyRaw(rawCommand) ?? findDeny(toolName, [rawCommand]);
17018
17081
  }
17019
17082
 
17020
17083
  // src/commands/cliHook/logDeniedToolCall.ts
@@ -17117,6 +17180,7 @@ function cliHookCheck(command, toolName = "Bash") {
17117
17180
  const result = splitCompound(trimmed);
17118
17181
  if (!result.ok) {
17119
17182
  if (reportDecision(findBuiltinDenyRaw(trimmed))) return;
17183
+ if (reportDecision(findTruncatedReadDenyRaw(trimmed))) return;
17120
17184
  if (reportDeny(toolName, [trimmed])) return;
17121
17185
  console.log(`not approved (${result.error})`);
17122
17186
  process.exitCode = 1;
@@ -22514,12 +22578,79 @@ function registerEditIssue(issueCommand) {
22514
22578
  ).action(editIssue);
22515
22579
  }
22516
22580
 
22581
+ // src/commands/github/issue/editIssueComment.ts
22582
+ import { execFileSync as execFileSync11 } from "child_process";
22583
+ var USAGE4 = "Usage: assist github issue edit-comment <comment-id> --body <body> [-R <owner>/<repo>]";
22584
+ async function editIssueComment(commentIdArg, options2) {
22585
+ const commentId = Number.parseInt(commentIdArg, 10);
22586
+ if (!Number.isInteger(commentId) || commentId <= 0 || !options2.body) {
22587
+ console.error(USAGE4);
22588
+ process.exit(1);
22589
+ }
22590
+ const { body } = options2;
22591
+ const { owner, repo } = resolveIssueRepoTarget(options2.repo);
22592
+ const target = `${owner}/${repo} comment ${commentId}`;
22593
+ validateProposedContent(
22594
+ { subject: "Comment", context: "GitHub issues" },
22595
+ "",
22596
+ body
22597
+ );
22598
+ await reviewProposedIssueComment(`Amend ${target}`, body);
22599
+ const args = [
22600
+ "api",
22601
+ "-X",
22602
+ "PATCH",
22603
+ `repos/${owner}/${repo}/issues/comments/${commentId}`,
22604
+ "--input",
22605
+ "-"
22606
+ ];
22607
+ let raw;
22608
+ try {
22609
+ raw = execFileSync11("gh", args, {
22610
+ input: JSON.stringify({ body }),
22611
+ encoding: "utf8"
22612
+ });
22613
+ } catch {
22614
+ process.exit(1);
22615
+ }
22616
+ console.log(`Comment updated: ${commentUrl(raw) ?? target}`);
22617
+ }
22618
+ function commentUrl(raw) {
22619
+ try {
22620
+ const parsed = JSON.parse(raw);
22621
+ return parsed.html_url;
22622
+ } catch {
22623
+ return void 0;
22624
+ }
22625
+ }
22626
+
22627
+ // src/commands/github/issue/registerEditIssueComment.ts
22628
+ function registerEditIssueComment(issueCommand) {
22629
+ issueCommand.command("edit-comment <comment-id>").description(
22630
+ "Replace the body of a posted GitHub issue comment (body of - reads it from stdin)"
22631
+ ).option("--body <body>", "Replacement comment body (- reads it from stdin)").option(
22632
+ "-R, --repo <owner/repo>",
22633
+ "Target repository (defaults to the current repo)"
22634
+ ).addHelpText(
22635
+ "after",
22636
+ "\nThe comment id is the numeric id from the comment's API url or the #issuecomment-<id> anchor on github.com, not the issue number.\nThe replacement is outward-facing: write it for the repo's readers, not the team. It is rejected if it references Claude or an assist backlog item.\nIn an assist web session the replacement is previewed for approve/reject first (with inline comments); the published comment is left alone until it is approved. The replacement is the whole body \u2014 what is there now is overwritten, so fetch it first if it should be built on."
22637
+ ).action(
22638
+ async (commentId, options2) => {
22639
+ await editIssueComment(commentId, {
22640
+ ...options2,
22641
+ body: options2.body ? await readBodyArgument(options2.body) : void 0
22642
+ });
22643
+ }
22644
+ );
22645
+ }
22646
+
22517
22647
  // src/commands/registerGithubIssue.ts
22518
22648
  function registerGithubIssue(githubCommand) {
22519
22649
  const issueCommand = githubCommand.command("issue").description("GitHub issue utilities");
22520
22650
  registerCreateIssue(issueCommand);
22521
22651
  registerEditIssue(issueCommand);
22522
22652
  registerCommentIssue(issueCommand);
22653
+ registerEditIssueComment(issueCommand);
22523
22654
  registerFixStructure(issueCommand);
22524
22655
  }
22525
22656
 
@@ -22948,7 +23079,7 @@ function registerRefineLaunch(program2, resumeFlag) {
22948
23079
  import { randomUUID as randomUUID10 } from "crypto";
22949
23080
 
22950
23081
  // src/commands/review/checkoutPr.ts
22951
- import { execFileSync as execFileSync12 } from "child_process";
23082
+ import { execFileSync as execFileSync13 } from "child_process";
22952
23083
  import chalk170 from "chalk";
22953
23084
 
22954
23085
  // src/commands/sessions/daemon/daemonLog.ts
@@ -23508,10 +23639,10 @@ async function moveToPrCheckoutTree() {
23508
23639
  }
23509
23640
 
23510
23641
  // src/commands/review/prHeadBranch.ts
23511
- import { execFileSync as execFileSync11 } from "child_process";
23642
+ import { execFileSync as execFileSync12 } from "child_process";
23512
23643
  function prHeadBranch(number) {
23513
23644
  try {
23514
- const out = execFileSync11(
23645
+ const out = execFileSync12(
23515
23646
  "gh",
23516
23647
  ["pr", "view", number, "--json", "headRefName", "-q", ".headRefName"],
23517
23648
  { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
@@ -23559,7 +23690,7 @@ async function checkoutPr(number) {
23559
23690
  if (headRef && moveToExistingCheckout(number, headRef)) return;
23560
23691
  await moveToPrCheckoutTree();
23561
23692
  try {
23562
- execFileSync12("gh", ["pr", "checkout", number], { stdio: "inherit" });
23693
+ execFileSync13("gh", ["pr", "checkout", number], { stdio: "inherit" });
23563
23694
  } catch {
23564
23695
  console.error(chalk170.red(`gh pr checkout ${number} failed; aborting.`));
23565
23696
  process.exit(1);
@@ -25221,13 +25352,13 @@ async function comment2(path90, line, body, startLine) {
25221
25352
  import { randomUUID as randomUUID14 } from "crypto";
25222
25353
 
25223
25354
  // src/commands/prs/applyEdit.ts
25224
- import { execFileSync as execFileSync13 } from "child_process";
25355
+ import { execFileSync as execFileSync14 } from "child_process";
25225
25356
  function applyEdit(number, title, body) {
25226
25357
  const args = ["pr", "edit", String(number)];
25227
25358
  if (title) args.push("--title", title);
25228
25359
  args.push("--body", body);
25229
25360
  try {
25230
- execFileSync13("gh", args, { stdio: "inherit" });
25361
+ execFileSync14("gh", args, { stdio: "inherit" });
25231
25362
  } catch {
25232
25363
  process.exit(1);
25233
25364
  }
@@ -26023,7 +26154,7 @@ function buildValidatedBody(options2, usage) {
26023
26154
  }
26024
26155
 
26025
26156
  // src/commands/prs/placePr.ts
26026
- import { execFileSync as execFileSync14 } from "child_process";
26157
+ import { execFileSync as execFileSync15 } from "child_process";
26027
26158
 
26028
26159
  // src/commands/prs/buildCreateArgs.ts
26029
26160
  function buildEditArgs(number, title, body) {
@@ -26090,7 +26221,7 @@ async function recordPrActivity() {
26090
26221
  // src/commands/prs/placePr.ts
26091
26222
  function hasUpstream2() {
26092
26223
  try {
26093
- execFileSync14(
26224
+ execFileSync15(
26094
26225
  "git",
26095
26226
  ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
26096
26227
  { stdio: "pipe" }
@@ -26102,13 +26233,13 @@ function hasUpstream2() {
26102
26233
  }
26103
26234
  function ensureBranchPushed() {
26104
26235
  const args = hasUpstream2() ? ["push"] : ["push", "--set-upstream", "origin", "HEAD"];
26105
- execFileSync14("git", args, { stdio: "inherit" });
26236
+ execFileSync15("git", args, { stdio: "inherit" });
26106
26237
  }
26107
26238
  async function placePr(prNumber, title, body, options2) {
26108
26239
  const args = prNumber !== null ? buildEditArgs(prNumber, title, body) : buildCreateArgs(title, body, options2);
26109
26240
  try {
26110
26241
  if (prNumber === null && !options2.head) ensureBranchPushed();
26111
- execFileSync14("gh", args, { stdio: "inherit" });
26242
+ execFileSync15("gh", args, { stdio: "inherit" });
26112
26243
  } catch {
26113
26244
  process.exit(1);
26114
26245
  }
@@ -26271,9 +26402,9 @@ function resolveDraftState(options2, command) {
26271
26402
  }
26272
26403
 
26273
26404
  // src/commands/prs/raise.ts
26274
- var USAGE4 = "Usage: assist prs raise --title <title> --what <what> --why <why> [--how <how>] [--resolves <key>] [--force]";
26405
+ var USAGE5 = "Usage: assist prs raise --title <title> --what <what> --why <why> [--how <how>] [--resolves <key>] [--force]";
26275
26406
  async function raise(options2, command) {
26276
- const { title, body } = buildValidatedBody(options2, USAGE4);
26407
+ const { title, body } = buildValidatedBody(options2, USAGE5);
26277
26408
  const resolved = { ...options2, draft: resolveDraftState(options2, command) };
26278
26409
  const existing = findCurrentPrNumber();
26279
26410
  const sessionId = process.env.ASSIST_SESSION_ID;
@@ -31265,7 +31396,7 @@ function slackWorkingFile(channel) {
31265
31396
  }
31266
31397
 
31267
31398
  // src/commands/slack/postSlackMessage.ts
31268
- var USAGE5 = "Usage: assist slack post [channel] --body <body|->";
31399
+ var USAGE6 = "Usage: assist slack post [channel] --body <body|->";
31269
31400
  async function postSlackMessage(channelArg, options2) {
31270
31401
  const channel = (channelArg ?? loadConfig().slack?.channel ?? "").trim();
31271
31402
  const body = options2.body?.trim();
@@ -31273,11 +31404,11 @@ async function postSlackMessage(channelArg, options2) {
31273
31404
  console.error(
31274
31405
  'No channel given and slack.channel is not set. Pass one, or set a default with: assist config set slack.channel "#general"'
31275
31406
  );
31276
- console.error(USAGE5);
31407
+ console.error(USAGE6);
31277
31408
  process.exit(1);
31278
31409
  }
31279
31410
  if (!body) {
31280
- console.error(USAGE5);
31411
+ console.error(USAGE6);
31281
31412
  process.exit(1);
31282
31413
  }
31283
31414
  const threadTs = options2.thread;
@@ -31874,7 +32005,7 @@ function syncPiHooks(sourceDir) {
31874
32005
  }
31875
32006
 
31876
32007
  // src/commands/sync/syncPi.ts
31877
- function unquote(value) {
32008
+ function unquote2(value) {
31878
32009
  return value.trim().replace(/^["']|["']$/g, "");
31879
32010
  }
31880
32011
  function commandToPrompt(name, content) {
@@ -31882,9 +32013,9 @@ function commandToPrompt(name, content) {
31882
32013
  const frontmatter = match ? match[1] : "";
31883
32014
  const body = match ? content.slice(match[0].length) : content;
31884
32015
  const descriptionMatch = frontmatter.match(/^description:\s*(.*)$/m);
31885
- const description = descriptionMatch ? unquote(descriptionMatch[1]) : name;
32016
+ const description = descriptionMatch ? unquote2(descriptionMatch[1]) : name;
31886
32017
  const argsMatch = frontmatter.match(/^allowed_args:\s*(.*)$/m);
31887
- const argumentHint = argsMatch ? unquote(argsMatch[1]) : void 0;
32018
+ const argumentHint = argsMatch ? unquote2(argsMatch[1]) : void 0;
31888
32019
  const header = [
31889
32020
  "---",
31890
32021
  `description: ${description}`,
@@ -32738,9 +32869,9 @@ function registerVoice(program2) {
32738
32869
  }
32739
32870
 
32740
32871
  // src/commands/watch/resolveUpstream.ts
32741
- import { execFileSync as execFileSync15 } from "child_process";
32872
+ import { execFileSync as execFileSync16 } from "child_process";
32742
32873
  function runGit3(args, cwd) {
32743
- return execFileSync15("git", args, {
32874
+ return execFileSync16("git", args, {
32744
32875
  encoding: "utf8",
32745
32876
  stdio: ["pipe", "pipe", "pipe"],
32746
32877
  cwd
@@ -33146,13 +33277,13 @@ import { spawn as spawn9 } from "child_process";
33146
33277
  import { existsSync as existsSync80 } from "fs";
33147
33278
 
33148
33279
  // src/commands/run/resolveCommand.ts
33149
- import { execFileSync as execFileSync16 } from "child_process";
33280
+ import { execFileSync as execFileSync17 } from "child_process";
33150
33281
  import { existsSync as existsSync79 } from "fs";
33151
33282
  import { dirname as dirname37, join as join92, resolve as resolve20 } from "path";
33152
33283
  function resolveCommand2(command) {
33153
33284
  if (process.platform !== "win32" || command !== "bash") return command;
33154
33285
  try {
33155
- const gitPath = execFileSync16("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
33286
+ const gitPath = execFileSync17("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
33156
33287
  const gitRoot = resolve20(dirname37(gitPath), "..");
33157
33288
  const gitBash = join92(gitRoot, "bin", "bash.exe");
33158
33289
  if (existsSync79(gitBash)) return gitBash;
@@ -33274,11 +33405,11 @@ async function reportSyncOrExit() {
33274
33405
  }
33275
33406
 
33276
33407
  // src/commands/watch/fetchQuietly.ts
33277
- import { execFileSync as execFileSync17 } from "child_process";
33408
+ import { execFileSync as execFileSync18 } from "child_process";
33278
33409
  var MIN_FETCH_TIMEOUT_MS = 6e4;
33279
33410
  function fetchQuietly(cwd, intervalMs) {
33280
33411
  try {
33281
- execFileSync17("git", ["fetch", "--quiet"], {
33412
+ execFileSync18("git", ["fetch", "--quiet"], {
33282
33413
  stdio: ["pipe", "pipe", "pipe"],
33283
33414
  cwd,
33284
33415
  timeout: Math.max(intervalMs, MIN_FETCH_TIMEOUT_MS)
@@ -33580,7 +33711,7 @@ async function auth() {
33580
33711
  }
33581
33712
 
33582
33713
  // src/commands/roam/postRoamActivity.ts
33583
- import { execFileSync as execFileSync18 } from "child_process";
33714
+ import { execFileSync as execFileSync19 } from "child_process";
33584
33715
  import { readdirSync as readdirSync21, readFileSync as readFileSync60, statSync as statSync12 } from "fs";
33585
33716
  import { join as join93 } from "path";
33586
33717
  function findPortFile(roamDir) {
@@ -33619,7 +33750,7 @@ function postRoamActivity(app, event) {
33619
33750
  const pid = PID_BY_APP[app] ?? 99999;
33620
33751
  const url = `http://127.0.0.1:${port}/api/v1/activity/${app}/${event}?pid=${pid}`;
33621
33752
  try {
33622
- execFileSync18("curl", ["-sf", "--max-time", "0.2", "-X", "POST", url], {
33753
+ execFileSync19("curl", ["-sf", "--max-time", "0.2", "-X", "POST", url], {
33623
33754
  stdio: "ignore"
33624
33755
  });
33625
33756
  } catch {
@@ -34097,11 +34228,11 @@ function screenshot(processName) {
34097
34228
  }
34098
34229
 
34099
34230
  // src/commands/sessions/daemon/listDaemonPids.ts
34100
- import { execFileSync as execFileSync19 } from "child_process";
34231
+ import { execFileSync as execFileSync20 } from "child_process";
34101
34232
  function listDaemonPids() {
34102
34233
  if (process.platform === "win32") return [];
34103
34234
  try {
34104
- const out = execFileSync19("ps", ["-eo", "pid=,args="], {
34235
+ const out = execFileSync20("ps", ["-eo", "pid=,args="], {
34105
34236
  encoding: "utf8"
34106
34237
  });
34107
34238
  return out.split("\n").filter((line) => line.includes("assist") && / daemon run\b/.test(line)).map((line) => Number.parseInt(line.trim(), 10)).filter((pid) => Number.isInteger(pid));
@@ -39728,7 +39859,7 @@ function cleanupOwnedFiles() {
39728
39859
  import * as net3 from "net";
39729
39860
 
39730
39861
  // src/commands/sessions/daemon/findPortHolderPid.ts
39731
- import { execFileSync as execFileSync20 } from "child_process";
39862
+ import { execFileSync as execFileSync21 } from "child_process";
39732
39863
  var PROBE_TIMEOUT_MS = 3e3;
39733
39864
  function findPortHolderPid(port) {
39734
39865
  try {
@@ -39738,7 +39869,7 @@ function findPortHolderPid(port) {
39738
39869
  }
39739
39870
  }
39740
39871
  function probe(command, args) {
39741
- return execFileSync20(command, args, {
39872
+ return execFileSync21(command, args, {
39742
39873
  encoding: "utf8",
39743
39874
  timeout: PROBE_TIMEOUT_MS,
39744
39875
  stdio: ["ignore", "pipe", "ignore"]
@@ -39977,7 +40108,7 @@ function summaryPathFor(jsonlPath2) {
39977
40108
  }
39978
40109
 
39979
40110
  // src/commands/sessions/summarise/summariseSession.ts
39980
- import { execFileSync as execFileSync21 } from "child_process";
40111
+ import { execFileSync as execFileSync22 } from "child_process";
39981
40112
  function summariseSession(jsonlPath2) {
39982
40113
  const firstMessage = extractFirstUserMessage(jsonlPath2);
39983
40114
  const backlogIds = scanSessionBacklogRefs(jsonlPath2);
@@ -39986,7 +40117,7 @@ function summariseSession(jsonlPath2) {
39986
40117
  }
39987
40118
  const prompt = buildPrompt6(firstMessage, backlogIds);
39988
40119
  try {
39989
- const output = execFileSync21("claude", ["-p", "--model", "haiku", prompt], {
40120
+ const output = execFileSync22("claude", ["-p", "--model", "haiku", prompt], {
39990
40121
  encoding: "utf8",
39991
40122
  timeout: 3e4,
39992
40123
  stdio: ["ignore", "pipe", "ignore"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.615.3",
3
+ "version": "0.616.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {