@staff0rd/assist 0.218.0 → 0.220.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +255 -126
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -88,7 +88,7 @@ After installation, the `assist` command will be available globally. You can als
88
88
  - `assist prs fixed <comment-id> <sha>` - Reply with commit link and resolve thread
89
89
  - `assist prs wontfix <comment-id> <reason>` - Reply with reason and resolve thread
90
90
  - `assist prs comment <path> <line> <body>` - Add a line comment to the pending review
91
- - `assist review [--no-prompt] [--submit] [--force] [--refine] [--apply] [--verbose]` - Run Claude and Codex in parallel to review the open PR for the current branch. The diff is fetched from GitHub (base SHA → head SHA via `gh pr diff`), so stale local base branches don't pollute the review; fails fast if no PR is open. By default, prompts before posting line-bound comments and then prompts again to submit the pending review (defaulting to no). Cached `claude.md` / `codex.md` / `synthesis.md` are reused when present; if any reviewer is re-run, the synthesis is invalidated. `--no-prompt` skips all confirmations. `--submit` defaults the submit prompt to yes (or auto-submits when combined with `--no-prompt`). `--force` clears all cached files and re-runs every phase. `--refine` skips posting and instead launches an interactive Claude session that walks through `synthesis.md` and edits it in place; a subsequent `assist review` (no flag) reuses the refined file and posts only the surviving findings. `--apply` skips posting and instead launches an interactive Claude session that walks through each remaining finding asking apply/skip; applied findings have their code fix made in the working tree (unstaged) and are removed from `synthesis.md`, while skipped findings stay so a subsequent `assist review` posts them. `--apply` cannot be combined with `--refine`. `--verbose` disables the stacked-spinner UI and falls back to per-line log output (per-tool lines, starting/done lines); non-TTY environments (CI) automatically fall back to verbose-style output
91
+ - `assist review [sha] [--no-prompt] [--submit] [--force] [--refine] [--apply] [--verbose]` - Run Claude and Codex in parallel to review the open PR for the current branch. The diff is fetched from GitHub (base SHA → head SHA via `gh pr diff`), so stale local base branches don't pollute the review; fails fast if no PR is open. By default, prompts before posting line-bound comments and then prompts again to submit the pending review (defaulting to no). Cached `claude.md` / `codex.md` / `synthesis.md` are reused when present; if any reviewer is re-run, the synthesis is invalidated. `--no-prompt` skips all confirmations. `--submit` defaults the submit prompt to yes (or auto-submits when combined with `--no-prompt`). `--force` clears all cached files and re-runs every phase. `--refine` skips posting and instead launches an interactive Claude session that walks through `synthesis.md` and edits it in place; a subsequent `assist review` (no flag) reuses the refined file and posts only the surviving findings. `--apply` skips posting and instead launches an interactive Claude session that walks through each remaining finding asking apply/skip; applied findings have their code fix made in the working tree (unstaged) and are removed from `synthesis.md`, while skipped findings stay so a subsequent `assist review` posts them. `--apply` cannot be combined with `--refine`. `--verbose` disables the stacked-spinner UI and falls back to per-line log output (per-tool lines, starting/done lines); non-TTY environments (CI) automatically fall back to verbose-style output. Pass a commit `<sha>` to review that commit's diff (`sha^..sha`) instead; the review files land under `.assist/reviews/<shortSha>/`, no GitHub lookup or posting happens, and `--refine` / `--apply` / `--submit` are rejected.
92
92
  - `assist news` - Start the news web UI showing latest RSS feed items (same as `news web`)
93
93
  - `assist news add [url]` - Add an RSS feed URL to the config
94
94
  - `assist news web [-p, --port <number>]` - Start a web view of the news feeds (default port 3001)
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.218.0",
9
+ version: "0.220.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -11639,60 +11639,6 @@ function registerRefactor(program2) {
11639
11639
  registerRestructure(refactorCommand);
11640
11640
  }
11641
11641
 
11642
- // src/commands/review/buildRequest.ts
11643
- import { execSync as execSync38 } from "child_process";
11644
-
11645
- // src/commands/review/fetchPrDiffInfo.ts
11646
- import { execSync as execSync37 } from "child_process";
11647
- function getCurrentBranch2() {
11648
- return execSync37("git rev-parse --abbrev-ref HEAD", {
11649
- encoding: "utf-8"
11650
- }).trim();
11651
- }
11652
- function fetchPrDiffInfo() {
11653
- const { org, repo } = getRepoInfo();
11654
- const branch = getCurrentBranch2();
11655
- const fields = "number,baseRefName,baseRefOid,headRefName,headRefOid";
11656
- let raw;
11657
- try {
11658
- raw = execSync37(`gh pr view ${branch} --json ${fields} -R ${org}/${repo}`, {
11659
- encoding: "utf-8",
11660
- stdio: ["ignore", "pipe", "pipe"]
11661
- });
11662
- } catch (error) {
11663
- if (error instanceof Error && error.message.includes("no pull requests")) {
11664
- console.error(
11665
- `Error: No open pull request found for branch \`${branch}\`. Open a PR for this branch before running \`assist review\`.`
11666
- );
11667
- process.exit(1);
11668
- }
11669
- throw error;
11670
- }
11671
- const parsed = JSON.parse(raw);
11672
- return {
11673
- prNumber: parsed.number,
11674
- baseRef: parsed.baseRefName,
11675
- baseSha: parsed.baseRefOid,
11676
- headRef: parsed.headRefName,
11677
- headSha: parsed.headRefOid
11678
- };
11679
- }
11680
- function fetchPrChangedFiles(prNumber) {
11681
- const { org, repo } = getRepoInfo();
11682
- const out = execSync37(`gh pr diff ${prNumber} --name-only -R ${org}/${repo}`, {
11683
- encoding: "utf-8",
11684
- maxBuffer: 64 * 1024 * 1024
11685
- });
11686
- return out.trim().split("\n").filter(Boolean);
11687
- }
11688
- function fetchPrDiff(prNumber) {
11689
- const { org, repo } = getRepoInfo();
11690
- return execSync37(`gh pr diff ${prNumber} -R ${org}/${repo}`, {
11691
- encoding: "utf-8",
11692
- maxBuffer: 256 * 1024 * 1024
11693
- });
11694
- }
11695
-
11696
11642
  // src/commands/review/formatPriorComments.ts
11697
11643
  function threadKey(c, byId) {
11698
11644
  if (c.threadId) return c.threadId;
@@ -11743,30 +11689,6 @@ ${blocks.join("\n\n")}`;
11743
11689
  }
11744
11690
 
11745
11691
  // src/commands/review/buildRequest.ts
11746
- function gatherContext() {
11747
- const branch = execSync38("git rev-parse --abbrev-ref HEAD", {
11748
- encoding: "utf-8"
11749
- }).trim();
11750
- const sha = execSync38("git rev-parse HEAD", { encoding: "utf-8" }).trim();
11751
- const shortSha = execSync38("git rev-parse --short=7 HEAD", {
11752
- encoding: "utf-8"
11753
- }).trim();
11754
- const prInfo = fetchPrDiffInfo();
11755
- const changedFiles = fetchPrChangedFiles(prInfo.prNumber);
11756
- const diff2 = fetchPrDiff(prInfo.prNumber);
11757
- return {
11758
- branch,
11759
- sha,
11760
- shortSha,
11761
- prNumber: prInfo.prNumber,
11762
- baseRef: prInfo.baseRef,
11763
- baseSha: prInfo.baseSha,
11764
- headRef: prInfo.headRef,
11765
- headSha: prInfo.headSha,
11766
- changedFiles,
11767
- diff: diff2
11768
- };
11769
- }
11770
11692
  function formatFiles(files) {
11771
11693
  if (files.length === 0) return "(none)";
11772
11694
  return files.map((file) => `- ${file}`).join("\n");
@@ -11790,6 +11712,23 @@ ${formatFiles(context.changedFiles)}
11790
11712
  ${priorBlock}
11791
11713
  ## Diff (PR #${context.prNumber}: ${context.baseSha}..${context.headSha})
11792
11714
 
11715
+ \`\`\`diff
11716
+ ${context.diff.trimEnd()}
11717
+ \`\`\`
11718
+ `;
11719
+ }
11720
+ function buildShaRequest(context) {
11721
+ return `# Code review request
11722
+
11723
+ - Commit: \`${context.sha}\`
11724
+ - Parent: \`${context.parentSha}\`
11725
+
11726
+ ## Changed files
11727
+
11728
+ ${formatFiles(context.changedFiles)}
11729
+
11730
+ ## Diff (commit ${context.sha}: ${context.parentSha}..${context.sha})
11731
+
11793
11732
  \`\`\`diff
11794
11733
  ${context.diff.trimEnd()}
11795
11734
  \`\`\`
@@ -11798,13 +11737,8 @@ ${context.diff.trimEnd()}
11798
11737
 
11799
11738
  // src/commands/review/buildReviewPaths.ts
11800
11739
  import { join as join35 } from "path";
11801
- function buildReviewPaths(repoRoot, branch, shortSha) {
11802
- const reviewDir = join35(
11803
- repoRoot,
11804
- ".assist",
11805
- "reviews",
11806
- `${branch}-${shortSha}`
11807
- );
11740
+ function buildReviewPaths(repoRoot, key) {
11741
+ const reviewDir = join35(repoRoot, ".assist", "reviews", key);
11808
11742
  return {
11809
11743
  reviewDir,
11810
11744
  requestPath: join35(reviewDir, "request.md"),
@@ -11815,9 +11749,9 @@ function buildReviewPaths(repoRoot, branch, shortSha) {
11815
11749
  }
11816
11750
 
11817
11751
  // src/commands/review/fetchExistingComments.ts
11818
- import { execSync as execSync39 } from "child_process";
11752
+ import { execSync as execSync37 } from "child_process";
11819
11753
  function fetchRawComments(org, repo, prNumber) {
11820
- const out = execSync39(
11754
+ const out = execSync37(
11821
11755
  `gh api --paginate repos/${org}/${repo}/pulls/${prNumber}/comments`,
11822
11756
  { encoding: "utf-8", maxBuffer: 64 * 1024 * 1024 }
11823
11757
  );
@@ -11847,6 +11781,86 @@ function fetchExistingComments() {
11847
11781
  });
11848
11782
  }
11849
11783
 
11784
+ // src/commands/review/gatherContext.ts
11785
+ import { execSync as execSync39 } from "child_process";
11786
+
11787
+ // src/commands/review/fetchPrDiffInfo.ts
11788
+ import { execSync as execSync38 } from "child_process";
11789
+ function getCurrentBranch2() {
11790
+ return execSync38("git rev-parse --abbrev-ref HEAD", {
11791
+ encoding: "utf-8"
11792
+ }).trim();
11793
+ }
11794
+ function fetchPrDiffInfo() {
11795
+ const { org, repo } = getRepoInfo();
11796
+ const branch = getCurrentBranch2();
11797
+ const fields = "number,baseRefName,baseRefOid,headRefName,headRefOid";
11798
+ let raw;
11799
+ try {
11800
+ raw = execSync38(`gh pr view ${branch} --json ${fields} -R ${org}/${repo}`, {
11801
+ encoding: "utf-8",
11802
+ stdio: ["ignore", "pipe", "pipe"]
11803
+ });
11804
+ } catch (error) {
11805
+ if (error instanceof Error && error.message.includes("no pull requests")) {
11806
+ console.error(
11807
+ `Error: No open pull request found for branch \`${branch}\`. Open a PR for this branch before running \`assist review\`.`
11808
+ );
11809
+ process.exit(1);
11810
+ }
11811
+ throw error;
11812
+ }
11813
+ const parsed = JSON.parse(raw);
11814
+ return {
11815
+ prNumber: parsed.number,
11816
+ baseRef: parsed.baseRefName,
11817
+ baseSha: parsed.baseRefOid,
11818
+ headRef: parsed.headRefName,
11819
+ headSha: parsed.headRefOid
11820
+ };
11821
+ }
11822
+ function fetchPrChangedFiles(prNumber) {
11823
+ const { org, repo } = getRepoInfo();
11824
+ const out = execSync38(`gh pr diff ${prNumber} --name-only -R ${org}/${repo}`, {
11825
+ encoding: "utf-8",
11826
+ maxBuffer: 64 * 1024 * 1024
11827
+ });
11828
+ return out.trim().split("\n").filter(Boolean);
11829
+ }
11830
+ function fetchPrDiff(prNumber) {
11831
+ const { org, repo } = getRepoInfo();
11832
+ return execSync38(`gh pr diff ${prNumber} -R ${org}/${repo}`, {
11833
+ encoding: "utf-8",
11834
+ maxBuffer: 256 * 1024 * 1024
11835
+ });
11836
+ }
11837
+
11838
+ // src/commands/review/gatherContext.ts
11839
+ function gatherContext() {
11840
+ const branch = execSync39("git rev-parse --abbrev-ref HEAD", {
11841
+ encoding: "utf-8"
11842
+ }).trim();
11843
+ const sha = execSync39("git rev-parse HEAD", { encoding: "utf-8" }).trim();
11844
+ const shortSha = execSync39("git rev-parse --short=7 HEAD", {
11845
+ encoding: "utf-8"
11846
+ }).trim();
11847
+ const prInfo = fetchPrDiffInfo();
11848
+ const changedFiles = fetchPrChangedFiles(prInfo.prNumber);
11849
+ const diff2 = fetchPrDiff(prInfo.prNumber);
11850
+ return {
11851
+ branch,
11852
+ sha,
11853
+ shortSha,
11854
+ prNumber: prInfo.prNumber,
11855
+ baseRef: prInfo.baseRef,
11856
+ baseSha: prInfo.baseSha,
11857
+ headRef: prInfo.headRef,
11858
+ headSha: prInfo.headSha,
11859
+ changedFiles,
11860
+ diff: diff2
11861
+ };
11862
+ }
11863
+
11850
11864
  // src/commands/review/postReviewToPr.ts
11851
11865
  import { readFileSync as readFileSync29 } from "fs";
11852
11866
 
@@ -12206,12 +12220,46 @@ async function runApplySession(synthesisPath) {
12206
12220
  import { existsSync as existsSync35, unlinkSync as unlinkSync12 } from "fs";
12207
12221
 
12208
12222
  // src/commands/review/buildReviewerStdin.ts
12209
- var REVIEW_PROMPT = `You are reviewing a code change. The full review request \u2014 branch, base, changed files, and unified diff \u2014 is in request.md in the current working directory.
12223
+ var REVIEW_PROMPT = `You are acting as a reviewer for a proposed code change made by another engineer. The full review request \u2014 branch, base, changed files, and unified diff \u2014 is in request.md in the current working directory.
12224
+
12225
+ Read request.md, then produce a thorough code review in Markdown.
12226
+
12227
+ ## When to flag a finding
12228
+
12229
+ A finding is worth raising only if all of the following hold:
12230
+
12231
+ 1. It meaningfully impacts the accuracy, performance, security, or maintainability of the code.
12232
+ 2. The issue is discrete and actionable \u2014 not a vague observation about the codebase or a tangle of several things.
12233
+ 3. Fixing it does not demand more rigour than the rest of the codebase already shows (e.g. don't ask for exhaustive input validation in a repo of one-off scripts).
12234
+ 4. The issue was introduced by this change. Do not flag pre-existing bugs.
12235
+ 5. The original author would likely fix it if made aware.
12236
+ 6. It does not rely on unstated assumptions about the codebase or author's intent.
12237
+ 7. You can name the concretely affected code path. Speculation that a change *might* disrupt something elsewhere is not enough \u2014 identify the other code that is provably affected.
12238
+ 8. It is clearly not an intentional change by the author.
12210
12239
 
12211
- Read request.md, then produce a thorough code review in Markdown. For each finding include:
12240
+ ## How to write the comment (Impact + Recommendation)
12241
+
12242
+ 1. Make clear *why* the issue is a bug.
12243
+ 2. Communicate severity accurately \u2014 do not inflate.
12244
+ 3. Keep it brief: at most one paragraph of prose. Avoid line breaks inside the natural-language flow unless needed for a code fragment.
12245
+ 4. Do not paste code chunks longer than 3 lines. Wrap short snippets in inline code or a fenced block.
12246
+ 5. State explicitly the scenarios, environments, or inputs needed for the bug to manifest \u2014 and signal up front that severity depends on those factors.
12247
+ 6. Tone is matter-of-fact: not accusatory, not gushing. Read as a helpful assistant, not a performative human reviewer.
12248
+ 7. Write so the author grasps the point on first read.
12249
+ 8. Avoid flattery and filler ("Great job\u2026", "Thanks for\u2026"). They are not useful to the author.
12250
+
12251
+ Ignore trivial style unless it obscures meaning or violates a documented standard. One finding per distinct issue.
12252
+
12253
+ ## How many findings to return
12254
+
12255
+ List every finding that the original author would want to know about and fix. Do not stop at the first qualifying one. If nothing clears the bar above, return no findings \u2014 empty is better than padded.
12256
+
12257
+ ## Output format
12258
+
12259
+ For each finding include:
12212
12260
  - Severity (blocker, major, minor, nit) \u2014 see rubric below
12213
12261
  - File and line (e.g. \`src/foo.ts:42\`) when the finding is tied to a specific location
12214
- - Impact: what could go wrong
12262
+ - Impact: what could go wrong, including the conditions under which it manifests
12215
12263
  - Recommendation: a concrete change
12216
12264
 
12217
12265
  Severity rubric:
@@ -12832,19 +12880,7 @@ async function runReviewPipeline(paths, options2) {
12832
12880
  }
12833
12881
  }
12834
12882
 
12835
- // src/commands/review/review.ts
12836
- function resolveRepoRoot() {
12837
- const repoRoot = findRepoRoot(process.cwd());
12838
- if (repoRoot) return repoRoot;
12839
- console.error("Error: not inside a git repository.");
12840
- process.exit(1);
12841
- }
12842
- function validateOptions(options2) {
12843
- if (options2.apply && options2.refine) {
12844
- console.error("Error: --apply cannot be combined with --refine.");
12845
- process.exit(1);
12846
- }
12847
- }
12883
+ // src/commands/review/reviewPr.ts
12848
12884
  function logPriorComments(count) {
12849
12885
  if (count === 0) return;
12850
12886
  console.log(`Including ${count} prior review comment(s) in request.md.`);
@@ -12858,7 +12894,10 @@ function gatherChangedContext() {
12858
12894
  process.exit(1);
12859
12895
  }
12860
12896
  function setupReviewDir(repoRoot, context, force) {
12861
- const paths = buildReviewPaths(repoRoot, context.branch, context.shortSha);
12897
+ const paths = buildReviewPaths(
12898
+ repoRoot,
12899
+ `${context.branch}-${context.shortSha}`
12900
+ );
12862
12901
  const priorComments = fetchExistingComments();
12863
12902
  logPriorComments(priorComments?.length ?? 0);
12864
12903
  prepareReviewDir(paths, buildRequest(context, priorComments), force);
@@ -12876,9 +12915,7 @@ async function runPostSynthesis(synthesisPath, options2) {
12876
12915
  submit: options2.submit ?? false
12877
12916
  });
12878
12917
  }
12879
- async function review(options2 = {}) {
12880
- validateOptions(options2);
12881
- const repoRoot = resolveRepoRoot();
12918
+ async function reviewPr(repoRoot, options2) {
12882
12919
  const context = gatherChangedContext();
12883
12920
  const paths = setupReviewDir(repoRoot, context, options2.force ?? false);
12884
12921
  const synthesisOk = await runReviewPipeline(paths, {
@@ -12888,10 +12925,100 @@ async function review(options2 = {}) {
12888
12925
  console.log(`Done. Review folder: ${paths.reviewDir}`);
12889
12926
  }
12890
12927
 
12928
+ // src/commands/review/gatherShaContext.ts
12929
+ import { execSync as execSync40 } from "child_process";
12930
+ function resolveSha(ref, format2) {
12931
+ const flag = format2 === "short" ? "--short=7 " : "";
12932
+ try {
12933
+ return execSync40(`git rev-parse --verify ${flag}${ref}^{commit}`, {
12934
+ encoding: "utf-8",
12935
+ stdio: ["ignore", "pipe", "pipe"]
12936
+ }).trim();
12937
+ } catch {
12938
+ console.error(`Error: could not resolve commit \`${ref}\`.`);
12939
+ process.exit(1);
12940
+ }
12941
+ }
12942
+ function gatherShaContext(ref) {
12943
+ const sha = resolveSha(ref, "long");
12944
+ const shortSha = resolveSha(sha, "short");
12945
+ const parentSha = resolveSha(`${sha}^`, "long");
12946
+ const range = `${parentSha}..${sha}`;
12947
+ const changedFiles = execSync40(`git diff --name-only ${range}`, {
12948
+ encoding: "utf-8",
12949
+ maxBuffer: 64 * 1024 * 1024
12950
+ }).trim().split("\n").filter(Boolean);
12951
+ const diff2 = execSync40(`git diff ${range}`, {
12952
+ encoding: "utf-8",
12953
+ maxBuffer: 256 * 1024 * 1024
12954
+ });
12955
+ return { sha, shortSha, parentSha, changedFiles, diff: diff2 };
12956
+ }
12957
+
12958
+ // src/commands/review/reviewSha.ts
12959
+ function gatherShaChangedContext(ref) {
12960
+ const context = gatherShaContext(ref);
12961
+ if (context.changedFiles.length > 0) return context;
12962
+ console.error(
12963
+ `Error: commit ${context.sha} has no changed files \u2014 nothing to review.`
12964
+ );
12965
+ process.exit(1);
12966
+ }
12967
+ function setupShaReviewDir(repoRoot, context, force) {
12968
+ const paths = buildReviewPaths(repoRoot, context.shortSha);
12969
+ prepareReviewDir(paths, buildShaRequest(context), force);
12970
+ console.log(`Review folder: ${paths.reviewDir}`);
12971
+ return paths;
12972
+ }
12973
+ async function reviewSha(repoRoot, options2) {
12974
+ const context = gatherShaChangedContext(options2.sha);
12975
+ const paths = setupShaReviewDir(repoRoot, context, options2.force ?? false);
12976
+ await runReviewPipeline(paths, { verbose: options2.verbose ?? false });
12977
+ console.log(`Done. Review folder: ${paths.reviewDir}`);
12978
+ }
12979
+
12980
+ // src/commands/review/review.ts
12981
+ function resolveRepoRoot() {
12982
+ const repoRoot = findRepoRoot(process.cwd());
12983
+ if (repoRoot) return repoRoot;
12984
+ console.error("Error: not inside a git repository.");
12985
+ process.exit(1);
12986
+ }
12987
+ function rejectShaFlag(flag) {
12988
+ console.error(`Error: ${flag} cannot be combined with a SHA argument.`);
12989
+ process.exit(1);
12990
+ }
12991
+ function validateOptions(options2) {
12992
+ if (options2.apply && options2.refine) {
12993
+ console.error("Error: --apply cannot be combined with --refine.");
12994
+ process.exit(1);
12995
+ }
12996
+ if (!options2.sha) return;
12997
+ if (options2.refine) rejectShaFlag("--refine");
12998
+ if (options2.apply) rejectShaFlag("--apply");
12999
+ if (options2.submit) rejectShaFlag("--submit");
13000
+ }
13001
+ async function review(options2 = {}) {
13002
+ validateOptions(options2);
13003
+ const repoRoot = resolveRepoRoot();
13004
+ if (options2.sha) {
13005
+ await reviewSha(repoRoot, {
13006
+ sha: options2.sha,
13007
+ force: options2.force,
13008
+ verbose: options2.verbose
13009
+ });
13010
+ return;
13011
+ }
13012
+ await reviewPr(repoRoot, options2);
13013
+ }
13014
+
12891
13015
  // src/commands/registerReview.ts
12892
13016
  function registerReview(program2) {
12893
13017
  program2.command("review").description(
12894
- "Run Claude and Codex in parallel to review the current branch"
13018
+ "Run Claude and Codex in parallel to review the current branch, or a single commit when a SHA is given"
13019
+ ).argument(
13020
+ "[sha]",
13021
+ "Optional commit SHA to review (sha^..sha); when provided, no PR lookup or GitHub posting happens"
12895
13022
  ).option(
12896
13023
  "--no-prompt",
12897
13024
  "Skip confirmation prompts; use flag defaults non-interactively"
@@ -12910,7 +13037,9 @@ function registerReview(program2) {
12910
13037
  ).option(
12911
13038
  "--verbose",
12912
13039
  "Disable spinner UI and use per-line log output (per-tool lines, starting/done lines)"
12913
- ).action((options2) => review(options2));
13040
+ ).action(
13041
+ (sha, options2) => review({ ...options2, sha })
13042
+ );
12914
13043
  }
12915
13044
 
12916
13045
  // src/commands/seq/seqAuth.ts
@@ -14254,7 +14383,7 @@ import { mkdirSync as mkdirSync14 } from "fs";
14254
14383
  import { join as join45 } from "path";
14255
14384
 
14256
14385
  // src/commands/voice/checkLockFile.ts
