@staff0rd/assist 0.293.0 → 0.294.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,9 +12,57 @@ Use `assist prs raise` to create the PR — do not use `gh pr create` directly.
12
12
  - `--how <how>` — optional; how the change works (rendered as `## How`). Omit it unless the approach genuinely needs explaining.
13
13
  - `--resolves <key>` — Jira issue key resolved by this PR; repeatable. Each key's browse URL is appended inline to the `## Why` section. Unless a Jira key is already known from the session (e.g. mentioned by the user or present on the branch), ask the user whether this PR resolves a Jira issue and for the key before raising; omit `--resolves` only if they say there isn't one.
14
14
 
15
- Keep each section to a brief plain-text summary. Wrap symbols, file paths, function names, class names, variable names, config keys, CLI commands, and flag names in backticks.
15
+ Wrap symbols, file paths, function names, class names, variable names, config keys, CLI commands, and flag names in backticks.
16
16
 
17
- Write the description in terms of behaviour and user-facing impact: what the change does, what's different for someone using it, and why. Keep technical detail to a minimum — do not walk through the implementation approach step by step, and do not restate what is already obvious from the diff or changelog (which files changed, which functions were added). The reviewer can read the code; the description should tell them what to expect from the change, not narrate how it was built.
17
+ One section, one question. Each section answers exactly one thing, and implementation detail lives only in `## How`:
18
+
19
+ - `## What` — what is observably different for someone using or calling this, and nothing else. Not how it's built, not which functions/files changed.
20
+ - `## Why` — the problem or motivation that made the change worth doing. Not how the solution works.
21
+ - `## How` — only the non-obvious decisions the diff alone won't explain: a deliberate trade-off, a workaround, a reason the obvious approach was rejected. Omit it entirely by default. Never restate `## What` as mechanism, and never walk through the diff.
22
+
23
+ The most common failure is altitude bleed: the author has an implementation detail they want to include, so they spray it into whichever section they're writing — `## What` narrates the diff, `## How` restates `## What`, `## Why` smuggles in mechanism. Don't. If a sentence describes a mechanism, it belongs in `## How` — and probably shouldn't exist at all unless it's a genuine non-obvious decision.
24
+
25
+ Litmus — a sentence is almost certainly mechanism (so `## How`, or cut it) if it contains "by …ing", "so that it can", "because the X filters/needs/uses", or names an internal component, property, function, or file.
26
+
27
+ Brevity budget — keep each section within these limits:
28
+
29
+ - `## What` — a few sentences (2–3).
30
+ - `## Why` — 1–2 sentences.
31
+ - `## How` — omit by default; a sentence or two, only for non-obvious decisions, not a diff walkthrough.
32
+
33
+ Write prose. A short paragraph of a few sentences is the natural form for `## What` and `## Why` — bullets in `## What` are a smell, usually a sign the change is being narrated point-by-point like a diff. Use bullets only when there are genuinely several independent, parallel items (3+) that don't read as a paragraph; a single bullet is never right — make it a sentence. Don't pack multiple points into one long running paragraph either: a single non-list paragraph over ~600 characters or ~4 sentences is a wall of text and `assist prs raise`/`assist prs edit` will reject it.
34
+
35
+ Worked example — altitude bleed across every section.
36
+
37
+ Bad — mechanism leaks everywhere:
38
+
39
+ > ## What
40
+ >
41
+ > Adds a `RetryPolicy` wrapper that catches transient errors and re-invokes the handler with exponential backoff so callers don't see intermittent failures.
42
+ >
43
+ > ## Why
44
+ >
45
+ > Intermittent failures were surfacing to users. The handler also needs to distinguish transient from permanent errors, which the new `isTransient` check does by inspecting the status code.
46
+ >
47
+ > ## How
48
+ >
49
+ > The wrapper retries with exponential backoff on transient errors.
50
+
51
+ `## What` describes the implementation (a wrapper, backoff) rather than the observable change. `## Why`'s second sentence is mechanism dressed as motivation. `## How` just restates `## What` at a lower altitude. Good — each section stays at its own altitude:
52
+
53
+ > ## What
54
+ >
55
+ > Intermittent failures are now retried automatically instead of surfacing to the user.
56
+ >
57
+ > ## Why
58
+ >
59
+ > Transient errors were failing requests that would have succeeded on a second attempt.
60
+ >
61
+ > ## How
62
+ >
63
+ > Only errors flagged transient are retried; permanent failures still fail fast, to avoid masking real bugs behind retries.
64
+
65
+ Only the genuinely non-obvious decision survives in `## How`; everything that merely paraphrased `## What` is gone.
18
66
 
19
67
  `assist prs raise` errors if a pull request already exists for the branch. Pass `--force` to fully overwrite the existing PR's title and body.
20
68
 
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.293.0",
9
+ version: "0.294.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -11327,6 +11327,45 @@ ${resolves}` : baseWhy);
11327
11327
  }
11328
11328
 
11329
11329
  // src/commands/prs/validatePrContent.ts
11330
+ var MAX_PARAGRAPH_CHARS = 600;
11331
+ var MAX_PARAGRAPH_SENTENCES = 4;
11332
+ function isListLine(line) {
11333
+ return /^\s*([-*+]|\d+[.)])\s/.test(line);
11334
+ }
11335
+ function countSentences(paragraph) {
11336
+ return paragraph.match(/[.!?]+(\s|$)/g)?.length ?? 0;
11337
+ }
11338
+ function splitParagraphs(body) {
11339
+ const paragraphs = [];
11340
+ let section2 = "(intro)";
11341
+ let lines = [];
11342
+ const flush = () => {
11343
+ if (lines.length > 0) {
11344
+ paragraphs.push({ section: section2, lines });
11345
+ lines = [];
11346
+ }
11347
+ };
11348
+ for (const line of body.split("\n")) {
11349
+ const heading = line.match(/^#{1,6}\s+(.*)$/);
11350
+ if (heading) {
11351
+ flush();
11352
+ section2 = heading[1].trim();
11353
+ } else if (line.trim() === "") {
11354
+ flush();
11355
+ } else {
11356
+ lines.push(line);
11357
+ }
11358
+ }
11359
+ flush();
11360
+ return paragraphs;
11361
+ }
11362
+ function isWallOfText(lines) {
11363
+ if (lines.some(isListLine)) {
11364
+ return false;
11365
+ }
11366
+ const text3 = lines.join(" ").trim();
11367
+ return text3.length > MAX_PARAGRAPH_CHARS || countSentences(text3) > MAX_PARAGRAPH_SENTENCES;
11368
+ }
11330
11369
  function validatePrContent(title, body) {
11331
11370
  if (title.toLowerCase().includes("claude")) {
11332
11371
  console.error("Error: PR title must not reference Claude");
@@ -11336,6 +11375,14 @@ function validatePrContent(title, body) {
11336
11375
  console.error("Error: PR body must not reference Claude");
11337
11376
  process.exit(1);
11338
11377
  }
11378
+ const wall = splitParagraphs(body).find((p) => isWallOfText(p.lines));
11379
+ if (wall) {
11380
+ const chars = wall.lines.join(" ").trim().length;
11381
+ console.error(
11382
+ `Error: the "${wall.section}" section contains a wall-of-text paragraph (${chars} chars). Be concise \u2014 split it into bullet points or trim it.`
11383
+ );
11384
+ process.exit(1);
11385
+ }
11339
11386
  }
11340
11387
 
11341
11388
  // src/commands/prs/edit.ts
@@ -14969,9 +15016,11 @@ function printReviewerFailures(results) {
14969
15016
  import { existsSync as existsSync38, unlinkSync as unlinkSync14 } from "fs";
14970
15017
 
14971
15018
  // src/commands/review/buildReviewerStdin.ts
14972
- 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.
15019
+ 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 the request file whose absolute path is given below.
15020
+
15021
+ Your working directory is the repository under review, so you can read any file in it for context beyond the diff.
14973
15022
 
14974
- Read request.md, then produce a thorough code review in Markdown.
15023
+ Read the request file, then produce a thorough code review in Markdown.
14975
15024
 
14976
15025
  ## When to flag a finding
14977
15026
 
@@ -15007,7 +15056,7 @@ List every finding that the original author would want to know about and fix. Do
15007
15056
 
15008
15057
  For each finding include:
15009
15058
  - Severity (blocker, major, minor, nit) \u2014 see rubric below
15010
- - File and line (e.g. \`src/foo.ts:42\`) when the finding is tied to a specific location. Take the line number from the diff's left gutter (its line in the new file); never count lines in request.md.
15059
+ - File and line (e.g. \`src/foo.ts:42\`) when the finding is tied to a specific location. Take the line number from the diff's left gutter (its line in the new file); never count lines in the request file.
15011
15060
  - Impact: what could go wrong, including the conditions under which it manifests
15012
15061
  - Recommendation: a concrete change
15013
15062
 
@@ -15411,11 +15460,9 @@ function parseCodexEvent(line) {
15411
15460
  }
15412
15461
 
15413
15462
  // src/commands/review/runCodexReviewer.ts
15414
- function codexArgs(reviewDir, outputPath) {
15463
+ function codexArgs(outputPath) {
15415
15464
  return [
15416
15465
  "exec",
15417
- "--cd",
15418
- reviewDir,
15419
15466
  "--sandbox",
15420
15467
  "read-only",
15421
15468
  "--json",
@@ -15429,7 +15476,7 @@ async function runCodexReviewer(spec) {
15429
15476
  const result = await runStreamingChild({
15430
15477
  name: spec.name,
15431
15478
  command,
15432
- args: codexArgs(spec.reviewDir, spec.outputPath),
15479
+ args: codexArgs(spec.outputPath),
15433
15480
  stdin: spec.stdin,
15434
15481
  quiet: Boolean(spinner),
15435
15482
  onLine: (line) => {
@@ -15453,7 +15500,6 @@ function resolveCodex(args) {
15453
15500
  const spinner = args.multi?.create("codex \u2014 starting");
15454
15501
  return runCodexReviewer({
15455
15502
  name: "codex",
15456
- reviewDir: args.reviewDir,
15457
15503
  stdin: args.stdin,
15458
15504
  outputPath: args.codexPath,
15459
15505
  spinner
@@ -15470,7 +15516,6 @@ async function runReviewers(reviewDir, claudePath, codexPath, stdinPrompt, optio
15470
15516
  multi: options2.multi
15471
15517
  });
15472
15518
  const codexPromise = resolveCodex({
15473
- reviewDir,
15474
15519
  codexPath,
15475
15520
  stdin: stdinPrompt,
15476
15521
  plan: options2.codexPlan,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.293.0",
3
+ "version": "0.294.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {