mdcompose 0.1.3__tar.gz → 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 (108) hide show
  1. mdcompose-0.2.0/.github/FUNDING.yml +1 -0
  2. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/workflows/ci.yml +25 -24
  3. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/workflows/supply-chain.yml +1 -1
  4. {mdcompose-0.1.3 → mdcompose-0.2.0}/CHANGELOG.md +6 -0
  5. {mdcompose-0.1.3 → mdcompose-0.2.0}/PKG-INFO +4 -7
  6. {mdcompose-0.1.3 → mdcompose-0.2.0}/README.md +3 -6
  7. {mdcompose-0.1.3 → mdcompose-0.2.0}/TODO.md +94 -197
  8. {mdcompose-0.1.3 → mdcompose-0.2.0}/docs/concepts.md +25 -6
  9. {mdcompose-0.1.3 → mdcompose-0.2.0}/docs/core-contract.md +63 -3
  10. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/commands/init_cmd.py +67 -0
  11. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/config.py +33 -1
  12. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/init_ops.py +218 -19
  13. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/managed_block.py +3 -0
  14. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/manifest.py +76 -4
  15. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/platform.py +2 -3
  16. mdcompose-0.2.0/mdcompose/core/skill_composition.py +81 -0
  17. mdcompose-0.2.0/mdcompose/core/skills.py +290 -0
  18. {mdcompose-0.1.3 → mdcompose-0.2.0}/pyproject.toml +1 -1
  19. mdcompose-0.2.0/tests/fixtures/mdcompose.lock.schema1.json +28 -0
  20. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_config.py +33 -0
  21. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_config_cmd.py +25 -0
  22. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_init.py +129 -0
  23. mdcompose-0.2.0/tests/test_init_ops.py +673 -0
  24. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_managed_block.py +21 -0
  25. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_manifest.py +147 -0
  26. mdcompose-0.2.0/tests/test_skill_composition.py +85 -0
  27. mdcompose-0.2.0/tests/test_skills.py +242 -0
  28. {mdcompose-0.1.3 → mdcompose-0.2.0}/uv.lock +258 -258
  29. mdcompose-0.1.3/tests/test_init_ops.py +0 -299
  30. {mdcompose-0.1.3 → mdcompose-0.2.0}/.gitattributes +0 -0
  31. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  32. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  33. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  34. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  35. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/workflows/bump.yml +0 -0
  36. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/workflows/codeql.yml +0 -0
  37. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/workflows/dependency-review.yml +0 -0
  38. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/workflows/mutation.yml +0 -0
  39. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/workflows/python-eol.yml +0 -0
  40. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/workflows/release.yml +0 -0
  41. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/workflows/scorecard.yml +0 -0
  42. {mdcompose-0.1.3 → mdcompose-0.2.0}/.github/zizmor.yml +0 -0
  43. {mdcompose-0.1.3 → mdcompose-0.2.0}/.gitignore +0 -0
  44. {mdcompose-0.1.3 → mdcompose-0.2.0}/.pre-commit-config.yaml +0 -0
  45. {mdcompose-0.1.3 → mdcompose-0.2.0}/CODE_OF_CONDUCT.md +0 -0
  46. {mdcompose-0.1.3 → mdcompose-0.2.0}/CONTRIBUTING.md +0 -0
  47. {mdcompose-0.1.3 → mdcompose-0.2.0}/LICENSE +0 -0
  48. {mdcompose-0.1.3 → mdcompose-0.2.0}/SECURITY.md +0 -0
  49. {mdcompose-0.1.3 → mdcompose-0.2.0}/docs/benchmarks.md +0 -0
  50. {mdcompose-0.1.3 → mdcompose-0.2.0}/docs/commit-and-release-tooling.md +0 -0
  51. {mdcompose-0.1.3 → mdcompose-0.2.0}/docs/hardening-review.md +0 -0
  52. {mdcompose-0.1.3 → mdcompose-0.2.0}/docs/optional-tooling.md +0 -0
  53. {mdcompose-0.1.3 → mdcompose-0.2.0}/docs/publishing.md +0 -0
  54. {mdcompose-0.1.3 → mdcompose-0.2.0}/docs/scorecard.md +0 -0
  55. {mdcompose-0.1.3 → mdcompose-0.2.0}/docs/versioning-explained.md +0 -0
  56. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/__init__.py +0 -0
  57. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/cli.py +0 -0
  58. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/commands/__init__.py +0 -0
  59. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/commands/config_cmd.py +0 -0
  60. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/commands/convert_cmd.py +0 -0
  61. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/commands/eject_cmd.py +0 -0
  62. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/commands/import_cmd.py +0 -0
  63. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/commands/snippet_cmd.py +0 -0
  64. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/commands/target_cmd.py +0 -0
  65. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/__init__.py +0 -0
  66. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/composition.py +0 -0
  67. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/convert_ops.py +0 -0
  68. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/eject.py +0 -0
  69. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/exit_codes.py +0 -0
  70. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/files.py +0 -0
  71. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/import_ops.py +0 -0
  72. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/library_ops.py +0 -0
  73. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/output.py +0 -0
  74. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/report.py +0 -0
  75. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/sections.py +0 -0
  76. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/snippets.py +0 -0
  77. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/core/targets.py +0 -0
  78. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/prompts.py +0 -0
  79. {mdcompose-0.1.3 → mdcompose-0.2.0}/mdcompose/version.py +0 -0
  80. {mdcompose-0.1.3 → mdcompose-0.2.0}/renovate.json +0 -0
  81. {mdcompose-0.1.3 → mdcompose-0.2.0}/scripts/check_ascii.py +0 -0
  82. {mdcompose-0.1.3 → mdcompose-0.2.0}/scripts/check_licenses.py +0 -0
  83. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/benchmarks/conftest.py +0 -0
  84. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/benchmarks/test_core_ops.py +0 -0
  85. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/conftest.py +0 -0
  86. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/fixtures/plain.bom.md +0 -0
  87. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/fixtures/plain.crlf.md +0 -0
  88. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/fixtures/plain.lf.md +0 -0
  89. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/fixtures/prose.cp1252.md +0 -0
  90. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_cli.py +0 -0
  91. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_convert.py +0 -0
  92. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_doctor_drift.py +0 -0
  93. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_e2e_import_convert.py +0 -0
  94. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_eject.py +0 -0
  95. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_files.py +0 -0
  96. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_fuzz.py +0 -0
  97. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_import_cmd.py +0 -0
  98. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_import_ops.py +0 -0
  99. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_layering.py +0 -0
  100. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_onedrive.py +0 -0
  101. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_output.py +0 -0
  102. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_platform.py +0 -0
  103. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_prompts.py +0 -0
  104. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_report.py +0 -0
  105. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_sections.py +0 -0
  106. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_snippet_commands.py +0 -0
  107. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_snippets.py +0 -0
  108. {mdcompose-0.1.3 → mdcompose-0.2.0}/tests/test_targets.py +0 -0
@@ -0,0 +1 @@
1
+ ko_fi: rovetown
@@ -292,27 +292,28 @@ jobs:
292
292
  esac
293
293
  done
294
294
 
295
- # DORMANT, repo-gated. The suite (tests/benchmarks) and the `benchmark`
296
- # dependency group both exist; what is missing is a CodSpeed project, which
297
- # needs the GitHub repo. CodSpeed runs the benchmarks instrumented rather than
298
- # wall-clock, so the numbers are stable across runners, and it posts the delta
299
- # as a pull request check rather than failing hard. Free for open source. To
300
- # enable: connect the repo at codspeed.io, uncomment this job, and leave it
301
- # out of the all-green needs list (benchmarks never block a merge).
302
- #
303
- # benchmarks:
304
- # name: benchmarks
305
- # runs-on: ubuntu-latest
306
- # timeout-minutes: 20
307
- # steps:
308
- # - uses: actions/checkout@v4
309
- # with:
310
- # persist-credentials: false
311
- # - uses: astral-sh/setup-uv@v5
312
- # with:
313
- # enable-cache: true
314
- # cache-dependency-glob: uv.lock
315
- # - run: uv sync --locked --group benchmark
316
- # - uses: CodSpeedHQ/action@v3
317
- # with:
318
- # run: uv run --no-sync pytest tests/benchmarks --codspeed
295
+ # CodSpeed runs the benchmarks instrumented rather than wall-clock, so the
296
+ # numbers are stable across runners, and it posts the delta as a pull request
297
+ # check rather than failing hard. Free for open source. Not in the all-green
298
+ # needs list: benchmarks never block a merge. Needs the repo connected at
299
+ # codspeed.io once before its first run.
300
+ benchmarks:
301
+ name: benchmarks
302
+ runs-on: ubuntu-latest
303
+ timeout-minutes: 20
304
+ permissions:
305
+ contents: read
306
+ id-token: write # OpenID Connect authentication with CodSpeed
307
+ steps:
308
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
309
+ with:
310
+ persist-credentials: false
311
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
312
+ with:
313
+ enable-cache: true
314
+ cache-dependency-glob: uv.lock
315
+ - run: uv sync --locked --group benchmark
316
+ - uses: CodSpeedHQ/action@373d6868929f444bc08d901fd0eb0ad52a8875ea # v5.2.1
317
+ with:
318
+ run: uv run --no-sync pytest tests/benchmarks --codspeed
319
+ mode: "simulation"
@@ -85,7 +85,7 @@ jobs:
85
85
  with:
86
86
  persist-credentials: false
87
87
 
88
- - uses: google/osv-scanner-action/osv-scanner-action@6e4298ebc4db23e847df9b2e2de2939d6f066c67 # v2.5.1
88
+ - uses: google/osv-scanner-action/osv-scanner-action@a345acffa64b0eaede81a3d9aae6141214d9c8fc # v2.6.0
89
89
  with:
90
90
  scan-args: >-
91
91
  --lockfile=uv.lock
@@ -3,6 +3,12 @@
3
3
  All notable changes to mdcompose are recorded here.
4
4
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## v0.2.0 (2026-09-14)
7
+
8
+ ### Added
9
+
10
+ - compose agent skills alongside AGENTS.md and CLAUDE.md
11
+
6
12
  ## v0.1.3 (2026-09-11)
7
13
 
8
14
  ## v0.1.2 (2026-09-11)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mdcompose
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary: Keep CLAUDE.md and AGENTS.md in sync and composed from a personal snippet library, without hand-editing every project
5
5
  Project-URL: Homepage, https://github.com/Rovetown/mdcompose
6
6
  Project-URL: Repository, https://github.com/Rovetown/mdcompose
@@ -40,15 +40,14 @@ Description-Content-Type: text/markdown
40
40
 
41
41
  <div align="center">
42
42
 
43
- [![PyPI](https://img.shields.io/pypi/v/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/) [![Python](https://img.shields.io/pypi/pyversions/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/) [![CI](https://img.shields.io/github/actions/workflow/status/Rovetown/mdcompose/ci.yml?branch=main&style=for-the-badge&label=CI)](https://github.com/Rovetown/mdcompose/actions/workflows/ci.yml) [![OpenSSF Scorecard](https://img.shields.io/ossf-scorecard/github.com/Rovetown/mdcompose?style=for-the-badge&label=scorecard)](https://scorecard.dev/viewer/?uri=github.com/Rovetown/mdcompose) [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](https://github.com/Rovetown/mdcompose/blob/main/LICENSE) [![Downloads](https://img.shields.io/pypi/dm/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/)
43
+ [![PyPI](https://img.shields.io/pypi/v/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/) [![Python](https://img.shields.io/pypi/pyversions/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/) [![CI](https://img.shields.io/github/actions/workflow/status/Rovetown/mdcompose/ci.yml?branch=main&style=for-the-badge&label=CI)](https://github.com/Rovetown/mdcompose/actions/workflows/ci.yml) [![CodSpeed Badge](https://img.shields.io/endpoint?url=https://app.codspeed.io//badge.json&style=for-the-badge)](https://app.codspeed.io//Rovetown/mdcompose?utm_source=badge) [![OpenSSF Scorecard](https://img.shields.io/ossf-scorecard/github.com/Rovetown/mdcompose?style=for-the-badge&label=scorecard)](https://scorecard.dev/viewer/?uri=github.com/Rovetown/mdcompose) [![OpenSSF Best Practices](https://img.shields.io/cii/summary/14614?style=for-the-badge&label=best%20practices)](https://www.bestpractices.dev/projects/14614) [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](https://github.com/Rovetown/mdcompose/blob/main/LICENSE) [![Downloads](https://img.shields.io/pypi/dm/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Y8Y31VUCGT)
44
44
 
45
45
  </div>
46
46
 
47
47
  Most repositories need the same handful of instructions for AI coding agents: how commits are written, which test command to run, which files not to touch.
48
48
  mdcompose keeps those as small snippets in one place and builds each project's `AGENTS.md` and `CLAUDE.md` from the ones you pick, so you stop copying the same paragraphs between repositories.
49
49
 
50
- > `mdcompose` composes Markdown.
51
- > It is unrelated to Docker Compose; the shared `-compose` suffix is a coincidence.
50
+ > `mdcompose` composes Markdown. It is unrelated to Docker Compose.
52
51
 
53
52
  ## Highlights
54
53
 
@@ -89,11 +88,9 @@ uvx mdcompose doctor
89
88
 
90
89
  The whole flow, start to finish:
91
90
 
92
- <!-- demo GIF: docs/assets/mdcompose-demo.gif -- not yet recorded, see TODO.md. Restore the <img> below once it exists. -->
93
-
94
91
  <div align="center">
95
92
 
96
- *(a short demo recording of `init` and `doctor` goes here -- not yet recorded)*
93
+ <img src="https://raw.githubusercontent.com/Rovetown/mdcompose/main/docs/assets/mdcompose-demo.gif" alt="mdcompose demo: init's interactive picker composing AGENTS.md and CLAUDE.md from selected snippets, then doctor confirming both are clean" width="700">
97
94
 
98
95
  </div>
99
96
 
@@ -10,15 +10,14 @@
10
10
 
11
11
  <div align="center">
12
12
 
13
- [![PyPI](https://img.shields.io/pypi/v/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/) [![Python](https://img.shields.io/pypi/pyversions/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/) [![CI](https://img.shields.io/github/actions/workflow/status/Rovetown/mdcompose/ci.yml?branch=main&style=for-the-badge&label=CI)](https://github.com/Rovetown/mdcompose/actions/workflows/ci.yml) [![OpenSSF Scorecard](https://img.shields.io/ossf-scorecard/github.com/Rovetown/mdcompose?style=for-the-badge&label=scorecard)](https://scorecard.dev/viewer/?uri=github.com/Rovetown/mdcompose) [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](https://github.com/Rovetown/mdcompose/blob/main/LICENSE) [![Downloads](https://img.shields.io/pypi/dm/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/)
13
+ [![PyPI](https://img.shields.io/pypi/v/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/) [![Python](https://img.shields.io/pypi/pyversions/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/) [![CI](https://img.shields.io/github/actions/workflow/status/Rovetown/mdcompose/ci.yml?branch=main&style=for-the-badge&label=CI)](https://github.com/Rovetown/mdcompose/actions/workflows/ci.yml) [![CodSpeed Badge](https://img.shields.io/endpoint?url=https://app.codspeed.io//badge.json&style=for-the-badge)](https://app.codspeed.io//Rovetown/mdcompose?utm_source=badge) [![OpenSSF Scorecard](https://img.shields.io/ossf-scorecard/github.com/Rovetown/mdcompose?style=for-the-badge&label=scorecard)](https://scorecard.dev/viewer/?uri=github.com/Rovetown/mdcompose) [![OpenSSF Best Practices](https://img.shields.io/cii/summary/14614?style=for-the-badge&label=best%20practices)](https://www.bestpractices.dev/projects/14614) [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](https://github.com/Rovetown/mdcompose/blob/main/LICENSE) [![Downloads](https://img.shields.io/pypi/dm/mdcompose?style=for-the-badge)](https://pypi.org/project/mdcompose/) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Y8Y31VUCGT)
14
14
 
15
15
  </div>
16
16
 
17
17
  Most repositories need the same handful of instructions for AI coding agents: how commits are written, which test command to run, which files not to touch.
18
18
  mdcompose keeps those as small snippets in one place and builds each project's `AGENTS.md` and `CLAUDE.md` from the ones you pick, so you stop copying the same paragraphs between repositories.
19
19
 
20
- > `mdcompose` composes Markdown.
21
- > It is unrelated to Docker Compose; the shared `-compose` suffix is a coincidence.
20
+ > `mdcompose` composes Markdown. It is unrelated to Docker Compose.
22
21
 
23
22
  ## Highlights
24
23
 
@@ -59,11 +58,9 @@ uvx mdcompose doctor
59
58
 
60
59
  The whole flow, start to finish:
61
60
 
62
- <!-- demo GIF: docs/assets/mdcompose-demo.gif -- not yet recorded, see TODO.md. Restore the <img> below once it exists. -->
63
-
64
61
  <div align="center">
65
62
 
66
- *(a short demo recording of `init` and `doctor` goes here -- not yet recorded)*
63
+ <img src="https://raw.githubusercontent.com/Rovetown/mdcompose/main/docs/assets/mdcompose-demo.gif" alt="mdcompose demo: init's interactive picker composing AGENTS.md and CLAUDE.md from selected snippets, then doctor confirming both are clean" width="700">
67
64
 
68
65
  </div>
69
66
 
@@ -5,129 +5,42 @@ management CLI. Everything here is either open or a decision worth keeping.
5
5
 
6
6
  ## Next
7
7
 
8
- The codebase and the automation are done. The repo is live at
9
- `github.com/Rovetown/mdcompose` and the account-side setup in "Repository and
10
- release setup" below is complete (ruleset, `RELEASE_TOKEN`, the `pypi` and
11
- `testpypi` environments, both trusted publishers, Dependabot, private
12
- vulnerability reporting). What remains is the first release, a few follow-ups,
13
- and a set of OpenSSF Scorecard items folded into the sequence at the step each
14
- one belongs to. The Scorecard reasoning is in [`docs/scorecard.md`](docs/scorecard.md).
15
-
16
- ### Scorecard, current state
17
-
18
- 1. Done. [`SECURITY.md`](SECURITY.md) carries the private-advisory URL
19
- (`https://github.com/Rovetown/mdcompose/security/advisories/new`). Confirmed
20
- in the `scorecard.yml` run for commit `b730e9e`: Security-Policy, along with
21
- Signed-Releases and Packaging, all score 10. Overall: 7.2. Remaining gap is
22
- Maintained (0, clears at 90 days), CII-Best-Practices (0, needs
23
- registration, see step 7 below), and the accepted solo-maintainer limits
24
- (Code-Review, Contributors, Branch-Protection, Fuzzing). See
25
- [`docs/scorecard.md`](docs/scorecard.md) for the full detail.
26
-
27
- ### First release
28
-
29
- 2. Done, verifies at step 4. `release.yml` `build` job runs
30
- `actions/attest-build-provenance` over `dist/*` (job now has
31
- `id-token: write` and `attestations: write`), stages the bundle as
32
- `mdcompose.intoto.jsonl`, and the `github-release` job attaches it to the
33
- Release (assets are now listed explicitly, since the artifact download keeps
34
- the `dist/` subdirectory and `gh release create` rejects a directory arg).
35
- A SLSA `*.intoto.jsonl` scores 10 on Signed-Releases. Only runs on a stable
36
- tag; pre-releases get no GitHub Release. Nothing to verify until the first
37
- stable release lands the assets.
38
- 3. Done. Alpha dry run published `0.1.0a0` to TestPyPI, verified end to end:
39
- `release.yml` build -> attest -> TestPyPI, `pypi` and `github-release` jobs
40
- correctly skipped, `gh attestation verify` passed online and against the
41
- staged `mdcompose.intoto.jsonl`, clean-venv `pip install` works. Two bugs
42
- found and fixed on the way: `cz bump` left `uv.lock` stale (fixed in #12,
43
- `bump.yml` now relocks and amends before the tag push) and a deleted
44
- prerelease tag left commitizen unable to bound the next changelog (fixed by
45
- reverting the botched bump in #13).
46
-
47
- ### README (active, before the stable release)
48
-
49
- The README should be good before `0.1.0` reaches PyPI, since `readme =
50
- "README.md"` becomes the PyPI long description. Structure is agreed: lean
51
- front-door, ~130 to 200 lines, `for-the-badge` style shields badges, near-zero
52
- emoji, ASCII diagrams and trees (no mermaid, because PyPI does not render it),
53
- HTML only for layout. Prose is neutral-dev, accessible to a non-expert without
54
- dropping the technical terms.
55
-
56
- Done: [`README.md`](README.md) written (from the v3 draft, demo GIF placed in Quickstart
57
- rather than Highlights, since the GIF shows the flow the Quickstart walks
58
- through), both drafts deleted, `scripts/check_ascii.py` exempts [`README.md`](README.md)
59
- via a `SKIP_FILES` set (Decisions log below records it), reference-level detail
60
- moved out to [`docs/concepts.md`](docs/concepts.md), and a logo added: `docs/assets/mdcompose-logo.svg`
61
- in the centered header (replacing the `# mdcompose` text heading), referenced
62
- by the same absolute `raw.githubusercontent.com/.../main/...` URL as the demo
63
- GIF so it also renders on PyPI, and `docs/assets/**` excluded from the sdist in
64
- `pyproject.toml` (`[tool.hatch.build]`, verified with a real `uv build`; the
65
- wheel was never affected, it already only packages `mdcompose`). Badges use
66
- `style=for-the-badge`, not `flat-square`. Not yet committed.
67
-
68
- Still to finish:
69
-
70
- - **Record the demo GIF.** Deliberately deferred, not blocking `0.1.0`: no
71
- time to set up the recording right now. The README's Quickstart spot now
72
- holds an italic text placeholder instead of a broken `<img>` tag, with a
73
- comment naming exactly what to restore once the GIF exists. Plan for when
74
- time allows: script an asciinema recording of the real flow (create a
75
- snippet file, `mdcompose init` with the picker, `mdcompose doctor` showing a
76
- clean then drifted state), convert with
77
- `agg demo.cast docs/assets/mdcompose-demo.gif`. Keep it short, roughly 15 to
78
- 25 seconds, one clear take. Shot list to write first: exact commands, where
79
- to pause, terminal size and theme so it is re-recordable when output
80
- changes. `docs/assets/` already exists (holds the logo).
81
- - **Verify the badges resolve** once `0.1.0` is on PyPI (the PyPI, pyversions,
82
- and scorecard badges 404 or show "unknown" until then).
83
-
84
- ### After the README, resume the release
85
-
86
- 4. Done. `bump.yml` ran with channel `stable`, `0.1.0a0` -> `0.1.0`, `v0.1.0`
87
- tagged, the `pypi` environment approved. `v0.1.0` is live on PyPI and the
88
- first GitHub Release exists with the SBOM and attestation assets attached.
89
- 5. Done. [`CHANGELOG.md`](CHANGELOG.md)'s `## v0.1.0` entry replaced cz's thin
90
- auto body ("break the cli and command import cycle", "relock uv.lock",
91
- etc., none of which belong in a *user-facing* changelog) with "Initial
92
- public release." plus the full `### Added` command list restored verbatim
93
- from the old `[Unreleased]` section (recovered from commit `2252051`, since
94
- the alpha bump had already consumed it) and the `### Security`
95
- SLSA-provenance note. cz's `### Changed` and `### Fixed` sections dropped:
96
- there is no prior release to change from.
97
-
98
- Also done in this pass, discovered from the live PyPI listing: the
99
- `pypi/pyversions` badge showed a bare "Python 3" rather than the real
100
- supported range, because shields.io reads PyPI's classifiers, not
101
- `requires-python`, and only a bare `Programming Language :: Python :: 3` was
102
- listed. `pyproject.toml` now lists `3.11` through `3.14` individually,
103
- matching the CI matrix. This only takes effect on the *next* release; it does
104
- not retroactively fix the metadata already published for `0.1.0`.
105
-
106
- ### After the first release
107
-
108
- 6. **Post-release hardening.** Partly done: `Development Status` moved to
109
- `4 - Beta` (the v1 feature set is complete and tested -- all 8 OpenSpec
110
- changes shipped, 93%+ coverage, mutation-tested, hardening review done --
111
- but pre-1.0 semver still allows a breaking change, so Beta fits better than
112
- a Stable claim; also takes effect on the next release only). Still open:
113
- point `platform.ONEDRIVE_HELP_URL` at a real page.
114
- 7. **OpenSSF Best Practices passing badge.** Register the repo at
115
- bestpractices.dev, complete the passing questionnaire (most criteria are
116
- already met by the existing CI, tests, license, and static analysis), embed
117
- the badge in the README. Scorecard's CII-Best-Practices check reads it
118
- through the API: 0 to 5. Silver and gold are not attainable for a
119
- solo-maintained project, so passing is the target. See [`docs/scorecard.md`](docs/scorecard.md).
120
- 8. **Dismiss the permanent Scorecard code-scanning alerts.** Code-Review,
121
- Branch-Protection, and Fuzzing are solo-maintainer structural or a
122
- deliberate non-goal (see [`docs/scorecard.md`](docs/scorecard.md), Accepted limitations). Dismiss
123
- them as "won't fix" in the Security tab so the alert count stays meaningful;
124
- leave Maintained and CII-Best-Practices to self-resolve.
125
-
126
- ### Not planned
127
-
128
- 9. **Fuzzing.** Left at 0. Scorecard does not detect Python Hypothesis, and an
129
- Atheris plus ClusterFuzzLite setup is out of proportion to the risk for two
130
- small parsers. Revisit only if the parser surface grows.
8
+ The codebase, the automation, and the account-side release setup are all done.
9
+ The repo is live at `github.com/Rovetown/mdcompose`, `v0.1.3` is on PyPI, and
10
+ the WSL demo recording did a clean-venv `pip install` against that real
11
+ published package. What is left is a short list of items that are either
12
+ genuinely still open or deliberately deferred to a future trigger (a date, a
13
+ second maintainer, a badge cache catching up). The Scorecard reasoning behind
14
+ the deferred items is in [`docs/scorecard.md`](docs/scorecard.md).
15
+
16
+ Done: CodSpeed badge added to the README badge row, `style=for-the-badge`,
17
+ placed next to the CI badge. Done: PyPI, Python-versions, and OpenSSF
18
+ Scorecard badges confirmed rendering real values, not "unknown" or a
19
+ broken-image placeholder.
20
+
21
+ ### Deferred
22
+
23
+ - **OpenSSF Scorecard: Maintained check.** Scores 0 regardless of activity
24
+ until the repository passes 90 days old; it was created 2026-09-09, so this
25
+ clears around 2026-12-08. Nothing to do before then; revisit only to
26
+ confirm the score actually moved once that date passes.
27
+ - **OpenSSF Scorecard: code-scanning alerts left open, not dismissed.**
28
+ Code-Review, Branch-Protection, and Fuzzing are solo-maintainer structural
29
+ limits (see [`docs/scorecard.md`](docs/scorecard.md), Accepted
30
+ limitations), but "won't fix" is not a permanent label here -- the
31
+ constraint holds only while there is one maintainer. Revisit once a second
32
+ maintainer or a collaborator with review rights joins: Code-Review and
33
+ Branch-Protection stop being structural at that point, and Fuzzing can be
34
+ reconsidered on its own merits.
35
+ - **Fuzzing.** Left at 0 on purpose, not a gap to close now. Scorecard does
36
+ not detect Python Hypothesis (already used in the parser tests), and an
37
+ Atheris plus ClusterFuzzLite setup is out of proportion to the risk for two
38
+ small parsers. Revisit only if the parser surface grows materially.
39
+ - **Add each new Python to the CI matrix by hand** when its final release
40
+ ships (annual cadence, not worth automating). 3.15 is deferred until then
41
+ (was rc2 on 2026-09-13, final due October 2026): a prerelease-specific
42
+ version string in `uv sync --python` is not worth carrying for a few weeks
43
+ of coverage the final release gets for free.
131
44
 
132
45
  ## Where things live
133
46
 
@@ -200,6 +113,33 @@ not retroactively fix the metadata already published for `0.1.0`.
200
113
  `version_provider = "scm"` handshake with commitizen) than a solo project
201
114
  bumping one line per release needs. Decision closed.
202
115
 
116
+ - **Demo GIF recording needs a pty CPR trick, if it is ever re-recorded.**
117
+ `questionary`'s picker sits on `prompt_toolkit`, which probes the terminal
118
+ with a cursor-position request (`\x1b[6n`) before it will live-redraw. A
119
+ bare `pexpect.spawn` pty never answers that probe, so the picker silently
120
+ degrades to a static, non-redrawing render -- every keystroke lands, but
121
+ nothing visibly updates until the final "done (N selections)" line. Fix
122
+ used for `docs/assets/mdcompose-demo.gif`: a background thread reads the
123
+ child pty directly (`os.read` on `child.child_fd`, not `child.expect`,
124
+ since only one reader can drain the fd), mirrors every byte to stdout for
125
+ `asciinema` to capture, and answers any `\x1b[6n` it sees with a plausible
126
+ `\x1b[<row>;1R` immediately. That alone is what makes the recording a real
127
+ live checkbox animation instead of a slideshow. The driver script and seed
128
+ snippets were scratch-only (`temp/`), never committed, so this needs
129
+ redoing from scratch next time.
130
+ - **PyPI's `pyversions` badge reads classifiers, not `requires-python`.**
131
+ `pyproject.toml` had only a bare `Programming Language :: Python :: 3`
132
+ classifier, so shields.io rendered the badge as "Python 3" instead of the
133
+ real supported range. Fixed by listing `3.11` through `3.14` individually
134
+ as classifiers, matching the CI matrix. Takes effect only on the next
135
+ release; already-published metadata for a prior version is not
136
+ retroactively fixed.
137
+ - **OpenSSF Best Practices registration is project 14614.**
138
+ `https://www.bestpractices.dev/projects/14614`, homepage set to the GitHub
139
+ repo URL, Passing tier across all six categories. Needed again only if the
140
+ self-assessment has to be revisited (a criterion changes, or silver/gold
141
+ becomes reachable with a second maintainer).
142
+
203
143
  ## Reference docs
204
144
 
205
145
  - [`docs/versioning-explained.md`](docs/versioning-explained.md) - how a number becomes `0.3.2` vs `0.4.0` vs
@@ -233,9 +173,10 @@ free for public or personal-account use:
233
173
  - OpenSSF Scorecard - free
234
174
  - CodeQL - free for public repos
235
175
 
236
- Excluded because metered or paid: Codecov, CodSpeed (CodSpeed has a free OSS
237
- tier and is kept dormant, see below). Coverage is gated in-repo with
238
- `coverage --fail-under` and uploaded as an artifact, never sent to a service.
176
+ Excluded because metered or paid: Codecov. CodSpeed is wired (free OSS tier,
177
+ connected at codspeed.io, see the `benchmarks` job below); Coverage itself is
178
+ gated in-repo with `coverage --fail-under` and uploaded as an artifact, never
179
+ sent to a service.
239
180
 
240
181
  One thing to confirm before wiring: `gitleaks/gitleaks-action` needs a free
241
182
  `GITLEAKS_LICENSE` key **only when the repo owner is a GitHub organisation**. If
@@ -257,7 +198,8 @@ One thing to confirm before wiring: `gitleaks/gitleaks-action` needs a free
257
198
 
258
199
  In the repo now: `ci.yml` (test matrix 3 OS x Python 3.11-3.14, `lint` (ruff +
259
200
  `mypy` + `deptry`), `coverage`, `ascii`, `build`, plus `hooks`, `commits`,
260
- `gitleaks`, `all-green`), `supply-chain.yml` (`licenses`, `audit`, `osv`,
201
+ `gitleaks`, `benchmarks` (CodSpeed, not in `all-green`'s needs), `all-green`),
202
+ `supply-chain.yml` (`licenses`, `audit`, `osv`,
261
203
  `sbom`), `dependency-review.yml` (PR-only, `deny-licenses` = the copyleft
262
204
  families), `release.yml` (`v*` tag), `bump.yml` (the dispatched release button),
263
205
  `mutation.yml` (weekly `mutmut`), `codeql.yml`, `scorecard.yml`,
@@ -303,11 +245,15 @@ tag through.
303
245
 
304
246
  ### Dormant - shipped commented, with an `# ENABLE WHEN:` marker
305
247
 
306
- - `codspeed` job in `ci.yml` - ENABLE WHEN the repo exists and a CodSpeed
307
- project is connected. The benchmark suite is written; only the hosted runner
308
- is missing.
309
248
  - all-contributors config - ENABLE WHEN the first outside contributor lands.
310
249
 
250
+ The `benchmarks` job in `ci.yml` was in this list; it is now uncommented,
251
+ pinned, and connected at codspeed.io (run `34781861376`, commit `4b43aa5`,
252
+ 2026-09-13, uploaded successfully after an earlier run failed with a
253
+ `401 Unauthorized` before the account-side connection existed). It runs on
254
+ every push and PR but is not in `all-green`'s needs list, so it cannot block a
255
+ merge.
256
+
311
257
  (`mutation.yml` is not dormant - it ships active on a weekly cron, non-blocking.)
312
258
 
313
259
  ### Local iteration
@@ -349,19 +295,13 @@ alerts + security updates and private vulnerability reporting. Allow the
349
295
 
350
296
  ### Explicitly not in the pipeline
351
297
 
352
- Codecov / CodSpeed / any metered service (cost); git-cliff / towncrier
298
+ Codecov / any other metered service (cost); git-cliff / towncrier
353
299
  (commitizen writes the changelog); release-please / python-semantic-release
354
300
  (rejected, philosophy C is the model); a triage bot (issue volume); hatch-vcs /
355
301
  `hatch version` / setuptools-scm (no version-derivation layer; `cz bump` writes
356
302
  `version` directly); trufflehog as a hook or job (it is a one-time local
357
303
  pre-publish run only).
358
304
 
359
- ## Open work
360
-
361
- Things that are not behavior, so they carry no core-contract change.
362
-
363
- - [ ] Add an optional funding link via `.github/FUNDING.yml` and a line in the README footer. Deferred at the maintainer's request.
364
-
365
305
  ## Standing decision: competitive landscape
366
306
 
367
307
  Three tools cover the core idea of a personal library of reusable snippets
@@ -394,58 +334,19 @@ but because it is the active brand of an existing company (ContextSmith Inc.,
394
334
  B2B customer-intelligence SaaS, founded 2015, Sunnyvale). Fine for purely
395
335
  personal unpublished use, never for anything released.
396
336
 
397
- ## Repository and release setup (needs the repo)
398
-
399
- Plan and rationale: [`docs/publishing.md`](docs/publishing.md). In-repo artifacts (`release.yml`,
400
- [`CHANGELOG.md`](CHANGELOG.md), `renovate.json`, `codeql.yml`, `scorecard.yml`,
401
- `python-eol.yml`, workflow `permissions`) are all in place; the CI/CD pipeline
402
- section is the reference. What is left is account-side, roughly in order.
403
-
404
- Status, 2026-09-10: every account-side item in this list is done. The repo is
405
- public, the ruleset is active, `RELEASE_TOKEN` and both Actions environments
406
- exist, both trusted publishers are registered, and `pre-commit.ci` is enabled.
407
- The checkboxes below are kept for the record and are not re-ticked here. The
408
- live remaining work is in the `Next` section at the top of this file.
409
-
410
- - [ ] Immediately before making the repo public: run `trufflehog git file://.
411
- --only-verified` over the full local history, confirm clean. One time, local,
412
- never a hook or a job.
413
- - [ ] Create `github.com/Rovetown/mdcompose` and push. Enable private
414
- vulnerability reporting (the CODE_OF_CONDUCT contact points at it), Dependabot
415
- alerts, and Dependabot security updates.
416
- - [ ] Confirm whether `Rovetown` is a personal account or an organisation: an
417
- organisation needs a free `GITLEAKS_LICENSE` key for `gitleaks-action`, a
418
- personal account does not.
419
- - [ ] Branch protection on `main`: require `all-green`, `supply chain / *`, and
420
- `CodeQL`; require one review; require the branch up to date; require linear
421
- history; block force-push. Allow the `bump.yml` token identity to bypass the
422
- push restriction.
423
- - [ ] Create a fine-grained PAT (or GitHub App token) with `contents: write`,
424
- store as the `RELEASE_TOKEN` secret; `bump.yml` uses it so the tag push
425
- triggers `release.yml` and the bump commit clears branch protection.
426
- - [ ] Register a PyPI pending publisher for `mdcompose` (owner `Rovetown`,
427
- repo `mdcompose`, workflow `release.yml`, environment `pypi`), and the same
428
- on TestPyPI.
429
- - [ ] Add GitHub Actions environments `pypi` (required reviewer) and `testpypi`.
430
- - [ ] Enable `pre-commit.ci` on the repo and delete the `hooks` job from
431
- `ci.yml` (run one, not both).
432
-
433
- Post-repo, at the release that earns it:
434
-
435
- - [x] Move the `Development Status` classifier off `3 - Alpha`. Done: moved to
436
- `4 - Beta` (see the `Next` section above for the reasoning).
437
- - [ ] Add a new Python to the CI matrix by hand when its first release
438
- candidate lands (annual, not worth a regex manager).
439
- - [ ] Point `platform.ONEDRIVE_HELP_URL` (`https://mdcompose.dev/onedrive`) at a
440
- real how-to-exclude video before the first release.
441
- - [ ] Wire the CodSpeed `benchmarks` job once the repo is up (it is written,
442
- just commented).
443
- - [ ] Optional: write up the OneDrive warning in [`docs/core-contract.md`](docs/core-contract.md) if the
444
- project keeps that discipline post-v1. Implemented directly for now.
445
-
446
- ## Roadmap (later)
447
-
448
- Everything here is a later, larger effort. The codebase-wide quality pass is
337
+ ## Repository and release setup
338
+
339
+ Done, 2026-09-10. Plan and rationale: [`docs/publishing.md`](docs/publishing.md).
340
+ Repo public, ruleset active, `RELEASE_TOKEN` and both Actions environments
341
+ exist, both trusted publishers registered, `pre-commit.ci` enabled. Nothing
342
+ open here; live remaining work is in `Next` at the top of this file.
343
+
344
+ ## Roadmap (later, deferred)
345
+
346
+ This section is TODO the same as `Next`, just longer-horizon: each item below
347
+ is real backlog, not idle brainstorming, and stays here until it is either
348
+ scoped into an OpenSpec change or explicitly dropped. The codebase-wide
349
+ quality pass is
449
350
  done: strict typing enforced in CI, a ratcheting coverage floor with its own
450
351
  job, a benchmark suite with a recorded baseline, the file-I/O hardening review
451
352
  and parser fuzzing. `platform.py` and the interactive questionary pickers in
@@ -475,6 +376,10 @@ having a real backing plan for, not yet an OpenSpec change.
475
376
  reuses the existing core rather than forking it, the standing bar for
476
377
  everything in this Roadmap section.
477
378
 
379
+ ### Third-party integrations
380
+
381
+ - [ ] Research and, where practical, build integrations that let a user manage their content and snippet library without leaving their usual environment. Candidates are a VS Code extension or a JetBrains plugin; the TUI is tracked separately below. Before building anything new, evaluate whether extending an existing tool in this space is a better use of effort than duplicating config-sync mechanics that are already solved, so the focus stays on the snippet library and composition workflow that is not well covered elsewhere.
382
+
478
383
  ### TUI (terminal user interface)
479
384
 
480
385
  Deferred branch. Not needed to make the CLI read well, which it already does;
@@ -527,10 +432,6 @@ own library one at a time from inside the TUI.
527
432
  - [ ] Revisit once the TUI itself exists, since this is an additional TUI-only
528
433
  caller of the library machinery, not a reason to build the TUI sooner.
529
434
 
530
- ### Third-party integrations
531
-
532
- - [ ] Research and, where practical, build integrations that let a user manage their content and snippet library without leaving their usual environment. Candidates are a VS Code extension or a JetBrains plugin; the TUI is tracked separately above. Before building anything new, evaluate whether extending an existing tool in this space is a better use of effort than duplicating config-sync mechanics that are already solved, so the focus stays on the snippet library and composition workflow that is not well covered elsewhere.
533
-
534
435
  ### Additional language implementations
535
436
 
536
437
  - [ ] Once v1 behavior is stable and [`docs/core-contract.md`](docs/core-contract.md) is complete, port the CLI to one or more additional languages. Node.js/TypeScript, Go and Rust are the likely candidates. Broader than the zero-install binary below: this is about giving people who prefer not to touch Python a native option, not only about removing a runtime dependency. The contract exists so a port reimplements a specification rather than translating Python.
@@ -539,10 +440,6 @@ own library one at a time from inside the TUI.
539
440
 
540
441
  - [ ] Once behavior and file formats are stable, evaluate a zero-install path so end users need no Python. Candidates are PyInstaller or Nuitka, both trading binary size for no runtime dependency. If a port lands in Go or Rust, prefer shipping the binary from that instead: smaller static binaries, better cross-compilation, and a natural fit for Homebrew, Scoop, WinGet or an install script.
541
442
 
542
- ### Final naming verification before public release
543
-
544
- - [ ] `mdcompose` was checked by search only. Before claiming it, run the exact checks: confirm `https://pypi.org/project/mdcompose/` returns 404, confirm `npm view mdcompose` errors, and register the GitHub repository before someone else does.
545
-
546
443
  ### Competitive landscape follow-up
547
444
 
548
445
  - [ ] Periodically revisit how the tools listed above evolve, to confirm this project's differentiation stays genuine rather than duplicated.
@@ -39,6 +39,28 @@ No substitution syntax is reserved either, deliberately -- a snippet containing
39
39
  The library itself is a flat directory of these files -- no index, no cache, no database.
40
40
  mdcompose never writes anything else into it and never touches an entry it did not create (a `.git` directory, a stray subdirectory, a file with another extension), which is what lets you keep the library under version control without the tool fighting you.
41
41
 
42
+ ## Skill frontmatter
43
+
44
+ A skill is a second library, entirely separate from the snippet library, in the same format: one markdown file per skill, optional YAML frontmatter then body, filename minus `.md` as the id.
45
+ Configured the same way too -- `skill_library_path` in the global config, defaulting to a `skills` sibling of the snippet library's own default directory.
46
+
47
+ | Field | Type | Meaning |
48
+ | ----- | ---- | ------- |
49
+ | `title` | string | shown to a person; the id is used when absent |
50
+ | `description` | string | one line of help, and what becomes the composed file's frontmatter description |
51
+ | `tags` | list of strings | for filtering |
52
+ | `stack_signals` | list of strings | filenames suggesting relevance |
53
+ | `category` | string | a grouping label in the picker |
54
+
55
+ There is no `applies_to` and no `order`: a skill is never split between AGENTS.md and CLAUDE.md the way a snippet can be, and its body is never concatenated with another skill's the way several snippet bodies share one block, so neither field has anything to do.
56
+
57
+ `init` offers the skill library as a second, independently optional checkbox picker alongside the snippet picker.
58
+ Each selected skill becomes its own file at `.claude/skills/<id>/SKILL.md`: generated `name`/`description` frontmatter first, then a managed block holding the skill's body, hashed and drift-checked the same way AGENTS.md's block is.
59
+ The frontmatter is regenerated from the library on every run, even when only the description changed and the block's content did not -- unlike a managed block's surroundings, it is not content mdcompose leaves alone, since Claude Code needs it to track the library, not a stale copy.
60
+
61
+ Deselecting a skill deletes its file outright, and its now-empty `.claude/skills/<id>/` directory with it, rather than leaving an empty block the way AGENTS.md would: an empty `SKILL.md` means nothing to a skill-loading agent.
62
+ An empty or unconfigured skill library never blocks `init`, and the reverse holds too -- an empty snippet library does not stop skills from composing.
63
+
42
64
  ## Drift detection: normalization and hashing
43
65
 
44
66
  A managed block's hash has to mean the same thing on Windows, under WSL, and on Linux, or a committed lock file could never be trusted across a team.
@@ -129,17 +151,14 @@ None of them do these, which is the actual reason to reach for mdcompose instead
129
151
  Composing from a repository already means trusting its authors the way you trust their build scripts; mdcompose adds no second channel on top of that.
130
152
  - **Every interactive prompt has a flag.** Nothing here requires a human at a keyboard: `init --reapply`, `import --save-as-snippet`, and their siblings mean every command that can prompt can also run unattended, with the exit code contract above making the result scriptable in CI.
131
153
 
132
- One more, planned rather than shipped: none of the three alternatives above appear to be aiming past agent *instructions*.
133
- A skill file (Claude Code Skills, or whatever equivalent another tool adds) is already snippet-shaped -- frontmatter, a body, sometimes a script alongside it -- so composing those the same way is a natural extension of the same idea, not a new mechanism.
134
- It is flagged here rather than claimed as a feature; see Planned below.
154
+ One more: none of the three alternatives above appear to be aiming past agent *instructions*.
155
+ A skill file (Claude Code Skills, or whatever equivalent another tool adds) is snippet-shaped -- frontmatter, then a body -- so mdcompose composes those the same way it composes AGENTS.md and CLAUDE.md, as a second, independent library; see Skill frontmatter above.
156
+ A skill bundled with an accompanying script is not yet supported, since the library and manifest model here is single-file-per-item throughout.
135
157
 
136
158
  ## Planned
137
159
 
138
160
  Larger efforts, tracked in [TODO.md](../TODO.md) under Roadmap, not needed for `0.1.0` and not yet built:
139
161
 
140
- - **Composing agent skills, not just AGENTS.md and CLAUDE.md.** A skill file (Claude Code Skills, or an equivalent another tool adds) is already snippet-shaped, so extending composition to skills would let a personal library of them be curated per project instead of an agent loading every skill unconditionally.
141
- Fewer unneeded skills loaded is context an agent never has to spend, which makes this a token-efficiency argument, not only an organizational one.
142
- Not scoped: a second composed-file family has to prove it reuses the existing core rather than forking it, the same bar every item below has to clear.
143
162
  - **A TUI**, likely on Textual, as a second adapter over the same core: browse and filter the library, tick snippets for a project, see per-file drift, review a diff before writing.
144
163
  The CLI stays the interface that has to work unattended -- exit codes, the stdout/stderr split, `--json`, `--quiet` are CLI concepts with no TUI equivalent, so the TUI is additive, never the only way to do something.
145
164
  - **A shared, browsable community snippet library, gated behind the TUI.** Concept borrowed from the skill marketplaces appearing around Claude Code Skills: a public GitHub repository as the source, crawled and indexed through GitHub's API into a locally cached, fuzzy-searchable list of community-contributed snippets, browsed and pulled into your own library one at a time from inside the TUI.