keymap-align 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. keymap_align-0.2.0/.editorconfig +69 -0
  2. keymap_align-0.2.0/.github/actionlint.yaml +8 -0
  3. keymap_align-0.2.0/.github/workflows/release.yml +68 -0
  4. keymap_align-0.2.0/.github/workflows/validate.yml +249 -0
  5. keymap_align-0.2.0/.gitignore +56 -0
  6. keymap_align-0.2.0/.markdownlint.yaml +42 -0
  7. keymap_align-0.2.0/.markdownlintignore +4 -0
  8. keymap_align-0.2.0/.pre-commit-config.yaml +237 -0
  9. keymap_align-0.2.0/.python-version +1 -0
  10. keymap_align-0.2.0/.shellcheckrc +21 -0
  11. keymap_align-0.2.0/CLAUDE.md +111 -0
  12. keymap_align-0.2.0/LICENSE +21 -0
  13. keymap_align-0.2.0/PKG-INFO +159 -0
  14. keymap_align-0.2.0/README.md +141 -0
  15. keymap_align-0.2.0/pyproject.toml +199 -0
  16. keymap_align-0.2.0/src/keymap_align/__init__.py +1 -0
  17. keymap_align-0.2.0/src/keymap_align/align.py +693 -0
  18. keymap_align-0.2.0/src/keymap_align/cli.py +168 -0
  19. keymap_align-0.2.0/src/keymap_align/config.py +135 -0
  20. keymap_align-0.2.0/src/keymap_align/layout_resolver.py +87 -0
  21. keymap_align-0.2.0/src/keymap_align/layouts/__init__.py +0 -0
  22. keymap_align-0.2.0/src/keymap_align/layouts/corne42.json +15 -0
  23. keymap_align-0.2.0/src/keymap_align/layouts/glove80.json +19 -0
  24. keymap_align-0.2.0/src/keymap_align/layouts/piantor.json +15 -0
  25. keymap_align-0.2.0/tests/layouts/corne_6key_simple_layout.json +7 -0
  26. keymap_align-0.2.0/tests/layouts/glove80_80key_layout.json +11 -0
  27. keymap_align-0.2.0/tests/simple_tests/corne_6key_simple_test.keymap +17 -0
  28. keymap_align-0.2.0/tests/test_align_keymap.py +680 -0
  29. keymap_align-0.2.0/tests/test_config.py +237 -0
  30. keymap_align-0.2.0/tests/test_display_name_preservation.py +211 -0
  31. keymap_align-0.2.0/tests/test_keymaps/correct/corne_6key_reference_comments_and_properties.keymap +38 -0
  32. keymap_align-0.2.0/tests/test_keymaps/correct/glove80_reference_properly_aligned.keymap +55 -0
  33. keymap_align-0.2.0/tests/test_keymaps/correct/glove80_reference_reverse_key_order.keymap +55 -0
  34. keymap_align-0.2.0/tests/test_keymaps/misaligned/corne_6key_input_comments_and_properties.keymap +35 -0
  35. keymap_align-0.2.0/tests/test_keymaps/misaligned/glove80_input_badly_aligned.keymap +55 -0
  36. keymap_align-0.2.0/tests/test_keymaps/misaligned/glove80_input_cramped_no_spacing.keymap +55 -0
  37. keymap_align-0.2.0/tests/test_keymaps/misaligned/glove80_input_excess_bindings.keymap +26 -0
  38. keymap_align-0.2.0/tests/test_keymaps/misaligned/glove80_input_insufficient_bindings.keymap +55 -0
  39. keymap_align-0.2.0/tests/test_layouts.py +151 -0
  40. keymap_align-0.2.0/tests/test_update.py +21 -0
  41. keymap_align-0.2.0/uv.lock +443 -0
@@ -0,0 +1,69 @@
1
+ # forge-managed
2
+ #
3
+ # Deployed as .editorconfig.
4
+ #
5
+ # This is where shell formatting is defined, not the shfmt hook. shfmt treats a
6
+ # parser or printer flag as a replacement for this file rather than an addition
7
+ # to it: a single `--indent 2` on the hook would discard switch_case_indent and
8
+ # binary_next_line along with it. Output flags (--write, --diff) are safe.
9
+ #
10
+ # The shell keys sit under [*] rather than [*.sh] because a shell executable is
11
+ # commonly shipped with no extension at all. A suffix pattern cannot reach one,
12
+ # and an unmatched file is not skipped: shfmt falls back to tab indentation and
13
+ # reformats it wrongly. shfmt only reads these for files it identifies as shell,
14
+ # so [*] costs nothing there. The per-language sections below exist because
15
+ # editors, unlike shfmt, read [*] for every file.
16
+ #
17
+ # indent_size, switch_case_indent and binary_next_line are shfmt's -i 2 -ci -bn,
18
+ # which the shfmt manual gives as its approximation of the Google Shell Style
19
+ # Guide. Google specifies all three: two-space indent, case alternatives
20
+ # indented, and a split pipeline carrying `|`, `||` and `&&` at the start of the
21
+ # continuation line rather than the end of the previous one.
22
+ #
23
+ # space_redirects is deliberately absent, leaving shfmt's default of `>file`.
24
+ # Google's guide does not rule on redirect spacing, and overriding a formatter's
25
+ # considered default with no standard behind it is config that cannot be
26
+ # justified later.
27
+ #
28
+ # simplify is likewise absent, and is not a formatting option: it rewrites
29
+ # `${var}` to `$var`, which Google's guide explicitly argues against.
30
+
31
+ root = true
32
+
33
+ [*]
34
+ charset = utf-8
35
+ end_of_line = lf
36
+ insert_final_newline = true
37
+ trim_trailing_whitespace = true
38
+ indent_style = space
39
+ indent_size = 2
40
+ switch_case_indent = true
41
+ binary_next_line = true
42
+
43
+ # gofmt is not configurable and emits tabs.
44
+ [*.go]
45
+ indent_style = tab
46
+
47
+ # PEP 8, and the default both ruff-format and rustfmt apply.
48
+ [*.{py,rs}]
49
+ indent_size = 4
50
+
51
+ # StyLua reads these where a repo carries no stylua.toml, and its own defaults
52
+ # are 120 columns and double quotes. 140 is the width ruff, prettier and rustfmt
53
+ # are given, and single quotes are what ruff's quote-style and prettier's
54
+ # singleQuote already choose.
55
+ [*.lua]
56
+ max_line_length = 140
57
+ quote_type = single
58
+
59
+ # Inherits indent_size = 2 to agree with markdownlint's MD007, which the shared
60
+ # .markdownlint.yaml leaves at its 2-space default via `default: true`.
61
+ # trim_trailing_whitespace matches the trailing-whitespace hook's
62
+ # --markdown-linebreak-ext=md, which preserves the two-space line break; an
63
+ # editor and the hook must not undo each other.
64
+ [*.md]
65
+ trim_trailing_whitespace = false
66
+
67
+ # make requires a literal tab before every recipe line.
68
+ [Makefile]
69
+ indent_style = tab
@@ -0,0 +1,8 @@
1
+ # forge-toolchain: 46
2
+ # Labels actionlint cannot discover. Its list of GitHub's hosted images is the
3
+ # one its release shipped with, and a self-hosted runner's labels are on no
4
+ # list. Without this every runs-on naming one is reported as a typo and the
5
+ # actionlint hook fails.
6
+ self-hosted-runner:
7
+ labels:
8
+ - ubuntu-26.04
@@ -0,0 +1,68 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ # Scoped per job rather than here: validate only reads, and it is the job that
8
+ # runs the most third-party code.
9
+ permissions:
10
+ contents: read
11
+
12
+ # Two pushes to main inside one run's window would otherwise race. Both runs
13
+ # resolve the same version, and the later one fails pushing its tag or its
14
+ # version commit.
15
+ # Never cancel-in-progress: a canceled release can leave a tag with no release.
16
+ concurrency:
17
+ group: release-${{ github.ref }}
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ # Without this, release publishes whatever is on main, tested or not.
22
+ validate:
23
+ uses: ./.github/workflows/validate.yml
24
+
25
+ release:
26
+ needs: validate
27
+ uses: datapointchris/reusable-workflows/.github/workflows/python-semantic-release.yml@v1
28
+ permissions:
29
+ contents: write
30
+
31
+ # A separate job in the same workflow, gated on the one above.
32
+ #
33
+ # Same workflow because a tag pushed with GITHUB_TOKEN does not trigger
34
+ # another workflow run, so a publish keyed on `push: tags` would never fire.
35
+ # Separate job so the publishing credential is reachable from these steps
36
+ # only, and not from the release job that runs python-semantic-release.
37
+ #
38
+ # A commit type that bumps nothing leaves `released` false, and this job is
39
+ # skipped. A skipped publish on a green run is the correct result there.
40
+ publish:
41
+ needs: release
42
+ if: needs.release.outputs.released == 'true'
43
+ runs-on: ubuntu-26.04
44
+ environment:
45
+ name: pypi
46
+ url: https://pypi.org/p/keymap-align
47
+ permissions:
48
+ # Trusted Publishing. No API token is stored in this repository; PyPI is
49
+ # configured to accept a short-lived OIDC credential from this workflow.
50
+ id-token: write
51
+ steps:
52
+ - uses: actions/checkout@v7
53
+ with:
54
+ # The tag, not the branch. The version is read from the tag, so a
55
+ # build anywhere else names the previous release plus a distance, and
56
+ # main may already have moved past what was released.
57
+ ref: ${{ needs.release.outputs.tag }}
58
+
59
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
60
+
61
+ - run: uv build
62
+
63
+ # A version cannot be replaced once uploaded, only yanked, so the sdist
64
+ # and wheel are checked before anything is irreversible.
65
+ - run: uv run --no-project --with twine twine check dist/*
66
+
67
+ - name: Publish to PyPI
68
+ run: uv publish
@@ -0,0 +1,249 @@
1
+ # forge-toolchain: 46
2
+ name: CI
3
+
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ pull_request:
8
+ workflow_call:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+
15
+ python:
16
+ runs-on: ubuntu-26.04
17
+ steps:
18
+
19
+ - uses: actions/checkout@v7
20
+
21
+ # generated:python
22
+ # enable-cache is left at setup-uv's `auto`, which caches on a hosted
23
+ # runner only. Every repo's self-hosted runner shares one ~/.cache/uv
24
+ # that outlives the job, so there the post step's prune and upload are
25
+ # work the disk already did, and two jobs pruning at once collide on the
26
+ # same files. version is the release the uv-lock hook pins, so the sync
27
+ # here and the lock check in the hooks job run one uv.
28
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
29
+ with:
30
+ version: "0.12.24"
31
+
32
+ - run: uv sync --all-extras --dev
33
+
34
+ # --with pytest, because skipping is the wrong answer here: a repo with a
35
+ # real test suite that never declared pytest as a dependency would
36
+ # silently stop being tested. Supplying the runner runs the tests; a suite
37
+ # needing plugins still declares those itself.
38
+ #
39
+ # pytest exits 5 when it collects nothing, which is not a failure for a
40
+ # repo that has no tests yet. Written explicitly rather than as an && chain
41
+ # because the runner uses `bash -e`, where the concise form depends on
42
+ # subtle errexit-exemption rules.
43
+ - name: Test
44
+ run: |
45
+ set +e
46
+ uv run --with pytest pytest
47
+ code=$?
48
+ set -e
49
+ if [ "$code" -eq 0 ] || [ "$code" -eq 5 ]; then exit 0; fi
50
+ exit 1
51
+
52
+ hooks:
53
+ runs-on: ubuntu-26.04
54
+ steps:
55
+
56
+ - uses: actions/checkout@v7
57
+
58
+ # generated:hooks
59
+ # Every hook a standard block put in the repo's committed config, which
60
+ # forge lists below. A push from a checkout that never ran `pre-commit
61
+ # install`, or a merge in the web UI, passes every hook by not running it,
62
+ # and this is the gate that catches both. CI runs the hooks themselves
63
+ # rather than copies of their checks, because a copy is a second spelling
64
+ # of the check that drifts from the first.
65
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
66
+ with:
67
+ version: "0.12.24"
68
+
69
+ # A self-hosted runner keeps ~/.cache/pre-commit between jobs, so there
70
+ # the archive is a download of what is already on disk.
71
+ - uses: actions/cache@v6
72
+ if: runner.environment == 'github-hosted'
73
+ with:
74
+ path: ~/.cache/pre-commit
75
+ key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
76
+
77
+ # The bats hook is `language: system`, so bats has to be on PATH. Every
78
+ # config carries the hook, and the step does nothing where there are no
79
+ # suites.
80
+ #
81
+ # All three are cloned at a tag rather than from main: a test harness
82
+ # that moves on its own turns an unrelated upstream change into a failing
83
+ # build, and nothing records which version a green run used.
84
+ #
85
+ # bats-core runs from its clone in RUNNER_TEMP, by PATH. Installed into
86
+ # /usr/local, it would be one copy every runner on a self-hosted box
87
+ # rewrites while the others execute it.
88
+ #
89
+ # The helper libraries go under ~/.local/lib because that is where the
90
+ # suites `load` them from — the same path they use on a workstation, so a
91
+ # test cannot pass locally and fail here over a lookup path. The suites
92
+ # name that path literally, and `load` resolves nothing, so BATS_LIB_PATH
93
+ # cannot move the libraries anywhere cheaper.
94
+ #
95
+ # On the self-hosted pool that $HOME outlives the job and is shared by
96
+ # every repo's runner, so concurrent jobs meet at ~/.local/lib. Each
97
+ # release is cloned once, into a directory named for its version, and
98
+ # nothing that has been loaded from is ever removed. The clone is staged
99
+ # beside its target and renamed into place, so a reader never sees a
100
+ # partial one, and a job that loses the race keeps the winner's copy. The
101
+ # path the suites load is a symlink to the pinned release, replaced by
102
+ # rename. A new pin names a new directory, so a runner never serves the
103
+ # tag it cloned first while CI reports the pin. A real directory at the
104
+ # symlink's path cannot be renamed over, so it is removed first.
105
+ #
106
+ # The hook runs tests/*.bats, flat. Recursing would sweep an integration
107
+ # tier in beside the unit one, and only the repo knows which of its tiers
108
+ # is runner-safe. A nested layout is named in a notice rather than passed
109
+ # over in silence, which is how one went unnoticed before.
110
+ #
111
+ # jq is installed ahead of the runner's own, because the scripts under
112
+ # test evaluate jq programs and jq's grammar moves between releases. 1.8
113
+ # accepts an object value piped into `//` without parentheses, and 1.7
114
+ # rejects the whole program. The version is the release the development
115
+ # machines run, so a program parses here exactly when it parses there.
116
+ - name: Set up bats
117
+ run: |
118
+ bats_version="1.14.0"
119
+ bats_support_version="0.3.0"
120
+ bats_assert_version="2.2.4"
121
+ jq_version="1.8.2"
122
+
123
+ if ! compgen -G "tests/*.bats" > /dev/null; then
124
+ if [ -n "$(find tests -name '*.bats' -print -quit 2>/dev/null)" ]; then
125
+ echo "::notice::bats suites exist under tests/ but none at its root, and the bats hook runs tests/*.bats only"
126
+ fi
127
+ exit 0
128
+ fi
129
+
130
+ git clone --depth 1 --branch "v${bats_version}" \
131
+ https://github.com/bats-core/bats-core.git "$RUNNER_TEMP/bats"
132
+ echo "$RUNNER_TEMP/bats/bin" >> "$GITHUB_PATH"
133
+
134
+ lib="$HOME/.local/lib"
135
+ mkdir -p "$lib"
136
+ for helper in "bats-support:${bats_support_version}" "bats-assert:${bats_assert_version}"; do
137
+ name="${helper%%:*}"
138
+ release="${name}-v${helper#*:}"
139
+ if [ ! -d "$lib/$release" ]; then
140
+ staged=$(mktemp -d "$lib/.$release.XXXXXX")
141
+ git clone --depth 1 --branch "v${helper#*:}" \
142
+ "https://github.com/bats-core/$name.git" "$staged"
143
+ mv -T "$staged" "$lib/$release" 2>/dev/null || rm -rf "$staged"
144
+ fi
145
+ [ -L "$lib/$name" ] || rm -rf "${lib:?}/$name"
146
+ ln -s "$release" "$lib/.$name.$$"
147
+ mv -T "$lib/.$name.$$" "$lib/$name"
148
+ done
149
+
150
+ mkdir -p "$RUNNER_TEMP/jq"
151
+ curl -fsSL --retry 3 -o "$RUNNER_TEMP/jq/jq" \
152
+ "https://github.com/jqlang/jq/releases/download/jq-${jq_version}/jq-linux-amd64"
153
+ chmod +x "$RUNNER_TEMP/jq/jq"
154
+ echo "$RUNNER_TEMP/jq" >> "$GITHUB_PATH"
155
+ "$RUNNER_TEMP/jq/jq" --version
156
+
157
+ # Over what the push or pull request changed, which is what the hooks
158
+ # would have seen at commit time. --all-files would fail a push on every
159
+ # finding in a file it never touched, so it is the scope only where no
160
+ # earlier commit can be fetched, as on a repo's first push.
161
+ #
162
+ # Or where the change touches a file that configures a hook or pins what
163
+ # it checks against: the hook config, a tool's config, a manifest, a lock
164
+ # or a toolchain file. None of those matches a hook's own file types, so
165
+ # a new release or rule would otherwise grade nothing in the commit that
166
+ # brought it, and its findings would land on the next author to touch the
167
+ # code.
168
+ #
169
+ # The checkout is one commit deep, and diffing needs one more. pre-commit
170
+ # falls back to a two-dot diff where the two commits share no history on
171
+ # disk, and refcheck's --moves reads the range as a two-dot diff too, so
172
+ # the base commit alone is enough.
173
+ #
174
+ # GITHUB_TOKEN is for the tflint hook, whose --init installs the plugins a
175
+ # .tflint.hcl names from GitHub, which rate-limits a runner that does not
176
+ # authenticate.
177
+ #
178
+ # setup-go exports GOTOOLCHAIN=local, and this job sets Go up once per
179
+ # module. Under local, a later setup-go ignores its go.mod's toolchain
180
+ # line, and each module's go refuses a go.mod asking for a newer release.
181
+ # auto lets each module's go switch to the toolchain its go.mod names. It
182
+ # is exported in the script, where no GITHUB_ENV write can override it.
183
+ - name: Run the hooks over the change
184
+ env:
185
+ BASE: ${{ github.event.pull_request.base.sha || github.event.before }}
186
+ GITHUB_TOKEN: ${{ github.token }}
187
+ run: |
188
+ export GOTOOLCHAIN=auto
189
+ from=
190
+ if [ -n "$BASE" ] && git fetch --quiet --depth=1 --no-tags origin "$BASE" 2>/dev/null; then
191
+ from=$BASE
192
+ elif git fetch --quiet --depth=2 --no-tags origin "$(git rev-parse HEAD)" &&
193
+ git rev-parse --quiet --verify HEAD~1 >/dev/null; then
194
+ echo "::notice::this event names no base commit, so only the head commit's change is checked"
195
+ from=HEAD~1
196
+ else
197
+ echo "::notice::no earlier commit to diff against, so every file is checked"
198
+ fi
199
+ scope=(--all-files)
200
+ if [ -n "$from" ]; then
201
+ scope=(--from-ref "$from" --to-ref HEAD)
202
+ while IFS= read -r changed; do
203
+ case "${changed##*/}" in
204
+ .pre-commit-config.yaml | .editorconfig | .codespellrc | .markdownlint.yaml | \
205
+ .shellcheckrc | .golangci.yml | .sqlfluff | .tflint.hcl | .prettierrc.yaml | \
206
+ .prettierignore | rustfmt.toml | .rustfmt.toml | \
207
+ eslint.config.* | tsconfig*.json | stylua.toml | .stylua.toml | \
208
+ pyproject.toml | uv.lock | go.mod | go.sum | Cargo.toml | Cargo.lock | \
209
+ rust-toolchain.toml | package.json | package-lock.json | .nvmrc | .terraform.lock.hcl)
210
+ echo "::notice::$changed changed, so every file is checked"
211
+ scope=(--all-files)
212
+ break
213
+ ;;
214
+ esac
215
+ done < <(git diff --name-only "$from" HEAD)
216
+ fi
217
+ hooks=(
218
+ check-yaml
219
+ check-toml
220
+ check-json
221
+ end-of-file-fixer
222
+ trailing-whitespace
223
+ check-executables-have-shebangs
224
+ check-shebang-scripts-are-executable
225
+ check-added-large-files
226
+ check-merge-conflict
227
+ mixed-line-ending
228
+ check-case-conflict
229
+ detect-private-key
230
+ check-json5
231
+ markdownlint
232
+ shellcheck
233
+ shfmt
234
+ bats
235
+ codespell
236
+ refcheck
237
+ uv-lock
238
+ ruff-format
239
+ ruff-check
240
+ mypy
241
+ actionlint
242
+ )
243
+ # Every hook runs whatever the one before it found, so one push
244
+ # reports every failing hook rather than the first.
245
+ status=0
246
+ for hook in "${hooks[@]}"; do
247
+ uvx pre-commit run "$hook" "${scope[@]}" --show-diff-on-failure || status=1
248
+ done
249
+ exit "$status"
@@ -0,0 +1,56 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+
24
+ # Virtual environments
25
+ .venv/
26
+ venv/
27
+ ENV/
28
+ env/
29
+
30
+ # IDEs
31
+ .vscode/
32
+ .idea/
33
+ *.swp
34
+ *.swo
35
+ *~
36
+ .DS_Store
37
+
38
+ # Testing
39
+ .coverage
40
+ .pytest_cache/
41
+ .mypy_cache/
42
+ .ruff_cache/
43
+ htmlcov/
44
+ .tox/
45
+ tests/test_keymaps/test_output/
46
+
47
+ # Documentation
48
+ site/
49
+ docs/_build/
50
+
51
+ # Misc
52
+ *.log
53
+ *.tmp
54
+ .cache/
55
+ .claude/
56
+ coverage.xml
@@ -0,0 +1,42 @@
1
+ # forge-managed
2
+ #
3
+ # Deployed as .markdownlint.yaml.
4
+ #
5
+ # YAML rather than JSON so the file can carry the marker on its first line. JSON
6
+ # has no comment syntax, and markdownlint reads either spelling — including at
7
+ # the rev the markdown block pins.
8
+ #
9
+ # markdownlint prefers .markdownlint.json when both are present, so a repo still
10
+ # holding the JSON spelling has it removed in the same run that writes this one.
11
+
12
+ default: true
13
+
14
+ # Line length. Prose here is wrapped for the editor, not for a linter, and a
15
+ # rule that fires on a long URL costs more than it finds.
16
+ MD013: false
17
+
18
+ # Duplicate headings, siblings only. A document with repeated subsections under
19
+ # different parents is ordinary; two identical headings under one parent is not.
20
+ MD024:
21
+ siblings_only: true
22
+
23
+ # Inline HTML. Markdown that needs a <details> block or an anchor needs it.
24
+ MD033: false
25
+
26
+ # First line a top-level heading, or a title in front matter. `name:` counts as
27
+ # a title as well as `title:`, because a skill or agent definition carries its
28
+ # title under that key and opens on no heading.
29
+ MD041:
30
+ front_matter_title: "^\\s*(title|name)\\s*[:=]"
31
+
32
+ # Emphasis used instead of a heading. Deliberate in short documents that want a
33
+ # bold run-in without adding a level to the outline.
34
+ MD036: false
35
+
36
+ # Spaces inside code span markers. Fires on a code span whose content is a
37
+ # literal space, which is a legitimate thing to write.
38
+ MD038: false
39
+
40
+ # Code block style. Both fenced and indented blocks appear in these repos and
41
+ # neither is wrong.
42
+ MD046: false
@@ -0,0 +1,4 @@
1
+ # Preventive: no release here writes a changelog. The entry stands so that one
2
+ # arriving generated is never linted, since normalization of a generated file
3
+ # is undone on the next release and returns as a conflict.
4
+ CHANGELOG.md