Invocation (explicit invocation): This skill runs only when the user explicitly invokes it—in Claude Code use
/fix-ado-comment-markdown, in Codex CLI use$fix-ado-comment-markdown; do not apply it automatically when it is not named.
This skill covers two things:Markdown posting/repair for ADO comments(flows A and B), anda general Markdown correction tool(section C, included).
The storage format of ADO comments is determined byeach comment's format field(markdown or html), not by the content.az boards work-item update --discussion and JSON Patch /fields/System.History are only stored as html; when several sentences are crammed into one paragraph, they stick together as one big block on ADO. Always post and modify comments through the Comments REST API and specify format=markdown, and correct the syntax with the included Markdown correction tool before sending.
The tools are all in this skill's directory:scripts/ado_comments.py(uses only the Python standard library, invoked via az rest ) and scripts/markdown-fixer.mjs(zero-dependency Node). Below, <skill> refers to this skill's directory (for example, ~/.agents/skills/fix-ado-comment-markdown),$T refers to <skill>/scripts/ado_comments.py,$MF refers to <skill>/scripts/markdown-fixer.mjs. Set up first:
export ADO_ORG=https://dev.azure.com/<org> ADO_PROJECT=<project>
Put drafts and manifests in a temporary or scratchpad directory, not in the project working tree.
ADO Markdown facts
- API:
POST .../_apis/wit/workItems/{id}/comments?format=markdown&api-version=7.1-preview.4adds;PATCH .../comments/{commentId}?format=markdown&api-version=7.1-preview.4modifies; the body is always{"text": "<markdown>"}。formatis a required query parameter. - Existing
htmlcomments can be PATCHed directly tomarkdown:versionplus 1, and the UI shows "edited". - When read back,
renderedTextis returned only when adding$expand=renderedText. #12345renders as a work item link.Bare URLs do not automatically become links: write the URL as[文字](URL)or<URL>。- Tables,
##/###headings, numbered and bulleted lists, bold, and inline code all render normally; table cells cannot contain unescaped|。 - To also change state or assignment, do it in two steps: first use
az boards work-item updateto change the fields (without--discussion), then post the Markdown comment. - Multi-line fields (Description, Repro Steps, etc.) are a different mechanism: JSON Patch adds
/multilineFieldsFormat/<欄位>=Markdown, and after saving it cannot be changed back to HTML. This skill does not handle fields unless the user explicitly asks.
A. Post a new comment
- Write the draft: write the content as
comment.md. Structure first: a one-sentence title, tables for comparison data (URLs, versions, Tasks, Commits), numbered lists for steps, bulleted lists for results; put backticks around SHAs and paths; write URLs as[文字](URL)。 Done condition: each paragraph has at most one topic, and there are no consecutive long paragraphs of more than two sentences. - Correct:
node $MF --check comment.md, thennode $MF --write --ignore-rule MDX018 comment.md, and rerun--check. MDX070 (bare URLs) must be changed to links; MDX018 (first heading is not H1) can be ignored for comments. Done condition:--checkonly MDX018 remains, or 0 issues. - Post:
python3 -I $T post --id <workItemId> --file comment.md。 Done condition: outputformatismarkdown,renderedLengthgreater than 0.
B. Repair existing HTML comments
- Inventory:
python3 -I $T pull --id <ID> [<ID> ...] --author <uniqueName> --out <dir>. By default only fetchformat=html;--format ''to fetch all. For each, output<wi>-<cid>.orig.txt(original text),<wi>-<cid>.md(draft), andmanifest.json(including version and work item status). Whole-project repair: When the user requests the entire project, WIQL cannot find comment authors, so first inventory candidate work items, then usepull --authorto filter comments:
When there are many IDs, batch them (about 50 per batch)az boards query --wiql "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @project ORDER BY [System.Id]" --query "workItems[].id" -o tsvpullto their respective<dir>, and complete steps 2–6 for each batch; do not stuff all project IDs into a single batch. Completion criteria: The number of comments listed matches the scope specified by the user; comments outside the scope have been removed from the manifest. - Reorganize drafts: The tool-generated
.mdis only a starting point (splitting sentences at periods/semicolons, bolding leading labels, adding backticks around SHAs). Reorganize each one against.orig.txt: turn comparable data into tables and steps into numbered lists. Only re-layout the text; do not change the meaning or add/remove facts. Common mechanical conversion errors: bold text cut off, ";" inside parentheses split into two items, 8-digit dates mistaken for SHAs, long labels not recognized. Completion criteria: Every draft has been read through and compared with the original text, and every fact from the original is still present. - Correct:
node $MF --check <dir>, then for the confirmed filesnode $MF --write --ignore-rule MDX018 <files>, rerun--check; convert bare URLs to links. Completion criteria: Only MDX018 remains, or 0 issues. - Dry run:
python3 -I $T apply --manifest <dir>/manifest.json --dry-run. It rereads each comment; ifversionorformatdiffers from the inventory, skip it; skip when the work item is Done/Removed/Closed, and add only when the user explicitly names it--include-closed。 Completion criteria: Each one isdry-runor has an explainedskip。 - Write: remove
--dry-runand run again (you can use--only <commentId ...>to try one first). Results are written to<dir>/apply-results.json。 Completion criteria: For each,resultisok、newFormatismarkdown。 - Spot-check rendering:
python3 -I $T verify --id <ID> --comment <commentId>, confirmrenderedTagscontains the expectedtable/ol/ul/strong; if needed, add--showto view the HTML. Completion criteria: Spot-check at least one of each layout type (table type, list type), and the rendering matches expectations; report to the user the number processed, number skipped, and reasons.
Security boundaries
- Only modify comments within the user-specified scope and by the specified author; do not touch others' comments.
- Whole-project repair involves a large number of changes: first
--dry-runand report the total number of comments and batches, and write only after user confirmation. - Modifying a comment shows "edited" in ADO; before rewriting handoff comments the other party has already read, explain the impact first.
- Done/Removed/Closed items are not modified by default; add only when the user names them
--include-closed。 - Credentials and tokens are obtained only via
az, and are not written to drafts, the manifest, or output.
C. Markdown correction tool (markdown-fixer.mjs)
A zero-dependency Node script (requires Node.js 20 or later; 22+ recommended) that divides common Markdown syntax errors into two categories: formatting issues that can be fixed automatically, and structural or semantic issues that require human judgment. The workflow is: check first, then write only to explicitly specified files, review the diff, and finally re-run to confirm.
Quick Start
# 檢查(預設模式;不帶路徑時掃描目前目錄)
node $MF --check README.md docs/
# 只寫入明確指定的檔案
node $MF --write README.md docs/guide.md
# 只看某幾條規則
node $MF --check --rule MDX010,MDX011 README.md
# 忽略某條規則
node $MF --check --ignore-rule MDX032 docs/
# CI 用的 JSON
node $MF --check --json docs/
# 列出規則
node $MF --list-rules
--checkis the default mode and does not write files.--writeAn explicit path is required; if a directory is given, it processes all Markdown files scanned under that directory. First run--checkto see the list; if no path is provided, it will refuse outright to avoid accidentally rewriting the entire working tree.- Exit codes:
0no issues require processing;1warnings/errors remain (with--strict, info also counts);2usage, I/O, or parsing error. --rule MDX090automatically enables a 120-character line length; you can also use--max-line-length Nto specify it.--json'schangedindicates that the write actually happened this time,wouldChangeindicates that the file needs changes; in check mode,changedis alwaysfalse。
Workflow
-
Define the scope. List the Markdown files to be processed this time; excluding
vendor/、node_modules/, generated files, and golden fixtures. Completion criteria: There is a clear list of files, and you know which files will not be touched. -
Check first, do not write first. Run
--checkon the list, recording rule IDs, line numbers, and severity. Completion criteria: You can distinguish which are fixable, which are report-only, and which are structural errors. -
Assess the risk. If MDX081 (unclosed frontmatter), MDX083 (unclosed HTML comment), MDX053 (invalid fence info), or MDX085 (unclosed
$$math block) appears, fix the structure manually first; do not expect--writeto handle it. Completion criteria: There are no structural errors that would cause the tool to refuse to write. -
Write only to explicit files. Use
--write <files>, one scope at a time. Do not use--write .。 Completion criteria: The command finishes, and no files outside the list were written. -
Review the diff. Run
git diff -- <files>, and confirm section by section. Pay special attention to headings, list boundaries, table column counts, and CJK line length. Completion criteria: Every diff can be explained as to why it was changed; unwanted changes have been excluded with--ignore-ruleor manually reverted. -
Handle report-only items manually. Fix according to the description in
reference/rules.md, or explicitly accept with--ignore-rule. Completion criteria: Every remaining warning/error has a rationale for how it was handled. -
Re-run verification. Run
--checkagain; the goal is 0 warnings/0 errors, or only info with a documented rationale. Completion criteria: The command exit code is0, or the remaining items have been clearly explained. -
Finish according to project conventions. If these are changes to be committed, follow
AGENTS.mdto update the version number and run the corresponding tests. Completion condition: The checks required by the project and the version number update are both completed.
Safety contract
- Only change Markdown syntax, not meaning, not wording, not paragraph re-layout.
- Do not touch fenced code, inline code, frontmatter, math expressions, URLs, raw HTML, link destinations. Two trailing spaces are treated as an intentional line break and will be preserved.
- Do not invent image alt text, link targets, heading levels, or table content.
- Leave uncertain places for human judgment; report-only rules will not be
--writeapplied. MDX081、MDX083、MDX053、MDX085, and the case where "the fix would turn---into frontmatter" will leave the file unchanged and report the reason; this is a deliberate safety refusal, not a bug. Blocking rules, even if named by--ignore-rule, will not be allowed to write.- This tool is a line-oriented heuristic checker, not a full CommonMark parser. Multi-line inline structures, MDX/JSX, and complex YAML will not be fully parsed.
Automatically fixable rules
| Rule | Content |
|---|---|
| MDX001 | Remove the UTF-8 BOM at the start of the file |
| MDX002 | Normalize CRLF/CR to LF |
| MDX003 | Keep exactly one newline at the end of the file |
| MDX004 | Remove trailing whitespace; preserve hard breaks with two spaces |
| MDX005 | Collapse multiple consecutive blank lines into one |
| MDX006 | Remove blank lines at the start of the file |
| MDX008 | Replace NBSP outside code/URL with a normal space |
| MDX010 | #Title → # Title |
| MDX011 | Keep only one space after the heading marker |
| MDX012 | Remove trailing ### |
| MDX013 | Add a blank line between a heading and an adjacent paragraph |
| MDX020 | blockquote's > keep only one space after; preserve indented code |
| MDX030 | -item → - item、1.item → 1. item |
| MDX031 | Lists at the same indentation level use the same bullet type |
| MDX040 | Remove **、__、*、_ extra whitespace on the inside (single */_ only fix asymmetric cases) |
| MDX052 | Add a blank line between top-level fenced code and adjacent blocks |
| MDX060 | Each cell of the table delimiter row has at least three - |
| MDX062 | Add a blank line between top-level tables and adjacent blocks |
| MDX082 | Add one blank line after frontmatter |
These fixes still need to be reviewed in the diff.MDX010 cannot distinguish #hashtag from a broken heading;MDX030 cannot distinguish -item from an English word starting with a hyphen;MDX031 may unify two lists separated by a blank line into the same marker.
Report-only rules that are not auto-fixed
- Headings and structure: MDX014 indented heading, MDX015 skipped level, MDX016 empty heading, MDX017 trailing punctuation in heading, MDX018 first heading is not H1, MDX019 multiple H1s, MDX080 Setext ambiguity, MDX081 unclosed frontmatter, MDX083 unclosed HTML comment, MDX084 full-width Markdown syntax, MDX085 unclosed
$$math block. - Lists and emphasis: MDX032 missing blank line between list and paragraph, MDX035 empty list item, MDX041 possibly unmatched emphasis markers.
- Code fence: MDX050 missing language, MDX051 unclosed fence, MDX053 invalid fence info.
- Tables: MDX061 inconsistent column count, MDX063 code/URL inside a table cell contains an unescaped
|。 - Links and images: MDX070 bare URL, MDX071 empty link, MDX072
] (spaces, MDX073 missing reference definition, MDX074 duplicate definition, MDX075 image missing alt. - Other: MDX007 tab, MDX009 zero-width character, MDX090 line length.
Full documentation, before/after examples, approximate markdownlint mapping, and CJK considerations are in reference/rules.md。
Common operations
# 只修標題相關
node $MF --write --rule MDX010,MDX011,MDX012,MDX013 README.md
# 先看某個目錄有多少問題,不要寫入
node $MF --check docs/
# 把 info 也視為失敗,適合嚴格 CI
node $MF --check --strict docs/
# 只看前幾行問題
node $MF --check docs/ | head -40
Paste-to-Markdown project notes
- This tool processesthe Markdown documents in the project, not the app's paste conversion logic. Runtime conversion is in
assets/clipboard2markdown.jsandassets/to-markdown.js; to change paste behavior, you must change the app logic and adde2e/tests; running this script alone is not enough. tests/tcN-perfect.mdare golden fixtures and represent the app's expected output. Unless the conversion behavior is intentionally changed, do not run--writeon them; if the conversion behavior really is changed, update the fixtures together with the corresponding E2E expectations.- Do not run
--write .in that repo. It will scanAGENTS.md、tasks/todo.mdand the golden fixtures, producing unnecessary diffs. - Any change to be committed must follow
AGENTS.md:index.html's footer and JSON-LDsoftwareVersionto bump the patch version in sync; the cache-busting query string only needs updating when JS/CSS is changed. - This script's own tests:
node --test <skill>/scripts/markdown-fixer.test.mjs
Known limitations
- Frontmatter must start at the first byte of the file (a BOM may be present).
---If there is a blank line before it, it is semantically a thematic break; if removing the blank line would make the first line (ignoring BOM) become---, the tool refuses to write even if that line might just be a thematic break; please confirm manually. - HTML blocks use an approximation of CommonMark type 1–7 detection. Types 6/7 continue until the next blank line, so when there is no blank line after
</div>, the following Markdown is treated as raw HTML and not processed. - Tables indented 4 or more space columns (tabs count as 4 columns), and tables inside blockquotes, may not be detected; tables indented 1–3 spaces inside lists are detected, but MDX062 does not handle them.
- Indented paragraph continuation (lazy continuation) and indented code boundaries are determined heuristically; a line indented 4 or more columns that immediately follows a paragraph is treated as paragraph continuation.
- Files starting with
---and containing a second---are always treated as frontmatter; if it is actually a Setext heading or horizontal rule, confirm manually first. If removing the leading blank line would turn the following---into frontmatter,--writerefuses to write. - Directory scanning skips
.git、node_modules、vendor、coverage、test-results、playwright-report、blob-report、.e2e-tmpby default; other hidden directories (for example.github、.agents) are still scanned. - Line length is counted in Unicode code points, with CJK counted as 1, not terminal display width; table rows are not counted in MDX090.
- Multi-line links/emphasis, headings and lists inside nested blockquotes, and complex YAML are not fully parsed.
- A multi-line math block starts with
$$at the start of a line, blockquote, list, or task-list content, and ends at any subsequent line containing$$; if the block content itself contains$$, it ends early. A leading$$is always treated as the start of a math block; if it is just text, please rewrite it or add a backslash. A multi-line single$...$is not supported;```mathis still protected by fenced code. Deeper mixed nesting (for example, a blockquote inside a list and then another list) may not match precisely, and MDX085 will be reported conservatively;\[...\]、\begin{...}and other math syntaxes are not specially protected either. MDX002When disabled, if there are no other fixes, the file remains unchanged (including mixed line endings); if there are other fixes, line endings are normalized with CRLF first, then CR.- NBSP in URLs is only reported, not automatically changed; please manually replace it with
%20or fix the URL.