@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.
- package/README.md +1 -1
- package/dist/index.js +255 -126
- 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.
|
|
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,
|
|
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
|
|
11752
|
+
import { execSync as execSync37 } from "child_process";
|
|
11819
11753
|
function fetchRawComments(org, repo, prNumber) {
|
|
11820
|
-
const out =
|
|
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
|
|
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
|
-
|
|
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/
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
14582
|
+
import { execSync as execSync42 } from "child_process";
|
|
14454
14583
|
function tryExec(commands) {
|
|
14455
14584
|
for (const cmd of commands) {
|
|
14456
14585
|
try {
|
|
14457
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
15758
|
+
execSync45("git pull", { cwd: installDir, stdio: "inherit" });
|
|
15630
15759
|
console.log("Installing dependencies...");
|
|
15631
|
-
|
|
15760
|
+
execSync45("npm i", { cwd: installDir, stdio: "inherit" });
|
|
15632
15761
|
console.log("Building...");
|
|
15633
|
-
|
|
15762
|
+
execSync45("npm run build", { cwd: installDir, stdio: "inherit" });
|
|
15634
15763
|
console.log("Syncing commands...");
|
|
15635
|
-
|
|
15764
|
+
execSync45("assist sync", { stdio: "inherit" });
|
|
15636
15765
|
} else if (isGlobalNpmInstall(installDir)) {
|
|
15637
15766
|
console.log("Detected global npm installation, updating...");
|
|
15638
|
-
|
|
15767
|
+
execSync45("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
|
|
15639
15768
|
console.log("Syncing commands...");
|
|
15640
|
-
|
|
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."
|