@staff0rd/assist 0.496.2 → 0.498.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.496.2",
9
+ version: "0.498.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -19738,7 +19738,18 @@ async function checkoutPr(number) {
19738
19738
  }
19739
19739
 
19740
19740
  // src/commands/reviewPrComments.ts
19741
- async function reviewPrComments(number) {
19741
+ function buildPrompt2(number, announce) {
19742
+ if (!announce) return "/review-pr-comments";
19743
+ return `/review-pr-comments --announce ${number}`;
19744
+ }
19745
+ function validateAnnounce(number, announce) {
19746
+ if (!announce || number) return;
19747
+ console.error("Error: --announce requires a PR number.");
19748
+ process.exit(1);
19749
+ }
19750
+ async function reviewPrComments(number, options2 = {}) {
19751
+ const announce = options2.announce === true;
19752
+ validateAnnounce(number, announce);
19742
19753
  if (number) await checkoutPr(number);
19743
19754
  const claudeSessionId = randomUUID6();
19744
19755
  emitActivity({
@@ -19746,7 +19757,7 @@ async function reviewPrComments(number) {
19746
19757
  name: "review-pr-comments",
19747
19758
  claudeSessionId
19748
19759
  });
19749
- const { done: done2 } = spawnClaude("/review-pr-comments", {
19760
+ const { done: done2 } = spawnClaude(buildPrompt2(number, announce), {
19750
19761
  permissionMode: "acceptEdits",
19751
19762
  sessionId: claudeSessionId
19752
19763
  });
@@ -19774,7 +19785,12 @@ function registerDescriptionLaunch(program2, name, slashCommand, description, al
19774
19785
  if (alias) command.alias(alias);
19775
19786
  }
19776
19787
  function registerReviewPrComments(program2) {
19777
- program2.command("review-pr-comments").argument("[number]", "PR number to check out first").description("Launch Claude in /review-pr-comments mode (single session)").action((number) => reviewPrComments(number));
19788
+ program2.command("review-pr-comments").argument("[number]", "PR number to check out first").description("Launch Claude in /review-pr-comments mode (single session)").option(
19789
+ "--announce",
19790
+ "After every comment thread is processed, announce the PR in Slack via /prs-slack <number> --no-confirm (requires a PR number)"
19791
+ ).action(
19792
+ (number, opts) => reviewPrComments(number, opts)
19793
+ );
19778
19794
  }
19779
19795
  function registerLaunch(program2) {
19780
19796
  registerNext(program2);
@@ -24323,22 +24339,19 @@ function gatherContext() {
24323
24339
  };
24324
24340
  }
24325
24341
 
24326
- // src/commands/review/postReviewToPr.ts
24327
- import { readFileSync as readFileSync41 } from "fs";
24328
-
24329
- // src/commands/sessions/shared/requestAssistSession.ts
24330
- function parseIncoming(line) {
24342
+ // src/commands/sessions/shared/requestSession.ts
24343
+ function parseIncoming(line, type) {
24331
24344
  try {
24332
24345
  const msg = JSON.parse(line);
24333
- if (msg.type === "error" && (msg.message ?? "").includes("create-assist"))
24334
- return { kind: "error", message: msg.message ?? "create-assist failed" };
24346
+ if (msg.type === "error" && (msg.message ?? "").includes(type))
24347
+ return { kind: "error", message: msg.message ?? `${type} failed` };
24335
24348
  if (msg.type !== "created" || !msg.sessionId) return null;
24336
24349
  return { kind: "created", sessionId: msg.sessionId };
24337
24350
  } catch {
24338
24351
  return null;
24339
24352
  }
24340
24353
  }
24341
- function requestAssistSession(assistArgs, cwd) {
24354
+ function requestSession(message3) {
24342
24355
  return new Promise((resolve22, reject) => {
24343
24356
  connectToDaemon().then((socket) => {
24344
24357
  let settled = false;
@@ -24350,7 +24363,7 @@ function requestAssistSession(assistArgs, cwd) {
24350
24363
  else resolve22(sessionId);
24351
24364
  };
24352
24365
  readSocketLines(socket, (line) => {
24353
- const incoming = parseIncoming(line);
24366
+ const incoming = parseIncoming(line, message3.type);
24354
24367
  if (!incoming) return;
24355
24368
  if (incoming.kind === "error") finish(new Error(incoming.message));
24356
24369
  else finish(void 0, incoming.sessionId);
@@ -24360,30 +24373,69 @@ function requestAssistSession(assistArgs, cwd) {
24360
24373
  "close",
24361
24374
  () => finish(new Error("daemon closed before the session was created"))
24362
24375
  );
24363
- socket.write(
24364
- `${JSON.stringify({ type: "create-assist", assistArgs, cwd })}
24365
- `
24366
- );
24376
+ socket.write(`${JSON.stringify(message3)}
24377
+ `);
24367
24378
  }, reject);
24368
24379
  });
24369
24380
  }
24370
24381
 
24371
- // src/commands/review/chainAddressComments.ts
24372
- async function chainAddressComments(prNumber) {
24382
+ // src/commands/sessions/shared/requestClaudeSession.ts
24383
+ function requestClaudeSession(prompt, cwd) {
24384
+ return requestSession({ type: "create", prompt, cwd });
24385
+ }
24386
+
24387
+ // src/commands/review/startChainedSession.ts
24388
+ async function startChainedSession(label2, start3) {
24373
24389
  if (process.env.ASSIST_SESSION !== "1") return;
24374
24390
  try {
24375
- await requestAssistSession(
24376
- ["review-pr-comments", String(prNumber)],
24377
- process.cwd()
24378
- );
24379
- console.log(`Started an Address Comments session for PR #${prNumber}.`);
24391
+ await start3();
24392
+ console.log(`Started ${label2}.`);
24380
24393
  } catch (error) {
24381
24394
  console.error(
24382
- `Warning: could not start an Address Comments session: ${error instanceof Error ? error.message : String(error)}`
24395
+ `Warning: could not start ${label2}: ${error instanceof Error ? error.message : String(error)}`
24383
24396
  );
24384
24397
  }
24385
24398
  }
24386
24399
 
24400
+ // src/commands/review/announcePr.ts
24401
+ function announcePr(prNumber) {
24402
+ return startChainedSession(
24403
+ `a Slack announce session for PR #${prNumber}`,
24404
+ () => requestClaudeSession(
24405
+ `/prs-slack ${prNumber} --no-confirm`,
24406
+ process.cwd()
24407
+ )
24408
+ );
24409
+ }
24410
+
24411
+ // src/commands/review/postReviewToPr.ts
24412
+ import { readFileSync as readFileSync41 } from "fs";
24413
+
24414
+ // src/commands/sessions/shared/requestAssistSession.ts
24415
+ function requestAssistSession(assistArgs, cwd) {
24416
+ return requestSession({ type: "create-assist", assistArgs, cwd });
24417
+ }
24418
+
24419
+ // src/commands/review/chainAddressComments.ts
24420
+ function chainAddressComments(prNumber, announce) {
24421
+ const args = ["review-pr-comments", String(prNumber)];
24422
+ if (announce) args.push("--announce");
24423
+ return startChainedSession(
24424
+ `an Address Comments session for PR #${prNumber}`,
24425
+ () => requestAssistSession(args, process.cwd())
24426
+ );
24427
+ }
24428
+
24429
+ // src/commands/review/chainAfterReview.ts
24430
+ async function chainAfterReview(prNumber, outcome, options2) {
24431
+ const raisedComments = outcome.posted > 0 && outcome.submitted;
24432
+ if (options2.addressComments && raisedComments) {
24433
+ await chainAddressComments(prNumber, options2.announce);
24434
+ return;
24435
+ }
24436
+ if (options2.announce) await announcePr(prNumber);
24437
+ }
24438
+
24387
24439
  // src/commands/review/parseFindings.ts
24388
24440
  var SEVERITIES = ["blocker", "major", "minor", "nit"];
24389
24441
  var SOURCES = [
@@ -24715,89 +24767,29 @@ function selectPostableFindings(markdown, prInfo) {
24715
24767
  }
24716
24768
 
24717
24769
  // src/commands/review/postReviewToPr.ts
24770
+ var NOTHING_POSTED = { posted: 0, submitted: false };
24718
24771
  async function confirmPost(prNumber, count8, options2) {
24719
24772
  if (!options2.prompt) return true;
24720
24773
  return promptConfirm(`Post ${count8} comment(s) to PR #${prNumber}?`, false);
24721
24774
  }
24722
- async function postReviewToPr(synthesisPath, options2) {
24723
- const prInfo = fetchPrDiffInfo();
24724
- const prNumber = prInfo.prNumber;
24775
+ async function postFindingsToPr(prInfo, synthesisPath, options2) {
24725
24776
  const markdown = readFileSync41(synthesisPath, "utf8");
24726
24777
  const inDiff = selectPostableFindings(markdown, prInfo);
24727
- if (inDiff.length === 0) return;
24778
+ if (inDiff.length === 0) return NOTHING_POSTED;
24728
24779
  console.log(
24729
- `Found PR #${prNumber} with ${inDiff.length} line-bound finding(s) in the diff.`
24780
+ `Found PR #${prInfo.prNumber} with ${inDiff.length} line-bound finding(s) in the diff.`
24730
24781
  );
24731
- const confirmed = await confirmPost(prNumber, inDiff.length, options2);
24782
+ const confirmed = await confirmPost(prInfo.prNumber, inDiff.length, options2);
24732
24783
  if (!confirmed) {
24733
24784
  console.log("Skipped posting.");
24734
- return;
24735
- }
24736
- const outcome = await postAndMaybeSubmit(inDiff, markdown, options2);
24737
- if (options2.addressComments && outcome.posted > 0 && outcome.submitted)
24738
- await chainAddressComments(prNumber);
24739
- }
24740
-
24741
- // src/commands/review/runRefineSession.ts
24742
- function buildRefinePrompt(synthesisPath) {
24743
- return `You are refining a code review synthesis with the user.
24744
-
24745
- Read ${synthesisPath} and walk the user through each finding one at a time. Before asking the user what to do with a finding, do some investigation so you can give an informed opinion:
24746
- - Read the referenced file/lines (use the Location field) to confirm the issue is real and current.
24747
- - Check nearby code, callers, or related files when the impact depends on context.
24748
- - Form a view on whether the finding is applicable, whether the stated severity is right, and whether the recommendation actually fixes the problem.
24749
-
24750
- Then present your assessment briefly (one or two sentences) and ask the user whether to keep it as-is, edit it (e.g. adjust severity, sharpen the recommendation), or drop it. After all existing findings are reviewed, ask whether to add any new findings.
24751
-
24752
- Edit ${synthesisPath} in place so the resulting file is the user's final list:
24753
- - To drop a finding, remove its entire \`### Finding:\` block.
24754
- - To edit a finding, modify only the fields the user wants changed.
24755
- - To add a new finding, append a block in the exact format below under the \`## Findings\` heading.
24756
-
24757
- Each finding block must use this exact format so it parses correctly:
24758
-
24759
- ### Finding: <short title>
24760
- - Severity: blocker | major | minor | nit
24761
- - Source: confirmed | disputed | claude-only | codex-only | already-raised
24762
- - Location: \`path/to/file.ext:LINE\` or \`n/a\` when not tied to a specific line
24763
- - Impact: one sentence on what could go wrong
24764
- - Recommendation: one or two sentences with a concrete change
24765
-
24766
- User-supplied findings should use Source: \`confirmed\`. Use Location \`n/a\` only when the finding is not tied to a specific line.
24767
-
24768
- Do not post anything to a PR; only edit ${synthesisPath}. When the user is done, exit.`;
24769
- }
24770
- async function runRefineSession(synthesisPath) {
24771
- const { done: done2 } = spawnClaude(buildRefinePrompt(synthesisPath), {
24772
- allowEdits: true
24773
- });
24774
- await done2;
24775
- }
24776
-
24777
- // src/commands/review/handlePostSynthesis.ts
24778
- async function handlePostSynthesis(synthesisPath, options2) {
24779
- if (options2.refine) {
24780
- await runRefineSession(synthesisPath);
24781
- return;
24782
- }
24783
- await postReviewToPr(synthesisPath, {
24784
- prompt: options2.prompt,
24785
- submit: options2.submit,
24786
- addressComments: options2.addressComments
24787
- });
24788
- }
24789
-
24790
- // src/commands/review/prepareReviewDir.ts
24791
- import { existsSync as existsSync49, mkdirSync as mkdirSync19, unlinkSync as unlinkSync17, writeFileSync as writeFileSync36 } from "fs";
24792
- function clearReviewFiles(paths) {
24793
- for (const path71 of [paths.claudePath, paths.codexPath, paths.synthesisPath]) {
24794
- if (existsSync49(path71)) unlinkSync17(path71);
24785
+ return NOTHING_POSTED;
24795
24786
  }
24787
+ return postAndMaybeSubmit(inDiff, markdown, options2);
24796
24788
  }
24797
- function prepareReviewDir(paths, requestBody, force) {
24798
- mkdirSync19(paths.reviewDir, { recursive: true });
24799
- if (force) clearReviewFiles(paths);
24800
- writeFileSync36(paths.requestPath, requestBody);
24789
+ async function postReviewToPr(synthesisPath, options2) {
24790
+ const prInfo = fetchPrDiffInfo();
24791
+ const outcome = await postFindingsToPr(prInfo, synthesisPath, options2);
24792
+ await chainAfterReview(prInfo.prNumber, outcome, options2);
24801
24793
  }
24802
24794
 
24803
24795
  // src/commands/review/runApplySession.ts
@@ -24858,6 +24850,77 @@ async function runBacklogSession(synthesisPath) {
24858
24850
  await done2;
24859
24851
  }
24860
24852
 
24853
+ // src/commands/review/runRefineSession.ts
24854
+ function buildRefinePrompt(synthesisPath) {
24855
+ return `You are refining a code review synthesis with the user.
24856
+
24857
+ Read ${synthesisPath} and walk the user through each finding one at a time. Before asking the user what to do with a finding, do some investigation so you can give an informed opinion:
24858
+ - Read the referenced file/lines (use the Location field) to confirm the issue is real and current.
24859
+ - Check nearby code, callers, or related files when the impact depends on context.
24860
+ - Form a view on whether the finding is applicable, whether the stated severity is right, and whether the recommendation actually fixes the problem.
24861
+
24862
+ Then present your assessment briefly (one or two sentences) and ask the user whether to keep it as-is, edit it (e.g. adjust severity, sharpen the recommendation), or drop it. After all existing findings are reviewed, ask whether to add any new findings.
24863
+
24864
+ Edit ${synthesisPath} in place so the resulting file is the user's final list:
24865
+ - To drop a finding, remove its entire \`### Finding:\` block.
24866
+ - To edit a finding, modify only the fields the user wants changed.
24867
+ - To add a new finding, append a block in the exact format below under the \`## Findings\` heading.
24868
+
24869
+ Each finding block must use this exact format so it parses correctly:
24870
+
24871
+ ### Finding: <short title>
24872
+ - Severity: blocker | major | minor | nit
24873
+ - Source: confirmed | disputed | claude-only | codex-only | already-raised
24874
+ - Location: \`path/to/file.ext:LINE\` or \`n/a\` when not tied to a specific line
24875
+ - Impact: one sentence on what could go wrong
24876
+ - Recommendation: one or two sentences with a concrete change
24877
+
24878
+ User-supplied findings should use Source: \`confirmed\`. Use Location \`n/a\` only when the finding is not tied to a specific line.
24879
+
24880
+ Do not post anything to a PR; only edit ${synthesisPath}. When the user is done, exit.`;
24881
+ }
24882
+ async function runRefineSession(synthesisPath) {
24883
+ const { done: done2 } = spawnClaude(buildRefinePrompt(synthesisPath), {
24884
+ allowEdits: true
24885
+ });
24886
+ await done2;
24887
+ }
24888
+
24889
+ // src/commands/review/handlePostSynthesis.ts
24890
+ function nonPostingSession(options2) {
24891
+ if (options2.backlog) return runBacklogSession;
24892
+ if (options2.apply) return runApplySession;
24893
+ if (options2.refine) return runRefineSession;
24894
+ return null;
24895
+ }
24896
+ async function handlePostSynthesis(synthesisPath, prNumber, options2) {
24897
+ const session = nonPostingSession(options2);
24898
+ if (!session) {
24899
+ await postReviewToPr(synthesisPath, {
24900
+ prompt: options2.prompt,
24901
+ submit: options2.submit,
24902
+ addressComments: options2.addressComments,
24903
+ announce: options2.announce
24904
+ });
24905
+ return;
24906
+ }
24907
+ await session(synthesisPath);
24908
+ if (options2.announce) await announcePr(prNumber);
24909
+ }
24910
+
24911
+ // src/commands/review/prepareReviewDir.ts
24912
+ import { existsSync as existsSync49, mkdirSync as mkdirSync19, unlinkSync as unlinkSync17, writeFileSync as writeFileSync36 } from "fs";
24913
+ function clearReviewFiles(paths) {
24914
+ for (const path71 of [paths.claudePath, paths.codexPath, paths.synthesisPath]) {
24915
+ if (existsSync49(path71)) unlinkSync17(path71);
24916
+ }
24917
+ }
24918
+ function prepareReviewDir(paths, requestBody, force) {
24919
+ mkdirSync19(paths.reviewDir, { recursive: true });
24920
+ if (force) clearReviewFiles(paths);
24921
+ writeFileSync36(paths.requestPath, requestBody);
24922
+ }
24923
+
24861
24924
  // src/commands/review/cachedReviewerResult.ts
24862
24925
  import { statSync as statSync8 } from "fs";
24863
24926
  function cachedReviewerResult(name, outputPath) {
@@ -25771,20 +25834,15 @@ function setupReviewDir(repoRoot, context, force) {
25771
25834
  console.log(`Review folder: ${paths.reviewDir}`);
25772
25835
  return paths;
25773
25836
  }
25774
- async function runPostSynthesis(synthesisPath, options2) {
25775
- if (options2.backlog) {
25776
- await runBacklogSession(synthesisPath);
25777
- return;
25778
- }
25779
- if (options2.apply) {
25780
- await runApplySession(synthesisPath);
25781
- return;
25782
- }
25783
- await handlePostSynthesis(synthesisPath, {
25837
+ function runPostSynthesis(synthesisPath, prNumber, options2) {
25838
+ return handlePostSynthesis(synthesisPath, prNumber, {
25784
25839
  refine: options2.refine ?? false,
25840
+ apply: options2.apply ?? false,
25841
+ backlog: options2.backlog ?? false,
25785
25842
  prompt: options2.prompt ?? true,
25786
25843
  submit: options2.submit ?? false,
25787
- addressComments: options2.addressComments ?? false
25844
+ addressComments: options2.addressComments ?? false,
25845
+ announce: options2.announce ?? false
25788
25846
  });
25789
25847
  }
25790
25848
  async function reviewPr(repoRoot, options2) {
@@ -25793,7 +25851,8 @@ async function reviewPr(repoRoot, options2) {
25793
25851
  const synthesisOk = await runReviewPipeline(paths, {
25794
25852
  verbose: options2.verbose ?? false
25795
25853
  });
25796
- if (synthesisOk) await runPostSynthesis(paths.synthesisPath, options2);
25854
+ if (synthesisOk)
25855
+ await runPostSynthesis(paths.synthesisPath, context.prNumber, options2);
25797
25856
  console.log(`Done. Review folder: ${paths.reviewDir}`);
25798
25857
  }
25799
25858
 
@@ -25853,6 +25912,9 @@ function registerReview(program2) {
25853
25912
  ).option(
25854
25913
  "--address-comments",
25855
25914
  "After posting and submitting the review, start an Address Comments session (assist review-pr-comments <n>) for the PR; no-op when nothing was posted or the review was not submitted, and only inside an assist session"
25915
+ ).option(
25916
+ "--announce",
25917
+ "Announce the PR in Slack (/prs-slack <n> --no-confirm) once the chain finishes: from the Address Comments session when one was started, otherwise from a session started directly; only inside an assist session"
25856
25918
  ).option(
25857
25919
  "--verbose",
25858
25920
  "Disable spinner UI and use per-line log output (per-tool lines, starting/done lines)"
@@ -26517,7 +26579,7 @@ function askQuestion(rl, question) {
26517
26579
  }
26518
26580
 
26519
26581
  // src/commands/transcript/configure.ts
26520
- function buildPrompt2(label2, current) {
26582
+ function buildPrompt3(label2, current) {
26521
26583
  return current ? `${label2} [${current}]: ` : `${label2}: `;
26522
26584
  }
26523
26585
  function printExisting(existing) {
@@ -26530,15 +26592,15 @@ function printExisting(existing) {
26530
26592
  async function promptDirectories(rl, existing) {
26531
26593
  const vttDir = await askQuestion(
26532
26594
  rl,
26533
- buildPrompt2("VTT directory", existing?.vttDir)
26595
+ buildPrompt3("VTT directory", existing?.vttDir)
26534
26596
  );
26535
26597
  const transcriptsDir = await askQuestion(
26536
26598
  rl,
26537
- buildPrompt2("Transcripts directory", existing?.transcriptsDir)
26599
+ buildPrompt3("Transcripts directory", existing?.transcriptsDir)
26538
26600
  );
26539
26601
  const summaryDir = await askQuestion(
26540
26602
  rl,
26541
- buildPrompt2("Summary directory", existing?.summaryDir)
26603
+ buildPrompt3("Summary directory", existing?.summaryDir)
26542
26604
  );
26543
26605
  return {
26544
26606
  vttDir: vttDir || existing?.vttDir || "",
@@ -29846,7 +29908,7 @@ async function generateSessionTitle(prompt) {
29846
29908
  try {
29847
29909
  const { stdout } = await execFileAsync7(
29848
29910
  "claude",
29849
- ["-p", "--model", "haiku", buildPrompt3(prompt)],
29911
+ ["-p", "--model", "haiku", buildPrompt4(prompt)],
29850
29912
  { encoding: "utf8", timeout: 3e4 }
29851
29913
  );
29852
29914
  return normaliseTitle(stdout);
@@ -29858,7 +29920,7 @@ function normaliseTitle(stdout) {
29858
29920
  const title = (stdout.trim().split(/\r?\n/)[0] ?? "").trim().replace(/^["'`]+/, "").replace(/["'`]+$/, "").replace(/[.,:;!?]+$/, "").trim().slice(0, SESSION_TITLE_MAX_LENGTH).trim();
29859
29921
  return title || void 0;
29860
29922
  }
29861
- function buildPrompt3(prompt) {
29923
+ function buildPrompt4(prompt) {
29862
29924
  return [
29863
29925
  "Summarise the task below as a short title for a session card.",
29864
29926
  "Rules:",
@@ -33402,7 +33464,7 @@ function summariseSession(jsonlPath2) {
33402
33464
  if (!firstMessage && backlogIds.length === 0) {
33403
33465
  return void 0;
33404
33466
  }
33405
- const prompt = buildPrompt4(firstMessage, backlogIds);
33467
+ const prompt = buildPrompt5(firstMessage, backlogIds);
33406
33468
  try {
33407
33469
  const output = execFileSync15("claude", ["-p", "--model", "haiku", prompt], {
33408
33470
  encoding: "utf8",
@@ -33416,7 +33478,7 @@ function summariseSession(jsonlPath2) {
33416
33478
  return void 0;
33417
33479
  }
33418
33480
  }
33419
- function buildPrompt4(firstMessage, backlogIds) {
33481
+ function buildPrompt5(firstMessage, backlogIds) {
33420
33482
  const parts = [
33421
33483
  "Summarise this Claude Code session in ONE short sentence (under 100 chars).",
33422
33484
  "Return ONLY the summary, no quotes or explanation."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.496.2",
3
+ "version": "0.498.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {