Repository navigation
Add a /reflow skill and the rules for refilling edited prose - #169
Conversation
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.
ad98d8a to
54383ed
Compare
Document Review (
|
| 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: 100allows exactly 100). Line 122 is the clearest: 52 characters ("…took the limit from. Where there is"), withnone,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.mdonce 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, andrubocop -Aapplies 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 writesrubocop -Awhere the user-wide checklist usesbin/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".
- (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.
- 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 -Aas the example)
F3 🟡 Medium Priority - Refilling a block quote prints > lines in a check that "must print nothing" in prose ✅ Fixed
> lines in a check that "must print nothing" in prose- 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/> moreremoves 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-indexrun 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 thegit diffon line 38), so/reflow notes.mdfor an untrackednotes.mdprints 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.ymlat its root (line-length: max: 100), so the skill misses the setting in its own repository. Other gaps: Ruff also readsruff.toml/.ruff.toml, flake8 alsotox.ini, rustfmt.rustfmt.toml(see F9), ESLint's current config iseslint.config.*(flat config) and Prettier also reads.editorconfigmax_line_lengthand formats Markdown atprintWidthunderproseWrap: always. - Suggestion — YAML cell: "
.yamllint*or.yaml-lint.ymlrules.line-length.max"; addruff.toml/.ruff.tomlandtox.inito the Python cell andeslint.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 -aat the repository root shows.yaml-lint.ymland.markdown-lint.yml, no.yamllint*. Ruff, flake8 and ESLint file names confirmed: docs.astral.sh/ruff/configuration ("Ruff can be configured through apyproject.toml,ruff.toml, or.ruff.tomlfile"), flake8.pycqa.org/en/latest/user/configuration.html (setup.cfg,tox.inior.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.tomlandeslint.config.*
Accuracy
F6 🟠 High Priority - Step 5's word-diff filter hides changed words that begin with - or + ✅ Fixed
- or +- 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--baseappears as---base, a removed-as--, a removed+1as-+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-indexthere is exactly one file header, and when the files are identicalseddeletes 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'sgrep '^[-+][^-+]'prints none of them, whilesed '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'slib/md013.mjs), yamllint 80 underextends: default(DEFAULT = {'max': 80inyamllint/rules/line_length.py), RuboCopLayout/LineLength120 (Max: 120, rubocop 1.92.1config/default.yml), Prettier 80, Black and Ruff 88, flake8 79, ESLintmax-len80 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
.editorconfigapplies, 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-widthreported "Defaults to 80". ESLint's 80 is confirmed on eslint.org/docs/latest/rules/max-len. Ruff 88unverified:ruff check --show-settings <file.py>could not run, since its placeholder does not parse and the repository has no.pyfile. flake8 79unverified:flake8is not installed, soflake8 --help | grep -A1 max-line-lengthcould not run. Black 88unverified. - 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
.editorconfigdoes
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--configwhen 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
comment_width has no effect without wrap_comments = true, which is unstable- Document —
home/.claude/skills/reflow/SKILL.md - Location — §2 table, Rust row (line 48)
- Issue — rustfmt's Configurations.md defines
comment_widthas "Maximum length of comments. No effect unlesswrap_comments = true", with a default of 80, and both options are "Stable: No" (nightly only). An agent reading the Rust row literally refills comments tocomment_width, or its default 80, in a project where rustfmt never applies it, whenmax_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.tomlor.rustfmt.tomlmax_width;comment_widthfor comments only wherewrap_comments = trueis 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, andcomment_widthonly wherewrap_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
# separator lines, lists and tags- 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
.editorconfig and a linter's setting is unstated- 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 = 120against.markdownlint.yamlMD013line_length: 100: both are scoped to the file type. Step 5 checks the width with the linter, so picking.editorconfigproduces 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
.editorconfigsection 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
.editorconfigsection
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-lenhas acommentsoption ("enforces a maximum line length for comments; defaults to value ofcode") andignoreComments, and pycodestyle/flake8max-doc-length(W505) and Rufflint.pycodestyle.max-doc-lengthlimit comments and docstrings separately. Since the skill refills comment blocks, these are exactly the limits that apply. - Suggestion — Append "(
commentsfor comments)" to the ESLint entry and "max-doc-lengthfor 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. pycodestylemax-doc-lengthinconclusive: 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'slint.pycodestyle.max-doc-length, stayunverified. - Status — ✅ Fixed: the table gives ESLint
commentsand Pythonmax-doc-lengthfor 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,<scriptand 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
--base must come first, which the skill doesn't say- Document —
home/.claude/skills/reflow/SKILL.md - Location — Lines 18–20 and §1 line 25
- Issue — The script recognizes
--baseonly as the first argument (scripts/changed-lines.shlines 16–19);/reflow README.md --base mainpasses--baseandmainthrough 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;
--baseis 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
max-len is deprecated in favor of @stylistic/max-len- 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 coremax-lenmay miss it. - Suggestion — "ESLint
@stylistic/max-len(or the deprecated coremax-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-lenbeside the deprecated coremax-len
Correctness
F18 🟠 High Priority - --base is recognized only as the first argument; anything else silently becomes a pathspec ✅ Fixed
--base is recognized only as the first argument; anything else silently becomes a pathspec-
Document —
home/.claude/skills/reflow/scripts/changed-lines.sh -
Location — lines 15–19, and line 39 (
-- "$@") -
Issue — Only
"$1" == --baseis parsed; every other word is handed togit diffas 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/mainignored--base(stderr showed the gh-resolved SHA, and--base/origin/mainwere treated as paths);changed-lines.sh --base=origin/mainandchanged-lines.sh --bsae origin/mainboth printed only the base line, no rows,rc=0;changed-lines.sh --base origin/main no/such/file.mdgave no rows,rc=0.home/.claude/skills/reflow/SKILL.mdstep 1 passes<arguments>from the user's free-text$ARGUMENTSstraight 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.--basewith 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[@]}"}togit diff(the guarded expansion keeps an empty array from trippingset -uunder the bash 3.2 at/bin/bash; the shebang resolves to Homebrew bash 5.3 here, but/bin/bashis 3.2.57 and the Brewfile does not listbash). -
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=,--bsaeand bare--baseeach 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
+++ header leaves the previous file's path in place, so its hunks are attributed to the wrong file-
Document —
home/.claude/skills/reflow/scripts/changed-lines.sh -
Location — awk program, lines 41–53
-
Issue —
core.quotePath=falsestops 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 -vprinted"a\"b.md","a\tb.md"and"a\\b.md"quoted,café.mdanda b.mdbare). The diff header for such a file is+++ "b/…", which/^\+\+\+ b\//does not match, sopathstill 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
pathon eachdiff --gitline, 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.mdandq"q.mdeach gaining a second line, the script run with--base HEADprintedone.md 2-2twice and noq"q.mdrow. -
Status — ✅ Fixed: the awk tracks header state per
diff --gitand reports a quoted path on stderr instead of misattributing it; in the disposable repository it now printsone.md 2-2once 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 diffflags, 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-1for a directory. The user's gitconfig setsdiff.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-1for the link's target string, which the Read/Edit tools would follow to an unrelated file. - Suggestion — Add
--ignore-submodulesto thegit diffcall. 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
subadvanced one commit:git -c diff.submodule=short diff --unified=0 --src-prefix=a/ --dst-prefix=b/ HEAD | grep -E '^(\+\+\+|@@)'printed+++ b/suband@@ -1 +1 @@. The symlink case was not run. - Status — ✅ Fixed:
git diffpasses--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 @@wherecis the file's last line,last = c + 1names 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
upstream remote, the base resolves to the fork's origin/<name>-
Document —
home/.claude/skills/reflow/scripts/changed-lines.sh -
Location — lines 22–24
-
Issue — Neither
ghcall passes--repo; with anupstreamremote, gh may resolve the base repository toupstream, giving the upstream pull request's base branch name, but the script then prefersrefs/remotes/origin/$name, the fork's copy, which is often stale. When the fork'smainlagsupstream/main, the merge base is older and the output includes upstream commits the branch never touched as "changed" lines. The user'shome/.claude/CLAUDE.md(Pull Requests) names exactly this clone shape as one where gh resolves to the parent. If noorigin/$nameexists, the fallbackbase=$nameuses a local branch that may be stale or missing (fatal: Not a valid object name …). -
Suggestion — Try
upstreamthenoriginbefore 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-codingorigin. -
Recommendation — Implement: the user's own guidance treats
upstreamclones as a real case, and the fix is small. -
Verification —
unverified. In a clone withorigin(a fork) andupstreamand nogh repo set-default:gh repo view --json nameWithOwner --jq .nameWithOwnerprinting the upstream owner confirms gh resolves to the parent;git rev-parse origin/main upstream/mainprinting different SHAs confirms the stale-base effect. -
Status — ✅ Fixed: base resolution prefers
upstream/<name>, thenorigin/<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-baseprints nothing and exits 1 when there is no common ancestor (a shallow clone, an unrelated base).set -ethen ends the script before the stderr base line, so the agent sees no output and no reason. An invalid ref does print git's ownfatal:line (checked:--base nosuchrefgives 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 basegave onlyrc=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.mdprinted 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>oruntracked, 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 printedbase: d22496ac… (merge base d22496ac…); with--base origin/mainit printedbase: origin/main (…).home/.claude/skills/reflow/SKILL.mdstep 1 says the script "names the base it chose on stderr". The line also omits whether the pull request, the repository default ororigin/HEADsupplied 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.shreports 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
$sourceset topull request,repository default,origin/HEADor--baseon 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 andpull request,repository default,origin/HEADor--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.mdstep 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 coversdiff.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-prefixand shorten the comment accordingly. The action was taken: the script'sgit diffnow passes--default-prefixin 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.shand reject unknown options - ✅ F15 - Say
--basecomes 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.ymlamong them - ✅ F9 - Fix the Rust row's
comment_widthand add.rustfmt.toml - ✅ F11 - State that a width set by a linter step 5 runs beats
.editorconfig - ✅ F12 - Add ESLint's
commentsand the Pythonmax-doc-lengthcomment limits - ✅ F16 - Write "an MD013"
- ✅ F17 - Name ESLint
@stylistic/max-lenbeside 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>overorigin/<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-submodulestogit 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
/reflowon 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.
4c56d8f to
adb3ed4
Compare
Summary
/reflowskill 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 breaksscripts/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, thenorigin/HEAD, preferring anupstreamremote'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".editorconfig, markdownlint, RuboCop, Prettier or ESLint, Ruff, Black or flake8, rustfmt, yamllint), then the configured tool's own default width, then 100CLAUDE.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;/reflowdoes this for a whole branchThe skill is written in the
home/.claude/skills/<name>/SKILL.mdlayout that #167 introduces, in a new directory of its own. TheCLAUDE.mdrules sit beside lines #167 also refills, so whichever of the two merges second needs a small rebase there.Test plan
lint(super-linter, including Bash and Markdown) green onadb3ed4SKILL.md, code-best-practices-reviewer on the script) with every actionable finding fixed or ruled; the review is posted belowchanged-lines.shin 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--base,--base=<ref>, a mistyped option, a bare--base, a missing path and an untracked path each behave as above, also under/bin/bash3.2shellcheckonchanged-lines.shpassesnpx markdownlint-cli --config .markdown-lint.ymlonSKILL.mdpasses, and every paragraph is filled to 100 columns-or+(--base,-,+1) and passes a pure reflow, moved block-quote marker includednpx markdownlint-cli --config .markdown-lint.ymlonhome/.claude/CLAUDE.mdpasses, and every existing multi-sentence bullet there already ends with a periodupstreampreference in a real fork clone/reflowend to end on a real branch once linked: on Convert the user-wide slash commands to skills #167's branch after its rebase ontomain, it refilled 32 blocks in 10 files; word streams are unchanged apart from moved#comment leaders, and markdownlint passes on the 6 Markdown files