mkdocs2confluence 0.12.2__tar.gz → 0.13.1__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 (100) hide show
  1. {mkdocs2confluence-0.12.2/src/mkdocs2confluence.egg-info → mkdocs2confluence-0.13.1}/PKG-INFO +20 -2
  2. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/README.md +19 -1
  3. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/pyproject.toml +4 -1
  4. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1/src/mkdocs2confluence.egg-info}/PKG-INFO +20 -2
  5. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs2confluence.egg-info/SOURCES.txt +6 -0
  6. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/cli.py +68 -3
  7. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/loader/config.py +18 -0
  8. mkdocs2confluence-0.13.1/src/mkdocs_to_confluence/publisher/changelog.py +131 -0
  9. mkdocs2confluence-0.13.1/src/mkdocs_to_confluence/skill_installer.py +99 -0
  10. mkdocs2confluence-0.13.1/src/mkdocs_to_confluence/skills/mkdocs-changelog/SKILL.md +78 -0
  11. mkdocs2confluence-0.13.1/tests/test_changelog_config.py +62 -0
  12. mkdocs2confluence-0.13.1/tests/test_changelog_publish.py +194 -0
  13. mkdocs2confluence-0.13.1/tests/test_skill_installer.py +126 -0
  14. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/LICENSE +0 -0
  15. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/setup.cfg +0 -0
  16. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
  17. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
  18. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs2confluence.egg-info/requires.txt +0 -0
  19. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
  20. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/__init__.py +0 -0
  21. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/compiler/__init__.py +0 -0
  22. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/compiler/models.py +0 -0
  23. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/compiler/page.py +0 -0
  24. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
  25. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/emitter/xhtml.py +0 -0
  26. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/ir/__init__.py +0 -0
  27. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/ir/document.py +0 -0
  28. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/ir/nodes.py +0 -0
  29. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
  30. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
  31. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
  32. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/loader/nav.py +0 -0
  33. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/loader/page.py +0 -0
  34. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/parser/__init__.py +0 -0
  35. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/parser/markdown.py +0 -0
  36. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/pdf/__init__.py +0 -0
  37. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/pdf/generator.py +0 -0
  38. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/pdf/render.py +0 -0
  39. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
  40. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
  41. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
  42. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/preprocess/frontmatter.py +0 -0
  43. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
  44. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
  45. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
  46. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
  47. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/preview/render.py +0 -0
  48. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/preview/server.py +0 -0
  49. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
  50. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/publisher/client.py +0 -0
  51. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/publisher/executor.py +0 -0
  52. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/publisher/http_retry.py +0 -0
  53. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/publisher/models.py +0 -0
  54. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/publisher/pipeline.py +0 -0
  55. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/publisher/planner.py +0 -0
  56. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/sync/__init__.py +0 -0
  57. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/sync/anchoring.py +0 -0
  58. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/sync/command.py +0 -0
  59. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/sync/comments.py +0 -0
  60. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/sync/github.py +0 -0
  61. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/sync/platform.py +0 -0
  62. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/sync/state.py +0 -0
  63. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
  64. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/transforms/abbrevs.py +0 -0
  65. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
  66. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
  67. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/transforms/footer.py +0 -0
  68. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/transforms/images.py +0 -0
  69. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
  70. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/src/mkdocs_to_confluence/transforms/mermaid.py +0 -0
  71. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_abbrevs.py +0 -0
  72. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_children_macro.py +0 -0
  73. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_cli.py +0 -0
  74. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_editlink.py +0 -0
  75. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_emitter.py +0 -0
  76. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_extra_css.py +0 -0
  77. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_footer.py +0 -0
  78. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_frontmatter.py +0 -0
  79. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_icons.py +0 -0
  80. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_images.py +0 -0
  81. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_internallinks.py +0 -0
  82. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_ir.py +0 -0
  83. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_linkdefs.py +0 -0
  84. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_loader.py +0 -0
  85. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_mermaid.py +0 -0
  86. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_page_loader.py +0 -0
  87. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_parser.py +0 -0
  88. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_pdf.py +0 -0
  89. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_preprocess.py +0 -0
  90. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_preview.py +0 -0
  91. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_publish_client.py +0 -0
  92. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_publish_config.py +0 -0
  93. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_publish_pipeline.py +0 -0
  94. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_server.py +0 -0
  95. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_sync_anchoring.py +0 -0
  96. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_sync_command.py +0 -0
  97. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_sync_comments.py +0 -0
  98. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_sync_github.py +0 -0
  99. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_sync_state.py +0 -0
  100. {mkdocs2confluence-0.12.2 → mkdocs2confluence-0.13.1}/tests/test_treeutil.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs2confluence
