chore: add markdownlint and fix violations across all markdown files - #1349
chore: add markdownlint and fix violations across all markdown files#1349snimu wants to merge 3 commits into
Conversation
Adds markdownlint-cli2 with the same rule baseline as research-environments/verifiers (MD013 off, MD024 siblings-only, MD041 off, MD060 compact tables, inline-HTML allowlist for README branding). Wired into 'npm run check' as 'check:md' (~0.6s), so the existing pre-commit hook and CI enforce it without workflow changes. File changes are almost entirely mechanical (--fix): blank lines around headings/lists/fences, table pipe spacing, bare URLs wrapped. Manual: language tags on 37 unlabeled code fences, a broken anchor in docs/rpc.md, two duplicate '### Added' siblings merged in packages/ai/CHANGELOG. No prose changes.
The unmatched fence before '## Limitations' made everything below it render as a code block (pre-existing; the MD040 pass had labeled it bash instead of noticing it was unmatched). Removing it exposed two mechanical blanks-around violations below, fixed with --fix. Also dedupes the accidental double 'check:md' invocation in npm run check.
…ing docs One logical line per paragraph/bullet: hard wraps distort line counts and make one-word edits touch whole paragraphs in diffs. Custom rule in scripts/markdownlint/no-hard-wraps.cjs (skips code fences, HTML blocks, tables, headings, blockquotes, reference definitions, and deliberate trailing-double-space/backslash breaks). 372 wrapped continuation lines joined across 22 files; word-level diff is whitespace-only.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 173e387. Configure here.
| @@ -1,14 +1,10 @@ | |||
| --- | |||
| name: agent-message | |||
| description: Message an agent's parent, siblings, or direct children through the daemon. Use the family roster to discover reachable agents and send direct text without spoofing sender identity. | |||
There was a problem hiding this comment.
Collapsed YAML breaks skill loading
High Severity
YAML frontmatter keys were joined onto one line in built-in SKILL.md files and the subagent reviewer example. parseFrontmatter no longer sees a description key (the yaml parse fails or treats the whole line as name), so loadSkillFromFile drops every affected skill and the example agent is skipped.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 173e387. Configure here.
| lineNumber: i + 2, | ||
| detail: "Continuation of the previous line; keep one logical line per paragraph/bullet.", | ||
| }); | ||
| } |
There was a problem hiding this comment.
Wrap rule flags YAML and fences
High Severity
PA001 treats YAML frontmatter keys as wrappable prose and closes an outer fence when it sees a shorter inner fence of the same character. That produced the collapsed frontmatter and joined nested samples, and it will still fail check:md on remaining valid files such as prime-intellect/SKILL.md and planner.md.
Reviewed by Cursor Bugbot for commit 173e387. Configure here.
| let inFence = false; | ||
| let fenceMarker = ""; | ||
| for (let i = 0; i < lines.length - 1; i++) { | ||
| const line = lines[i]; | ||
| const fence = line.match(/^\s*(`{3,}|~{3,})/); | ||
| if (fence) { | ||
| if (!inFence) { | ||
| inFence = true; | ||
| fenceMarker = fence[1][0]; | ||
| } else if (fence[1][0] === fenceMarker) { | ||
| inFence = false; | ||
| } |
There was a problem hiding this comment.
🟡 Medium markdownlint/no-hard-wraps.cjs:24
The linter closes a four-backtick fenced block with a later three-backtick line, and it also closes a block on an in-fence line such as ```javascript; the remaining code is then scanned as prose and can produce false PA001 errors. The closing check must require the same fence character, a length at least as long as the opener, and only whitespace after the fence.
let inFence = false;
let fenceMarker = "";
+ let fenceLength = 0;
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i];
- const fence = line.match(/^\s*(`{3,}|~{3,})/);
+ const fence = line.match(/^\s*(`{3,}|~{3,})(.*)$/);
if (fence) {
if (!inFence) {
inFence = true;
fenceMarker = fence[1][0];
- } else if (fence[1][0] === fenceMarker) {
+ fenceLength = fence[1].length;
+ } else if (
+ fence[1][0] === fenceMarker &&
+ fence[1].length >= fenceLength &&
+ /^\s*$/.test(fence[2])
+ ) {
inFence = false;
}🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @scripts/markdownlint/no-hard-wraps.cjs around lines 24-35:
The linter closes a four-backtick fenced block with a later three-backtick line, and it also closes a block on an in-fence line such as [code fence]javascript; the remaining code is then scanned as prose and can produce false `PA001` errors. The closing check must require the same fence character, a length at least as long as the opener, and only whitespace after the fence.
Evidence trail:
scripts/markdownlint/no-hard-wraps.cjs:24-38 (REVIEWED_COMMIT); https://spec.commonmark.org/0.28/#fenced-code-blocks
| termux-open file.pdf # Opens with default app termux-open -c image.jpg # Choose app | ||
| ``` | ||
|
|
||
| ## Clipboard | ||
|
|
||
| ```bash | ||
| termux-clipboard-set "text" # Copy | ||
| termux-clipboard-get # Paste | ||
| termux-clipboard-set "text" # Copy termux-clipboard-get # Paste | ||
| ``` | ||
|
|
||
| ## Notifications | ||
|
|
||
| ```bash | ||
| termux-notification -t "Title" -c "Content" | ||
| ``` | ||
|
|
||
| ## Device Info | ||
|
|
||
| ```bash | ||
| termux-battery-status # Battery info | ||
| termux-wifi-connectioninfo # WiFi info | ||
| termux-telephony-deviceinfo # Device info | ||
| termux-battery-status # Battery info termux-wifi-connectioninfo # WiFi info termux-telephony-deviceinfo # Device info | ||
| ``` | ||
|
|
||
| ## Sharing | ||
|
|
||
| ```bash | ||
| termux-share -a send file.txt # Share file | ||
| ``` | ||
|
|
||
| ## Other Useful Commands | ||
|
|
||
| ```bash | ||
| termux-toast "message" # Quick toast popup | ||
| termux-vibrate # Vibrate device | ||
| termux-tts-speak "hello" # Text to speech | ||
| termux-camera-photo out.jpg # Take photo | ||
| termux-toast "message" # Quick toast popup termux-vibrate # Vibrate device termux-tts-speak "hello" # Text to speech termux-camera-photo out.jpg # Take photo |
There was a problem hiding this comment.
🟡 Medium docs/termux.md:55
The Opening Files, Clipboard, Device Info, and Other Useful Commands examples execute only their first command when copied into a shell, so users cannot perform the documented operations. The commands were collapsed into lines where # comments out everything after the first command; restore each command to its own line.
-termux-open file.pdf # Opens with default app termux-open -c image.jpg # Choose app
+termux-open file.pdf # Opens with default app
+termux-open -c image.jpg # Choose app
-termux-clipboard-set "text" # Copy termux-clipboard-get # Paste
+termux-clipboard-set "text" # Copy
+termux-clipboard-get # Paste
-termux-battery-status # Battery info termux-wifi-connectioninfo # WiFi info termux-telephony-deviceinfo # Device info
+termux-battery-status # Battery info
+termux-wifi-connectioninfo # WiFi info
+termux-telephony-deviceinfo # Device info
-termux-toast "message" # Quick toast popup termux-vibrate # Vibrate device termux-tts-speak "hello" # Text to speech termux-camera-photo out.jpg # Take photo
+termux-toast "message" # Quick toast popup
+termux-vibrate # Vibrate device
+termux-tts-speak "hello" # Text to speech
+termux-camera-photo out.jpg # Take photoAlso found in 1 other location(s)
packages/coding-agent/docs/skills.md:377
The two documented Brave Search invocations were collapsed onto one shell line:
./search.js "query" --contentis now part of the comment after#. Copying the documented command therefore only runs the basic search and never enables the advertised--contentoption, so users cannot follow the example to retrieve page content.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/docs/termux.md around lines 55-85:
The `Opening Files`, `Clipboard`, `Device Info`, and `Other Useful Commands` examples execute only their first command when copied into a shell, so users cannot perform the documented operations. The commands were collapsed into lines where `#` comments out everything after the first command; restore each command to its own line.
Evidence trail:
packages/coding-agent/docs/termux.md:52-85 at 173e387f68
Also found in 1 other location(s):
- packages/coding-agent/docs/skills.md:377 -- The two documented Brave Search invocations were collapsed onto one shell line: `./search.js "query" --content` is now part of the comment after `#`. Copying the documented command therefore only runs the basic search and never enables the advertised `--content` option, so users cannot follow the example to retrieve page content.
| --- | ||
| name: attach-image | ||
| description: Load an on-disk image (PNG, JPEG, GIF, WebP) into the model's context as a viewable attachment so the model can directly SEE it — for screenshots, diagrams, charts, photos, or scanned pages. Use this when you need to perceive an image's visual contents. Requires a vision-capable model; errors clearly otherwise. | ||
| name: attach-image description: Load an on-disk image (PNG, JPEG, GIF, WebP) into the model's context as a viewable attachment so the model can directly SEE it — for screenshots, diagrams, charts, photos, or scanned pages. Use this when you need to perceive an image's visual contents. Requires a vision-capable model; errors clearly otherwise. |
There was a problem hiding this comment.
🟠 High attach-image/SKILL.md:2
Skill discovery rejects this file because the collapsed frontmatter leaves frontmatter.description undefined, so the bundled attach-image skill is not loaded or invocable. Keep name and description as separate YAML mappings.
| name: attach-image description: Load an on-disk image (PNG, JPEG, GIF, WebP) into the model's context as a viewable attachment so the model can directly SEE it — for screenshots, diagrams, charts, photos, or scanned pages. Use this when you need to perceive an image's visual contents. Requires a vision-capable model; errors clearly otherwise. | |
| name: attach-image | |
| description: Load an on-disk image (PNG, JPEG, GIF, WebP) into the model's context as a viewable attachment so the model can directly SEE it — for screenshots, diagrams, charts, photos, or scanned pages. Use this when you need to perceive an image's visual contents. Requires a vision-capable model; errors clearly otherwise. |
Also found in 11 other location(s)
packages/coding-agent/examples/extensions/subagent/agents/reviewer.md:2
The markdownlint fix merged the YAML frontmatter fields into one physical line.
parseFrontmatterpasses this block to the YAML parser, soname,description,tools, andmodelare no longer separate mappings (and this may parse as an invalid YAML mapping). The subagent loader therefore skips or fails to load the bundledrevieweragent instead of exposing its configured description/tools/model.
packages/coding-agent/skills/agent-message/SKILL.md:2
The markdownlint rewrite removes the newline between the
nameanddescriptionfrontmatter fields, so YAML parses line 2 as a singlenamevalue and leavesfrontmatter.descriptionundefined.loadSkillFromFiletherefore discardsagent-messageat lines 416-419, meaning the bundled messaging skill is not loaded or exposed to the model (and its Python package is not provisioned through normal skill discovery).
packages/coding-agent/skills/agent-observe/SKILL.md:2
The frontmatter was collapsed into a single YAML key/value on line 2:
namebecomesagent-observe description: ...and the requireddescriptionfield is absent.loadSkillsFromDirvalidatesfrontmatter.descriptionand rejects skills without it, so this built-inagent-observeskill is omitted from discovery and cannot be invoked.
packages/coding-agent/skills/compact/SKILL.md:2
The markdownlint rewrite merged the required YAML frontmatter fields into one line:
name: compact description: .... The YAML parser therefore treats the whole value asnameand leavesdescriptionmissing;loadSkillrejects skills withoutfrontmatter.description, so the built-incompactskill is skipped and/skill:compactplus its startup metadata disappear.
packages/coding-agent/skills/edit/SKILL.md:2
The frontmatter edit removes the newline between
name: editanddescription: ..., turning them into one YAML mapping entry (namewhose value containsdescription: ...) and leaving the requireddescriptionfield absent. Skill discovery therefore cannot parse this file with the intendedname/descriptionmetadata, so the built-ineditskill may be omitted or exposed with incorrect metadata instead of being available as documented.
packages/coding-agent/skills/goal/SKILL.md:2
The frontmatter keys were collapsed onto one line at
SKILL.mdline 2. The YAML parser therefore reads a singlenamefield whose value starts withgoal description: ...; it does not createdescription.skills.tsrejects skills with a missing description, so the built-ingoalskill is omitted from discovery and itsgoalAPI is no longer available to the agent.
packages/coding-agent/skills/linear/SKILL.md:2
The YAML front matter now puts
nameanddescriptionon one line (name: linear description: ...), so it parses as a singlenamevalue and provides nodescriptionfield. Skill discovery/loading that expects the required front-matter keys can reject or misidentify the Linear skill, making it unavailable despite the file existing.
packages/coding-agent/skills/refine/SKILL.md:2
The formatter joined the two YAML frontmatter fields into one line, so
parseFrontmatterreads this as a singlenamevalue ("refine description: ...") and leavesfrontmatter.descriptionundefined.loadSkillsFromDirrejects skills without a non-empty description, so the built-inrefineskill is no longer loaded or exposed (including itsawait refine.run()API). Keepnameanddescriptionon separate YAML lines.
packages/coding-agent/skills/rlm-heartbeat/SKILL.md:2
The Markdown formatter collapsed the YAML frontmatter's
nameanddescriptionmappings onto one line:name: rlm-heartbeat description: .... This makes the frontmatter parse as a singlenamevalue and removes the separatedescriptionfield, so the skill loader can no longer read the intended metadata (and may reject or misidentify the skill).
packages/coding-agent/skills/skill-creator/SKILL.md:2
The frontmatter was collapsed into a single YAML mapping line:
namebecomes the entire stringskill-creator description: Create, ...and there is nodescriptionkey.loadSkillFromFilerejects skills with a missing description atskills.ts:417-419, so this built-inskill-creatorskill is skipped entirely at startup (and its name would also fail validation if it were loaded). Restore separatename:anddescription:frontmatter lines.
packages/coding-agent/skills/websearch/SKILL.md:2
The frontmatter keys were collapsed onto one YAML line:
yamlparses this as a singlenamevalue ("websearch description: ...") rather than separatenameanddescriptionfields.skills.tsrequiresfrontmatter.descriptionto be present and therefore drops this bundled skill entirely, so thewebsearchskill no longer appears or can be invoked.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/skills/attach-image/SKILL.md around line 2:
Skill discovery rejects this file because the collapsed frontmatter leaves `frontmatter.description` undefined, so the bundled `attach-image` skill is not loaded or invocable. Keep `name` and `description` as separate YAML mappings.
Evidence trail:
173e387f6813f439dab47cd7ead8c59ec93afbeb: packages/coding-agent/skills/attach-image/SKILL.md:1-3; packages/coding-agent/src/utils/frontmatter.ts:28-36; packages/coding-agent/src/core/skills.ts:395-418
Also found in 11 other location(s):
- packages/coding-agent/examples/extensions/subagent/agents/reviewer.md:2 -- The markdownlint fix merged the YAML frontmatter fields into one physical line. `parseFrontmatter` passes this block to the YAML parser, so `name`, `description`, `tools`, and `model` are no longer separate mappings (and this may parse as an invalid YAML mapping). The subagent loader therefore skips or fails to load the bundled `reviewer` agent instead of exposing its configured description/tools/model.
- packages/coding-agent/skills/agent-message/SKILL.md:2 -- The markdownlint rewrite removes the newline between the `name` and `description` frontmatter fields, so YAML parses line 2 as a single `name` value and leaves `frontmatter.description` undefined. `loadSkillFromFile` therefore discards `agent-message` at lines 416-419, meaning the bundled messaging skill is not loaded or exposed to the model (and its Python package is not provisioned through normal skill discovery).
- packages/coding-agent/skills/agent-observe/SKILL.md:2 -- The frontmatter was collapsed into a single YAML key/value on line 2: `name` becomes `agent-observe description: ...` and the required `description` field is absent. `loadSkillsFromDir` validates `frontmatter.description` and rejects skills without it, so this built-in `agent-observe` skill is omitted from discovery and cannot be invoked.
- packages/coding-agent/skills/compact/SKILL.md:2 -- The markdownlint rewrite merged the required YAML frontmatter fields into one line: `name: compact description: ...`. The YAML parser therefore treats the whole value as `name` and leaves `description` missing; `loadSkill` rejects skills without `frontmatter.description`, so the built-in `compact` skill is skipped and `/skill:compact` plus its startup metadata disappear.
- packages/coding-agent/skills/edit/SKILL.md:2 -- The frontmatter edit removes the newline between `name: edit` and `description: ...`, turning them into one YAML mapping entry (`name` whose value contains `description: ...`) and leaving the required `description` field absent. Skill discovery therefore cannot parse this file with the intended `name`/`description` metadata, so the built-in `edit` skill may be omitted or exposed with incorrect metadata instead of being available as documented.
- packages/coding-agent/skills/goal/SKILL.md:2 -- The frontmatter keys were collapsed onto one line at `SKILL.md` line 2. The YAML parser therefore reads a single `name` field whose value starts with `goal description: ...`; it does not create `description`. `skills.ts` rejects skills with a missing description, so the built-in `goal` skill is omitted from discovery and its `goal` API is no longer available to the agent.
- packages/coding-agent/skills/linear/SKILL.md:2 -- The YAML front matter now puts `name` and `description` on one line (`name: linear description: ...`), so it parses as a single `name` value and provides no `description` field. Skill discovery/loading that expects the required front-matter keys can reject or misidentify the Linear skill, making it unavailable despite the file existing.
- packages/coding-agent/skills/refine/SKILL.md:2 -- The formatter joined the two YAML frontmatter fields into one line, so `parseFrontmatter` reads this as a single `name` value (`"refine description: ..."`) and leaves `frontmatter.description` undefined. `loadSkillsFromDir` rejects skills without a non-empty description, so the built-in `refine` skill is no longer loaded or exposed (including its `await refine.run()` API). Keep `name` and `description` on separate YAML lines.
- packages/coding-agent/skills/rlm-heartbeat/SKILL.md:2 -- The Markdown formatter collapsed the YAML frontmatter's `name` and `description` mappings onto one line: `name: rlm-heartbeat description: ...`. This makes the frontmatter parse as a single `name` value and removes the separate `description` field, so the skill loader can no longer read the intended metadata (and may reject or misidentify the skill).
- packages/coding-agent/skills/skill-creator/SKILL.md:2 -- The frontmatter was collapsed into a single YAML mapping line: `name` becomes the entire string `skill-creator description: Create, ...` and there is no `description` key. `loadSkillFromFile` rejects skills with a missing description at `skills.ts:417-419`, so this built-in `skill-creator` skill is skipped entirely at startup (and its name would also fail validation if it were loaded). Restore separate `name:` and `description:` frontmatter lines.
- packages/coding-agent/skills/websearch/SKILL.md:2 -- The frontmatter keys were collapsed onto one YAML line: `yaml` parses this as a single `name` value (`"websearch description: ..."`) rather than separate `name` and `description` fields. `skills.ts` requires `frontmatter.description` to be present and therefore drops this bundled skill entirely, so the `websearch` skill no longer appears or can be invoked.
jonaowen
left a comment
There was a problem hiding this comment.
The mechanical unwrap changed significant literal bytes inside inline code spans, contradicting the “no prose changes” claim.
Examples on exact head 173e387f6:
packages/ai/CHANGELOG.md0.2.2 previously documented dropping the suffix` (Prime Inference)`(including the leading space). The rewrite now says`(Prime Inference)`, which is a different suffix.packages/coding-agent/CHANGELOG.mdpreviously documented connectors`├ `/`└ `and replacements`├─ `/`└─ `with significant trailing spaces. The rewrite removes every trailing space and now documents different strings.
These are user-facing historical/behavioral claims, not style. A formatter/lint rule must preserve whitespace inside code spans and other literal regions. Restore the exact literals and add focused regression fixtures for inline code with leading/trailing spaces (including spans split by historical hard wraps) before applying this rule repo-wide. Please also audit all 67 changed Markdown files for the same class; a green Markdown lint cannot detect semantic byte loss it caused.
|
Additional binding evidence from the PR's own required CI: |
|
Fresh independent review found three further semantic classes on the same head:
The corrupted subagent reviewer frontmatter also collapses |


Adds
markdownlint-cli2(same tool and rule baseline as research-environments and verifiers) and fixes all existing violations.Config (
.markdownlint-cli2.yaml): MD013 (line length) off, MD024 siblings-only (CHANGELOG### Fixedrepeats per version), MD041 off (several docs open with a callout or centered branding), MD060 compact tables, inline-HTML allowlist for the README branding headers.node_modulesandpackages/coding-agent/distignored.Wiring:
npm run check:md, included innpm run check(~0.6s), so the existing pre-commit hook and CI enforce it without workflow changes.Fixes (96 files linted, was ~1400 violations): almost entirely mechanical via
--fix— blank lines around headings/lists/fences, table pipe spacing, bare URLs wrapped in angle brackets. Manual: language tags on 37 unlabeled code fences, one broken anchor indocs/rpc.md(#message-types→#types), and two duplicate sibling### Addedsections merged inpackages/ai/CHANGELOG.md0.17.0. No prose changes.No CHANGELOG bullet: tooling/docs formatting only, no user-visible behavior.
Note
Low Risk
Documentation and dev-tooling only; no runtime, auth, or application logic changes.
Overview
Introduces markdownlint-cli2 with repo config in
.markdownlint-cli2.yaml(line-length off, sibling-only duplicate headings, compact tables, HTML allowlist for README branding) plus a custom no-hard-wraps rule underscripts/markdownlint/.npm run check:mdlints**/*.mdand is wired intonpm run check, so existing pre-commit/CI paths enforce it without workflow edits.The bulk of the diff is mechanical markdown cleanup across docs and changelogs: fence language tags (often
text), table pipe spacing, blank lines around headings/lists/fences, angle-bracket URLs, and minor structural fixes (e.g. merged duplicate CHANGELOG headings, one RPC anchor).package-lock.jsonupdates reflect the new dev dependency and transitive packages only.Reviewed by Cursor Bugbot for commit 173e387. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add markdownlint and fix violations across all markdown files
markdownlint-cli2as a dev dependency with a config file that includes a customno-hard-wrapsrule, disables MD013, and configures several other rules.checkscript via a newcheck:mdnpm script, so CI will fail on markdown violations.text), and reflows hard-wrapped paragraphs into single lines.Macroscope summarized 173e387.