Expand description
The markdown/autofix policy and its in-process linter.
What:
The markdown/autofix policy and its in-process linter.
Why:
The linter runs on a guarded thread and has its own configuration,
so it stays apart from the other policies.
In TS you’d write (pseudocode):
// markdown/index.ts: one `export * from './file'` per module below.Modules§
- markdown_
exclude - The gitignore-syntax
excludepatterns ofmarkdown/autofix, matched as the linter matches them. What: Theexcludeoption ofmarkdown/autofix: gitignore-syntax patterns, relative to the repository’s top level, naming candidates the policy leaves alone. Why: The installed wrapper filtered candidates with theignorepackage before starting the linter (rewrite-candidates.ts,rewriteCandidates), so an excluded file never starts a linter and never fails for one. The linter evaluates its own copy of the option with theignorecrate’s gitignore matcher and a walk over ancestor directories (package/linter/monochromatic-lint/src/markdown_lfs_patterns.rs); this module uses the same matcher and the same walk, so both agree on every path. - markdown_
linter - The executable’s linter for that policy:
monochromatic-lintlinked in-process, guarded by a deadline, once per candidate. What: The linter the executable givesmarkdown/autofix:monochromatic-lint, linked into the executable and run in-process once per candidate, on a guarded thread with a quiet window. Why: The owner’s rule is that the wrapper embeds the repository’s own tools and starts other programs as rarely as possible. The linter’s library entrymonochromatic_lint::run_embedded::fix_sourceis the executable’s--config --stdin --stdin-filename --fixrun without the program start (package/linter/monochromatic-lint/src/run_embedded.rs), so there is no program to find on a search path, no configuration file to write and no stream contract to parse. The child process used to be the wrapper’s kill boundary;markdown_linter_guard.rsreplaces it with a thread, a quiet window and panic containment, which is a hang and panic boundary and not a crash boundary: a stack overflow or an abort in the linter ends the wrapper. The size of a finished result is checked against the former child’s output bounds (markdown_linter_output.rs), after the linter has produced it. The configuration is rebuilt from the policy’s options for each candidate, which costs a small JSONC document, so no state outlives the options it was built from. - markdown_
linter_ config - The one-rule linter configuration document.
What:
The linter configuration the
markdown/autofixpolicy handsmonochromatic-lint: one block that selects the policy’s rules for Markdown and MDX files, as JSONC text for the linter library’s in-process entry. Why: The plan’s contract (doc/planning/unified-linter.md, section “Distribution and consumers”) is a one-rule configuration of the policy’s own, so a configuration the repository keeps for its own linting never changes what the commit-time policy does. Each rule runs atwarn, so a finding the fixes leave is a warning finding and the run counts as usable, and only a run that could not finish fails. The document is built as a value and printed by the JSONC emitter, so no pattern text can break out of its string. - markdown_
linter_ guard - A job on a thread of its own with a deadline and panic containment.
What:
Run one job on a thread of its own and wait for it until it ends, or until the thread has shown no sign of work for a
window, so a job that panics or never ends costs the caller one failed result, not the process.
Why:
The Markdown linter used to run as a child process,
and the operating system gave the wrapper a free boundary:
a child that hung could be killed and a child that crashed could not take the wrapper down.
Linked into the wrapper,
the linter shares its address space,
so a boundary is rebuilt here from what the language offers,
and it is a smaller one than a process gave:
a hang and a panic are contained, a stack overflow, an out-of-memory abort or any other abort is not,
and ends the wrapper like any other crash.
The size of a finished result is bounded by the caller after the fact.
The long-term answer is to run the linter in the resident service, outside the client.
A panic is caught on the job’s thread and returned as text.
A job that shows no sign of work for the window cannot be stopped from outside,
so the wrapper stops waiting for it and leaves the thread to end with the process,
which is short-lived;
the thread owns everything it uses and shares nothing with the caller,
so abandoning it leaves nothing locked.
The thread has the linter’s own stack size,
because the main thread’s stack is set by the platform and a stack overflow cannot be caught;
the size makes an overflow as unlikely as in the executable, it does not contain one.
Elapsed time never ends the wait (owner, 2026-11-03: “Stop only on no progress”; pick 9 of
doc/planning/cli-git-shipped-limits-audit.md, the coordinator’s under the owner’s delegation and open to the owner’s veto). The signs of work are those the push reads, taken from the job thread’s own files under/proc/self/task/(thread_quiet.rs): the bytes it read and wrote, its processor time, its state. The longest quiet gap of every run is kept in the ledger undermonochromatic-lint(quiet_ledger.rs). Where/proccannot be read the wait has no end but the job’s. - markdown_
linter_ output - What one in-process linter run produced:
the fixed source and the findings left,
or why it is unusable.
What:
Read what one in-process
monochromatic-lintfix run produced: the fixed source and the findings it could not fix, or the reason the run cannot be trusted. Why: The linter’s own contract (package/linter/monochromatic-lint/src/run_embedded.rs) is the executable’s--stdin --fixrun with its one-rule configuration: status 0 with the fixed source and warning findings, because the policy’s configuration gives the rule severitywarn; 1 only for error findings or a warning limit, neither of which that configuration can produce; and 2 for a run that could not finish. Anything else, and any finding that is not a warning of a selected rule with a position, means the run cannot be trusted, so the policy fails instead of passing an unchecked file. - policy_
markdown - The
markdown/autofixpolicy over candidates, behind its linter interface. What: The optionalmarkdown/autofixpolicy: every Markdown candidate the lifecycle offers is passed throughmonochromatic-lint --fixwith the policy’s rules; a changed source is reported, and corrected ongit cli-git fix, and each finding the fixes leave is reported as it is. Why: The installed wrapper’smarkdownLintPolicy(package/git-policy/markdown-lint/src) decides the behaviour: deleted files, symbolic links and submodules are skipped, and so are excluded paths before any linter starts and contents that are not UTF-8; candidates are linted one at a time, in candidate order; the codes and words of both findings are kept. One thing differs by design: the extension test is the linter’s own (.mdand.mdx, exact case), so no file is sent that the linter refuses. The autofix finding keeps the installed wrapper’s words, which name its former linter, so both wrappers report the same file the same way while both are in use.