3
- Version: 0.12.2
3
+ Version: 0.13.1
4
4
  Summary: Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more
5
5
  Author: Anders Hybertz
6
6
  License: GPL-3.0-or-later
@@ -153,10 +153,28 @@ confluence:
153
153
  parent_page_id: "123456" # optional root page
154
154
  mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none"
155
155
  full_width: true # default: true
156
+ changelog: CHANGELOG.md # optional: publish as a top-level "What's New" page
156
157
  ```
157
158
 
158
159
  The `confluence:` block is also accepted under `extra:` for MkDocs strict-mode compatibility. The API token is read from `token:` in `mkdocs.yml`, then `CONFLUENCE_API_TOKEN`, then `MK2CONF_TOKEN`.
159
160
 
161
+ ### Changelog / What's New page
162
+
163
+ Set `changelog:` to a Markdown file path (relative to `docs_dir`) to have mk2conf publish it as a permanent top-level page on every full `mk2conf publish` run. The page title comes from YAML front matter `title:`; it defaults to `"What's New"` if absent.
164
+
165
+ ```yaml
166
+ confluence:
167
+ changelog: CHANGELOG.md # relative to docs_dir
168
+ ```
169
+
170
+ - The page does **not** need to appear in `nav:` — it is always placed at the top level of the space (or under `parent_page_id` if set).
171
+ - If it also appears in `nav:`, it is published once; no duplication.
172
+ - `--prune` never deletes it — it is a pinned page, not a nav-derived page.
173
+ - Partial runs (`--page` / `--section`) skip the changelog page, consistent with other publish behaviour.
174
+ - Omit the key, or set it to an empty string, to disable the feature entirely.
175
+
176
+ Run `mk2conf install-skill` once after setting `changelog:` to install the changelog AI skill into your AI tool (Claude Code, Copilot, Cursor, Hermes). The skill analyses git changes to your docs since the last `CHANGELOG.md` commit and drafts an entry when the changes qualify as significant.
177
+
160
178
  **Your first publish:**
161
179
 
162
180
  ```bash
@@ -172,7 +190,7 @@ mk2conf publish # go live
172
190
 
173
191
  | | |
174
192
  |---|---|
175
- | [docs/commands.md](docs/commands.md) | Full flag reference for all four commands |
193
+ | [docs/commands.md](docs/commands.md) | Full flag reference for all five commands |
176
194
  | [docs/features.md](docs/features.md) | Supported Markdown / Material features and known limitations |
177
195
  | [Setup.md](Setup.md) | Development environment setup |
178
196
 
@@ -111,10 +111,28 @@ confluence:
111
111
  parent_page_id: "123456" # optional root page
112
112
  mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none"
113
113
  full_width: true # default: true
114
+ changelog: CHANGELOG.md # optional: publish as a top-level "What's New" page
114
115
  ```
115
116
 
116
117
  The `confluence:` block is also accepted under `extra:` for MkDocs strict-mode compatibility. The API token is read from `token:` in `mkdocs.yml`, then `CONFLUENCE_API_TOKEN`, then `MK2CONF_TOKEN`.
117
118
 
119
+ ### Changelog / What's New page
120
+
121
+ Set `changelog:` to a Markdown file path (relative to `docs_dir`) to have mk2conf publish it as a permanent top-level page on every full `mk2conf publish` run. The page title comes from YAML front matter `title:`; it defaults to `"What's New"` if absent.
122
+
123
+ ```yaml
124
+ confluence:
125
+ changelog: CHANGELOG.md # relative to docs_dir
126
+ ```
127
+
128
+ - The page does **not** need to appear in `nav:` — it is always placed at the top level of the space (or under `parent_page_id` if set).
129
+ - If it also appears in `nav:`, it is published once; no duplication.
130
+ - `--prune` never deletes it — it is a pinned page, not a nav-derived page.
131
+ - Partial runs (`--page` / `--section`) skip the changelog page, consistent with other publish behaviour.
132
+ - Omit the key, or set it to an empty string, to disable the feature entirely.
133
+
134
+ Run `mk2conf install-skill` once after setting `changelog:` to install the changelog AI skill into your AI tool (Claude Code, Copilot, Cursor, Hermes). The skill analyses git changes to your docs since the last `CHANGELOG.md` commit and drafts an entry when the changes qualify as significant.
135
+
118
136
  **Your first publish:**
119
137
 
120
138
  ```bash
@@ -130,7 +148,7 @@ mk2conf publish # go live
130
148
 
131
149
  | | |
132
150
  |---|---|
133
- | [docs/commands.md](docs/commands.md) | Full flag reference for all four commands |
151
+ | [docs/commands.md](docs/commands.md) | Full flag reference for all five commands |
134
152
  | [docs/features.md](docs/features.md) | Supported Markdown / Material features and known limitations |
135
153
  | [Setup.md](Setup.md) | Development environment setup |
136
154
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mkdocs2confluence"
3
- version = "0.12.2"
3
+ version = "0.13.1"
4
4
  description = "Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more"
5
5
  readme = "README.md"
6
6
  license = { text = "GPL-3.0-or-later" }
@@ -67,6 +67,9 @@ build-backend = "setuptools.build_meta"
67
67
  [tool.setuptools.packages.find]
68
68
  where = ["src"]
69
69
 
70
+ [tool.setuptools.package-data]
71
+ "mkdocs_to_confluence" = ["skills/**/*.md"]
72
+
70
73
  [tool.ruff]
71
74
  line-length = 120
72
75
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs2confluence
3
- Version: 0.12.2
3
+ Version: 0.13.1
4
4
  Summary: Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more
5
5
  Author: Anders Hybertz
6
6
  License: GPL-3.0-or-later
@@ -153,10 +153,28 @@ confluence:
153
153
  parent_page_id: "123456" # optional root page
154
154
  mermaid_render: kroki # "kroki" (default) | "kroki:https://your-kroki" | "none"
155
155
  full_width: true # default: true
156
+ changelog: CHANGELOG.md # optional: publish as a top-level "What's New" page
156
157
  ```
157
158
 
158
159
  The `confluence:` block is also accepted under `extra:` for MkDocs strict-mode compatibility. The API token is read from `token:` in `mkdocs.yml`, then `CONFLUENCE_API_TOKEN`, then `MK2CONF_TOKEN`.
159
160
 
161
+ ### Changelog / What's New page
162
+
163
+ Set `changelog:` to a Markdown file path (relative to `docs_dir`) to have mk2conf publish it as a permanent top-level page on every full `mk2conf publish` run. The page title comes from YAML front matter `title:`; it defaults to `"What's New"` if absent.
164
+
165
+ ```yaml
166
+ confluence:
167
+ changelog: CHANGELOG.md # relative to docs_dir
168
+ ```
169
+
170
+ - The page does **not** need to appear in `nav:` — it is always placed at the top level of the space (or under `parent_page_id` if set).
171
+ - If it also appears in `nav:`, it is published once; no duplication.
172
+ - `--prune` never deletes it — it is a pinned page, not a nav-derived page.
173
+ - Partial runs (`--page` / `--section`) skip the changelog page, consistent with other publish behaviour.
174
+ - Omit the key, or set it to an empty string, to disable the feature entirely.
175
+
176
+ Run `mk2conf install-skill` once after setting `changelog:` to install the changelog AI skill into your AI tool (Claude Code, Copilot, Cursor, Hermes). The skill analyses git changes to your docs since the last `CHANGELOG.md` commit and drafts an entry when the changes qualify as significant.
177
+
160
178
  **Your first publish:**
161
179
 
162
180
  ```bash
@@ -172,7 +190,7 @@ mk2conf publish # go live
172
190
 
173
191
  | | |
174
192
  |---|---|
175
- | [docs/commands.md](docs/commands.md) | Full flag reference for all four commands |
193
+ | [docs/commands.md](docs/commands.md) | Full flag reference for all five commands |
176
194
  | [docs/features.md](docs/features.md) | Supported Markdown / Material features and known limitations |
177
195
  | [Setup.md](Setup.md) | Development environment setup |
178
196
 
@@ -9,6 +9,7 @@ src/mkdocs2confluence.egg-info/requires.txt
9
9
  src/mkdocs2confluence.egg-info/top_level.txt
10
10
  src/mkdocs_to_confluence/__init__.py
11
11
  src/mkdocs_to_confluence/cli.py
12
+ src/mkdocs_to_confluence/skill_installer.py
12
13
  src/mkdocs_to_confluence/compiler/__init__.py
13
14
  src/mkdocs_to_confluence/compiler/models.py
14
15
  src/mkdocs_to_confluence/compiler/page.py
@@ -39,12 +40,14 @@ src/mkdocs_to_confluence/preview/__init__.py
39
40
  src/mkdocs_to_confluence/preview/render.py
40
41
  src/mkdocs_to_confluence/preview/server.py
41
42
  src/mkdocs_to_confluence/publisher/__init__.py
43
+ src/mkdocs_to_confluence/publisher/changelog.py
42
44
  src/mkdocs_to_confluence/publisher/client.py
43
45
  src/mkdocs_to_confluence/publisher/executor.py
44
46
  src/mkdocs_to_confluence/publisher/http_retry.py
45
47
  src/mkdocs_to_confluence/publisher/models.py
46
48
  src/mkdocs_to_confluence/publisher/pipeline.py
47
49
  src/mkdocs_to_confluence/publisher/planner.py
50
+ src/mkdocs_to_confluence/skills/mkdocs-changelog/SKILL.md
48
51
  src/mkdocs_to_confluence/sync/__init__.py
49
52
  src/mkdocs_to_confluence/sync/anchoring.py
50
53
  src/mkdocs_to_confluence/sync/command.py
@@ -61,6 +64,8 @@ src/mkdocs_to_confluence/transforms/images.py
61
64
  src/mkdocs_to_confluence/transforms/internallinks.py
62
65
  src/mkdocs_to_confluence/transforms/mermaid.py
63
66
  tests/test_abbrevs.py
67
+ tests/test_changelog_config.py
68
+ tests/test_changelog_publish.py
64
69
  tests/test_children_macro.py
65
70
  tests/test_cli.py
66
71
  tests/test_editlink.py
@@ -84,6 +89,7 @@ tests/test_publish_client.py
84
89
  tests/test_publish_config.py
85
90
  tests/test_publish_pipeline.py
86
91
  tests/test_server.py
92
+ tests/test_skill_installer.py
87
93
  tests/test_sync_anchoring.py
88
94
  tests/test_sync_command.py
89
95
  tests/test_sync_comments.py
@@ -265,6 +265,33 @@ def _build_parser() -> argparse.ArgumentParser:
265
265
  help="Suppress progress output.",
266
266
  )
267
267
 
268
+ # --- install-skill ---
269
+ is_ = sub.add_parser(
270
+ "install-skill",
271
+ help="Install the mkdocs-changelog AI skill into detected tool directories.",
272
+ formatter_class=argparse.RawDescriptionHelpFormatter,
273
+ epilog=(
274
+ "Detected targets (all installed when no --tool given):\n"
275
+ " hermes ~/.hermes/skills/tooling/mkdocs-changelog/SKILL.md\n"
276
+ " github-skills .github/skills/tooling/mkdocs-changelog/SKILL.md\n"
277
+ " claude .claude/commands/changelog.md (frontmatter stripped)\n"
278
+ " copilot .github/instructions/mk2conf-changelog.instructions.md\n"
279
+ " cursor .cursor/rules/mk2conf-changelog.mdc\n"
280
+ "\n"
281
+ "Examples:\n"
282
+ " mk2conf install-skill\n"
283
+ " mk2conf install-skill --tool claude\n"
284
+ " mk2conf install-skill --tool hermes\n"
285
+ ),
286
+ )
287
+ is_.add_argument(
288
+ "--tool",
289
+ metavar="NAME",
290
+ default=None,
291
+ choices=["hermes", "github-skills", "claude", "copilot", "cursor"],
292
+ help="Install only to a specific tool (hermes, github-skills, claude, copilot, cursor).",
293
+ )
294
+
268
295
  return parser
269
296
 
270
297
 
@@ -288,6 +315,8 @@ def main(argv: list[str] | None = None) -> None:
288
315
  _cmd_pdf(args)
289
316
  elif args.command == "sync-comments":
290
317
  _cmd_sync_comments(args)
318
+ elif args.command == "install-skill":
319
+ _cmd_install_skill(args)
291
320
  except (ValueError, FileNotFoundError) as exc:
292
321
  print(f"error: {exc}", file=sys.stderr)
293
322
  sys.exit(1)
@@ -499,6 +528,8 @@ def _cmd_publish(args: argparse.Namespace) -> None:
499
528
  sys.exit(1)
500
529
  nav_nodes = [node]
501
530
 
531
+ partial = bool(getattr(args, "page", None) or getattr(args, "section", None))
532
+
502
533
  if args.dry_run:
503
534
  # When a section is given, show what node was matched so the user can
504
535
  # verify the section resolved correctly (section vs. leaf page).
@@ -513,8 +544,14 @@ def _cmd_publish(args: argparse.Namespace) -> None:
513
544
  print(f"Dry run: would publish {len(pages)} page(s) to {conf_config.base_url}")
514
545
  for page in pages:
515
546
  print(f" {page.docs_path} → '{page.title}'")
547
+ if conf_config.changelog_file and not partial:
548
+ from mkdocs_to_confluence.publisher.changelog import _extract_title
549
+ cl_path = config.docs_dir / conf_config.changelog_file
550
+ cl_title = _extract_title(cl_path) or "What's New"
551
+ print(f" {conf_config.changelog_file} → '{cl_title}' (changelog, top-level)")
516
552
  return
517
553
 
554
+ from mkdocs_to_confluence.publisher.changelog import publish_changelog
518
555
  from mkdocs_to_confluence.publisher.client import ConfluenceClient, ConfluenceError
519
556
  from mkdocs_to_confluence.publisher.pipeline import execute_publish, plan_publish
520
557
 
@@ -535,9 +572,6 @@ def _cmd_publish(args: argparse.Namespace) -> None:
535
572
  nav_nodes, client, config, conf_config,
536
573
  space_id=space_id, quiet=args.quiet, full_nav_nodes=all_nav_nodes,
537
574
  )
538
- # --prune is silently disabled for partial publishes (--page / --section)
539
- # because published_ids would only cover the subset, not the full nav.
540
- partial = bool(getattr(args, "page", None) or getattr(args, "section", None))
541
575
  report = execute_publish(
542
576
  plan, client, dry_run=False, space_id=space_id,
543
577
  space_key=conf_config.space_key,
@@ -546,6 +580,13 @@ def _cmd_publish(args: argparse.Namespace) -> None:
546
580
  prune=getattr(args, "prune", False) and not partial,
547
581
  quiet=args.quiet,
548
582
  )
583
+ # Changelog is a pinned top-level page — always publish on full runs,
584
+ # skip on partial runs (--page / --section) like all other publish behaviour.
585
+ if not partial:
586
+ publish_changelog(
587
+ config, conf_config, client, space_id,
588
+ space_key=conf_config.space_key, quiet=args.quiet,
589
+ )
549
590
  except ConfluenceError as exc:
550
591
  print(f"error: {exc}", file=sys.stderr)
551
592
  sys.exit(1)
@@ -599,6 +640,30 @@ def _cmd_publish(args: argparse.Namespace) -> None:
599
640
  sys.exit(1)
600
641
 
601
642
 
643
+ def _cmd_install_skill(args: argparse.Namespace) -> None:
644
+ from mkdocs_to_confluence.skill_installer import install_skill
645
+
646
+ installed = install_skill(tool=getattr(args, "tool", None))
647
+
648
+ if not installed:
649
+ print(
650
+ "No AI tool directories detected and no --tool specified.\n"
651
+ "Run with --tool to install to a specific tool, e.g.:\n"
652
+ " mk2conf install-skill --tool claude",
653
+ file=sys.stderr,
654
+ )
655
+ sys.exit(1)
656
+
657
+ for tool_name, dest in installed:
658
+ print(f" installed [{tool_name}] {dest}")
659
+
660
+ if any(name == "fallback" for name, _ in installed):
661
+ print(
662
+ "\nNo AI tool directories were detected. Skill written to .mk2conf/changelog-skill.md.\n"
663
+ "Copy its contents to your AI tool's custom instructions or skill directory.",
664
+ )
665
+
666
+
602
667
  def _cmd_pdf(args: argparse.Namespace) -> None:
603
668
  # On macOS with uv/non-system Python, Homebrew libs are not on the dyld search
604
669
  # path. Re-exec once with DYLD_LIBRARY_PATH set — the sentinel prevents loops.
@@ -34,6 +34,7 @@ class ConfluenceConfig:
34
34
  github_token: str | None = None # GitHub PAT (falls back to GITHUB_TOKEN env var)
35
35
  github_base_branch: str = "main" # base branch for review PRs
36
36
  allow_any_host: bool = False # set True to allow non-Atlassian Cloud base_url hosts
37
+ changelog_file: str | None = None # path relative to docs_dir; None means disabled
37
38
 
38
39
 
39
40
  @dataclass(frozen=True)
@@ -259,6 +260,22 @@ def load_config(mkdocs_yml: Path) -> MkDocsConfig:
259
260
  if not token:
260
261
  token = os.environ.get("MK2CONF_TOKEN", "")
261
262
 
263
+ # changelog (optional) — path relative to docs_dir
264
+ changelog_file: str | None = None
265
+ raw_changelog = raw_conf.get("changelog")
266
+ if raw_changelog is not None:
267
+ cl_str = str(raw_changelog).strip()
268
+ if cl_str:
269
+ candidate = (docs_dir / cl_str).resolve()
270
+ try:
271
+ candidate.relative_to(docs_dir)
272
+ except ValueError:
273
+ raise ConfigError(
274
+ f"mkdocs.yml: 'confluence.changelog' path {cl_str!r} "
275
+ "escapes docs_dir. The path must be relative to the docs directory."
276
+ )
277
+ changelog_file = cl_str
278
+
262
279
  confluence = ConfluenceConfig(
263
280
  base_url=base_url.rstrip("/"),
264
281
  space_key=space_key,
@@ -273,6 +290,7 @@ def load_config(mkdocs_yml: Path) -> MkDocsConfig:
273
290
  else os.environ.get("GITHUB_TOKEN") or None),
