@staff0rd/assist 0.572.0 → 0.573.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.
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.572.0",
9
+ version: "0.573.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -16726,6 +16726,10 @@ var BUILTIN_DENIES = [
16726
16726
  pattern: "gh issue create",
16727
16727
  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."
16728
16728
  },
16729
+ {
16730
+ pattern: "gh issue comment",
16731
+ 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."
16732
+ },
16729
16733
  {
16730
16734
  pattern: "git commit",
16731
16735
  message: `Do not run 'git commit' directly. Use 'assist commit "<message>"' instead.`
@@ -20629,7 +20633,29 @@ function commits(org, options2) {
20629
20633
  }
20630
20634
  }
20631
20635
 
20632
- // src/commands/github/issue/createIssue.ts
20636
+ // src/commands/github/parseSinceDate.ts
20637
+ import { InvalidArgumentError } from "commander";
20638
+ function parseSinceDate(value) {
20639
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
20640
+ throw new InvalidArgumentError("Expected a date in YYYY-MM-DD format.");
20641
+ }
20642
+ const date = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
20643
+ if (Number.isNaN(date.getTime()) || date.toISOString().slice(0, 10) !== value) {
20644
+ throw new InvalidArgumentError(`Not a valid calendar date: ${value}.`);
20645
+ }
20646
+ return value;
20647
+ }
20648
+
20649
+ // src/commands/github/parseTopCount.ts
20650
+ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
20651
+ function parseTopCount(value) {
20652
+ if (!/^\d+$/.test(value) || Number.parseInt(value, 10) < 1) {
20653
+ throw new InvalidArgumentError2("Expected a positive integer.");
20654
+ }
20655
+ return Number.parseInt(value, 10);
20656
+ }
20657
+
20658
+ // src/commands/github/issue/commentIssue.ts
20633
20659
  import { execFileSync as execFileSync6 } from "child_process";
20634
20660
 
20635
20661
  // src/shared/validateProposedContent.ts
@@ -20657,14 +20683,58 @@ function validateProposedContent(labels, title, body) {
20657
20683
  }
20658
20684
  }
20659
20685
 
20660
- // src/commands/github/issue/reviewProposedIssue.ts
20686
+ // src/commands/github/issue/reviewProposedIssueComment.ts
20661
20687
  import { randomUUID as randomUUID7 } from "crypto";
