kingmadoc 0.3.0.dev7__tar.gz → 0.3.0.dev9__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 (172) hide show
  1. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/CHANGELOG.md +16 -0
  2. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/CLAUDE.md +1 -1
  3. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/PKG-INFO +8 -9
  4. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/README.md +6 -8
  5. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/pyproject.toml +7 -1
  6. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/SKILL.md +23 -10
  7. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/reference/arc42.md +1 -1
  8. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/reference/c4-model.md +10 -5
  9. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/reference/c4.md +1 -1
  10. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/reference/models.md +2 -0
  11. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/reference/split.md +1 -1
  12. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/cli.py +54 -23
  13. kingmadoc-0.3.0.dev9/src/kingmadoc/raster.py +141 -0
  14. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/render.py +93 -15
  15. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/vscode.py +41 -4
  16. kingmadoc-0.3.0.dev9/tests/conftest.py +14 -0
  17. kingmadoc-0.3.0.dev9/tests/data/d2-sample.svg +51 -0
  18. kingmadoc-0.3.0.dev9/tests/test_raster.py +96 -0
  19. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_render.py +103 -26
  20. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_skill_explaining_code.py +21 -1
  21. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_skill_models.py +17 -0
  22. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_vscode_preview.py +69 -6
  23. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/uv.lock +67 -0
  24. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.featuredoc.yml +0 -0
  25. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  26. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  27. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.github/workflows/ci.yml +0 -0
  28. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.github/workflows/release.yml +0 -0
  29. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.gitignore +0 -0
  30. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/CONTRIBUTING.md +0 -0
  31. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/LICENSE +0 -0
  32. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/docs/conventions.md +0 -0
  33. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/docs/index.md +0 -0
  34. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/docs/releasing.md +0 -0
  35. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/docs/roadmap.md +0 -0
  36. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/docs/test-plan.md +0 -0
  37. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/README.md +0 -0
  38. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/manage.py +0 -0
  39. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/requirements.txt +0 -0
  40. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/__init__.py +0 -0
  41. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/models.py +0 -0
  42. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/services.py +0 -0
  43. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/settings.py +0 -0
  44. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/urls.py +0 -0
  45. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/views.py +0 -0
  46. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop-discount/shop/discounts.py +0 -0
  47. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop-discount/shop/services.py +0 -0
  48. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/results/2026-09-27T111837Z.json +0 -0
  49. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/results/2026-09-27T112600Z.json +0 -0
  50. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/results/2026-09-27T162715Z.json +0 -0
  51. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/scenarios/explain-branch.yml +0 -0
  52. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/scenarios/explain-feature.yml +0 -0
  53. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/scenarios/plan-feature.yml +0 -0
  54. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/examples/verify-mode-plan.md +0 -0
  55. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/scripts/build_skill_variants.py +0 -0
  56. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/scripts/run_evals.py +0 -0
  57. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/SKILL.md +0 -0
  58. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/codex.md +0 -0
  59. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/copilot.md +0 -0
  60. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/cursor.md +0 -0
  61. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/reference/diagram-rules.md +0 -0
  62. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/reference/formats.md +0 -0
  63. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/__init__.py +0 -0
  64. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/about.py +0 -0
  65. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/adr.py +0 -0
  66. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/config.py +0 -0
  67. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/d2_binary.py +0 -0
  68. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/diagrams/__init__.py +0 -0
  69. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/diagrams/base.py +0 -0
  70. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/diagrams/d2.py +0 -0
  71. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/diagrams/mermaid.py +0 -0
  72. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/diagrams/plantuml.py +0 -0
  73. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/documents.py +0 -0
  74. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/exceptions.py +0 -0
  75. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/explain.py +0 -0
  76. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/__init__.py +0 -0
  77. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/branch.py +0 -0
  78. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/collect.py +0 -0
  79. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/data_model.py +0 -0
  80. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/dominators.py +0 -0
  81. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/js_modules.py +0 -0
  82. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/projects.py +0 -0
  83. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/routes.py +0 -0
  84. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/services.py +0 -0
  85. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/git.py +0 -0
  86. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/naming.py +0 -0
  87. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plan/__init__.py +0 -0
  88. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plan/analyzer.py +0 -0
  89. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plan/dependencies.py +0 -0
  90. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plan/generator.py +0 -0
  91. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plan/models.py +0 -0
  92. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plandoc.py +0 -0
  93. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/skills.py +0 -0
  94. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/adr.md.j2 +0 -0
  95. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/domain_design.md.j2 +0 -0
  96. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/functional_design.md.j2 +0 -0
  97. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/plan_default.md.j2 +0 -0
  98. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/security_design.md.j2 +0 -0
  99. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/technical_design.md.j2 +0 -0
  100. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templating.py +0 -0
  101. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/__init__.py +0 -0
  102. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/changes.py +0 -0
  103. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/commands.py +0 -0
  104. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/deviations.py +0 -0
  105. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/locate.py +0 -0
  106. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/report.py +0 -0
  107. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/d2/class.d2 +0 -0
  108. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/d2/component.d2 +0 -0
  109. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/d2/container.d2 +0 -0
  110. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/d2/context.d2 +0 -0
  111. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/d2/sequence.d2 +0 -0
  112. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/mermaid/class.mmd +0 -0
  113. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/mermaid/component.mmd +0 -0
  114. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/mermaid/container.mmd +0 -0
  115. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/mermaid/context.mmd +0 -0
  116. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/mermaid/sequence.mmd +0 -0
  117. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/plantuml/class.puml +0 -0
  118. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/plantuml/component.puml +0 -0
  119. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/plantuml/container.puml +0 -0
  120. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/plantuml/context.puml +0 -0
  121. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/plantuml/sequence.puml +0 -0
  122. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/mermaid/component.mmd +0 -0
  123. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/mermaid/container.mmd +0 -0
  124. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/mermaid/context.mmd +0 -0
  125. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_adr.py +0 -0
  126. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_adr_numbering.py +0 -0
  127. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_analyzer.py +0 -0
  128. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli.py +0 -0
  129. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli_encoding.py +0 -0
  130. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli_init.py +0 -0
  131. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli_plan_custom_template.py +0 -0
  132. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli_sigint.py +0 -0
  133. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli_verify_config.py +0 -0
  134. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_config.py +0 -0
  135. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_config_poetry.py +0 -0
  136. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_config_shape.py +0 -0
  137. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_d2_download.py +0 -0
  138. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_dependencies.py +0 -0
  139. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_dependency_graph_model.py +0 -0
  140. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_design_models.py +0 -0
  141. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_diagram_backends.py +0 -0
  142. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_diagrams_mermaid.py +0 -0
  143. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_documents.py +0 -0
  144. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_duplicate_names.py +0 -0
  145. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_evals.py +0 -0
  146. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_explain.py +0 -0
  147. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_explain_config.py +0 -0
  148. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_explain_status.py +0 -0
  149. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_extra_designs_coverage.py +0 -0
  150. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_facts.py +0 -0
  151. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_facts_code.py +0 -0
  152. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_facts_data_model.py +0 -0
  153. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_facts_dominators.py +0 -0
  154. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_functional_design.py +0 -0
  155. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_generator.py +0 -0
  156. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_grep_performance.py +0 -0
  157. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_manifests.py +0 -0
  158. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_max_lines_per_file.py +0 -0
  159. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_output_dir.py +0 -0
  160. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_plan_e2e.py +0 -0
  161. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_plandoc.py +0 -0
  162. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_properties.py +0 -0
  163. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_security_domain_designs.py +0 -0
  164. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_skill.py +0 -0
  165. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_skills_install.py +0 -0
  166. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_source_dirs.py +0 -0
  167. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_summary_slug_diagram_defaults.py +0 -0
  168. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_technical_design.py +0 -0
  169. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_templating_security.py +0 -0
  170. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_verify.py +0 -0
  171. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_verify_locate.py +0 -0
  172. {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_version.py +0 -0
@@ -9,6 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Changed
11
11
 
12
+ - `kingmadoc render` links a PNG for each diagram, so the pictures show in every
13
+ Markdown preview (VS Code, Visual Studio, Rider, GitHub, GitLab, Bitbucket; several
14
+ block or mishandle SVG). The PNG is made offline with resvg (`resvg-py`, a small
15
+ wheel for every platform) using D2's own embedded fonts; the SVG (dark mode) and the
16
+ `.d2` source stay next to it, and `--format svg` links the SVG as before.
17
+ `explaining-code` 5.8 no longer lets the agent convert images itself.
18
+ - `kingmadoc skills install --vscode-user`: VS Code opens explainers as a preview in
19
+ every folder (user settings); the question in a terminal sets that up.
20
+ - Tidier diagrams: `kingmadoc render` lays figures out with ELK (straight,
21
+ right-angled arrows with fewer crossings; a diagram that sets its own
22
+ `layout-engine` keeps it) and warns about more than 12 arrows or two arrows between
23
+ the same shapes. `explaining-code` 5.7: one flow direction, one arrow per pair, at
24
+ most 12 arrows, labels of at most six words; C4 boundary labels sit top-left in a
25
+ small font so arrows do not cross them. At hand-over the agent asks whether VS Code
26
+ should open explainers as a preview (the pictures only show there) and sets it up on
27
+ yes.
12
28
  - Beta channel: every commit on `main` that passes CI is published to PyPI as a
13
29
  development version (`0.3.0.devN`); follow it with
14
30
  `pipx install --pip-args=--pre kingmadoc` and `pipx upgrade kingmadoc`.
@@ -33,7 +33,7 @@ Flow for `plan "<description>"` (`cli.py`): `config.load_config` → `plan.analy
33
33
  - `plan/analyzer.py`: pathlib walk with fnmatch excludes ("glob", hard cap `MAX_FILES_LIMIT` = 5000), manifest parsing (`pyproject.toml`, `requirements.txt`, `package.json`, `go.mod`, `Cargo.toml`, compose files → `DEPENDENCY_TECH`), regex import scanning ("grep"). Returns an immutable `CodebaseReport` (`kingmadoc analyze` prints it via `format_report`, `--json` via `report_to_dict`); `source_dirs` drives container inference (descends into `src/` layouts, skips tests/docs/examples).
34
34
  - `skill/SKILL.md`: the product skill (Markdown-only KingmaDoc for users without Python; not the repo's `checking-conventions` dev skill): the workflow, under 250 lines. Its document formats live in `skill/reference/formats.md` and must keep the same headings as `src/kingmadoc/templates/plan_default.md.j2`, the extra-design templates and `verify/report.py` — `tests/test_skill.py` fails otherwise, so change both together; the Mermaid rules in `skill/reference/diagram-rules.md`. SKILL.md keeps a `## Output format` / `## Diagram rules` stub that links each reference. `skill/cursor.md`, `codex.md`, `copilot.md` are single files generated by `scripts/build_skill_variants.py`, which inlines each reference in place of its stub — never edit them; rerun the script after changing SKILL.md or a reference (`tests/test_skill.py` fails on stale variants). `kingmadoc skills install` copies SKILL.md plus `reference/`.
35
35
  - `adr.py` (`kingmadoc adr "<title>"`, gated by the top-level `adr.enabled`; `adr.template` selects the template): `adr_path` → `next_number` (max existing + 1, never reused) + `adr_filename` → `render_adr` (`templates/adr.md.j2`, bundled) → `docs/adr/<NNNN>-<slug>.md`. Not a mode; shares `naming.slugify` and `templating.load_template` with plan.
36
- - `render.py` (`kingmadoc render <doc>`): renders each ```` ```d2 ```` block to `img/<doc>-<n>.svg` (D2 via `d2_binary.ensure_d2`), moves its source to `img/<doc>-<n>.d2`, and leaves only `<!-- kingmadoc:diagram img/<doc>-<n>.d2 -->` + the image in the document (the comment is invisible in previews; re-rendering reads the `.d2` files, so edit those). Converts the older `<details>` format. Renders everything to a temp dir first, so a failing diagram changes nothing.
36
+ - `render.py` (`kingmadoc render <doc>`): renders each ```` ```d2 ```` block with ELK to `img/<doc>-<n>.svg` (dark theme; D2 via `d2_binary.ensure_d2`) and, by default, `img/<doc>-<n>.png` via `raster.svg_to_png` (resvg; D2's embedded WOFF fonts unpacked to TTF and the `d2-N-font-*` CSS names mapped to family + weight, else text is missing), moves its source to `img/<doc>-<n>.d2`, and leaves only `<!-- kingmadoc:diagram img/<doc>-<n>.d2 -->` + the PNG (`--format svg`: the SVG) in the document; `diagram_warnings` = dark-mode styles (only reported for `--format svg`) + crowding (> 12 arrows, doubled pairs) (the comment is invisible in previews; re-rendering reads the `.d2` files, so edit those). Converts the older `<details>` format. Renders everything to a temp dir first, so a failing diagram changes nothing.
37
37
  - `d2_binary.py`: `ensure_d2` finds D2 (`KINGMADOC_D2`, PATH, cache) or downloads the pinned release once into the user cache; the SHA-256 per platform is pinned in `D2_ASSETS` (update version + all hashes together, cross-checked with the release's SHA256SUMS). `skills.py` + `kingmadoc skills install`: the skills in `skill/` ship as package data (hatch force-include) and are copied into the agent's skills dir; a `.kingmadoc-skill.json` manifest per skill folder records the installed hashes, so a reinstall updates unchanged files and only blocks local edits (`PREVIOUS_RELEASES` is the frozen hash list for installs from before the manifest).
38
38
  - `skill/explaining-code/`: second product skill; explains existing code (feature, branch, project, part) with D2 pictures, one folder per subject `docs/explain/<NNNN>-<slug>/README.md` (`explain.py` + `kingmadoc explain new`: next ID, never reused, same subject reuses its folder; regenerates the index `docs/explain/README.md`, also after `render` of such a file; `explain status` → `freshness`: `git diff --relative --name-only <Based on commit>` over the paths the explainer names in code spans (else the whole project), excluding `docs/explain`; `render` names a README's images `figure-<n>`). `SKILL.md` = workflow and D2 examples; the formats are `reference/arc42.md` (default) and `reference/c4.md`, chosen by `explain.format`; `reference/split.md` for `explain.documents: split` (cover + `functional.md` + `technical.md`); `reference/c4-model.md` (Simon Brown's C4: abstractions, 7 diagrams, notation, D2 style block, checklist) and `reference/models.md` (decision table + rules + one example per other model). `tests/test_skill_explaining_code.py` checks its format and compiles every D2 example with the real `d2` when available (`D2_BIN`); `tests/test_skill_models.py` checks every example obeys its model's rules. D2 pitfalls: quote labels with `[`/`:`; `source-arrowhead` only works on `<->`.
39
39
  - `facts/` (`kingmadoc explain facts`): pure parsers `projects.project_references` (.csproj) and `data_model.data_model` (EF Core via `DbSet<T>`, Prisma, Django, SQLAlchemy, TypeORM; regex, test files skipped) → frozen `Entity`/`Field`/`Relation`; `routes.routes` (ASP.NET controllers + minimal APIs, Next.js app/pages, Django urls + decorators, FastAPI, Flask, Express; `access` = what the code states), `services.services` (.NET DI), `js_modules.js_dependencies` (relative + tsconfig `paths` imports, `plan.dependencies.collapse` above 25 modules, `max_nodes=None` for the raw graph), `dominators.private_modules` (Cooper–Harvey–Kennedy on the raw Python + JS graphs; virtual root above the modules nothing imports; a single entry point is left out); `branch.branch_changes` (git I/O via `git.run_git`, merge base → working tree); `collect.collect_facts` reads files from the `CodebaseReport` and renders Markdown/JSON.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: kingmadoc
3
- Version: 0.3.0.dev7
3
+ Version: 0.3.0.dev9
4
4
  Summary: Feature docs for AI coding agents: a plan with C4 diagrams before the code, a verification doc after.
5
5
  Project-URL: Homepage, https://github.com/ATkingma/KingmaDoc
6
6
  Project-URL: Issues, https://github.com/ATkingma/KingmaDoc/issues
@@ -42,6 +42,7 @@ Requires-Python: >=3.11
42
42
  Requires-Dist: click>=8.1
43
43
  Requires-Dist: jinja2>=3.1
44
44
  Requires-Dist: pyyaml>=6.0
45
+ Requires-Dist: resvg-py<0.6,>=0.5
45
46
  Provides-Extra: dev
46
47
  Requires-Dist: hypothesis>=6.100; extra == 'dev'
47
48
  Requires-Dist: import-linter>=2.1; extra == 'dev'
@@ -107,12 +108,11 @@ them are updated; files you edited are kept (`--force` replaces them too).
107
108
 
108
109
  Nothing else to install: the first `kingmadoc render` downloads the D2 diagram renderer by
109
110
  itself (pinned version, checksum-verified; `KINGMADOC_D2_DOWNLOAD=0` turns that off).
110
- VS Code opens a `.md` file as text, which shows no pictures. `kingmadoc skills install`
111
- asks whether VS Code should open explainers (`docs/explain/`) as a rendered preview
112
- instead, so you see the pictures right away (one setting in `.vscode/settings.json`;
113
- `--vscode` / `--no-vscode` answer up front). Without it, press Ctrl+Shift+V in an
114
- explainer. Visual Studio shows a preview by default. The pictures follow VS Code's light
115
- or dark theme.
111
+ The pictures are PNG files linked from plain Markdown, so every Markdown preview shows
112
+ them: Visual Studio and Rider (preview on by default), VS Code (Ctrl+Shift+V), GitHub,
113
+ GitLab and Bitbucket. `render --format svg` links the SVG instead, which follows dark
114
+ mode. VS Code opens `.md` as text: `kingmadoc skills install` offers to make it open
115
+ explainers as a preview (`--vscode-user` for every folder, `--vscode` for this project).
116
116
 
117
117
  ### pip
118
118
 
@@ -158,8 +158,7 @@ agent adds the models the code calls for (UML sequence, state machine, class, ac
158
158
  with swimlanes, use case, ER, data flow with trust boundaries, context map), each drawn
159
159
  by its own notation rules. No stories, no audit. Install it next to the first one and ask the agent to "explain <feature / branch /
160
160
  project>". `kingmadoc skills install` installs it together with the first skill; the
161
- pictures are rendered with `kingmadoc render`; they follow the viewer's light or dark
162
- theme (`--light` for light only).
161
+ pictures are rendered with `kingmadoc render` as PNG, which every editor shows.
163
162
 
164
163
  - The Codex and Copilot files are loaded in **every** session (about 17 KB). Codex
165
164
  stops reading `AGENTS.md` files after 32 KiB in total by default
@@ -53,12 +53,11 @@ them are updated; files you edited are kept (`--force` replaces them too).
53
53
 
54
54
  Nothing else to install: the first `kingmadoc render` downloads the D2 diagram renderer by
55
55
  itself (pinned version, checksum-verified; `KINGMADOC_D2_DOWNLOAD=0` turns that off).
56
- VS Code opens a `.md` file as text, which shows no pictures. `kingmadoc skills install`
57
- asks whether VS Code should open explainers (`docs/explain/`) as a rendered preview
58
- instead, so you see the pictures right away (one setting in `.vscode/settings.json`;
59
- `--vscode` / `--no-vscode` answer up front). Without it, press Ctrl+Shift+V in an
60
- explainer. Visual Studio shows a preview by default. The pictures follow VS Code's light
61
- or dark theme.
56
+ The pictures are PNG files linked from plain Markdown, so every Markdown preview shows
57
+ them: Visual Studio and Rider (preview on by default), VS Code (Ctrl+Shift+V), GitHub,
58
+ GitLab and Bitbucket. `render --format svg` links the SVG instead, which follows dark
59
+ mode. VS Code opens `.md` as text: `kingmadoc skills install` offers to make it open
60
+ explainers as a preview (`--vscode-user` for every folder, `--vscode` for this project).
62
61
 
63
62
  ### pip
64
63
 
@@ -104,8 +103,7 @@ agent adds the models the code calls for (UML sequence, state machine, class, ac
104
103
  with swimlanes, use case, ER, data flow with trust boundaries, context map), each drawn
105
104
  by its own notation rules. No stories, no audit. Install it next to the first one and ask the agent to "explain <feature / branch /
106
105
  project>". `kingmadoc skills install` installs it together with the first skill; the
107
- pictures are rendered with `kingmadoc render`; they follow the viewer's light or dark
108
- theme (`--light` for light only).
106
+ pictures are rendered with `kingmadoc render` as PNG, which every editor shows.
109
107
 
110
108
  - The Codex and Copilot files are loaded in **every** session (about 17 KB). Codex
111
109
  stops reading `AGENTS.md` files after 32 KiB in total by default
@@ -26,6 +26,8 @@ dependencies = [
26
26
  "click>=8.1",
27
27
  "jinja2>=3.1",
28
28
  "pyyaml>=6.0",
29
+ # SVG -> PNG for rendered diagrams (offline, wheels for every platform).
30
+ "resvg-py>=0.5,<0.6",
29
31
  ]
30
32
 
31
33
  [project.urls]
@@ -88,6 +90,10 @@ select = ["E", "W", "F", "I", "UP", "B", "S"]
88
90
  strict = true
89
91
  files = ["src"]
90
92
 
93
+ [[tool.mypy.overrides]]
94
+ module = ["resvg_py"] # ships no type information
95
+ ignore_missing_imports = true
96
+
91
97
  [tool.pytest.ini_options]
92
98
  testpaths = ["tests"]
93
99
  addopts = "-ra"
@@ -125,7 +131,7 @@ source_modules = [
125
131
  "kingmadoc.plan", "kingmadoc.verify", "kingmadoc.facts", "kingmadoc.diagrams",
126
132
  "kingmadoc.about", "kingmadoc.adr", "kingmadoc.config", "kingmadoc.d2_binary",
127
133
  "kingmadoc.documents", "kingmadoc.exceptions", "kingmadoc.explain", "kingmadoc.git",
128
- "kingmadoc.naming", "kingmadoc.plandoc", "kingmadoc.render", "kingmadoc.skills",
134
+ "kingmadoc.naming", "kingmadoc.plandoc", "kingmadoc.raster", "kingmadoc.render", "kingmadoc.skills",
129
135
  "kingmadoc.templating", "kingmadoc.vscode",
130
136
  ]
131
137
  forbidden_modules = ["click", "kingmadoc.cli"]
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: explaining-code
3
3
  description: "Explains existing code with rendered diagrams (C4 in Simon Brown's notation; UML sequence, state, class, activity, use case; ER; data flow) and short tables, as an arc42 or compact C4 document, one file or split into functional and technical. Fixes code blindness, e.g. after an agent wrote the code. Scope: a feature, a branch or PR, a whole project, or a folder, service or module. Use when the user asks to explain, describe, document, map, diagram, draw, visualise or give an overview of existing code or architecture; asks how something works, what it does, how the parts fit together, where something happens, or what a branch, PR, commit or task changed; wants onboarding, a walkthrough, a codebase tour, an architecture or design document, arc42, C4, UML, sequence, ER or deployment diagrams of existing code; or no longer understands the code. The request may be in any language. Not for features that are not built yet."
4
- version: 5.6.0
4
+ version: 5.8.0
5
5
  allowed-tools: [Read, Write, Glob, Grep, Bash]
6
6
  ---
7
7
 
@@ -158,11 +158,17 @@ Write every diagram in **D2** (Step 5 turns them into images).
158
158
  eight figures in total.
159
159
  3. **Place them** where the format (or, when split, [reference/split.md](reference/split.md))
160
160
  says: e.g. arc42 section 6 for flows and lifecycles, section 8 for data and domain.
161
- 4. **Readable in dark mode:** the images follow the viewer's light or dark theme. Give
161
+ 4. **Readable in dark mode:** the SVG next to each PNG follows the viewer's theme. Give
162
162
  every shape you fill (`fill:`) a `font-color` too, and black dots a grey `stroke`.
163
163
  Never set a `font-color` without a fill (titles, labels: the theme picks the colour),
164
164
  and never fill white: boundaries and nodes are `fill: transparent`. Put
165
165
  `shape: sequence_diagram` at the top level, or give its container the label `""`.
166
+ 5. **Tidy arrows:** set `direction: down` (people on top, data stores at the bottom;
167
+ `right` only for timelines and swimlanes), draw one arrow per pair of shapes with a
168
+ combined label, keep at most 12 arrows per figure (split it otherwise, e.g. one
169
+ figure per container), label each arrow in at most six words plus `[protocol]`, and
170
+ point arrows at the shapes inside a boundary, not at the boundary. `kingmadoc render`
171
+ lays figures out with straight, right-angled arrows (ELK) and warns about crowded ones.
166
172
 
167
173
  For a **branch**, mark changes by border, so the C4 colours stay meaningful, and add
168
174
  both to the legend:
@@ -174,6 +180,7 @@ classes: {
174
180
  changed: {style: {stroke: "#ef6c00"; stroke-width: 4}}
175
181
  }
176
182
  title: "[Container] Webshop - branch feature/invoices" {shape: text; near: top-center; style: {font-size: 24; bold: true}}
183
+ direction: down
177
184
  vars: {
178
185
  d2-legend: {
179
186
  n: New in this branch {class: [container; new]}
@@ -212,10 +219,12 @@ An explainer is not finished until its diagrams are pictures. Run:
212
219
  kingmadoc render docs/explain/<NNNN>-<slug>/*.md
213
220
  ```
214
221
 
215
- It replaces each diagram block with its image (`img/figure-<n>.svg` for `README.md`,
216
- `img/functional-<n>.svg` and `img/technical-<n>.svg` when split) and moves the D2
217
- source next to it (`.d2`), so the documents show only pictures. Each image has a light
218
- and a dark theme and follows the viewer's (`--light`: light only). To change a diagram
222
+ It replaces each diagram block with a PNG (`img/figure-<n>.png` for `README.md`,
223
+ `img/functional-<n>.png` and `img/technical-<n>.png` when split), which every editor
224
+ and Git host shows: VS Code, Visual Studio, Rider, GitHub, GitLab, Bitbucket. Next to it
225
+ go an `.svg` (sharp, follows dark mode) and the D2 source (`.d2`), so the documents show
226
+ only pictures. Never convert the images yourself (PNG scripts, HTML or PDF exports):
227
+ `render` already makes the PNG. To change a diagram
219
228
  later, edit its `.d2` file and render again. The first time it downloads D2 by itself
220
229
  (checksum-verified). **Never ask the user to install D2**, even when `d2` is not
221
230
  on the PATH: `kingmadoc render` does not need it.
@@ -225,7 +234,7 @@ on the PATH: `kingmadoc render` does not need it.
225
234
  `pipx upgrade kingmadoc` (installed from GitHub: `pipx reinstall kingmadoc`), then render.
226
235
  - `kingmadoc` is not installed at all: render with `d2` if it happens to be available
227
236
  (save each diagram as `img/figure-<n>.d2` in the subject's folder, run
228
- `d2 --pad 20 <that>.d2 <that>.svg`, and replace the block with
237
+ `d2 --pad 20 --layout elk <that>.d2 <that>.svg`, and replace the block with
229
238
  `![<caption>](img/figure-<n>.svg)`); otherwise say that installing KingmaDoc gives
230
239
  the pictures.
231
240
 
@@ -236,6 +245,10 @@ Go through the checklist of each figure's model (end of
236
245
  and fix what fails; check that the document embeds every image. Then hand over in at most five lines:
237
246
  the path, one or two sentences on what the system is, the number of figures, and the
238
247
  "Couldn't work out" questions (at most three), which you then answer into the explainer.
239
- Do not paste the explainer or its figures into the chat. If VS Code opens it as text,
240
- say once that Ctrl+Shift+V shows the pictures, or `kingmadoc skills install --vscode`
241
- always does; never run that without the user's consent.
248
+ Do not paste the explainer or its figures into the chat.
249
+
250
+ VS Code shows the pictures only in its Markdown preview. If `.vscode/settings.json` does
251
+ not yet open `docs/explain/` as a preview, Ask once: "Should VS Code open explainers
252
+ directly as a preview, with the pictures?" and, on yes, run
253
+ `kingmadoc skills install --vscode` (it adds one setting). Never run it without that yes;
254
+ without it, mention that Ctrl+Shift+V shows the pictures.
@@ -63,7 +63,7 @@ Text in `<angle brackets>` is filled in; leave out subsections marked optional.
63
63
  | **Scope** | <feature / branch `<branch>` vs `<base>` / project / part `<path>`> |
64
64
  | **Stack** | <languages, frameworks, data stores> |
65
65
  | **Entry points** | <`path`, …> |
66
- | **Based on** | <commit hash (branch)> · <ISO date> · KingmaDoc skill explaining-code 5.6.0 (arc42) |
66
+ | **Based on** | <commit hash (branch)> · <ISO date> · KingmaDoc skill explaining-code 5.8.0 (arc42) |
67
67
 
68
68
  ## What changed (branch only)
69
69
 
@@ -106,10 +106,11 @@ classes: {
106
106
  container: {shape: rectangle; style: {fill: "#438dd5"; stroke: "#3c7fc0"; font-color: "#ffffff"}}
107
107
  database: {shape: cylinder; style: {fill: "#438dd5"; stroke: "#3c7fc0"; font-color: "#ffffff"}}
108
108
  component: {shape: rectangle; style: {fill: "#85bbf0"; stroke: "#5d82a8"; font-color: "#000000"}}
109
- boundary: {style: {fill: transparent; stroke: "#888888"; stroke-dash: 4}}
110
- node: {style: {fill: transparent; stroke: "#888888"}}
109
+ boundary: {label.near: top-left; style: {fill: transparent; stroke: "#888888"; stroke-dash: 4; font-size: 15}}
110
+ node: {label.near: top-left; style: {fill: transparent; stroke: "#888888"; font-size: 15}}
111
111
  }
112
112
  title: "[System Context] Webshop" {
113
+ direction: down
113
114
  shape: text
114
115
  near: top-center
115
116
  style: {font-size: 24; bold: true}
@@ -155,9 +156,10 @@ classes: {
155
156
  external: {shape: rectangle; style: {fill: "#999999"; stroke: "#6b6b6b"; font-color: "#ffffff"}}
156
157
  container: {shape: rectangle; style: {fill: "#438dd5"; stroke: "#3c7fc0"; font-color: "#ffffff"}}
157
158
  database: {shape: cylinder; style: {fill: "#438dd5"; stroke: "#3c7fc0"; font-color: "#ffffff"}}
158
- boundary: {style: {fill: transparent; stroke: "#888888"; stroke-dash: 4}}
159
+ boundary: {label.near: top-left; style: {fill: transparent; stroke: "#888888"; stroke-dash: 4; font-size: 15}}
159
160
  }
160
161
  title: "[Container] Webshop" {shape: text; near: top-center; style: {font-size: 24; bold: true}}
162
+ direction: down
161
163
  vars: {
162
164
  d2-legend: {
163
165
  p: Person {class: person}
@@ -213,9 +215,10 @@ classes: {
213
215
  container: {shape: rectangle; style: {fill: "#438dd5"; stroke: "#3c7fc0"; font-color: "#ffffff"}}
214
216
  database: {shape: cylinder; style: {fill: "#438dd5"; stroke: "#3c7fc0"; font-color: "#ffffff"}}
215
217
  component: {shape: rectangle; style: {fill: "#85bbf0"; stroke: "#5d82a8"; font-color: "#000000"}}
216
- boundary: {style: {fill: transparent; stroke: "#888888"; stroke-dash: 4}}
218
+ boundary: {label.near: top-left; style: {fill: transparent; stroke: "#888888"; stroke-dash: 4; font-size: 15}}
217
219
  }
218
220
  title: "[Component] Webshop - API" {shape: text; near: top-center; style: {font-size: 24; bold: true}}
221
+ direction: down
219
222
  vars: {
220
223
  d2-legend: {
221
224
  c: Container {class: container}
@@ -262,9 +265,10 @@ instances they run; infrastructure (proxy, DNS) only when the code configures it
262
265
  classes: {
263
266
  container: {shape: rectangle; style: {fill: "#438dd5"; stroke: "#3c7fc0"; font-color: "#ffffff"}}
264
267
  database: {shape: cylinder; style: {fill: "#438dd5"; stroke: "#3c7fc0"; font-color: "#ffffff"}}
265
- node: {style: {fill: transparent; stroke: "#888888"}}
268
+ node: {label.near: top-left; style: {fill: transparent; stroke: "#888888"; font-size: 15}}
266
269
  }
267
270
  title: "[Deployment] Webshop - production" {shape: text; near: top-center; style: {font-size: 24; bold: true}}
271
+ direction: down
268
272
  vars: {
269
273
  d2-legend: {
270
274
  n: Deployment node {class: node}
@@ -298,6 +302,7 @@ classes: {
298
302
  database: {shape: cylinder; style: {fill: "#438dd5"; stroke: "#3c7fc0"; font-color: "#ffffff"}}
299
303
  }
300
304
  title: "[Dynamic] Webshop - placing an order" {shape: text; near: top-center; style: {font-size: 24; bold: true}}
305
+ direction: down
301
306
  vars: {
302
307
  d2-legend: {
303
308
  p: Person {class: person}
@@ -22,7 +22,7 @@ Text in `<angle brackets>` is filled in.
22
22
  | **Scope** | <feature / branch `<branch>` vs `<base>` / project / part `<path>`> |
23
23
  | **Stack** | <languages, frameworks, data stores> |
24
24
  | **Entry points** | <`path`, …> |
25
- | **Based on** | <commit hash (branch)> · <ISO date> · KingmaDoc skill explaining-code 5.6.0 |
25
+ | **Based on** | <commit hash (branch)> · <ISO date> · KingmaDoc skill explaining-code 5.8.0 |
26
26
 
27
27
  ## In short
28
28
 
@@ -140,6 +140,7 @@ no methods, no types, multiplicities kept.
140
140
 
141
141
  ```d2
142
142
  title: "[Class] Diagram backends" {shape: text; near: top-center; style: {font-size: 24; bold: true}}
143
+ direction: down
143
144
  backend: "«interface» DiagramBackend" {
144
145
  shape: class
145
146
  "+render_context(diagram)": str
@@ -171,6 +172,7 @@ inside it.
171
172
 
172
173
  ```d2
173
174
  title: "[Package] kingmadoc" {shape: text; near: top-center; style: {font-size: 24; bold: true}}
175
+ direction: down
174
176
  cli: cli {shape: package}
175
177
  plan: plan {shape: package}
176
178
  diagrams: diagrams {shape: package}
@@ -43,7 +43,7 @@ in its first line. `kingmadoc render` takes all three files at once.
43
43
  | **Scope** | <feature / branch `<branch>` vs `<base>` / project / part `<path>`> |
44
44
  | **Stack** | <languages, frameworks, data stores> |
45
45
  | **Entry points** | <`path`, …> |
46
- | **Based on** | <commit hash (branch)> · <ISO date> · KingmaDoc skill explaining-code 5.6.0 (split) |
46
+ | **Based on** | <commit hash (branch)> · <ISO date> · KingmaDoc skill explaining-code 5.8.0 (split) |
47
47
 
48
48
  <at most three plain sentences: what it is, for whom, what it does>
49
49
 
@@ -49,7 +49,7 @@ from kingmadoc.plan.generator import (
49
49
  render_plan,
50
50
  )
51
51
  from kingmadoc.plandoc import check_plan, generated_at, parse_plan, set_status
52
- from kingmadoc.render import dark_mode_warnings, render_file
52
+ from kingmadoc.render import IMAGE_FORMATS, diagram_warnings, render_file
53
53
  from kingmadoc.skills import AGENT_DIRS, install_skills
54
54
  from kingmadoc.verify.changes import detect_changes
55
55
  from kingmadoc.verify.commands import MARKER_FILES, detect_commands, run_check
@@ -61,7 +61,11 @@ from kingmadoc.verify.deviations import (
61
61
  )
62
62
  from kingmadoc.verify.locate import find_plan, verify_output_path
63
63
  from kingmadoc.verify.report import render_verify, verify_status
64
- from kingmadoc.vscode import enable_markdown_preview, preview_enabled
64
+ from kingmadoc.vscode import (
65
+ enable_markdown_preview,
66
+ enable_user_markdown_preview,
67
+ preview_enabled,
68
+ )
65
69
 
66
70
  ROOT_OPTION = click.option(
67
71
  "--root",
@@ -438,18 +442,29 @@ def adr(title: str, root: Path, config_path: Path | None, status: str) -> None:
438
442
  "--light", is_flag=True, help="Light images only (by default they follow dark mode too)."
439
443
  )
440
444
  @click.option("--verbose", is_flag=True, help="Print every image path (default: one line).")
441
- def render_command(documents: tuple[Path, ...], light: bool, verbose: bool) -> None:
442
- """Render the D2 diagrams in DOCUMENTS to SVG images and embed them.
445
+ @click.option(
446
+ "--format",
447
+ "image_format",
448
+ type=click.Choice(IMAGE_FORMATS),
449
+ default="png",
450
+ show_default=True,
451
+ help="What the document links: png shows in every Markdown viewer; svg follows dark mode.",
452
+ )
453
+ def render_command(
454
+ documents: tuple[Path, ...], light: bool, verbose: bool, image_format: str
455
+ ) -> None:
456
+ """Render the D2 diagrams in DOCUMENTS to images and embed them.
443
457
 
444
- Images go to img/<document>-<n>.svg next to each document (img/figure-<n>.svg for a
445
- README.md), their D2 sources to img/*.d2. D2 is downloaded once (pinned,
446
- checksum-verified) unless it is on PATH or in KINGMADOC_D2. Prints the image paths.
458
+ Each diagram becomes img/<document>-<n>.png (linked: every Markdown viewer shows it)
459
+ and .svg (sharp, follows dark mode; --format svg links it instead), with its D2 source
460
+ in img/*.d2; a README.md's images are img/figure-<n>. D2 is downloaded once (pinned,
461
+ checksum-verified) unless it is on PATH or in KINGMADOC_D2.
447
462
  """
448
463
  try:
449
464
  d2 = ensure_d2(lambda message: click.echo(message, err=True))
450
465
  hinted = False
451
466
  for document in documents:
452
- images = render_file(document, d2, dark=not light)
467
+ images = render_file(document, d2, dark=not light, image_format=image_format)
453
468
  index = index_path(document)
454
469
  if index is not None:
455
470
  _write_explain_index(index.parent)
@@ -459,7 +474,7 @@ def render_command(documents: tuple[Path, ...], light: bool, verbose: bool) -> N
459
474
  click.echo(
460
475
  "VS Code shows the pictures in the preview: open the file and press "
461
476
  "Ctrl+Shift+V (macOS: Cmd+Shift+V), or run `kingmadoc skills install "
462
- "--vscode` to always open explainers as a preview.",
477
+ "--vscode-user` to always open explainers as a preview.",
463
478
  err=True,
464
479
  )
465
480
  if not images:
@@ -474,10 +489,12 @@ def render_command(documents: tuple[Path, ...], light: bool, verbose: bool) -> N
474
489
  noun = "image" if len(images) == 1 else "images"
475
490
  click.echo(f"{document}: {len(images)} {noun} ({span})")
476
491
  for image in images:
477
- if not light:
478
- source = image.with_suffix(".d2")
479
- for warning in dark_mode_warnings(source.read_text(encoding="utf-8")):
480
- click.echo(f"{source}: {warning} (dark mode)", err=True)
492
+ source = image.with_suffix(".d2") # next to the .png and .svg
493
+ for warning in diagram_warnings(source.read_text(encoding="utf-8")):
494
+ dark_mode = not light and image_format == "svg"
495
+ if not dark_mode and warning.endswith("(dark mode)"):
496
+ continue
497
+ click.echo(f"{source}: {warning}", err=True)
481
498
  except KingmaDocError as exc:
482
499
  raise click.ClickException(str(exc)) from exc
483
500
 
@@ -625,10 +642,18 @@ def skills_group() -> None:
625
642
  @click.option(
626
643
  "--vscode/--no-vscode",
627
644
  default=None,
628
- help="Make VS Code open explainers as a rendered preview (.vscode/settings.json), or "
629
- "not. Without either, a terminal asks; other runs only print a tip.",
645
+ help="Make VS Code open this project's explainers as a rendered preview "
646
+ "(.vscode/settings.json; only when this folder is the open workspace), or not.",
630
647
  )
631
- def skills_install(root: Path, agent: str, force: bool, vscode: bool | None) -> None:
648
+ @click.option(
649
+ "--vscode-user",
650
+ is_flag=True,
651
+ help="Make VS Code open explainers as a rendered preview in every folder (user "
652
+ "settings). Without a VS Code option, a terminal asks; other runs print a tip.",
653
+ )
654
+ def skills_install(
655
+ root: Path, agent: str, force: bool, vscode: bool | None, vscode_user: bool
656
+ ) -> None:
632
657
  """Install the KingmaDoc skills into the project for AGENT (Agent Skills standard).
633
658
 
634
659
  Also offers to make VS Code open docs/explain/ as a rendered preview, so the pictures
@@ -636,16 +661,21 @@ def skills_install(root: Path, agent: str, force: bool, vscode: bool | None) ->
636
661
  """
637
662
  try:
638
663
  result = install_skills(root, agent, force)
639
- if vscode is None and not preview_enabled(root) and _interactive():
664
+ asked = vscode is None and not vscode_user
665
+ if asked and not preview_enabled(root) and _interactive():
640
666
  try:
641
- vscode = click.confirm(
667
+ vscode_user = click.confirm(
642
668
  "Make VS Code open explainers (docs/explain/) as a rendered preview, so "
643
- "the pictures show right away?", default=True, err=True,
669
+ "the pictures show right away? (VS Code user settings)",
670
+ default=True, err=True,
644
671
  )
645
672
  except click.Abort: # stdin closed without an answer: change nothing
646
673
  click.echo("", err=True)
647
- vscode = False
648
- preview = enable_markdown_preview(root) if vscode else None
674
+ preview = None
675
+ if vscode_user:
676
+ preview = enable_user_markdown_preview()
677
+ elif vscode:
678
+ preview = enable_markdown_preview(root)
649
679
  except KingmaDocError as exc:
650
680
  raise click.ClickException(str(exc)) from exc
651
681
  for path in result.written:
@@ -656,9 +686,10 @@ def skills_install(root: Path, agent: str, force: bool, vscode: bool | None) ->
656
686
  click.echo(f"{len(result.up_to_date)} skill file(s) already up to date.", err=True)
657
687
  if preview:
658
688
  click.echo(preview, err=True)
659
- elif vscode is None and not preview_enabled(root):
689
+ elif asked and not preview_enabled(root):
660
690
  click.echo(
661
- "Tip: --vscode makes VS Code open explainers (docs/explain/) as a rendered preview.",
691
+ "Tip: --vscode-user makes VS Code open explainers (docs/explain/) as a rendered "
692
+ "preview, with the pictures.",
662
693
  err=True,
663
694
  )
664
695
 
@@ -0,0 +1,141 @@
1
+ """SVG to PNG for rendered diagrams, so every Markdown viewer shows them.
2
+
3
+ VS Code, Visual Studio, Rider, GitHub, GitLab and Bitbucket all show a PNG in Markdown;
4
+ several block or mishandle SVG. The conversion runs offline with resvg (a small wheel,
5
+ no system libraries). D2 embeds its fonts (Source Sans Pro, as WOFF subsets) in each
6
+ SVG; resvg cannot read WOFF or CSS ``@font-face``, so the fonts are unpacked to TrueType
7
+ and the CSS names D2 made up (``d2-123-font-bold``) become the real family and weight.
8
+ Without that, text is measured with another font and clipped.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import base64
14
+ import re
15
+ import struct
16
+ import tempfile
17
+ import zlib
18
+ from collections.abc import Mapping
19
+ from pathlib import Path
20
+ from types import MappingProxyType
21
+
22
+ import resvg_py
23
+
24
+ from kingmadoc.exceptions import RenderError
25
+
26
+ # 2x: sharp on high-DPI screens, still small (a typical diagram is 100-300 KB).
27
+ ZOOM = 2
28
+ _FONT_FACE = re.compile(
29
+ r"@font-face\s*\{\s*font-family:\s*(d2-\d+-font-[\w-]+);\s*"
30
+ r"src:\s*url\(\"data:application/font-woff;base64,([A-Za-z0-9+/=]+)\"\)[^}]*\}"
31
+ )
32
+ _FONT_USE = re.compile(r"font-family:\s*\"?d2-\d+-font-([\w-]+)\"?")
33
+ _STYLES: Mapping[str, str] = MappingProxyType({
34
+ "regular": "font-weight: normal; font-style: normal",
35
+ "bold": "font-weight: bold; font-style: normal",
36
+ "semibold": "font-weight: 600; font-style: normal",
37
+ "italic": "font-weight: normal; font-style: italic",
38
+ })
39
+
40
+
41
+ def svg_to_png(svg: str) -> bytes:
42
+ """Rasterize a D2 SVG with its own fonts.
43
+
44
+ Args:
45
+ svg: The SVG text D2 wrote.
46
+
47
+ Returns:
48
+ PNG bytes at :data:`ZOOM` times the SVG size (light theme: resvg ignores the
49
+ dark-mode media query, so the picture reads on any background).
50
+
51
+ Raises:
52
+ RenderError: If the SVG cannot be rasterized.
53
+ """
54
+ faces = _FONT_FACE.findall(svg)
55
+ families: dict[str, str] = {}
56
+ with tempfile.TemporaryDirectory(prefix="kingmadoc-fonts-") as tmp:
57
+ files = []
58
+ for index, (name, data) in enumerate(faces):
59
+ try:
60
+ sfnt = woff_to_sfnt(base64.b64decode(data))
61
+ families[name] = font_family(sfnt) or "sans-serif"
62
+ except (RenderError, ValueError, struct.error, zlib.error):
63
+ continue # an unreadable font: its text falls back to another font
64
+ path = Path(tmp) / f"{index}.ttf"
65
+ path.write_bytes(sfnt)
66
+ files.append(str(path))
67
+ family = next(iter(families.values()), "sans-serif")
68
+ text = _FONT_USE.sub(
69
+ lambda m: f'font-family: "{family}"; {_STYLES.get(m.group(1), _STYLES["regular"])}',
70
+ re.sub(r"@font-face\s*\{[^}]*\}", "", svg),
71
+ )
72
+ try:
73
+ png = resvg_py.svg_to_bytes(
74
+ svg_string=text, font_files=files, skip_system_fonts=bool(files), zoom=ZOOM
75
+ )
76
+ except Exception as exc: # noqa: BLE001 - resvg raises bare exceptions (and panics)
77
+ raise RenderError(f"Could not convert the diagram to PNG: {exc}") from exc
78
+ return bytes(png)
79
+
80
+
81
+ def woff_to_sfnt(data: bytes) -> bytes:
82
+ """Unpack a WOFF 1.0 font to the TrueType/OpenType file it wraps.
83
+
84
+ Args:
85
+ data: The WOFF file.
86
+
87
+ Returns:
88
+ The sfnt (``.ttf``/``.otf``) bytes.
89
+
90
+ Raises:
91
+ RenderError: If ``data`` is not WOFF 1.0.
92
+ """
93
+ if len(data) < 44 or data[:4] != b"wOFF":
94
+ raise RenderError("not a WOFF 1.0 font")
95
+ flavor, _length, count = struct.unpack(">4xIIH", data[:14])
96
+ tables = []
97
+ for i in range(count):
98
+ tag, offset, size, original, checksum = struct.unpack(
99
+ ">4sIIII", data[44 + 20 * i : 64 + 20 * i]
100
+ )
101
+ raw = data[offset : offset + size]
102
+ tables.append((tag, zlib.decompress(raw) if size < original else raw, checksum))
103
+ tables.sort()
104
+ search = 1
105
+ while search * 2 <= count:
106
+ search *= 2
107
+ header = struct.pack(
108
+ ">IHHHH", flavor, count, search * 16, search.bit_length() - 1, count * 16 - search * 16
109
+ )
110
+ offset = 12 + 16 * count
111
+ directory, body = b"", b""
112
+ for tag, table, checksum in tables:
113
+ directory += struct.pack(">4sIII", tag, checksum, offset + len(body), len(table))
114
+ body += table + b"\0" * (-len(table) % 4)
115
+ return header + directory + body
116
+
117
+
118
+ def font_family(sfnt: bytes) -> str:
119
+ """Return a font's family name (name table, name ID 1), or "" when it has none.
120
+
121
+ Args:
122
+ sfnt: A TrueType/OpenType font.
123
+
124
+ Returns:
125
+ E.g. ``"Source Sans Pro"``.
126
+ """
127
+ count = struct.unpack(">H", sfnt[4:6])[0]
128
+ for i in range(count):
129
+ tag, _checksum, offset, length = struct.unpack(">4sIII", sfnt[12 + 16 * i : 28 + 16 * i])
130
+ if tag != b"name":
131
+ continue
132
+ table = sfnt[offset : offset + length]
133
+ _format, records, strings = struct.unpack(">HHH", table[:6])
134
+ for j in range(records):
135
+ platform, _enc, _lang, name_id, size, start = struct.unpack(
136
+ ">HHHHHH", table[6 + 12 * j : 18 + 12 * j]
137
+ )
138
+ if name_id == 1:
139
+ raw = table[strings + start : strings + start + size]
140
+ return raw.decode("utf-16-be" if platform in (0, 3) else "latin-1", "replace")
141
+ return ""