274
291
  github_base_branch=str(raw_conf.get("github_base_branch", "main")),
275
292
  allow_any_host=allow_any_host,
293
+ changelog_file=changelog_file,
276
294
  )
277
295
 
278
296
  # --- extra_css (optional) ---
@@ -0,0 +1,131 @@
1
+ """Compile and publish the standalone changelog page."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING
9
+
10
+ import yaml
11
+
12
+ from mkdocs_to_confluence.loader.nav import NavNode
13
+ from mkdocs_to_confluence.publisher.executor import _upload_assets
14
+ from mkdocs_to_confluence.publisher.planner import _xhtml_hash, compile_page
15
+
16
+ if TYPE_CHECKING:
17
+ from mkdocs_to_confluence.loader.config import ConfluenceConfig, MkDocsConfig
18
+ from mkdocs_to_confluence.publisher.client import ConfluenceClient
19
+
20
+ _FRONT_MATTER_RE = re.compile(r"\A---\s*\n(.*?\n?)---\s*\n?", re.DOTALL)
21
+
22
+
23
+ def _extract_title(source_path: Path) -> str | None:
24
+ """Return the ``title`` value from YAML front matter, or ``None`` if absent."""
25
+ try:
26
+ raw = source_path.read_text(encoding="utf-8")
27
+ except OSError:
28
+ return None
29
+ m = _FRONT_MATTER_RE.match(raw)
30
+ if not m:
31
+ return None
32
+ try:
33
+ fm: object = yaml.safe_load(m.group(1))
34
+ except yaml.YAMLError:
35
+ return None
36
+ if not isinstance(fm, dict):
37
+ return None
38
+ val = fm.get("title")
39
+ return str(val).strip() if val else None
40
+
41
+
42
+ def publish_changelog(
43
+ config: MkDocsConfig,
44
+ conf_config: ConfluenceConfig,
45
+ client: ConfluenceClient,
46
+ space_id: str,
47
+ *,
48
+ space_key: str | None = None,
49
+ quiet: bool = False,
50
+ ) -> None:
51
+ """Compile and publish the changelog page if ``conf_config.changelog_file`` is set."""
52
+ if not conf_config.changelog_file:
53
+ return
54
+
55
+ changelog_path = config.docs_dir / conf_config.changelog_file
56
+ if not changelog_path.exists():
57
+ print(
58
+ f" [warn] changelog: file not found: {changelog_path}",
59
+ file=sys.stderr,
60
+ )
61
+ return
62
+
63
+ title = _extract_title(changelog_path) or "What's New"
64
+
65
+ node = NavNode(
66
+ title=title,
67
+ docs_path=str(changelog_path.relative_to(config.docs_dir)),
68
+ source_path=changelog_path,
69
+ level=0,
70
+ )
71
+
72
+ if not quiet:
73
+ print(f" compiling '{title}' (changelog)")
74
+
75
+ xhtml, attachments, labels, confluence_status, version_message = compile_page(
76
+ node, config, quiet=quiet
77
+ )
78
+
79
+ xhtml_hash = _xhtml_hash(xhtml)
80
+ existing = client.find_page(space_id, title)
81
+
82
+ if existing is not None and client.get_content_hash(str(existing["id"])) == xhtml_hash:
83
+ if not quiet:
84
+ print(f" unchanged '{title}' (changelog)")
85
+ return
86
+
87
+ parent_id = conf_config.parent_page_id
88
+
89
+ if existing is None:
90
+ page = client.create_page(space_id, title, xhtml, parent_id=parent_id)
91
+ page_id = str(page["id"])
92
+ # Do NOT stamp as managed: _prune_orphans skips unmanaged pages, so
93
+ # this ensures --prune never deletes the changelog page.
94
+ if not quiet:
95
+ print(f" created '{title}' (changelog)")
96
+ else:
97
+ page_id = str(existing["id"])
98
+ version: int = existing["version"]["number"]
99
+ client.update_page(
100
+ page_id, title, xhtml, version + 1,
101
+ parent_id=parent_id,
102
+ version_message=version_message,
103
+ )
104
+ if not quiet:
105
+ print(f" updated '{title}' (changelog)")
106
+
107
+ if attachments:
108
+ _upload_assets(page_id, attachments, config.docs_dir, client, quiet=quiet)
109
+
110
+ try:
111
+ client.set_content_hash(page_id, xhtml_hash)
112
+ except Exception:
113
+ pass
114
+
115
+ if labels:
116
+ try:
117
+ client.set_page_labels(page_id, labels)
118
+ except Exception:
119
+ pass
120
+
121
+ if conf_config.full_width:
122
+ try:
123
+ client.set_page_full_width(page_id)
124
+ except Exception:
125
+ pass
126
+
127
+ if confluence_status:
128
+ try:
129
+ client.set_page_status(page_id, confluence_status, space_key=space_key)
130
+ except Exception:
131
+ pass
@@ -0,0 +1,99 @@
1
+ """Install the bundled mkdocs-changelog skill into detected AI tool directories."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+
8
+ _FRONT_MATTER_RE = re.compile(r"\A---\s*\n.*?\n---\s*\n?", re.DOTALL)
9
+ _SKILL_NAME = "mkdocs-changelog"
10
+
11
+
12
+ def _read_skill() -> str:
13
+ from importlib.resources import files
14
+ return files("mkdocs_to_confluence").joinpath(f"skills/{_SKILL_NAME}/SKILL.md").read_text(encoding="utf-8")
15
+
16
+
17
+ def _strip_front_matter(content: str) -> str:
18
+ return _FRONT_MATTER_RE.sub("", content).lstrip("\n")
19
+
20
+
21
+ def install_skill(
22
+ project_dir: Path | None = None,
23
+ tool: str | None = None,
24
+ ) -> list[tuple[str, Path]]:
25
+ """Detect AI tools and install the changelog skill.
26
+
27
+ Args:
28
+ project_dir: Root of the current project (default: ``Path.cwd()``).
29
+ tool: If given, install only to this tool name (``hermes``, ``claude``,
30
+ ``copilot``, ``cursor``). Skips detection and always writes.
31
+
32
+ Returns:
33
+ List of ``(tool_name, install_path)`` pairs for each file written.
34
+ """
35
+ if project_dir is None:
36
+ project_dir = Path.cwd()
37
+
38
+ content_full = _read_skill()
39
+ content_body = _strip_front_matter(content_full)
40
+
41
+ installed: list[tuple[str, Path]] = []
42
+ explicit = tool is not None
43
+
44
+ def _want(name: str) -> bool:
45
+ return tool is None or tool == name
46
+
47
+ # Hermes — user-level, installed regardless of project directory
48
+ if _want("hermes"):
49
+ hermes_dir = Path.home() / ".hermes"
50
+ if explicit or hermes_dir.exists():
51
+ dest = hermes_dir / "skills" / "tooling" / _SKILL_NAME / "SKILL.md"
52
+ dest.parent.mkdir(parents=True, exist_ok=True)
53
+ dest.write_text(content_full, encoding="utf-8")
54
+ installed.append(("hermes", dest))
55
+
56
+ # .github/skills — project-level, skills repo format (full SKILL.md)
57
+ if _want("github-skills"):
58
+ gh_skills = project_dir / ".github" / "skills"
59
+ if explicit or gh_skills.exists():
60
+ dest = gh_skills / "tooling" / _SKILL_NAME / "SKILL.md"
61
+ dest.parent.mkdir(parents=True, exist_ok=True)
62
+ dest.write_text(content_full, encoding="utf-8")
63
+ installed.append(("github-skills", dest))
64
+
65
+ # Claude Code — project-level, YAML frontmatter stripped
66
+ if _want("claude"):
67
+ claude_dir = project_dir / ".claude"
68
+ if explicit or claude_dir.exists():
69
+ dest = claude_dir / "commands" / "changelog.md"
70
+ dest.parent.mkdir(parents=True, exist_ok=True)
71
+ dest.write_text(content_body, encoding="utf-8")
72
+ installed.append(("claude", dest))
73
+
74
+ # GitHub Copilot — project-level, body only as .instructions.md
75
+ if _want("copilot"):
76
+ copilot_marker = project_dir / ".github" / "copilot-instructions.md"
77
+ if explicit or copilot_marker.exists():
78
+ dest = project_dir / ".github" / "instructions" / "mk2conf-changelog.instructions.md"
79
+ dest.parent.mkdir(parents=True, exist_ok=True)
80
+ dest.write_text(content_body, encoding="utf-8")
81
+ installed.append(("copilot", dest))
82
+
83
+ # Cursor — project-level, body as .mdc
84
+ if _want("cursor"):
85
+ cursor_dir = project_dir / ".cursor"
86
+ if explicit or cursor_dir.exists():
87
+ dest = cursor_dir / "rules" / "mk2conf-changelog.mdc"
88
+ dest.parent.mkdir(parents=True, exist_ok=True)
89
+ dest.write_text(content_body, encoding="utf-8")
90
+ installed.append(("cursor", dest))
91
+
92
+ # Fallback — no markers detected in auto mode
93
+ if not installed and not explicit:
94
+ dest = project_dir / ".mk2conf" / "changelog-skill.md"
95
+ dest.parent.mkdir(parents=True, exist_ok=True)
96
+ dest.write_text(content_full, encoding="utf-8")
97
+ installed.append(("fallback", dest))
98
+
99
+ return installed
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: mkdocs-changelog
3
+ description: Analyse doc changes since the last CHANGELOG.md update and draft a major-change entry if the changes qualify.
4
+ version: "1.0.0"
5
+ tags: [documentation, git, changelog, mkdocs, confluence]
6
+ specificity: context-specific
7
+ tool_agnostic: true
8
+ authors: [Anders Hybertz]
9
+ tested_on: []
10
+ ---
11
+
12
+ # MkDocs Changelog Entry
13
+
14
+ Analyse git changes to the docs directory since the last `CHANGELOG.md` commit. If any changes qualify as **MAJOR**, draft a dated changelog entry and prepend it to `CHANGELOG.md`. If not, explain why and exit without modifying any file.
15
+
16
+ ## When to Use
17
+
18
+ - After making one or more documentation changes and before running `mk2conf publish`
19
+ - Any point in the writing flow when you want to assess whether a "What's New" entry is warranted
20
+
21
+ ## Steps
22
+
23
+ 1. **Find the baseline** — run `git log --follow -1 --format="%H" -- <docs_dir>/CHANGELOG.md` to get the last commit that touched `CHANGELOG.md`. If no commit is found, use the root commit as the baseline.
24
+
25
+ 2. **Collect doc changes** — run `git diff <baseline>..HEAD -- <docs_dir>/` to see everything that changed in the docs directory since that baseline.
26
+
27
+ 3. **Read the existing changelog** — read `<docs_dir>/CHANGELOG.md` for context on what was previously recorded.
28
+
29
+ 4. **Decide: is this MAJOR?**
30
+
31
+ **MAJOR criteria — any one of these qualifies:**
32
+ - A new top-level documentation area or section added (a new folder or nav section that didn't exist before)
33
+ - A significant area deleted or substantially restructured (not just moved or renamed)
34
+ - A fundamental definition, concept, or policy changed in a way that affects how readers understand the subject
35
+
36
+ **NOT major — do not draft an entry for:**
37
+ - Typo fixes, grammar corrections, spelling
38
+ - Formatting, diagram adjustments, image swaps
39
+ - Small additions (a paragraph, a note, a clarification) that do not change the substance
40
+ - Rewordings that preserve the original meaning
41
+ - Internal restructuring with no reader-facing impact
42
+
43
+ 5. **If NOT MAJOR** — report what was found, explain in one sentence why it did not qualify, and stop. Do not modify any file.
44
+
45
+ 6. **If MAJOR** — draft an entry using this format and prepend it to `CHANGELOG.md`:
46
+
47
+ ```markdown
48
+ ## YYYY-MM-DD — Brief title describing the major change
49
+
50
+ One or two sentences summarising what fundamentally changed and why it matters to readers.
51
+
52
+ ### Added
53
+ - …
54
+
55
+ ### Changed
56
+ - …
57
+
58
+ ### Removed
59
+ - …
60
+ ```
61
+
62
+ Rules for the entry:
63
+ - Date is today's date in `YYYY-MM-DD` format
64
+ - Sections (`Added`, `Changed`, `Removed`) are included only when non-empty
65
+ - No version numbers — dates only
66
+ - Title is a brief, reader-facing description (not a git commit message)
67
+
68
+ Prepend the entry above any existing entries in `CHANGELOG.md`. Do not commit — the user reviews, edits if needed, and commits manually.
69
+
70
+ ## Pitfalls
71
+
72
+ - **Do not draft an entry for every change.** The changelog is for readers who want to know what fundamentally changed, not a git log. When in doubt, do not draft.
73
+ - **Do not commit.** Always leave the file for the user to review. The user runs `git add` and `git commit` themselves before publishing.
74
+ - **If CHANGELOG.md does not exist yet**, create it with just the new entry (no header needed).
75
+
76
+ ## Verification
77
+
78
+ After drafting, show the user the proposed entry in the terminal and remind them to review `CHANGELOG.md` before committing and running `mk2conf publish`.