@staff0rd/assist 0.293.0 → 0.294.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.
@@ -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.0",
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.293.0",
3
+ "version": "0.294.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {