Skip to content

Add a /reflow skill and the rules for refilling edited prose - #169

Merged
mattmenefee merged 2 commits into
mainfrom
add-reflow-skill
Oct 24, 2026
Merged

mattmenefee merged 2 commits into
mainfrom
add-reflow-skill

Conversation

@mattmenefee

@mattmenefee mattmenefee commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Add a /reflow skill that refills the paragraphs, list items and comment blocks a branch adds or modifies so each line uses the full line length for its file type, changing only line breaks
  • Bundle scripts/changed-lines.sh, which prints each added or modified line range against the merge base with the pull request's base branch, then the repository default, then origin/HEAD, preferring an upstream remote's copy in a fork. It names the base and what chose it on stderr, accepts --base <ref> or --base=<ref> anywhere, rejects unknown options and names each untracked or missing path it is given, so a mistake never reads as "no changes"
  • Take each file's limit from the project's configuration (.editorconfig, markdownlint, RuboCop, Prettier or ESLint, Ruff, Black or flake8, rustfmt, yamllint), then the configured tool's own default width, then 100
  • Leave alone what must not be wrapped: code statements (reported for the project's formatter instead), tables, code blocks, long URLs, tool directives, comment structure such as lists and doc tags, working documents and text meant for GitHub, Teams or Slack
  • Verify each file with a word diff against a backup, so only line breaks may change, then with the project's linter
  • Add a Code Style rule to the user-wide CLAUDE.md: when an edit changes a line of wrapped prose or comment, refill the paragraph or comment block it belongs to so every line again uses the full limit, changing only line breaks; /reflow does this for a whole branch
  • Add a Writing & Copy Conventions rule: a list item of one phrase or sentence takes no final period, and one of several sentences ends each of them with a period, the last included

The skill is written in the home/.claude/skills/<name>/SKILL.md layout that #167 introduces, in a new directory of its own. The CLAUDE.md rules sit beside lines #167 also refills, so whichever of the two merges second needs a small rebase there.

Test plan

  • CI lint (super-linter, including Bash and Markdown) green on adb3ed4
  • A two-reviewer document review (documentation-expert on SKILL.md, code-best-practices-reviewer on the script) with every actionable finding fixed or ruled; the review is posted below
  • changed-lines.sh in disposable repositories: an added, edited and deleted line, a deleted file and a name with a space give the expected ranges; a name with a " is reported on stderr rather than credited to another file; a submodule bump gives no row; a missing merge base and an invalid ref each print a reason
  • Argument handling: a path before --base, --base=<ref>, a mistyped option, a bare --base, a missing path and an untracked path each behave as above, also under /bin/bash 3.2
  • shellcheck on changed-lines.sh passes
  • npx markdownlint-cli --config .markdown-lint.yml on SKILL.md passes, and every paragraph is filled to 100 columns
  • The step 5 word-diff check flags changed words starting with - or + (--base, -, +1) and passes a pure reflow, moved block-quote marker included
  • npx markdownlint-cli --config .markdown-lint.yml on home/.claude/CLAUDE.md passes, and every existing multi-sentence bullet there already ends with a period
  • The upstream preference in a real fork clone
  • Run /reflow end to end on a real branch once linked: on Convert the user-wide slash commands to skills #167's branch after its rebase onto main, it refilled 32 blocks in 10 files; word streams are unchanged apart from moved # comment leaders, and markdownlint passes on the 6 Markdown files

@mattmenefee mattmenefee self-assigned this Oct 24, 2026
Edits to wrapped prose and comments leave lines that break well short
of the column limit, or run past it, and refilling them by hand means
working out which paragraphs the branch touched and what limit each
file type carries.

The skill finds the lines the branch adds or modifies with a bundled
script, measured from the merge base with the pull request's base
branch, then the repository default, then origin/HEAD, preferring an
upstream remote's copy of the branch in a fork. The script names the
base and what chose it on stderr, takes --base anywhere in its
arguments, rejects an unknown option and names each untracked or
missing path it is given, so a mistake never reads as "no changes".

Each file's limit comes from the project's linter or editor
configuration, then the configured tool's own default width, then
100. The skill refills each paragraph, list item or comment block
holding a changed line and stops at the block's edges, so untouched
lines stay put. Code, tables, long URLs, tool directives and documents
that must not be hard-wrapped are left alone; over-limit code lines
are reported for the project's formatter instead.

A word diff against a backup then confirms that only line breaks
changed, catching a lost word even when it starts with a dash, and
the project's linter checks the width.
@mattmenefee

Copy link
Copy Markdown
Owner Author

Document Review (add-reflow-skill-DOC-REVIEW.md) — all clear

27 findings across 2 documents: 25 fixed, 1 ignored, 1 observation (acted on)

Redacted before posting: none

Click to expand full details

Document Review: 2 documents on add-reflow-skill

Scope home/.claude/skills/reflow/SKILL.md, home/.claude/skills/reflow/scripts/changed-lines.sh (changes on add-reflow-skill since base d22496a)
Branch add-reflow-skill
Reviewed 2026-10-24
Reviewed by Opus 5.5 (claude-opus-5-5)
Collated by Opus 5.5 (claude-opus-5-5)

Documents

Document Commit
home/.claude/skills/reflow/SKILL.md ad98d8a (reviewed), now 54383ed with the fixes folded in
home/.claude/skills/reflow/scripts/changed-lines.sh ad98d8a (reviewed), now 54383ed with the fixes folded in

Review History

2026-10-24 — Initial review by Opus 5.5 (claude-opus-5-5)

Two reviewers, one per document: a documentation-expert on SKILL.md and a code-best-practices-reviewer on scripts/changed-lines.sh. The script's findings keep its reviewer's code categories (Correctness, Robustness, Accuracy of comments, Style/Consistency), placed after the document criteria. One duplicate was merged (F25, the stderr base line). No handed-back check was run: each needed a disposable repository, network access or a tool outside the read-only set, so every one stays unverified on its finding's Verification line.

Phase 1 rulings and check results recorded: F2 ruled (a) Leave code alone, edit pending in G1. Checks confirmed F5, F19, F20 and F23 and the Prettier default in F7; F7's Ruff and flake8 defaults and F12's Python half stay unverified, and F22 was not run. No finding was refuted. Phase 2 rulings recorded: F24 ruled Implement and moved into G2, F21 ignored. Groups G1–G8 were then worked in the uncommitted working tree and their findings marked ✅ Fixed; F27's optional action was taken as well.

The fixes were amended into the reviewed commit and force-pushed, so ad98d8a became 54383ed; the reviewed revision is no longer on the branch.

Overall Assessment

Every actionable finding is now off the pre-merge path: of the 26, 25 are ✅ Fixed and F21 is 🚫 Ignored, so no ❓ row or open box remains. All groups G1–G8 are complete, including the three 🟠 defects: step 5's word-diff filter (F6), step 2's fallback to 100 (F7) and the script's argument handling (F18). F2's ruling (a) is in step 3, and F24's ruled warning is in the script. The fixes are folded into 54383ed.

Formatting

F1 🟢 Low Priority - The skill's own paragraphs are not greedily filled to 100 ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — Lines 13, 29, 60, 66, 70, 94, 115, 116 and 122
  • Issue — The skill requires each line to take "as many words as fit", but on each of these lines the next line's first word fits within 100 columns (MD013 line_length: 100 allows exactly 100). Line 122 is the clearest: 52 characters ("…took the limit from. Where there is"), with none, waiting on line 123. Lines 66, 70 and 94 stay under 100 even if the author counted the limit as exclusive.
  • Suggestion — Refill these paragraphs, e.g. by running /reflow home/.claude/skills/reflow/SKILL.md once the other findings are fixed.
  • Recommendation — Implement: the skill should meet its own rule, and the fix changes only line breaks.
  • Verification — An awk pass over the file comparing each line's length plus one plus the next line's first word against 100; the file is pure ASCII (LC_ALL=C grep -n '[^ -~]' finds nothing), so awk's byte lengths equal character counts.
  • Status — ✅ Fixed: refilled SKILL.md; a greedy-fill check at 100 columns flags no line except three whose next word opens a code span that does not fit whole, which step 4 keeps unbroken; markdownlint passes

Consistency

F2 ⚖️ Decision - Running the project's formatter on code statements contradicts "only line breaks change" ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §3, the "Code statements" bullet (lines 70–72), against the description, line 13 and §5
  • Issue — The description and line 13 promise that only line breaks change and every file's words read the same. Step 3 tells the agent to "run [the formatter] on the file" (rubocop -A, prettier, ruff format, rustfmt). These reformat the whole file, untouched lines included, and rubocop -A applies every autocorrection, unsafe ones included. Step 5's word diff then reports those changes as changed words and tells the agent to "restore that block from the backup and refill it again", which loops or undoes the formatter. The bullet also writes rubocop -A where the user-wide checklist uses bin/rubocop -A.
  • Options
    • (a) Leave code statements alone — list over-limit code lines in the step 6 report and suggest the project's formatter or linter (e.g. bin/rubocop -A). The skill stays true to its description and step 5 stays valid; the user runs the formatter as a separate, visible step.
    • (b) Keep running the formatter, scoped — limit it to layout-only changes where the tool allows (e.g. bin/rubocop -a --only Layout/LineLength), run it before taking the backup and exempt it from step 5. Keeps code within the limit in one pass, but adds per-tool rules and the skill no longer changes "only line breaks".
  • Recommended — (a): the skill's contract and its only check both assume nothing but line breaks change, and code formatting already has its own step in the user's task completion checklist.
  • Status — ✅ Fixed: ruled (a) Leave code alone — reflow only prose and comments; list over-limit code lines in the report and suggest the formatter as a separate step. §3's code bullet now leaves code statements, commented-out code and comment diagrams alone, and step 6 lists over-limit code lines with the formatter that would fix them (bin/rubocop -A as the example)

F3 🟡 Medium Priority - Refilling a block quote prints > lines in a check that "must print nothing" in prose ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §5 Check, lines 115–117, against §3 line 58 ("a block-quote paragraph")
  • Issue — Step 3 refills block-quote paragraphs. Joining > quoted text / > more removes a > token, which the porcelain diff reports as ->. Step 5 says the check "must print nothing" in prose and "anything else is a changed word", so a literal agent restores every block quote it refills and tries again, forever.
  • Suggestion — "In prose it prints only > block-quote markers that moved between lines; in a comment block, only comment leaders (#, //) that moved. Anything else is a changed word…"
  • Recommendation — Implement: the check as written rejects a block type step 3 tells the agent to refill.
  • Verification — Same in-memory git diff --no-index run as F6, with '> quoted text\n> more\n' vs '> quoted text more\n', prints ->.
  • Status — ✅ Fixed: step 5 now says prose prints only moved > markers and comment blocks only moved leaders; a pure reflow with a block quote printed only ->

F4 🟡 Medium Priority - A named untracked file hits the "empty output, stop" rule ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §1, lines 30–35
  • Issue — Line 31 says to reflow an untracked file "only when the user names it", but the script never reports untracked files (see scripts/changed-lines.sh, header comment and the git diff on line 38), so /reflow notes.md for an untracked notes.md prints nothing, and lines 34–35 then say "say so and stop". The skill gives no line range for the named untracked case and routes it to the stop.
  • Suggestion — Add after line 31: "For an untracked file the user names, treat every line as changed." and qualify line 34: "Empty output, with no untracked file named, means…"
  • Recommendation — Implement: two clauses close a direct contradiction between adjacent paragraphs.
  • Status — ✅ Fixed: §1 says to treat every line of a named untracked file as changed, and the empty-output rule excludes a named untracked file

F5 🟡 Medium Priority - The config table misses file names, including this repository's own YAML config ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §2 table (lines 41–49)
  • Issue — The Markdown row includes the super-linter name .markdown-lint.yml, but the YAML row has only .yamllint*, while this repository configures yamllint through .yaml-lint.yml at its root (line-length: max: 100), so the skill misses the setting in its own repository. Other gaps: Ruff also reads ruff.toml/.ruff.toml, flake8 also tox.ini, rustfmt .rustfmt.toml (see F9), ESLint's current config is eslint.config.* (flat config) and Prettier also reads .editorconfig max_line_length and formats Markdown at printWidth under proseWrap: always.
  • Suggestion — YAML cell: ".yamllint* or .yaml-lint.yml rules.line-length.max"; add ruff.toml/.ruff.toml and tox.ini to the Python cell and eslint.config.* to the JavaScript cell.
  • Recommendation — Implement: the YAML name appears in this very repository, and the others are cheap while the table is open.
  • Verification — ls -a at the repository root shows .yaml-lint.yml and .markdown-lint.yml, no .yamllint*. Ruff, flake8 and ESLint file names confirmed: docs.astral.sh/ruff/configuration ("Ruff can be configured through a pyproject.toml, ruff.toml, or .ruff.toml file"), flake8.pycqa.org/en/latest/user/configuration.html (setup.cfg, tox.ini or .flake8) and eslint.org/docs/latest/use/configure/configuration-files (eslint.config.{js,mjs,cjs,ts,mts,cts}).
  • Status — ✅ Fixed: the table names .yaml-lint.yml, ruff.toml/.ruff.toml, tox.ini, .rustfmt.toml and eslint.config.*

Accuracy

F6 🟠 High Priority - Step 5's word-diff filter hides changed words that begin with - or + ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §5 Check, lines 111–113
  • Issue — grep '^[-+][^-+]' is meant to drop the --- a/… and +++ b/… file headers, but it also drops every porcelain line whose word begins with - or +: a removed --base appears as ---base, a removed - as --, a removed +1 as -+1, and all three are filtered out. The check then prints nothing and the agent reports "only line breaks changed" when a word was lost or added. Markdown in this repository is full of such words: CLI flags, - used as a dash, +/- list markers moved inside a line, ->.
  • Suggestion — Skip the file header by position, not by the second character: git diff --no-index --word-diff=porcelain --word-diff-regex='[^[:space:]]+' <backup> <file> | sed '1,/^+++ /d' | grep '^[-+]'. With --no-index there is exactly one file header, and when the files are identical sed deletes everything, which is still the correct "prints nothing".
  • Recommendation — Implement: this check is the skill's only proof that it changed nothing but line breaks, and it silently passes today.
  • Verification — Ran with git 2.56.0 on in-memory inputs (process substitution, no files written): git diff --no-index --word-diff=porcelain --word-diff-regex='[^[:space:]]+' <(printf 'Pass --base main\nor a - dash +1 here.\n') <(printf 'Pass main\nor a dash here.\n') emits ---base, -- and -+1; the document's grep '^[-+][^-+]' prints none of them, while sed '1,/^+++ /d' | grep '^[-+]' prints all three, and a pure line-break change ('a b\nc\n' vs 'a b c\n') prints nothing.
  • Status — ✅ Fixed: step 5 pipes through sed '1,/^+++ /d' | grep '^[-+]'; on the finding's own example it now prints ---base, -- and -+1

F7 🟠 High Priority - The 100 fallback overrides a linter's own default when a project configures the linter but not the width ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §2 Pick Each File's Limit, line 51 ("Where nothing sets one, use the user-wide default of 100")
  • Issue — The table names only explicit width keys. When a project runs a linter whose configuration leaves the width unset, the linter enforces its own default, but a literal agent finds "nothing sets one" and reflows to 100. Those defaults differ: markdownlint MD013 80 (Number(params.config.line_length || 80) in markdownlint's lib/md013.mjs), yamllint 80 under extends: default (DEFAULT = {'max': 80 in yamllint/rules/line_length.py), RuboCop Layout/LineLength 120 (Max: 120, rubocop 1.92.1 config/default.yml), Prettier 80, Black and Ruff 88, flake8 79, ESLint max-len 80 when enabled. A project with a stock .markdownlint.yaml (default: true) gets 81–100-column lines that step 5's markdownlint run then flags; a stock Rails project gets refilled 20 columns short. This contradicts the user-wide rule to "Defer to a project's own linter".
  • Suggestion — Replace the line 51 sentence with: "Where the project runs one of these tools but leaves the width unset, the tool's default governs (markdownlint and yamllint 80, Prettier 80, Black and Ruff 88, flake8 79, RuboCop 120, rustfmt 100); where no tool or .editorconfig applies, use the user-wide default of 100." Optionally name the effective-config queries, e.g. rubocop --show-cops Layout/LineLength, ruff check --show-settings <file>, rustfmt --print-config current <file>.
  • Recommendation — Implement: the common case gets the wrong width under the current wording.
  • Verification — markdownlint, yamllint and RuboCop defaults verified from installed sources (paths above). Prettier 80 confirmed: npx --yes prettier --help | grep -A1 print-width reported "Defaults to 80". ESLint's 80 is confirmed on eslint.org/docs/latest/rules/max-len. Ruff 88 unverified: ruff check --show-settings <file.py> could not run, since its placeholder does not parse and the repository has no .py file. flake8 79 unverified: flake8 is not installed, so flake8 --help | grep -A1 max-line-length could not run. Black 88 unverified.
  • Status — ✅ Fixed: step 2 says a tool's default governs where the project runs it without a width, and the table gives each default; 100 applies only where no tool or .editorconfig does

F8 🟡 Medium Priority - markdownlint-cli does not find the project's config from any directory ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §5 Check, lines 119–121
  • Issue — The text says npx --yes markdownlint-cli <file> "finds the project's .markdownlint* file itself". The markdownlint-cli README says it "overlays any configuration from the first matching file of .markdownlint.jsonc/.markdownlint.json/.markdownlint.yaml/.markdownlint.yml in the current folder", not the file's directory or its parents. Run from anywhere but the directory holding the config, MD013 runs at its default of 80. The command also reports every rule, not just MD013, so pre-existing violations elsewhere read as noise an agent may try to "fix".
  • Suggestion — Reword to: "npx --yes markdownlint-cli <file> run from the directory that holds the project's .markdownlint.{jsonc,json,yaml,yml} (pass --config <path> for any other name or location, such as .markdown-lint.yml), and act only on its MD013 lines."
  • Recommendation — Implement: a one-clause fix keeps the width check from silently using 80.
  • Verification — markdownlint-cli README (github.com/igorshubovych/markdownlint-cli), quoted above.
  • Status — ✅ Fixed: step 5 runs npx --yes markdownlint-cli --config <config file> <file> with the config named by path, drops --config when there is none, and acts only on MD013

F9 🟡 Medium Priority - rustfmt comment_width has no effect without wrap_comments = true, which is unstable ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §2 table, Rust row (line 48)
  • Issue — rustfmt's Configurations.md defines comment_width as "Maximum length of comments. No effect unless wrap_comments = true", with a default of 80, and both options are "Stable: No" (nightly only). An agent reading the Rust row literally refills comments to comment_width, or its default 80, in a project where rustfmt never applies it, when max_width (default 100, stable) is the only live width. The row also omits .rustfmt.toml, the other name rustfmt reads.
  • Suggestion — Change the cell to: "rustfmt.toml or .rustfmt.toml max_width; comment_width for comments only where wrap_comments = true is also set".
  • Recommendation — Implement: the row as written states a limit that is not live.
  • Verification — raw.githubusercontent.com/rust-lang/rustfmt/master/Configurations.md.
  • Status — ✅ Fixed: the Rust row gives max_width, and comment_width only where wrap_comments = true

Clarity and Structure

F10 🟡 Medium Priority - Comment blocks have no internal boundaries, so the agent fills across # separator lines, lists and tags ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §3, lines 58–62, and §4 lines 99–102
  • Issue — A comment block is "a run of consecutive line comments sharing one indentation and leader", and its edges include "a blank line", but a lone # or // line is not blank, so a literal agent fills straight across it. The sibling script's own header shows the pattern: paragraph, #, # Usage: …, #, paragraph. The block-start guard in §4 is Markdown-only, so lists inside comments (# - item), YARD/JSDoc tags (# @param, * @returns), Usage: lines, indented example code within a comment, ASCII diagrams and commented-out code all get joined into prose.
  • Suggestion — Add to the §3 edges: "…or, inside a comment block, a leader-only line (#, //), a list marker or doc tag after the leader, or a more-indented line after the leader." Add to "Leave these alone": "commented-out code and comment diagrams."
  • Recommendation — Implement: comment blocks are half the skill's stated scope, and the current rule damages common comment structure.
  • Status — ✅ Fixed: §3 makes a leader-only line, a list marker or doc tag after the leader and a further-indented line edges inside a comment block, and leaves commented-out code and comment diagrams alone

F11 🟡 Medium Priority - Precedence between .editorconfig and a linter's setting is unstated ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §2, line 39 ("the most specific setting winning") and the "Any" row
  • Issue — "Most specific" does not settle .editorconfig [*.md] max_line_length = 120 against .markdownlint.yaml MD013 line_length: 100: both are scoped to the file type. Step 5 checks the width with the linter, so picking .editorconfig produces lines the check then flags.
  • Suggestion — Replace "the most specific setting winning" with: "where a linter step 5 runs sets the width, it wins; otherwise the .editorconfig section that matches the file most narrowly."
  • Recommendation — Implement: ties the chosen limit to the check that enforces it.
  • Status — ✅ Fixed: step 2 says a linter step 5 runs wins; otherwise the narrowest .editorconfig section

F12 🟢 Low Priority - Comment-specific limits in ESLint and pycodestyle/Ruff are missing from the table ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §2 table, JavaScript and Python rows
  • Issue — The Rust row gives comments their own limit, but ESLint max-len has a comments option ("enforces a maximum line length for comments; defaults to value of code") and ignoreComments, and pycodestyle/flake8 max-doc-length (W505) and Ruff lint.pycodestyle.max-doc-length limit comments and docstrings separately. Since the skill refills comment blocks, these are exactly the limits that apply.
  • Suggestion — Append "(comments for comments)" to the ESLint entry and "max-doc-length for comments" to the Python row.
  • Recommendation — Implement: a few words that bear directly on the skill's comment-refill scope.
  • Verification — ESLint comments/ignoreComments: eslint.org/docs/latest/rules/max-len. pycodestyle max-doc-length inconclusive: pycodestyle.pycqa.org/en/latest/intro.html#configuration shows the option exists ("set maximum allowed doc line length and perform these checks (unchecked if not set)", reported as W505) but does not say whether doc lines include comments, so whether it covers comments, and Ruff's lint.pycodestyle.max-doc-length, stay unverified.
  • Status — ✅ Fixed: the table gives ESLint comments and Python max-doc-length for comments

F13 🟢 Low Priority - "A change of indentation" ends a block where a list item's own continuation lines begin ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §3, line 61
  • Issue — A list item's first line starts at the marker column and its continuation lines at the text column, so the indentation changes inside one item; the same holds between a YAML folded scalar's key line and its body. Read literally, the edge rule cuts the block that §4 (lines 96–97) then says to fill with "a list item's text column".
  • Suggestion — "a change of indentation other than a list item's or folded scalar's continuation indent".
  • Recommendation — Implement: a short qualifier that keeps §3 and §4 consistent.
  • Status — ✅ Fixed: the indentation edge excepts a list item's or folded scalar's continuation indent

F14 🟢 Low Priority - The Markdown continuation guard misses fences, HTML block openers and thematic breaks ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §4, lines 99–101
  • Issue — In CommonMark, a fence (three or more backticks or ~), an HTML block opener (<!--, <div, <pre, <script and the like) and a thematic break (***, ___) can also interrupt a paragraph, so a word like that wrapped to the start of a continuation line changes the rendering, breaking the line 14 promise.
  • Suggestion — Add "a fence (three or more backticks or tildes), < opening an HTML block, a run of * or _" to the list.
  • Recommendation — Implement: one phrase, and the case is rare but silent.
  • Status — ✅ Fixed: the continuation guard adds thematic breaks, fences and < opening an HTML block

F15 🟢 Low Priority - --base must come first, which the skill doesn't say ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — Lines 18–20 and §1 line 25
  • Issue — The script recognizes --base only as the first argument (scripts/changed-lines.sh lines 16–19); /reflow README.md --base main passes --base and main through as pathspecs, so the scope is wrong with no error, and the stderr line still names the default base.
  • Suggestion — Line 18: "--base <ref>, given before any path, sets…"
  • Recommendation — Implement: a few words prevent a silent wrong scope.
  • Status — ✅ Fixed: fixed by F18; --base is now accepted anywhere in the argument list, which the Scope paragraph says ("anywhere in the list"), so no ordering note is needed

Spelling and Grammar

F16 🟢 Low Priority - "a MD013" should be "an MD013" ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §2, lines 51–52
  • Issue — "MD013" is read "em-dee", so it takes "an".
  • Suggestion — "an MD013 line_length".
  • Recommendation — Implement: one word.
  • Status — ✅ Fixed: §2 now reads "an MD013"

Staleness

F17 🟢 Low Priority - ESLint max-len is deprecated in favor of @stylistic/max-len ✅ Fixed

  • Document — home/.claude/skills/reflow/SKILL.md
  • Location — §2 table, JavaScript row (line 46)
  • Issue — eslint.org: "This rule was deprecated in ESLint v8.53.0. It will be removed in v11.0.0. Please use the corresponding rule in @stylistic/eslint-plugin." Current projects set the width under @stylistic/max-len, which the row does not name, so an agent searching only for core max-len may miss it.
  • Suggestion — "ESLint @stylistic/max-len (or the deprecated core max-len)".
  • Recommendation — Implement: a small edit that keeps the row true past ESLint v11.
  • Verification — eslint.org/docs/latest/rules/max-len.
  • Status — ✅ Fixed: the ESLint entry names @stylistic/max-len beside the deprecated core max-len

Correctness

F18 🟠 High Priority - --base is recognized only as the first argument; anything else silently becomes a pathspec ✅ Fixed

  • Document — home/.claude/skills/reflow/scripts/changed-lines.sh

  • Location — lines 15–19, and line 39 (-- "$@")

  • Issue — Only "$1" == --base is parsed; every other word is handed to git diff as a pathspec, where an unmatched pathspec is not an error. Run from the checkout: changed-lines.sh home/.claude/skills/reflow/SKILL.md --base origin/main ignored --base (stderr showed the gh-resolved SHA, and --base/origin/main were treated as paths); changed-lines.sh --base=origin/main and changed-lines.sh --bsae origin/main both printed only the base line, no rows, rc=0; changed-lines.sh --base origin/main no/such/file.md gave no rows, rc=0. home/.claude/skills/reflow/SKILL.md step 1 passes <arguments> from the user's free-text $ARGUMENTS straight through, then says "Empty output means the branch changes nothing in tracked files under the given paths; say so and stop" — so a reordered flag, a --base= form or a typo makes the skill confidently report that nothing changed. --base with no value does error (line 17: 2: --base needs a ref, rc=1), though the message is bash's, not the script's.

  • Suggestion — Parse every argument in a loop, collecting paths and rejecting unknown options:

    base= paths=()
    while (($#)); do
      case $1 in
        --base) (($# >= 2)) || { echo "--base needs a ref" >&2; exit 2; }; base=$2; shift 2 ;;
        --base=*) base=${1#--base=}; shift ;;
        --) shift; paths+=("$@"); break ;;
        -*) echo "Unknown option $1. Usage: changed-lines.sh [--base <ref>] [path...]" >&2; exit 2 ;;
        *) paths+=("$1"); shift ;;
      esac
    done

    then pass -- ${paths[@]+"${paths[@]}"} to git diff (the guarded expansion keeps an empty array from tripping set -u under the bash 3.2 at /bin/bash; the shebang resolves to Homebrew bash 5.3 here, but /bin/bash is 3.2.57 and the Brewfile does not list bash).

  • Recommendation — Implement: this is the main path by which a user's input reaches the script, and the failure is indistinguishable from a legitimate "no changes" result.

  • Status — ✅ Fixed: the script parses every argument in a loop, accepts --base <ref> and --base=<ref> anywhere, and exits 2 with a usage line on an unknown option or a missing ref; checked: path-then---base, --base=, --bsae and bare --base each behave so

F19 🟡 Medium Priority - A quoted +++ header leaves the previous file's path in place, so its hunks are attributed to the wrong file ✅ Fixed

  • Document — home/.claude/skills/reflow/scripts/changed-lines.sh

  • Location — awk program, lines 41–53

  • Issue — core.quotePath=false stops git escaping only non-ASCII bytes; git still C-quotes a name holding ", \, a tab, a newline or another control character (git -c core.quotePath=false check-ignore --no-index --non-matching -v printed "a\"b.md", "a\tb.md" and "a\\b.md" quoted, café.md and a b.md bare). The diff header for such a file is +++ "b/…", which /^\+\+\+ b\// does not match, so path still holds the previous file's name and the following @@ lines print rows for that file; the skill would then refill unchanged lines in the wrong file. If the quoted file is first, rows print with an empty path. A related edge: a header match anywhere in the stream treats an added content line of ++ b/x (shown as +++ b/x) as a new file header.

  • Suggestion — Track header state: reset path on each diff --git line, accept +++ only before the file's first @@, and skip-and-warn rather than misattribute a quoted name:

    /^diff --git / { header = 1; path = ""; next }
    header && /^\+\+\+ / {
      if ($0 ~ /^\+\+\+ b\//) { path = substr($0, 7); sub(/\t$/, "", path) }
      else print "changed-lines.sh: skipped quoted path " substr($0, 5) > "/dev/stderr"
      next
    }
    /^@@ / { header = 0; if (path == "") next; ... }
  • Recommendation — Implement: rare names, but the failure mode is edits to the wrong file, which is worse than a miss, and the fix is a few lines of awk.

  • Verification — Confirmed in a disposable repository under the scratchpad: with one.md and q"q.md each gaining a second line, the script run with --base HEAD printed one.md 2-2 twice and no q"q.md row.

  • Status — ✅ Fixed: the awk tracks header state per diff --git and reports a quoted path on stderr instead of misattributing it; in the disposable repository it now prints one.md 2-2 once plus a "skipped a quoted path" line

F20 🟢 Low Priority - A submodule bump or symlink change prints a row for a non-text target ✅ Fixed

  • Document — home/.claude/skills/reflow/scripts/changed-lines.sh
  • Location — git diff flags, lines 38–39
  • Issue — With git's default diff.submodule=short, a changed submodule prints +++ b/<sub> and @@ -1 +1 @@ (Subproject commit …), so the script prints <sub> 1-1 for a directory. The user's gitconfig sets diff.submodule log, which happens to suppress this (git config --get-regexp '^diff\.'), so the output depends on config exactly as the prefix flags were added to prevent. A changed symlink likewise prints <link> 1-1 for the link's target string, which the Read/Edit tools would follow to an unrelated file.
  • Suggestion — Add --ignore-submodules to the git diff call. Symlinks are rare enough to leave.
  • Recommendation — Implement for the submodule flag (one word, removes a config dependency); Skip for symlinks.
  • Verification — Confirmed in a disposable repository under the scratchpad with submodule sub advanced one commit: git -c diff.submodule=short diff --unified=0 --src-prefix=a/ --dst-prefix=b/ HEAD | grep -E '^(\+\+\+|@@)' printed +++ b/sub and @@ -1 +1 @@. The symlink case was not run.
  • Status — ✅ Fixed: git diff passes --ignore-submodules; the disposable submodule repository now prints no row. Symlinks left alone, per the finding's Skip half

F21 🟢 Low Priority - A pure deletion at the end of a file reports a line past EOF 🚫 Ignored

  • Document — home/.claude/skills/reflow/scripts/changed-lines.sh
  • Location — awk, lines 46–48
  • Issue — For @@ -n,k +c,0 @@ where c is the file's last line, last = c + 1 names a line that does not exist. The header comment says "the two lines either side of the gap", which is not true at EOF.
  • Suggestion — None needed in code; the consumer reads a range and a past-EOF bound is harmless.
  • Recommendation — Skip: awk cannot see the file length without a second read, and the skill tolerates an out-of-range bound.
  • Status — 🚫 Ignored: harmless; the skill reads the range and an out-of-range bound does nothing

Robustness

F22 🟡 Medium Priority - In a fork with an upstream remote, the base resolves to the fork's origin/<name> ✅ Fixed

  • Document — home/.claude/skills/reflow/scripts/changed-lines.sh

  • Location — lines 22–24

  • Issue — Neither gh call passes --repo; with an upstream remote, gh may resolve the base repository to upstream, giving the upstream pull request's base branch name, but the script then prefers refs/remotes/origin/$name, the fork's copy, which is often stale. When the fork's main lags upstream/main, the merge base is older and the output includes upstream commits the branch never touched as "changed" lines. The user's home/.claude/CLAUDE.md (Pull Requests) names exactly this clone shape as one where gh resolves to the parent. If no origin/$name exists, the fallback base=$name uses a local branch that may be stale or missing (fatal: Not a valid object name …).

  • Suggestion — Try upstream then origin before the bare name:

    for remote in upstream origin; do
      git rev-parse --verify --quiet "refs/remotes/$remote/$name" >/dev/null \
        && { base=$remote/$name; break; }
    done
    base=${base:-$name}

    This also matches home/.claude/skills/handoff/scripts/gather-state.sh, which takes a remote argument rather than hard-coding origin.

  • Recommendation — Implement: the user's own guidance treats upstream clones as a real case, and the fix is small.

  • Verification — unverified. In a clone with origin (a fork) and upstream and no gh repo set-default: gh repo view --json nameWithOwner --jq .nameWithOwner printing the upstream owner confirms gh resolves to the parent; git rev-parse origin/main upstream/main printing different SHAs confirms the stale-base effect.

  • Status — ✅ Fixed: base resolution prefers upstream/<name>, then origin/<name>, then the bare name (not exercised in a real fork clone)

F23 🟢 Low Priority - A missing merge base exits 1 with no message ✅ Fixed

  • Document — home/.claude/skills/reflow/scripts/changed-lines.sh

  • Location — line 33

  • Issue — git merge-base prints nothing and exits 1 when there is no common ancestor (a shallow clone, an unrelated base). set -e then ends the script before the stderr base line, so the agent sees no output and no reason. An invalid ref does print git's own fatal: line (checked: --base nosuchref gives rc=128).

  • Suggestion —

    merge_base=$(git merge-base HEAD "$base") || {
      echo "No merge base between HEAD and $base; fetch more history or pass --base <ref>." >&2
      exit 1
    }
  • Recommendation — Implement: three lines turn a silent stop into an actionable one.

  • Verification — Confirmed in a disposable repository under the scratchpad with an orphan branch: the script run with --base base gave only rc=1, with nothing on stderr.

  • Status — ✅ Fixed: a missing merge base prints "No merge base between HEAD and ; fetch more history or pass --base ." and an invalid ref "Not a commit: ." followed by the usage line; checked in the disposable orphan-branch repository

F24 🟢 Low Priority - A path that matches no tracked file prints nothing, which reads as "no changes" ✅ Fixed

  • Document — home/.claude/skills/reflow/scripts/changed-lines.sh
  • Location — line 39
  • Issue — changed-lines.sh --base origin/main no/such/file.md printed only the base line and exit 0. SKILL.md step 1 treats that as "the branch changes nothing … under the given paths" and stops, so a mistyped path ends the run without a warning. Distinct from F18: the argument here is parsed correctly.
  • Suggestion — After parsing, warn on stderr for each path that is neither on disk nor matched by git ls-files, e.g. [[ -e $p ]] || git ls-files --error-unmatch -- "$p" >/dev/null 2>&1 || echo "No such path: $p" >&2.
  • Recommendation — Defer: useful, but the user named the path and is likely to notice; F18 is the higher-value parsing fix.
  • Status — ✅ Fixed: ruled Implement — warn on stderr for each path neither on disk nor tracked, inside F18's argument loop (moved to G2). The script prints no such path: <p> or untracked, so not diffed: <p> on stderr for each named path, in a pass right after the argument loop; checked with a missing path and an untracked one

Accuracy of comments

F25 🟡 Medium Priority - The stderr line prints a SHA, not the ref chosen, and not which source chose it ✅ Fixed

  • Document — home/.claude/skills/reflow/scripts/changed-lines.sh

  • Location — line 24 and line 34

  • Issue — base=$(git rev-parse --verify --quiet "refs/remotes/origin/$name") stores the SHA, so running the script with no arguments printed base: d22496ac… (merge base d22496ac…); with --base origin/main it printed base: origin/main (…). home/.claude/skills/reflow/SKILL.md step 1 says the script "names the base it chose on stderr". The line also omits whether the pull request, the repository default or origin/HEAD supplied it, which is the one fact the agent needs to catch the case SKILL.md warns about: a stacked branch with no pull request silently falling back to the default branch. home/.claude/skills/handoff/scripts/gather-state.sh reports both (base: $base ($base_source)).

  • Suggestion — Keep the ref name, test with >/dev/null, and record the source:

    if git rev-parse --verify --quiet "refs/remotes/origin/$name" >/dev/null; then
      base=origin/$name
    else
      base=$name
    fi
    ...
    echo "base: $base ($source; merge base $merge_base)" >&2

    with $source set to pull request, repository default, origin/HEAD or --base on each branch.

  • Recommendation — Implement: the output contradicts SKILL.md as written, and the source is what lets the skill catch a wrong default.

  • Concurred by — home/.claude/skills/reflow/SKILL.md (its report filed this under Consistency at 🟢 Low Priority, citing §1 lines 28–30, and also recommended implementing the fix in the script)

  • Status — ✅ Fixed: stderr now reads base: <ref> (<chosen by>; merge base <sha>) with the ref name and pull request, repository default, origin/HEAD or --base; §1 says so

F26 🟢 Low Priority - "joining them" in the header comment has no clear referent ✅ Fixed

  • Document — home/.claude/skills/reflow/scripts/changed-lines.sh
  • Location — lines 10–11
  • Issue — "since joining them can leave the surrounding paragraph short" — "them" grammatically points to "the two lines", but it is the deletion, not a join, that leaves the paragraph short. home/.claude/skills/reflow/SKILL.md step 1 says it more clearly: "since the paragraph around a deletion can be left short".
  • Suggestion — "A hunk that only deletes lines is reported as the two lines either side of the gap, since the deletion can leave the paragraph around it short."
  • Recommendation — Implement: a one-line wording fix that also aligns the comment with the skill.
  • Status — ✅ Fixed: the header comment now says "since the deletion can leave the paragraph around it short"

Observations

F27 💡 Observation (optional action) - --default-prefix replaces the two prefix flags

  • Document — home/.claude/skills/reflow/scripts/changed-lines.sh
  • Location — lines 36–39 (filed under Style/Consistency)
  • Issue — The installed git 2.56.0 documents --default-prefix ("Use the default source and destination prefixes … overrides … diff.noprefix, diff.srcPrefix, diff.dstPrefix, and diff.mnemonicPrefix"). It is one readable flag and it also covers diff.srcPrefix/diff.dstPrefix, which the comment does not mention. The user's Git convention says to assume the latest Git. Optional action: replace --src-prefix=a/ --dst-prefix=b/ with --default-prefix and shorten the comment accordingly. The action was taken: the script's git diff now passes --default-prefix in place of the two prefix flags, and its comment says so.

Consolidated Summary

Finding Type Category Description Document Location Recommendation Group Status
F1 🟢 Low Formatting The skill's own paragraphs are not greedily filled to 100 home/.claude/skills/reflow/SKILL.md Lines 13, 29, 60, 66, 70, 94, 115, 116, 122 Implement G8 ✅
F2 ⚖️ Decision Consistency Running the formatter on code contradicts "only line breaks change" home/.claude/skills/reflow/SKILL.md §3 "Code statements" bullet Options (a) G1 ✅
F3 🟡 Medium Consistency Refilled block quotes print > lines in a check that must print nothing home/.claude/skills/reflow/SKILL.md §5 Check, lines 115–117 Implement G3 ✅
F4 🟡 Medium Consistency A named untracked file hits the "empty output, stop" rule home/.claude/skills/reflow/SKILL.md §1, lines 30–35 Implement G2 ✅
F5 🟡 Medium Consistency The config table misses file names, including this repository's YAML config home/.claude/skills/reflow/SKILL.md §2 table Implement G4 ✅
F6 🟠 High Accuracy Step 5's word-diff filter hides changed words that begin with - or + home/.claude/skills/reflow/SKILL.md §5 Check, lines 111–113 Implement G3 ✅
F7 🟠 High Accuracy The 100 fallback overrides a configured linter's own default width home/.claude/skills/reflow/SKILL.md §2, line 51 Implement G4 ✅
F8 🟡 Medium Accuracy markdownlint-cli reads its config only from the current directory home/.claude/skills/reflow/SKILL.md §5 Check, lines 119–121 Implement G3 ✅
F9 🟡 Medium Accuracy rustfmt comment_width is inert without unstable wrap_comments home/.claude/skills/reflow/SKILL.md §2 table, Rust row Implement G4 ✅
F10 🟡 Medium Clarity and Structure Comment blocks have no internal boundaries home/.claude/skills/reflow/SKILL.md §3 lines 58–62, §4 lines 99–102 Implement G7 ✅
F11 🟡 Medium Clarity and Structure Precedence between .editorconfig and a linter's setting is unstated home/.claude/skills/reflow/SKILL.md §2, line 39 Implement G4 ✅
F12 🟢 Low Clarity and Structure Comment-specific limits in ESLint and pycodestyle/Ruff are missing home/.claude/skills/reflow/SKILL.md §2 table, JavaScript and Python rows Implement G4 ✅
F13 🟢 Low Clarity and Structure "A change of indentation" cuts a list item at its continuation lines home/.claude/skills/reflow/SKILL.md §3, line 61 Implement G7 ✅
F14 🟢 Low Clarity and Structure The continuation guard misses fences, HTML block openers and thematic breaks home/.claude/skills/reflow/SKILL.md §4, lines 99–101 Implement G7 ✅
F15 🟢 Low Clarity and Structure --base must come first, which the skill doesn't say home/.claude/skills/reflow/SKILL.md Lines 18–20, §1 line 25 Implement G2 ✅
F16 🟢 Low Spelling and Grammar "a MD013" should be "an MD013" home/.claude/skills/reflow/SKILL.md §2, lines 51–52 Implement G4 ✅
F17 🟢 Low Staleness ESLint max-len is deprecated in favor of @stylistic/max-len home/.claude/skills/reflow/SKILL.md §2 table, JavaScript row Implement G4 ✅
F18 🟠 High Correctness --base is recognized only first; other words silently become pathspecs home/.claude/skills/reflow/scripts/changed-lines.sh lines 15–19, 39 Implement G2 ✅
F19 🟡 Medium Correctness A quoted +++ header attributes hunks to the previous file home/.claude/skills/reflow/scripts/changed-lines.sh awk, lines 41–53 Implement G6 ✅
F20 🟢 Low Correctness A submodule bump or symlink change prints a row for a non-text target home/.claude/skills/reflow/scripts/changed-lines.sh lines 38–39 Implement (submodule flag) G6 ✅
F21 🟢 Low Correctness A pure deletion at end of file reports a line past EOF home/.claude/skills/reflow/scripts/changed-lines.sh awk, lines 46–48 Skip — 🚫
F22 🟡 Medium Robustness In a fork with upstream, the base resolves to the fork's origin/<name> home/.claude/skills/reflow/scripts/changed-lines.sh lines 22–24 Implement G5 ✅
F23 🟢 Low Robustness A missing merge base exits 1 with no message home/.claude/skills/reflow/scripts/changed-lines.sh line 33 Implement G5 ✅
F24 🟢 Low Robustness A path that matches no tracked file reads as "no changes" home/.claude/skills/reflow/scripts/changed-lines.sh line 39 Defer G2 ✅
F25 🟡 Medium Accuracy of comments The stderr line prints a SHA, not the ref chosen or its source home/.claude/skills/reflow/scripts/changed-lines.sh lines 24, 34 Implement G5 ✅
F26 🟢 Low Accuracy of comments "joining them" in the header comment has no clear referent home/.claude/skills/reflow/scripts/changed-lines.sh lines 10–11 Implement G6 ✅
F27 💡 Observation Style/Consistency --default-prefix replaces the two prefix flags home/.claude/skills/reflow/scripts/changed-lines.sh lines 36–39 — — —

Pre-Merge Checklist

G1 ✅ — Settle whether step 3 runs the project's formatter

Closed by F2's ruling (a): §3's code bullet now leaves code statements alone, and step 6 lists over-limit code lines with the formatter that would fix them.

  • ✅ F2 - Whether step 3 runs the project's formatter on code statements (ruled (a) — leave code alone)

G2 ✅ — Make step 1's scope fail loudly instead of reading as "no changes"

Closed by the script's argument loop (F18), which accepts --base anywhere and so made F15's ordering note unneeded, its stderr warning for each untracked or missing path (F24) and §1's rule for a named untracked file (F4).

  • ✅ F18 - Parse every argument in changed-lines.sh and reject unknown options
  • ✅ F15 - Say --base comes before any path, unless F18 makes argument order free
  • ✅ F4 - Treat a named untracked file as wholly changed instead of stopping on empty output
  • ✅ F24 - Warn when a path matches no tracked file

G3 ✅ — Make step 5's check catch what it claims to

Closed by step 5's position-based header skip (F6), its allowance for moved > markers and comment leaders (F3) and a markdownlint-cli run that names the config with --config and acts only on MD013 (F8).

  • ✅ F6 - Skip the word-diff file header by position so changed words starting with - or + show
  • ✅ F3 - Let step 5 print moved > markers and comment leaders without counting them as changed words
  • ✅ F8 - Run markdownlint-cli from the config's directory or with --config, and act only on MD013

G4 ✅ — Correct step 2's limit table and fallback

Closed by seven edits to §2: tool defaults ahead of the 100 fallback (F7), the missing config names (F5), the Rust row (F9), linter-over-.editorconfig precedence (F11), the comment limits (F12), "an MD013" (F16) and @stylistic/max-len (F17).

  • ✅ F7 - Let a configured tool's default width govern before the 100 fallback
  • ✅ F5 - Add the missing config file names, .yaml-lint.yml among them
  • ✅ F9 - Fix the Rust row's comment_width and add .rustfmt.toml
  • ✅ F11 - State that a width set by a linter step 5 runs beats .editorconfig
  • ✅ F12 - Add ESLint's comments and the Python max-doc-length comment limits
  • ✅ F16 - Write "an MD013"
  • ✅ F17 - Name ESLint @stylistic/max-len beside the deprecated core rule

G5 ✅ — Report the base the script chose

Closed by the stderr line naming the base ref and what chose it (F25), the upstream-then-origin remote loop (F22) and messages for a missing merge base or an invalid ref (F23).

  • ✅ F25 - Print the base ref and its source on stderr, not a SHA
  • ✅ F22 - Prefer upstream/<name> over origin/<name> in a fork clone
  • ✅ F23 - Explain a missing merge base instead of exiting silently

G6 ✅ — Harden the script's diff parsing

Closed by header-state tracking in the awk with a stderr note for a quoted path (F19), --ignore-submodules on the git diff call (F20) and the reworded header comment (F26).

  • ✅ F19 - Track header state in awk so a quoted +++ path cannot misattribute hunks
  • ✅ F20 - Pass --ignore-submodules to git diff
  • ✅ F26 - Reword the header comment's "joining them"

G7 ✅ — Keep comment and Markdown structure out of the fill

Closed by internal comment-block edges in §3 (F10), the continuation-indent exception to the indentation edge (F13) and the extended continuation guard in §4 (F14).

  • ✅ F10 - Give comment blocks internal boundaries and leave commented-out code and diagrams alone
  • ✅ F13 - Exempt list-item and folded-scalar continuation indent from the indentation edge
  • ✅ F14 - Add fences, HTML block openers and thematic breaks to the continuation guard

G8 ✅ — Refill SKILL.md's own paragraphs

Closed by refilling SKILL.md's final text after every other group's edits (F1).

  • ✅ F1 - Refill the under-filled paragraphs, e.g. with /reflow on the file

Not recommended for this revision

  • 🚫 F21 - Clamp a deletion hunk's bound at end of file (ignored — harmless; an out-of-range bound does nothing)

An edit inside wrapped prose or a comment usually leaves the lines it
touched breaking well short of the column limit, or pushes one past
it, and the wrap rule alone does not say to fix the paragraph around
the edit. Add a Code Style rule beside it: refill the paragraph or
comment block an edit touches so every line again takes as many words
as fit, changing only line breaks and leaving untouched blocks alone.
The rule points at /reflow, which does this for every block a branch
changed.

Every multi-sentence bullet in this file already ends with a period,
but only comments had a written rule for it, so the single-phrase
habit of dropping the period read as covering bullets too. Add a
Writing & Copy Conventions rule: a list item of one phrase or sentence
takes no final period, and one of several sentences ends each of them
with a period, the last included.
@mattmenefee mattmenefee changed the title Add a /reflow skill to refill a branch's changed lines Add a /reflow skill and the rules for refilling edited prose Oct 24, 2026
@mattmenefee
mattmenefee merged commit e5e4f95 into main Oct 24, 2026
8 checks passed
@mattmenefee
mattmenefee deleted the add-reflow-skill branch October 24, 2026 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant