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.
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/CHANGELOG.md +16 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/CLAUDE.md +1 -1
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/PKG-INFO +8 -9
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/README.md +6 -8
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/pyproject.toml +7 -1
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/SKILL.md +23 -10
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/reference/arc42.md +1 -1
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/reference/c4-model.md +10 -5
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/reference/c4.md +1 -1
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/reference/models.md +2 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/explaining-code/reference/split.md +1 -1
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/cli.py +54 -23
- kingmadoc-0.3.0.dev9/src/kingmadoc/raster.py +141 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/render.py +93 -15
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/vscode.py +41 -4
- kingmadoc-0.3.0.dev9/tests/conftest.py +14 -0
- kingmadoc-0.3.0.dev9/tests/data/d2-sample.svg +51 -0
- kingmadoc-0.3.0.dev9/tests/test_raster.py +96 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_render.py +103 -26
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_skill_explaining_code.py +21 -1
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_skill_models.py +17 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_vscode_preview.py +69 -6
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/uv.lock +67 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.featuredoc.yml +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.github/workflows/ci.yml +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.github/workflows/release.yml +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/.gitignore +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/CONTRIBUTING.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/LICENSE +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/docs/conventions.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/docs/index.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/docs/releasing.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/docs/roadmap.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/docs/test-plan.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/README.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/manage.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/requirements.txt +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/__init__.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/models.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/services.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/settings.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/urls.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop/shop/views.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop-discount/shop/discounts.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/fixtures/shop-discount/shop/services.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/results/2026-09-27T111837Z.json +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/results/2026-09-27T112600Z.json +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/results/2026-09-27T162715Z.json +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/scenarios/explain-branch.yml +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/scenarios/explain-feature.yml +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/evals/scenarios/plan-feature.yml +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/examples/verify-mode-plan.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/scripts/build_skill_variants.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/scripts/run_evals.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/SKILL.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/codex.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/copilot.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/cursor.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/reference/diagram-rules.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/skill/reference/formats.md +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/__init__.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/about.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/adr.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/config.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/d2_binary.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/diagrams/__init__.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/diagrams/base.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/diagrams/d2.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/diagrams/mermaid.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/diagrams/plantuml.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/documents.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/exceptions.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/explain.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/__init__.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/branch.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/collect.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/data_model.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/dominators.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/js_modules.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/projects.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/routes.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/facts/services.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/git.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/naming.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plan/__init__.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plan/analyzer.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plan/dependencies.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plan/generator.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plan/models.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/plandoc.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/skills.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/adr.md.j2 +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/domain_design.md.j2 +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/functional_design.md.j2 +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/plan_default.md.j2 +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/security_design.md.j2 +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templates/technical_design.md.j2 +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/templating.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/__init__.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/changes.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/commands.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/deviations.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/locate.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/src/kingmadoc/verify/report.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/d2/class.d2 +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/d2/component.d2 +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/d2/container.d2 +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/d2/context.d2 +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/d2/sequence.d2 +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/mermaid/class.mmd +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/mermaid/component.mmd +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/mermaid/container.mmd +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/mermaid/context.mmd +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/mermaid/sequence.mmd +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/plantuml/class.puml +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/plantuml/component.puml +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/plantuml/container.puml +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/plantuml/context.puml +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/backends/plantuml/sequence.puml +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/mermaid/component.mmd +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/mermaid/container.mmd +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/fixtures/mermaid/context.mmd +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_adr.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_adr_numbering.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_analyzer.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli_encoding.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli_init.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli_plan_custom_template.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli_sigint.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_cli_verify_config.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_config.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_config_poetry.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_config_shape.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_d2_download.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_dependencies.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_dependency_graph_model.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_design_models.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_diagram_backends.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_diagrams_mermaid.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_documents.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_duplicate_names.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_evals.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_explain.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_explain_config.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_explain_status.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_extra_designs_coverage.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_facts.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_facts_code.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_facts_data_model.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_facts_dominators.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_functional_design.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_generator.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_grep_performance.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_manifests.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_max_lines_per_file.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_output_dir.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_plan_e2e.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_plandoc.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_properties.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_security_domain_designs.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_skill.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_skills_install.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_source_dirs.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_summary_slug_diagram_defaults.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_technical_design.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_templating_security.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_verify.py +0 -0
- {kingmadoc-0.3.0.dev7 → kingmadoc-0.3.0.dev9}/tests/test_verify_locate.py +0 -0
- {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
|
|
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.
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
216
|
-
`img/functional-<n>.
|
|
217
|
-
|
|
218
|
-
|
|
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
|
``); 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.
|
|
240
|
-
|
|
241
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
442
|
-
""
|
|
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
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
|
629
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
664
|
+
asked = vscode is None and not vscode_user
|
|
665
|
+
if asked and not preview_enabled(root) and _interactive():
|
|
640
666
|
try:
|
|
641
|
-
|
|
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?
|
|
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
|
-
|
|
648
|
-
|
|
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
|
|
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
|
|
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 ""
|