20688
+ async function reviewProposedIssueComment(title, body) {
20689
+ const sessionId = process.env.ASSIST_SESSION_ID;
20690
+ if (process.env.ASSIST_SESSION !== "1" || !sessionId) return;
20691
+ await awaitPreviewApproval("GitHub issue comment preview", {
20692
+ sessionId,
20693
+ requestId: randomUUID7(),
20694
+ title,
20695
+ body,
20696
+ prNumber: null,
20697
+ kind: "github-issue-comment"
20698
+ });
20699
+ }
20700
+
20701
+ // src/commands/github/issue/commentIssue.ts
20702
+ var USAGE = "Usage: assist github issue comment <number> --body <body> [-R <owner>/<repo>]";
20703
+ async function commentIssue(numberArg, options2) {
20704
+ const number = Number.parseInt(numberArg, 10);
20705
+ if (!Number.isInteger(number) || number <= 0 || !options2.body) {
20706
+ console.error(USAGE);
20707
+ process.exit(1);
20708
+ }
20709
+ const { body } = options2;
20710
+ const target = options2.repo ? `${options2.repo}#${number}` : `issue #${number}`;
20711
+ validateProposedContent(
20712
+ { subject: "Comment", context: "GitHub issues" },
20713
+ "",
20714
+ body
20715
+ );
20716
+ await reviewProposedIssueComment(`Comment on ${target}`, body);
20717
+ const args = ["issue", "comment", String(number), "--body", body];
20718
+ if (options2.repo) args.push("--repo", options2.repo);
20719
+ try {
20720
+ execFileSync6("gh", args, { stdio: "inherit" });
20721
+ } catch {
20722
+ process.exit(1);
20723
+ }
20724
+ console.log(`Comment posted to ${target}`);
20725
+ }
20726
+
20727
+ // src/commands/github/issue/createIssue.ts
20728
+ import { execFileSync as execFileSync7 } from "child_process";
20729
+
20730
+ // src/commands/github/issue/reviewProposedIssue.ts
20731
+ import { randomUUID as randomUUID8 } from "crypto";
20662
20732
  async function reviewProposedIssue(title, body) {
20663
20733
  const sessionId = process.env.ASSIST_SESSION_ID;
20664
20734
  if (process.env.ASSIST_SESSION !== "1" || !sessionId) return;
20665
20735
  await awaitPreviewApproval("GitHub issue preview", {
20666
20736
  sessionId,
20667
- requestId: randomUUID7(),
20737
+ requestId: randomUUID8(),
20668
20738
  title,
20669
20739
  body,
20670
20740
  prNumber: null,
@@ -20673,10 +20743,10 @@ async function reviewProposedIssue(title, body) {
20673
20743
  }
20674
20744
 
20675
20745
  // src/commands/github/issue/createIssue.ts
20676
- var USAGE = "Usage: assist github issue create --title <title> --body <body> [-R <owner>/<repo>]";
20746
+ var USAGE2 = "Usage: assist github issue create --title <title> --body <body> [-R <owner>/<repo>]";
20677
20747
  async function createIssue(options2) {
20678
20748
  if (!options2.title || !options2.body) {
20679
- console.error(USAGE);
20749
+ console.error(USAGE2);
20680
20750
  process.exit(1);
20681
20751
  }
20682
20752
  const { title, body } = options2;
@@ -20689,32 +20759,47 @@ async function createIssue(options2) {
20689
20759
  const args = ["issue", "create", "--title", title, "--body", body];
20690
20760
  if (options2.repo) args.push("--repo", options2.repo);
20691
20761
  try {
20692
- execFileSync6("gh", args, { stdio: "inherit" });
20762
+ execFileSync7("gh", args, { stdio: "inherit" });
20693
20763
  } catch {
20694
20764
  process.exit(1);
20695
20765
  }
20696
20766
  }
20697
20767
 
20698
- // src/commands/github/parseSinceDate.ts
20699
- import { InvalidArgumentError } from "commander";
20700
- function parseSinceDate(value) {
20701
- if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
20702
- throw new InvalidArgumentError("Expected a date in YYYY-MM-DD format.");
20703
- }
20704
- const date = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
20705
- if (Number.isNaN(date.getTime()) || date.toISOString().slice(0, 10) !== value) {
20706
- throw new InvalidArgumentError(`Not a valid calendar date: ${value}.`);
20768
+ // src/commands/prs/readBodyArgument.ts
20769
+ async function readBodyArgument(value) {
20770
+ if (value !== "-") return value;
20771
+ const body = (await readStdinBuffer()).toString("utf8").replace(/\n+$/, "");
20772
+ if (body.trim().length === 0) {
20773
+ console.error("Error: No body was provided on stdin.");
20774
+ process.exit(1);
20707
20775
  }
20708
- return value;
20776
+ return body;
20709
20777
  }
20710
20778
 
20711
- // src/commands/github/parseTopCount.ts
20712
- import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
20713
- function parseTopCount(value) {
20714
- if (!/^\d+$/.test(value) || Number.parseInt(value, 10) < 1) {
20715
- throw new InvalidArgumentError2("Expected a positive integer.");
20716
- }
20717
- return Number.parseInt(value, 10);
20779
+ // src/commands/registerGithubIssue.ts
20780
+ function registerGithubIssue(githubCommand) {
20781
+ const issueCommand = githubCommand.command("issue").description("GitHub issue utilities");
20782
+ issueCommand.command("create").description("Create a GitHub issue").option("--title <title>", "Issue title").option("--body <body>", "Issue body").option(
20783
+ "-R, --repo <owner/repo>",
20784
+ "Target repository (defaults to the current repo)"
20785
+ ).addHelpText(
20786
+ "after",
20787
+ "\nThere is no What/Why/How template: an issue reports a problem, and the target repo's own issue template is unknowable from here. Write the body as the repo's maintainers would expect.\nIn an assist web session the title and body are previewed for approve/reject first (with inline comments); nothing is created until it is approved."
20788
+ ).action(createIssue);
20789
+ issueCommand.command("comment <number>").description("Comment on a GitHub issue (body of - reads it from stdin)").option("--body <body>", "Comment body (- reads it from stdin)").option(
20790
+ "-R, --repo <owner/repo>",
20791
+ "Target repository (defaults to the current repo)"
20792
+ ).addHelpText(
20793
+ "after",
20794
+ "\nThe comment 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 body is previewed for approve/reject first (with inline comments); nothing is posted until it is approved."
20795
+ ).action(
20796
+ async (number, options2) => {
20797
+ await commentIssue(number, {
20798
+ ...options2,
20799
+ body: options2.body ? await readBodyArgument(options2.body) : void 0
20800
+ });
20801
+ }
20802
+ );
20718
20803
  }
20719
20804
 
20720
20805
  // src/commands/registerGithub.ts
@@ -20729,14 +20814,7 @@ function registerGithub(program2) {
20729
20814
  "only report the top <n> repos by commit count",
20730
20815
  parseTopCount
20731
20816
  ).option("--json", "Output as JSON").action(commits);
20732
- const issueCommand = githubCommand.command("issue").description("GitHub issue utilities");
20733
- issueCommand.command("create").description("Create a GitHub issue").option("--title <title>", "Issue title").option("--body <body>", "Issue body").option(
20734
- "-R, --repo <owner/repo>",
20735
- "Target repository (defaults to the current repo)"
20736
- ).addHelpText(
20737
- "after",
20738
- "\nThere is no What/Why/How template: an issue reports a problem, and the target repo's own issue template is unknowable from here. Write the body as the repo's maintainers would expect.\nIn an assist web session the title and body are previewed for approve/reject first (with inline comments); nothing is created until it is approved."
20739
- ).action(createIssue);
20817
+ registerGithubIssue(githubCommand);
20740
20818
  }
20741
20819
 
20742
20820
  // src/commands/handover/countPendingHandovers.ts
@@ -21146,10 +21224,10 @@ function registerRefineLaunch(program2, resumeFlag) {
21146
21224
  }
21147
21225
 
21148
21226
  // src/commands/reviewPrComments.ts
21149
- import { randomUUID as randomUUID8 } from "crypto";
21227
+ import { randomUUID as randomUUID9 } from "crypto";
21150
21228
 
21151
21229
  // src/commands/review/checkoutPr.ts
21152
- import { execFileSync as execFileSync8 } from "child_process";
21230
+ import { execFileSync as execFileSync9 } from "child_process";
21153
21231
  import chalk164 from "chalk";
21154
21232
 
21155
21233
  // src/commands/sessions/daemon/daemonLog.ts
@@ -21709,10 +21787,10 @@ async function moveToPrCheckoutTree() {
21709
21787
  }
21710
21788
 
21711
21789
  // src/commands/review/prHeadBranch.ts
21712
- import { execFileSync as execFileSync7 } from "child_process";
21790
+ import { execFileSync as execFileSync8 } from "child_process";
21713
21791
  function prHeadBranch(number) {
21714
21792
  try {
21715
- const out = execFileSync7(
21793
+ const out = execFileSync8(
21716
21794
  "gh",
21717
21795
  ["pr", "view", number, "--json", "headRefName", "-q", ".headRefName"],
21718
21796
  { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
@@ -21760,7 +21838,7 @@ async function checkoutPr(number) {
21760
21838
  if (headRef && moveToExistingCheckout(number, headRef)) return;
21761
21839
  await moveToPrCheckoutTree();
21762
21840
  try {
21763
- execFileSync8("gh", ["pr", "checkout", number], { stdio: "inherit" });
21841
+ execFileSync9("gh", ["pr", "checkout", number], { stdio: "inherit" });
21764
21842
  } catch {
21765
21843
  console.error(chalk164.red(`gh pr checkout ${number} failed; aborting.`));
21766
21844
  process.exit(1);
@@ -21782,7 +21860,7 @@ async function reviewPrComments(number, options2 = {}) {
21782
21860
  const resumeSessionId = options2.resumeSessionId;
21783
21861
  validateAnnounce(number, announce);
21784
21862
  if (number && !resumeSessionId) await checkoutPr(number);
21785
- const claudeSessionId = resumeSessionId ?? randomUUID8();
21863
+ const claudeSessionId = resumeSessionId ?? randomUUID9();
21786
21864
  emitActivity({
21787
21865
  kind: "command",
21788
21866
  name: "review-pr-comments",
@@ -21800,14 +21878,14 @@ async function reviewPrComments(number, options2 = {}) {
21800
21878
  }
21801
21879
 
21802
21880
  // src/commands/fixConflict.ts
21803
- import { randomUUID as randomUUID9 } from "crypto";
21881
+ import { randomUUID as randomUUID10 } from "crypto";
21804
21882
  function buildPrompt3(rebase) {
21805
21883
  return rebase ? "/fix-conflict --rebase" : "/fix-conflict";
21806
21884
  }
21807
21885
  async function fixConflict(number, options2 = {}) {
21808
21886
  const { resumeSessionId } = options2;
21809
21887
  if (number && !resumeSessionId) await checkoutPr(number);
21810
- const claudeSessionId = resumeSessionId ?? randomUUID9();
21888
+ const claudeSessionId = resumeSessionId ?? randomUUID10();
21811
21889
  emitActivity({
21812
21890
  kind: "command",
21813
21891
  name: "fix-conflict",
@@ -22829,13 +22907,13 @@ function postReviewComment(vars) {
22829
22907
  }
22830
22908
 
22831
22909
  // src/commands/prs/reviewProposedPrComment.ts
22832
- import { randomUUID as randomUUID10 } from "crypto";
22910
+ import { randomUUID as randomUUID11 } from "crypto";
22833
22911
  async function reviewProposedPrComment(title, body, prNumber) {
22834
22912
  const sessionId = process.env.ASSIST_SESSION_ID;
22835
22913
  if (process.env.ASSIST_SESSION !== "1" || !sessionId) return;
22836
22914
  await awaitPreviewApproval("PR comment preview", {
22837
22915
  sessionId,
22838
- requestId: randomUUID10(),
22916
+ requestId: randomUUID11(),
22839
22917
  title,
22840
22918
  body,
22841
22919
  prNumber,
@@ -22961,7 +23039,7 @@ async function comment2(path80, line, body, startLine) {
22961
23039
  }
22962
23040
 
22963
23041
  // src/commands/prs/edit.ts
22964
- import { randomUUID as randomUUID11 } from "crypto";
23042
+ import { randomUUID as randomUUID12 } from "crypto";
22965
23043
 
22966
23044
  // src/commands/prs/appendScreenshots.ts
22967
23045
  function appendScreenshots(body, screenshots) {
@@ -22974,13 +23052,13 @@ ${screenshots.join("\n\n")}`;
22974
23052
  }
22975
23053
 
22976
23054
  // src/commands/prs/applyEdit.ts
22977
- import { execFileSync as execFileSync9 } from "child_process";
23055
+ import { execFileSync as execFileSync10 } from "child_process";
22978
23056
  function applyEdit(number, title, body) {
22979
23057
  const args = ["pr", "edit", String(number)];
22980
23058
  if (title) args.push("--title", title);
22981
23059
  args.push("--body", body);
22982
23060
  try {
22983
- execFileSync9("gh", args, { stdio: "inherit" });
23061
+ execFileSync10("gh", args, { stdio: "inherit" });
22984
23062
  } catch {
22985
23063
  process.exit(1);
22986
23064
  }
@@ -23158,7 +23236,7 @@ async function edit(options2) {
23158
23236
  if (process.env.ASSIST_SESSION === "1" && sessionId) {
23159
23237
  const decision = await awaitPreviewApproval("PR preview", {
23160
23238
  sessionId,
23161
- requestId: randomUUID11(),
23239
+ requestId: randomUUID12(),
23162
23240
  title: options2.title ?? title,
23163
23241
  body: newBody,
23164
23242
  prNumber: number
@@ -23776,7 +23854,7 @@ function buildValidatedBody(options2, usage) {
23776
23854
  }
23777
23855
 
23778
23856
  // src/commands/prs/placePr.ts
23779
- import { execFileSync as execFileSync10 } from "child_process";
23857
+ import { execFileSync as execFileSync11 } from "child_process";
23780
23858
 
23781
23859
  // src/commands/prs/buildCreateArgs.ts
23782
23860
  function buildEditArgs(number, title, body) {
@@ -23843,7 +23921,7 @@ async function recordPrActivity() {
23843
23921
  // src/commands/prs/placePr.ts
23844
23922
  function hasUpstream2() {
23845
23923
  try {
23846
- execFileSync10(
23924
+ execFileSync11(
23847
23925
  "git",
23848
23926
  ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
23849
23927
  { stdio: "pipe" }
@@ -23855,13 +23933,13 @@ function hasUpstream2() {
23855
23933
  }
23856
23934
  function ensureBranchPushed() {
23857
23935
  const args = hasUpstream2() ? ["push"] : ["push", "--set-upstream", "origin", "HEAD"];
23858
- execFileSync10("git", args, { stdio: "inherit" });
23936
+ execFileSync11("git", args, { stdio: "inherit" });
23859
23937
  }
23860
23938
  async function placePr(prNumber, title, body, options2) {
23861
23939
  const args = prNumber !== null ? buildEditArgs(prNumber, title, body) : buildCreateArgs(title, body, options2);
23862
23940
  try {
23863
23941
  if (prNumber === null && !options2.head) ensureBranchPushed();
23864
- execFileSync10("gh", args, { stdio: "inherit" });
23942
+ execFileSync11("gh", args, { stdio: "inherit" });
23865
23943
  } catch {
23866
23944
  process.exit(1);
23867
23945
  }
@@ -23869,7 +23947,7 @@ async function placePr(prNumber, title, body, options2) {
23869
23947
  }
23870
23948
 
23871
23949
  // src/commands/prs/previewAndPlace.ts
23872
- import { randomUUID as randomUUID12 } from "crypto";
23950
+ import { randomUUID as randomUUID13 } from "crypto";
23873
23951
 
23874
23952
  // src/commands/sessions/shared/requestSession.ts
23875
23953
  function parseIncoming(line, type) {
@@ -24004,7 +24082,7 @@ function warn(reason4) {
24004
24082
  async function previewAndPlace(args) {
24005
24083
  const decision = await awaitPreviewApproval("PR preview", {
24006
24084
  sessionId: args.sessionId,
24007
- requestId: randomUUID12(),
24085
+ requestId: randomUUID13(),
24008
24086
  title: args.title,
24009
24087
  body: args.body,
24010
24088
  prNumber: args.prNumber,
@@ -24024,9 +24102,9 @@ function resolveDraftState(options2, command) {
24024
24102
  }
24025
24103
 
24026
24104
  // src/commands/prs/raise.ts
24027
- var USAGE2 = "Usage: assist prs raise --title <title> --what <what> --why <why> [--how <how>] [--resolves <key>] [--force]";
24105
+ var USAGE3 = "Usage: assist prs raise --title <title> --what <what> --why <why> [--how <how>] [--resolves <key>] [--force]";
24028
24106
  async function raise(options2, command) {
24029
- const { title, body } = buildValidatedBody(options2, USAGE2);
24107
+ const { title, body } = buildValidatedBody(options2, USAGE3);
24030
24108
  const resolved = { ...options2, draft: resolveDraftState(options2, command) };
24031
24109
  const existing = findCurrentPrNumber();
24032
24110
  const sessionId = process.env.ASSIST_SESSION_ID;
@@ -24125,17 +24203,6 @@ async function wontfix(commentId, reason4) {
24125
24203
  }
24126
24204
  }
24127
24205
 
24128
- // src/commands/prs/readBodyArgument.ts
24129
- async function readBodyArgument(value) {
24130
- if (value !== "-") return value;
24131
- const body = (await readStdinBuffer()).toString("utf8").replace(/\n+$/, "");
24132
- if (body.trim().length === 0) {
24133
- console.error("Error: No body was provided on stdin.");
24134
- process.exit(1);
24135
- }
24136
- return body;
24137
- }
24138
-
24139
24206
  // src/commands/registerPrsComments.ts
24140
24207
  function registerPrsComments(prsCommand) {
24141
24208
  prsCommand.command("list-comments").description("List all comments on the current branch's pull request").action(() => {
@@ -26487,10 +26554,10 @@ function registerRefactor(program2) {
26487
26554
  }
26488
26555
 
26489
26556
  // src/commands/review/checkoutOnlySession.ts
26490
- import { randomUUID as randomUUID13 } from "crypto";
26557
+ import { randomUUID as randomUUID14 } from "crypto";
26491
26558
  async function checkoutOnlySession(number) {
26492
26559
  await checkoutPr(number);
26493
- const claudeSessionId = randomUUID13();
26560
+ const claudeSessionId = randomUUID14();
26494
26561
  emitActivity({ kind: "command", name: "review", claudeSessionId });
26495
26562
  const { done: done2 } = spawnClaude("", {
26496
26563
  permissionMode: "acceptEdits",
@@ -30122,9 +30189,9 @@ function registerVoice(program2) {
30122
30189
  import { join as join83 } from "path";
30123
30190
 
30124
30191
  // src/commands/watch/resolveUpstream.ts
30125
- import { execFileSync as execFileSync11 } from "child_process";
30192
+ import { execFileSync as execFileSync12 } from "child_process";
30126
30193
  function runGit2(args, cwd) {
30127
- return execFileSync11("git", args, {
30194
+ return execFileSync12("git", args, {
30128
30195
  encoding: "utf8",
30129
30196
  stdio: ["pipe", "pipe", "pipe"],
30130
30197
  cwd
@@ -30485,13 +30552,13 @@ import { spawn as spawn9 } from "child_process";
30485
30552
  import { existsSync as existsSync68 } from "fs";
30486
30553
 
30487
30554
  // src/commands/run/resolveCommand.ts
30488
- import { execFileSync as execFileSync12 } from "child_process";
30555
+ import { execFileSync as execFileSync13 } from "child_process";
30489
30556
  import { existsSync as existsSync67 } from "fs";
30490
30557
  import { dirname as dirname35, join as join84, resolve as resolve19 } from "path";
30491
30558
  function resolveCommand2(command) {
30492
30559
  if (process.platform !== "win32" || command !== "bash") return command;
30493
30560
  try {
30494
- const gitPath = execFileSync12("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
30561
+ const gitPath = execFileSync13("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
30495
30562
  const gitRoot = resolve19(dirname35(gitPath), "..");
30496
30563
  const gitBash = join84(gitRoot, "bin", "bash.exe");
30497
30564
  if (existsSync67(gitBash)) return gitBash;
@@ -30588,11 +30655,11 @@ async function reportBuildOrExit(entry) {
30588
30655
  }
30589
30656
 
30590
30657
  // src/commands/watch/fetchQuietly.ts
30591
- import { execFileSync as execFileSync13 } from "child_process";
30658
+ import { execFileSync as execFileSync14 } from "child_process";
30592
30659
  var MIN_FETCH_TIMEOUT_MS = 6e4;
30593
30660
  function fetchQuietly(cwd, intervalMs) {
30594
30661
  try {
30595
- execFileSync13("git", ["fetch", "--quiet"], {
30662
+ execFileSync14("git", ["fetch", "--quiet"], {
30596
30663
  stdio: ["pipe", "pipe", "pipe"],
30597
30664
  cwd,
30598
30665
  timeout: Math.max(intervalMs, MIN_FETCH_TIMEOUT_MS)
@@ -30892,7 +30959,7 @@ async function auth() {
30892
30959
  }
30893
30960
 
30894
30961
  // src/commands/roam/postRoamActivity.ts
30895
- import { execFileSync as execFileSync14 } from "child_process";
30962
+ import { execFileSync as execFileSync15 } from "child_process";
30896
30963
  import { readdirSync as readdirSync20, readFileSync as readFileSync52, statSync as statSync11 } from "fs";
30897
30964
  import { join as join85 } from "path";
30898
30965
  function findPortFile(roamDir) {
@@ -30925,7 +30992,7 @@ function postRoamActivity(app, event) {
30925
30992
  }
30926
30993
  const url = `http://127.0.0.1:${port}/api/v1/activity/${app}/${event}?pid=${app === "codex" ? 99998 : 99999}`;
30927
30994
  try {
30928
- execFileSync14("curl", ["-sf", "--max-time", "0.2", "-X", "POST", url], {
30995
+ execFileSync15("curl", ["-sf", "--max-time", "0.2", "-X", "POST", url], {
30929
30996
  stdio: "ignore"
30930
30997
  });
30931
30998
  } catch {
@@ -31401,11 +31468,11 @@ function screenshot(processName) {
31401
31468
  }
31402
31469
 
31403
31470
  // src/commands/sessions/daemon/listDaemonPids.ts
31404
- import { execFileSync as execFileSync15 } from "child_process";
31471
+ import { execFileSync as execFileSync16 } from "child_process";
31405
31472
  function listDaemonPids() {
31406
31473
  if (process.platform === "win32") return [];
31407
31474
  try {
31408
- const out = execFileSync15("ps", ["-eo", "pid=,args="], {
31475
+ const out = execFileSync16("ps", ["-eo", "pid=,args="], {
31409
31476
  encoding: "utf8"
31410
31477
  });
31411
31478
  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));
@@ -31450,17 +31517,9 @@ function applyLine(result, pending, line) {
31450
31517
  }
31451
31518
  }
31452
31519
 
31453
- // src/commands/sessions/daemon/reportStolenSocket.ts
31520
+ // src/commands/sessions/daemon/readDaemonPidFile.ts
31454
31521
  import { readFileSync as readFileSync53 } from "fs";
31455
- function reportStolenSocket(socketPid) {
31456
- if (!socketPid) return;
31457
- const filePid = readPidFile();
31458
- if (filePid === void 0 || filePid === socketPid) return;
31459
- console.error(
31460
- `Warning: daemon.pid records PID ${filePid} but the socket is owned by PID ${socketPid} (stolen socket)`
31461
- );
31462
- }
31463
- function readPidFile() {
31522
+ function readDaemonPidFile() {
31464
31523
  try {
31465
31524
  const pid = Number.parseInt(
31466
31525
  readFileSync53(daemonPaths.pid, "utf8").trim(),
@@ -31471,6 +31530,24 @@ function readPidFile() {
31471
31530
  return void 0;
31472
31531
  }
31473
31532
  }
31533
+ function isPidAlive(pid) {
31534
+ try {
31535
+ process.kill(pid, 0);
31536
+ return true;
31537
+ } catch (error) {
31538
+ return error.code === "EPERM";
31539
+ }
31540
+ }
31541
+
31542
+ // src/commands/sessions/daemon/reportStolenSocket.ts
31543
+ function reportStolenSocket(socketPid) {
31544
+ if (!socketPid) return;
31545
+ const filePid = readDaemonPidFile();
31546
+ if (filePid === void 0 || filePid === socketPid) return;
31547
+ console.error(
31548
+ `Warning: daemon.pid records PID ${filePid} but the socket is owned by PID ${socketPid} (stolen socket)`
31549
+ );
31550
+ }
31474
31551
 
31475
31552
  // src/commands/sessions/daemon/daemonStatus.ts
31476
31553
  async function daemonStatus() {
@@ -31852,7 +31929,7 @@ var ClientHub = class extends Set {
31852
31929
  };
31853
31930
 
31854
31931
  // src/commands/sessions/daemon/createSession.ts
31855
- import { randomUUID as randomUUID14 } from "crypto";
31932
+ import { randomUUID as randomUUID15 } from "crypto";
31856
31933
 
31857
31934
  // src/commands/sessions/daemon/sessionBase.ts
31858
31935
  function sessionBase(id, status3) {
@@ -32048,7 +32125,7 @@ function spawnRun(opts) {
32048
32125
  function createSession(id, { prompt, cwd, design, auto, harness, holdPty } = {}) {
32049
32126
  if (harness && harness !== "claude")
32050
32127
  return createHarnessSession(id, harness, prompt, cwd, holdPty);
32051
- const claudeSessionId = randomUUID14();
32128
+ const claudeSessionId = randomUUID15();
32052
32129
  return {
32053
32130
  ...sessionBase(id, prompt ? "running" : "waiting"),
32054
32131
  name: `Session ${id}`,
@@ -32309,7 +32386,10 @@ function holdFailedDiscard(session, reason4, notify2) {
32309
32386
  // src/commands/sessions/daemon/killPtyTree.ts
32310
32387
  function killPtyTree(pty2) {
32311
32388
  if (process.platform === "win32") {
32312
- pty2.kill();
32389
+ try {
32390
+ pty2.kill();
32391
+ } catch {
32392
+ }
32313
32393
  return;
32314
32394
  }
32315
32395
  try {
@@ -32641,7 +32721,8 @@ var PREVIEW_KINDS = [
32641
32721
  "backlog-item",
32642
32722
  "backlog-comment",
32643
32723
  "pr-comment",
32644
- "github-issue"
32724
+ "github-issue",
32725
+ "github-issue-comment"
32645
32726
  ];
32646
32727
  function isPreviewKind(value) {
32647
32728
  return PREVIEW_KINDS.includes(value);
@@ -32651,6 +32732,7 @@ function isPreviewKind(value) {
32651
32732
  function previewTargetLabel(kind, itemType, prNumber, draft) {
32652
32733
  if (kind === "backlog-comment") return "backlog comment";
32653
32734
  if (kind === "pr-comment") return "pr comment";
32735
+ if (kind === "github-issue-comment") return "github issue comment";
32654
32736
  if (kind === "github-issue") return "github issue";
32655
32737
  if (kind === "backlog-item") return `backlog ${itemType}`;
32656
32738
  if (prNumber !== null) return `edit #${prNumber}`;
@@ -34111,7 +34193,7 @@ function codexRespawnPlan(session) {
34111
34193
  }
34112
34194
 
34113
34195
  // src/commands/sessions/daemon/interactiveRespawnPlan.ts
34114
- import { randomUUID as randomUUID15 } from "crypto";
34196
+ import { randomUUID as randomUUID16 } from "crypto";
34115
34197
  function interactiveRespawnPlan(session, resumes) {
34116
34198
  const { claudeSessionId, cwd, initialPrompt, design, auto } = session;
34117
34199
  if (!resumes) return null;
@@ -34131,7 +34213,7 @@ function interactiveRespawnPlan(session, resumes) {
34131
34213
  return null;
34132
34214
  }
34133
34215
  function freshClaudePlan(session, prompt, cwd) {
34134
- const claudeSessionId = randomUUID15();
34216
+ const claudeSessionId = randomUUID16();
34135
34217
  return {
34136
34218
  spawn: () => {
34137
34219
  session.claudeSessionId = claudeSessionId;
@@ -35016,10 +35098,10 @@ function startReusedRunPty(session, assistArgs, itemId2, hold, clients, onStatus
35016
35098
  }
35017
35099
 
35018
35100
  // src/commands/sessions/daemon/createWatcherSession.ts
35019
- import { randomUUID as randomUUID16 } from "crypto";
35101
+ import { randomUUID as randomUUID17 } from "crypto";
35020
35102
  var WATCH_PROMPT = "/watch";
35021
35103
  function createWatcherSession(id, cwd) {
35022
- const claudeSessionId = randomUUID16();
35104
+ const claudeSessionId = randomUUID17();
35023
35105
  return {
35024
35106
  ...sessionBase(id, "running"),
35025
35107
  name: `Session ${id}`,
@@ -35208,9 +35290,23 @@ function reuseSessionForRun(session, itemId2, clients, onStatusChange, tree) {
35208
35290
  // src/commands/sessions/daemon/shutdownSessions.ts
35209
35291
  function shutdownSessions(sessions) {
35210
35292
  daemonLog(`shutting down: killing ${sessions.size} session(s)`);
35293
+ let failures = 0;
35211
35294
  for (const session of sessions.values()) {
35212
- if (session.status !== "done") session.pty?.kill();
35295
+ if (session.status === "done") continue;
35296
+ try {
35297
+ session.pty?.kill();
35298
+ } catch (error) {
35299
+ failures++;
35300
+ const reason4 = error instanceof Error ? error.message : String(error);
35301
+ daemonLog(
35302
+ `shutting down: killing session ${session.name} (${session.id}) failed: ${reason4}`
35303
+ );
35304
+ }
35213
35305
  }
35306
+ if (failures > 0)
35307
+ daemonLog(
35308
+ `shutting down: ${failures} session(s) failed to die; continuing so the process still exits and releases its listeners`
35309
+ );
35214
35310
  }
35215
35311
 
35216
35312
  // src/commands/sessions/daemon/heldStartBlocked.ts
@@ -36711,8 +36807,13 @@ function handleFetchTranscript(client, _manager, data) {
36711
36807
  );
36712
36808
  }
36713
36809
  async function handleShutdown(client, manager) {
36714
- await manager.flushActiveMs();
36715
- manager.shutdown();
36810
+ try {
36811
+ await manager.flushActiveMs();
36812
+ manager.shutdown();
36813
+ } catch (error) {
36814
+ const reason4 = error instanceof Error ? error.message : String(error);
36815
+ daemonLog(`shutdown teardown failed: ${reason4}; exiting anyway`);
36816
+ }
36716
36817
  sendTo(client, { type: "shutting-down" });
36717
36818
  setImmediate(() => process.exit(0));
36718
36819
  }
@@ -36903,7 +37004,12 @@ function onListening(manager, checkAutoExit) {
36903
37004
  startPidFileWatchdog(() => {
36904
37005
  daemonLog("lost daemon.pid ownership; shutting down sessions and exiting");
36905
37006
  void manager.flushActiveMs().finally(() => {
36906
- manager.shutdown();
37007
+ try {
37008
+ manager.shutdown();
37009
+ } catch (error) {
37010
+ const reason4 = error instanceof Error ? error.message : String(error);
37011
+ daemonLog(`shutdown teardown failed: ${reason4}; exiting anyway`);
37012
+ }
36907
37013
  process.exit(0);
36908
37014
  });
36909
37015
  });
@@ -36933,6 +37039,45 @@ function cleanupOwnedFiles() {
36933
37039
 
36934
37040
  // src/commands/sessions/daemon/startWindowsBridge.ts
36935
37041
  import * as net3 from "net";
37042
+
37043
+ // src/commands/sessions/daemon/findPortHolderPid.ts
37044
+ import { execFileSync as execFileSync17 } from "child_process";
37045
+ var PROBE_TIMEOUT_MS = 3e3;
37046
+ function findPortHolderPid(port) {
37047
+ try {
37048
+ return process.platform === "win32" ? netstatListenerPid(probe("netstat", ["-ano"]), port) : firstPid(probe("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"]));
37049
+ } catch {
37050
+ return void 0;
37051
+ }
37052
+ }
37053
+ function probe(command, args) {
37054
+ return execFileSync17(command, args, {
37055
+ encoding: "utf8",
37056
+ timeout: PROBE_TIMEOUT_MS,
37057
+ stdio: ["ignore", "pipe", "ignore"]
37058
+ });
37059
+ }
37060
+ function netstatListenerPid(output, port) {
37061
+ const suffix = `:${port}`;
37062
+ for (const line of output.split("\n")) {
37063
+ const [protocol, local, , state, pid] = line.trim().split(/\s+/);
37064
+ if (protocol?.toUpperCase() !== "TCP") continue;
37065
+ if (state?.toUpperCase() !== "LISTENING") continue;
37066
+ if (!local?.endsWith(suffix)) continue;
37067
+ const holder = Number.parseInt(pid ?? "", 10);
37068
+ if (Number.isInteger(holder)) return holder;
37069
+ }
37070
+ return void 0;
37071
+ }
37072
+ function firstPid(output) {
37073
+ for (const line of output.split("\n")) {
37074
+ const pid = Number.parseInt(line.trim(), 10);
37075
+ if (Number.isInteger(pid)) return pid;
37076
+ }
37077
+ return void 0;
37078
+ }
37079
+
37080
+ // src/commands/sessions/daemon/startWindowsBridge.ts
36936
37081
  var BIND_ATTEMPTS = 3;
36937
37082
  var BIND_RETRY_DELAY_MS = 250;
36938
37083
  var KEEPALIVE_PROBE_MS2 = 1e4;
@@ -36950,10 +37095,14 @@ async function startWindowsBridge(manager) {
36950
37095
  if (attempt < BIND_ATTEMPTS) await delay3(BIND_RETRY_DELAY_MS);
36951
37096
  }
36952
37097
  daemonLog(
36953
- `${WINDOWS_BRIDGE_FAILURE_PREFIX} could not bind port ${port} after ${BIND_ATTEMPTS} attempts; the port is held by another process or reserved by a Hyper-V/WSL dynamic port range \u2014 set sessions.windowsDaemonPort to a free port outside 49152-65535`
37098
+ `${WINDOWS_BRIDGE_FAILURE_PREFIX} could not bind port ${port} after ${BIND_ATTEMPTS} attempts; ${describePortHolder(port)}`
36954
37099
  );
36955
37100
  return false;
36956
37101
  }
37102
+ function describePortHolder(port) {
37103
+ const holder = findPortHolderPid(port);
37104
+ return holder !== void 0 && holder !== process.pid ? `PID ${holder} is listening on it \u2014 kill PID ${holder} to free the port` : "the port is held by another process or reserved by a Hyper-V/WSL dynamic port range \u2014 set sessions.windowsDaemonPort to a free port outside 49152-65535";
37105
+ }
36957
37106
  function bindBridge(manager, port) {
36958
37107
  return new Promise((resolve24) => {
36959
37108
  const bridge = net3.createServer((socket) => {
@@ -36981,6 +37130,14 @@ function delay3(ms) {
36981
37130
  return new Promise((resolve24) => setTimeout(resolve24, ms));
36982
37131
  }
36983
37132
 
37133
+ // src/commands/sessions/daemon/describeWedgedHolder.ts
37134
+ function describeWedgedHolder() {
37135
+ const recorded = readDaemonPidFile();
37136
+ if (recorded !== void 0 && recorded !== process.pid && isPidAlive(recorded))
37137
+ return `daemon.pid records PID ${recorded}, which is still alive \u2014 kill PID ${recorded} to free the pipe and the windows bridge port`;
37138
+ return "the holding process could not be identified \u2014 look for a stray assist daemon and kill it";
37139
+ }
37140
+
36984
37141
  // src/commands/sessions/daemon/startDaemonServer.ts
36985
37142
  async function startDaemonServer(manager, checkAutoExit) {
36986
37143
  if (process.platform === "win32" && !await startWindowsBridge(manager)) {
@@ -37013,12 +37170,17 @@ async function recoverFromAddrInUse(server, manager, checkAutoExit) {
37013
37170
  daemonLog("another daemon owns the socket; exiting");
37014
37171
  process.exit(1);
37015
37172
  }
37173
+ if (process.platform === "win32") {
37174
+ daemonLog(
37175
+ `${daemonPaths.socket} is bound but not answering: ${describeWedgedHolder()}; a named pipe cannot be removed, so exiting instead of retrying the bind`
37176
+ );
37177
+ exitAfterFlush(1);
37178
+ return;
37179
+ }
37016
37180
  daemonLog("removing stale socket left by a crashed daemon");
37017
- if (process.platform !== "win32") {
37018
- try {
37019
- unlinkSync24(daemonPaths.socket);
37020
- } catch {
37021
- }
37181
+ try {
37182
+ unlinkSync24(daemonPaths.socket);
37183
+ } catch {
37022
37184
  }
37023
37185
  listenWithSingleOnListening(server, manager, checkAutoExit);
37024
37186
  }
@@ -37096,7 +37258,7 @@ function summaryPathFor(jsonlPath2) {
37096
37258
  }
37097
37259
 
37098
37260
  // src/commands/sessions/summarise/summariseSession.ts
37099
- import { execFileSync as execFileSync16 } from "child_process";
37261
+ import { execFileSync as execFileSync18 } from "child_process";
37100
37262
  function summariseSession(jsonlPath2) {
37101
37263
  const firstMessage = extractFirstUserMessage(jsonlPath2);
37102
37264
  const backlogIds = scanSessionBacklogRefs(jsonlPath2);
@@ -37105,7 +37267,7 @@ function summariseSession(jsonlPath2) {
37105
37267
  }
37106
37268
  const prompt = buildPrompt6(firstMessage, backlogIds);
37107
37269
  try {
37108
- const output = execFileSync16("claude", ["-p", "--model", "haiku", prompt], {
37270
+ const output = execFileSync18("claude", ["-p", "--model", "haiku", prompt], {
37109
37271
  encoding: "utf8",
37110
37272
  timeout: 3e4,
37111
37273
  stdio: ["ignore", "pipe", "ignore"]