14257
- import { execSync as execSync40 } from "child_process";
14386
+ import { execSync as execSync41 } from "child_process";
14258
14387
  import { existsSync as existsSync42, mkdirSync as mkdirSync13, readFileSync as readFileSync34, writeFileSync as writeFileSync29 } from "fs";
14259
14388
  import { join as join44 } from "path";
14260
14389
  function isProcessAlive2(pid) {
@@ -14283,7 +14412,7 @@ function bootstrapVenv() {
14283
14412
  if (existsSync42(getVenvPython())) return;
14284
14413
  console.log("Setting up Python environment...");
14285
14414
  const pythonDir = getPythonDir();
14286
- execSync40(
14415
+ execSync41(
14287
14416
  `uv sync --project "${pythonDir}" --extra runtime --no-install-project`,
14288
14417
  {
14289
14418
  stdio: "inherit",
@@ -14450,11 +14579,11 @@ import { randomBytes } from "crypto";
14450
14579
  import chalk143 from "chalk";
14451
14580
 
14452
14581
  // src/lib/openBrowser.ts
14453
- import { execSync as execSync41 } from "child_process";
14582
+ import { execSync as execSync42 } from "child_process";
14454
14583
  function tryExec(commands) {
14455
14584
  for (const cmd of commands) {
14456
14585
  try {
14457
- execSync41(cmd);
14586
+ execSync42(cmd);
14458
14587
  return true;
14459
14588
  } catch {
14460
14589
  }
@@ -14796,11 +14925,11 @@ function resolveParams(params, cliArgs) {
14796
14925
  }
14797
14926
 
14798
14927
  // src/commands/run/runPreCommands.ts
14799
- import { execSync as execSync42 } from "child_process";
14928
+ import { execSync as execSync43 } from "child_process";
14800
14929
  function runPreCommands(pre, cwd) {
14801
14930
  for (const cmd of pre) {
14802
14931
  try {
14803
- execSync42(cmd, { stdio: "inherit", cwd });
14932
+ execSync43(cmd, { stdio: "inherit", cwd });
14804
14933
  } catch (err) {
14805
14934
  const code = err && typeof err === "object" && "status" in err ? err.status : 1;
14806
14935
  process.exit(code);
@@ -15063,7 +15192,7 @@ function registerRun(program2) {
15063
15192
  }
15064
15193
 
15065
15194
  // src/commands/screenshot/index.ts
15066
- import { execSync as execSync43 } from "child_process";
15195
+ import { execSync as execSync44 } from "child_process";
15067
15196
  import { existsSync as existsSync47, mkdirSync as mkdirSync17, unlinkSync as unlinkSync15, writeFileSync as writeFileSync32 } from "fs";
15068
15197
  import { tmpdir as tmpdir7 } from "os";
15069
15198
  import { join as join51, resolve as resolve13 } from "path";
@@ -15206,7 +15335,7 @@ function runPowerShellScript(processName, outputPath) {
15206
15335
  const scriptPath = join51(tmpdir7(), `assist-screenshot-${Date.now()}.ps1`);
15207
15336
  writeFileSync32(scriptPath, captureWindowPs1, "utf-8");
15208
15337
  try {
15209
- execSync43(
15338
+ execSync44(
15210
15339
  `powershell -NoProfile -ExecutionPolicy Bypass -File "${scriptPath}" -ProcessName "${processName}" -OutputPath "${outputPath}"`,
15211
15340
  { stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }
15212
15341
  );
@@ -15607,7 +15736,7 @@ function syncCommands(claudeDir, targetBase) {
15607
15736
  }
15608
15737
 
15609
15738
  // src/commands/update.ts
15610
- import { execSync as execSync44 } from "child_process";
15739
+ import { execSync as execSync45 } from "child_process";
15611
15740
  import * as path51 from "path";
15612
15741
  function isGlobalNpmInstall(dir) {
15613
15742
  try {
@@ -15615,7 +15744,7 @@ function isGlobalNpmInstall(dir) {
15615
15744
  if (resolved.split(path51.sep).includes("node_modules")) {
15616
15745
  return true;
15617
15746
  }
15618
- const globalPrefix = execSync44("npm prefix -g", { stdio: "pipe" }).toString().trim();
15747
+ const globalPrefix = execSync45("npm prefix -g", { stdio: "pipe" }).toString().trim();
15619
15748
  return resolved.toLowerCase().startsWith(path51.resolve(globalPrefix).toLowerCase());
15620
15749
  } catch {
15621
15750
  return false;
@@ -15626,18 +15755,18 @@ async function update2() {
15626
15755
  console.log(`Assist is installed at: ${installDir}`);
15627
15756
  if (isGitRepo(installDir)) {
15628
15757
  console.log("Detected git repo installation, pulling latest...");
15629
- execSync44("git pull", { cwd: installDir, stdio: "inherit" });
15758
+ execSync45("git pull", { cwd: installDir, stdio: "inherit" });
15630
15759
  console.log("Installing dependencies...");
15631
- execSync44("npm i", { cwd: installDir, stdio: "inherit" });
15760
+ execSync45("npm i", { cwd: installDir, stdio: "inherit" });
15632
15761
  console.log("Building...");
15633
- execSync44("npm run build", { cwd: installDir, stdio: "inherit" });
15762
+ execSync45("npm run build", { cwd: installDir, stdio: "inherit" });
15634
15763
  console.log("Syncing commands...");
15635
- execSync44("assist sync", { stdio: "inherit" });
15764
+ execSync45("assist sync", { stdio: "inherit" });
15636
15765
  } else if (isGlobalNpmInstall(installDir)) {
15637
15766
  console.log("Detected global npm installation, updating...");
15638
- execSync44("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
15767
+ execSync45("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
15639
15768
  console.log("Syncing commands...");
15640
- execSync44("assist sync", { stdio: "inherit" });
15769
+ execSync45("assist sync", { stdio: "inherit" });
15641
15770
  } else {
15642
15771
  console.error(
15643
15772
  "Could not determine installation method. Expected a git repo or global npm install."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.218.0",
3
+ "version": "0.220.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {