curfew 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- curfew-0.1.0/.cache/.gitignore +1 -0
- curfew-0.1.0/.cache/12842651320362419197 +4 -0
- curfew-0.1.0/.cache/1322386826961527888 +4 -0
- curfew-0.1.0/.cache/1578724436212372168 +61 -0
- curfew-0.1.0/.cache/17346873189989222451 +4 -0
- curfew-0.1.0/.cache/1885359050967441258 +77 -0
- curfew-0.1.0/.cache/3476900567878811119 +4 -0
- curfew-0.1.0/.cache/3541202890535126406 +61 -0
- curfew-0.1.0/.cache/5638187333346058815 +93 -0
- curfew-0.1.0/.cache/595805366992470604 +4 -0
- curfew-0.1.0/.cache/7920992169555033711 +28 -0
- curfew-0.1.0/.gitattributes +5 -0
- curfew-0.1.0/.github/workflows/ci.yml +86 -0
- curfew-0.1.0/.github/workflows/docs.yml +43 -0
- curfew-0.1.0/.github/workflows/release.yml +37 -0
- curfew-0.1.0/.gitignore +29 -0
- curfew-0.1.0/.python-version +1 -0
- curfew-0.1.0/CLAUDE.md +82 -0
- curfew-0.1.0/LICENSE +201 -0
- curfew-0.1.0/PKG-INFO +122 -0
- curfew-0.1.0/README.md +94 -0
- curfew-0.1.0/docs/configuration.md +63 -0
- curfew-0.1.0/docs/graph.md +130 -0
- curfew-0.1.0/docs/index.md +39 -0
- curfew-0.1.0/docs/usage.md +53 -0
- curfew-0.1.0/docs/why.md +46 -0
- curfew-0.1.0/pyproject.toml +162 -0
- curfew-0.1.0/scripts/gen_graph.py +39 -0
- curfew-0.1.0/src/curfew/__init__.py +16 -0
- curfew-0.1.0/src/curfew/__main__.py +8 -0
- curfew-0.1.0/src/curfew/checks/__init__.py +16 -0
- curfew-0.1.0/src/curfew/checks/boundaries.py +129 -0
- curfew-0.1.0/src/curfew/checks/externals.py +203 -0
- curfew-0.1.0/src/curfew/checks/result.py +20 -0
- curfew-0.1.0/src/curfew/checks/runner.py +37 -0
- curfew-0.1.0/src/curfew/classify/__init__.py +1 -0
- curfew-0.1.0/src/curfew/classify/classifier.py +60 -0
- curfew-0.1.0/src/curfew/classify/distributions.py +48 -0
- curfew-0.1.0/src/curfew/classify/stdlib.py +17 -0
- curfew-0.1.0/src/curfew/cli.py +246 -0
- curfew-0.1.0/src/curfew/config.py +111 -0
- curfew-0.1.0/src/curfew/discovery/__init__.py +57 -0
- curfew-0.1.0/src/curfew/discovery/pyproject.py +81 -0
- curfew-0.1.0/src/curfew/discovery/source_root.py +49 -0
- curfew-0.1.0/src/curfew/discovery/workspace.py +44 -0
- curfew-0.1.0/src/curfew/errors.py +26 -0
- curfew-0.1.0/src/curfew/extraction/__init__.py +1 -0
- curfew-0.1.0/src/curfew/extraction/parser.py +66 -0
- curfew-0.1.0/src/curfew/extraction/path_to_module.py +43 -0
- curfew-0.1.0/src/curfew/extraction/relative.py +39 -0
- curfew-0.1.0/src/curfew/extraction/walker.py +49 -0
- curfew-0.1.0/src/curfew/graph/__init__.py +26 -0
- curfew-0.1.0/src/curfew/graph/algorithms.py +152 -0
- curfew-0.1.0/src/curfew/graph/build.py +184 -0
- curfew-0.1.0/src/curfew/matching.py +44 -0
- curfew-0.1.0/src/curfew/model.py +183 -0
- curfew-0.1.0/src/curfew/py.typed +0 -0
- curfew-0.1.0/src/curfew/report/__init__.py +8 -0
- curfew-0.1.0/src/curfew/report/_color.py +75 -0
- curfew-0.1.0/src/curfew/report/json_out.py +39 -0
- curfew-0.1.0/src/curfew/report/text.py +50 -0
- curfew-0.1.0/src/curfew/scaffold.py +87 -0
- curfew-0.1.0/src/curfew/viz/__init__.py +13 -0
- curfew-0.1.0/src/curfew/viz/_view.py +77 -0
- curfew-0.1.0/src/curfew/viz/dot.py +53 -0
- curfew-0.1.0/src/curfew/viz/mermaid.py +62 -0
- curfew-0.1.0/tests/conftest.py +35 -0
- curfew-0.1.0/tests/fixtures/single_pkg/pyproject.toml +7 -0
- curfew-0.1.0/tests/fixtures/single_pkg/src/solo/__init__.py +0 -0
- curfew-0.1.0/tests/fixtures/single_pkg/src/solo/cli.py +7 -0
- curfew-0.1.0/tests/fixtures/single_pkg/src/solo/core.py +6 -0
- curfew-0.1.0/tests/fixtures/single_pkg/src/solo/io.py +9 -0
- curfew-0.1.0/tests/fixtures/single_pkg/src/solo/util/__init__.py +0 -0
- curfew-0.1.0/tests/fixtures/single_pkg/src/solo/util/helpers.py +7 -0
- curfew-0.1.0/tests/fixtures/workspace/packages/core/pyproject.toml +5 -0
- curfew-0.1.0/tests/fixtures/workspace/packages/core/src/acme_core/__init__.py +0 -0
- curfew-0.1.0/tests/fixtures/workspace/packages/core/src/acme_core/engine.py +7 -0
- curfew-0.1.0/tests/fixtures/workspace/packages/core/src/acme_core/internal.py +5 -0
- curfew-0.1.0/tests/fixtures/workspace/packages/web/pyproject.toml +6 -0
- curfew-0.1.0/tests/fixtures/workspace/packages/web/src/acme_web/__init__.py +0 -0
- curfew-0.1.0/tests/fixtures/workspace/packages/web/src/acme_web/app.py +10 -0
- curfew-0.1.0/tests/fixtures/workspace/pyproject.toml +7 -0
- curfew-0.1.0/tests/test_algorithms.py +55 -0
- curfew-0.1.0/tests/test_benchmark.py +58 -0
- curfew-0.1.0/tests/test_boundaries.py +136 -0
- curfew-0.1.0/tests/test_build_graph.py +81 -0
- curfew-0.1.0/tests/test_classifier.py +78 -0
- curfew-0.1.0/tests/test_cli.py +75 -0
- curfew-0.1.0/tests/test_config.py +53 -0
- curfew-0.1.0/tests/test_discovery.py +33 -0
- curfew-0.1.0/tests/test_externals.py +186 -0
- curfew-0.1.0/tests/test_matching.py +53 -0
- curfew-0.1.0/tests/test_parser.py +55 -0
- curfew-0.1.0/tests/test_path_to_module.py +40 -0
- curfew-0.1.0/tests/test_relative.py +36 -0
- curfew-0.1.0/tests/test_renderers.py +54 -0
- curfew-0.1.0/tests/test_report.py +59 -0
- curfew-0.1.0/uv.lock +472 -0
- curfew-0.1.0/zensical.toml +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": "\r\n<!doctype html>\r\n<html lang=\"en\" class=\"no-js\">\r\n <head>\r\n \r\n <meta charset=\"utf-8\">\r\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\r\n \r\n <meta name=\"description\" content=\"Local-first Python dependency & module-boundary checker. No network, no binaries, zero runtime deps.\">\r\n \r\n \r\n <meta name=\"author\" content=\"OpenAfterHours\">\r\n \r\n \r\n <link rel=\"canonical\" href=\"https://openafterhours.github.io/curfew/usage/\">\r\n \r\n \r\n <link rel=\"prev\" href=\"../why/\">\r\n \r\n \r\n <link rel=\"next\" href=\"../configuration/\">\r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"icon\" href=\"../assets/images/favicon.png\">\r\n <meta name=\"generator\" content=\"zensical-0.0.44\">\r\n \r\n \r\n \r\n <title>Usage - curfew</title>\r\n \r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"stylesheet\" href=\"../assets/stylesheets/modern/main.fba56155.min.css\">\r\n \r\n \r\n \r\n \r\n <link rel=\"stylesheet\" href=\"../assets/stylesheets/modern/palette.dfe2e883.min.css\">\r\n \r\n \r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\r\n <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback\">\r\n <style>:root{--md-text-font:\"Inter\";--md-code-font:\"JetBrains Mono\"}</style>\r\n \r\n \r\n \r\n <script>__md_scope=new URL(\"..\",location),__md_scope.pathname.endsWith(\"/\")||(__md_scope=new URL(__md_scope.pathname+\"/\",location)),__md_hash=e=>[...e].reduce(((e,t)=>(e<<5)-e+t.charCodeAt(0)),0),__md_get=(e,t=localStorage,_=__md_scope)=>JSON.parse(t.getItem(_.pathname+\".\"+e)),__md_set=(e,t,_=localStorage,a=__md_scope)=>{try{_.setItem(a.pathname+\".\"+e,JSON.stringify(t))}catch(e){}},document.documentElement.setAttribute(\"data-platform\",navigator.platform)</script>\r\n \r\n \r\n\r\n \r\n \r\n </head>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <body dir=\"ltr\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\">\r\n \r\n \r\n <input class=\"md-toggle\" data-md-toggle=\"drawer\" type=\"checkbox\" id=\"__drawer\" autocomplete=\"off\">\r\n <input class=\"md-toggle\" data-md-toggle=\"search\" type=\"checkbox\" id=\"__search\" autocomplete=\"off\">\r\n <label class=\"md-overlay\" for=\"__drawer\" aria-label=\"Navigation\"></label>\r\n <div data-md-component=\"skip\">\r\n \r\n \r\n <a href=\"#usage\" class=\"md-skip\">\r\n Skip to content\r\n </a>\r\n \r\n </div>\r\n <div data-md-component=\"announce\">\r\n \r\n </div>\r\n \r\n \r\n \r\n\r\n \r\n\r\n<header class=\"md-header md-header--shadow\" data-md-component=\"header\">\r\n <nav class=\"md-header__inner md-grid\" aria-label=\"Header\">\r\n <a href=\"./..\" title=\"curfew\" class=\"md-header__button md-logo\" aria-label=\"curfew\" data-md-component=\"logo\">\r\n \r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\r\n\r\n </a>\r\n <label class=\"md-header__button md-icon\" for=\"__drawer\" aria-label=\"Navigation\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-menu\" viewBox=\"0 0 24 24\"><path d=\"M4 5h16M4 12h16M4 19h16\"/></svg>\r\n </label>\r\n <div class=\"md-header__title\" data-md-component=\"header-title\">\r\n <div class=\"md-header__ellipsis\">\r\n <div class=\"md-header__topic\">\r\n <span class=\"md-ellipsis\">\r\n curfew\r\n </span>\r\n </div>\r\n <div class=\"md-header__topic\" data-md-component=\"header-topic\">\r\n <span class=\"md-ellipsis\">\r\n \r\n Usage\r\n \r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n <form class=\"md-header__option\" data-md-component=\"palette\">\r\n \r\n \r\n \r\n \r\n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to dark mode\" type=\"radio\" name=\"__palette\" id=\"__palette_0\">\r\n \r\n <label class=\"md-header__button md-icon\" title=\"Switch to dark mode\" for=\"__palette_1\" hidden>\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-sun\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"/></svg>\r\n </label>\r\n \r\n \r\n \r\n \r\n \r\n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"slate\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to light mode\" type=\"radio\" name=\"__palette\" id=\"__palette_1\">\r\n \r\n <label class=\"md-header__button md-icon\" title=\"Switch to light mode\" for=\"__palette_0\" hidden>\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-moon\" viewBox=\"0 0 24 24\"><path d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\"/></svg>\r\n </label>\r\n \r\n \r\n</form>\r\n \r\n \r\n \r\n <script>var palette=__md_get(\"__palette\");if(palette&&palette.color){if(\"(prefers-color-scheme)\"===palette.color.media){var media=matchMedia(\"(prefers-color-scheme: light)\"),input=document.querySelector(media.matches?\"[data-md-color-media='(prefers-color-scheme: light)']\":\"[data-md-color-media='(prefers-color-scheme: dark)']\");palette.color.media=input.getAttribute(\"data-md-color-media\"),palette.color.scheme=input.getAttribute(\"data-md-color-scheme\"),palette.color.primary=input.getAttribute(\"data-md-color-primary\"),palette.color.accent=input.getAttribute(\"data-md-color-accent\")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute(\"data-md-color-\"+key,value)}</script>\r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-header__button md-icon\" for=\"__search\" aria-label=\"Search\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-search\" viewBox=\"0 0 24 24\"><path d=\"m21 21-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/></svg>\r\n </label>\r\n <div class=\"md-search\" data-md-component=\"search\" role=\"dialog\" aria-label=\"Search\">\r\n <button type=\"button\" class=\"md-search__button\">\r\n Search\r\n </button>\r\n</div>\r\n \r\n \r\n <div class=\"md-header__source\">\r\n \r\n <a href=\"https://github.com/OpenAfterHours/curfew\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\r\n <div class=\"md-source__icon md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\r\n </div>\r\n <div class=\"md-source__repository\">\r\n OpenAfterHours/curfew\r\n </div>\r\n</a>\r\n \r\n </div>\r\n </nav>\r\n \r\n</header>\r\n \r\n <div class=\"md-container\" data-md-component=\"container\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <main class=\"md-main\" data-md-component=\"main\">\r\n <div class=\"md-main__inner md-grid\">\r\n \r\n \r\n \r\n <div class=\"md-sidebar md-sidebar--primary\" data-md-component=\"sidebar\" data-md-type=\"navigation\" >\r\n <div class=\"md-sidebar__scrollwrap\">\r\n <div class=\"md-sidebar__inner\">\r\n \r\n\r\n\r\n\r\n<nav class=\"md-nav md-nav--primary\" aria-label=\"Navigation\" data-md-level=\"0\">\r\n <label class=\"md-nav__title\" for=\"__drawer\">\r\n <a href=\"./..\" title=\"curfew\" class=\"md-nav__button md-logo\" aria-label=\"curfew\" data-md-component=\"logo\">\r\n \r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\r\n\r\n </a>\r\n curfew\r\n </label>\r\n \r\n <div class=\"md-nav__source\">\r\n <a href=\"https://github.com/OpenAfterHours/curfew\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\r\n <div class=\"md-source__icon md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\r\n </div>\r\n <div class=\"md-source__repository\">\r\n OpenAfterHours/curfew\r\n </div>\r\n</a>\r\n </div>\r\n \r\n <ul class=\"md-nav__list\" data-md-scrollfix>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"./..\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Home\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"../why/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Why curfew\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item md-nav__item--active\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__link md-nav__link--active\" for=\"__toc\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Usage\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n </label>\r\n \r\n <a href=\"././\" class=\"md-nav__link md-nav__link--active\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Usage\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n \r\n \r\n\r\n\r\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__title\" for=\"__toc\">\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n On this page\r\n </label>\r\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#curfew-check\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n <code>curfew check</code>\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#curfew-show\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n <code>curfew show</code>\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#curfew-report\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n <code>curfew report</code>\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#curfew-init\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n <code>curfew init</code>\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n </ul>\r\n \r\n</nav>\r\n \r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"../configuration/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Configuration\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"../graph/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Dependency graph\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n </ul>\r\n</nav>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n \r\n <div class=\"md-sidebar md-sidebar--secondary\" data-md-component=\"sidebar\" data-md-type=\"toc\" >\r\n <div class=\"md-sidebar__scrollwrap\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <input class=\"md-nav__toggle md-toggle\" type=\"checkbox\" id=\"__toc\">\r\n <div class=\"md-sidebar-button__wrapper\">\r\n <label class=\"md-sidebar-button\" for=\"__toc\"></label>\r\n </div>\r\n \r\n \r\n <div class=\"md-sidebar__inner\">\r\n \r\n\r\n\r\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__title\" for=\"__toc\">\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n On this page\r\n </label>\r\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#curfew-check\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n <code>curfew check</code>\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#curfew-show\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n <code>curfew show</code>\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#curfew-report\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n <code>curfew report</code>\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#curfew-init\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n <code>curfew init</code>\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n </ul>\r\n \r\n</nav>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n \r\n <div class=\"md-content\" data-md-component=\"content\">\r\n \r\n <article class=\"md-content__inner md-typeset\">\r\n \r\n \r\n \r\n \r\n\r\n\r\n<h1 id=\"usage\">Usage<a class=\"headerlink\" href=\"#usage\" title=\"Permanent link\">¶</a></h1>\n<p>All commands accept <code>--root PATH</code> (project root, default <code>.</code>) and\n<code>--config PATH</code> (a <code>curfew.toml</code> override).</p>\n<h2 id=\"curfew-check\"><code>curfew check</code><a class=\"headerlink\" href=\"#curfew-check\" title=\"Permanent link\">¶</a></h2>\n<p>Runs the configured checks and <strong>exits non-zero on any error</strong> — the CI gate.</p>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a>curfew<span class=\"w\"> </span>check<span class=\"w\"> </span><span class=\"c1\"># both checks</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a>curfew<span class=\"w\"> </span>check<span class=\"w\"> </span>--boundaries<span class=\"w\"> </span><span class=\"c1\"># module-boundary check only</span>\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a>curfew<span class=\"w\"> </span>check<span class=\"w\"> </span>--externals<span class=\"w\"> </span><span class=\"c1\"># external/workspace check only</span>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a>curfew<span class=\"w\"> </span>check<span class=\"w\"> </span>--strict<span class=\"w\"> </span><span class=\"c1\"># treat warnings as errors too</span>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a>curfew<span class=\"w\"> </span>check<span class=\"w\"> </span>--strict-unresolved<span class=\"w\"> </span><span class=\"c1\"># unresolved imports become errors</span>\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a>curfew<span class=\"w\"> </span>check<span class=\"w\"> </span>--unused-tools<span class=\"w\"> </span><span class=\"c1\"># also flag unused dev/optional-group deps</span>\n</span><span id=\"__span-0-7\"><a id=\"__codelineno-0-7\" name=\"__codelineno-0-7\" href=\"#__codelineno-0-7\"></a>curfew<span class=\"w\"> </span>check<span class=\"w\"> </span>--output<span class=\"w\"> </span>json<span class=\"w\"> </span><span class=\"c1\"># machine-readable output for CI/editors</span>\n</span><span id=\"__span-0-8\"><a id=\"__codelineno-0-8\" name=\"__codelineno-0-8\" href=\"#__codelineno-0-8\"></a>curfew<span class=\"w\"> </span>check<span class=\"w\"> </span>--color<span class=\"w\"> </span>never<span class=\"w\"> </span><span class=\"c1\"># disable colour (also honours NO_COLOR)</span>\n</span></code></pre></div>\n<p>Exit codes: <code>0</code> clean · <code>1</code> violations · <code>2</code> config/discovery error · <code>3</code> internal.</p>\n<h2 id=\"curfew-show\"><code>curfew show</code><a class=\"headerlink\" href=\"#curfew-show\" title=\"Permanent link\">¶</a></h2>\n<p>Emits the dependency graph. <strong>Mermaid by default</strong>; DOT optional. No <code>--web</code>.</p>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a>curfew<span class=\"w\"> </span>show<span class=\"w\"> </span>--mermaid<span class=\"w\"> </span>-o<span class=\"w\"> </span>docs/graph.md<span class=\"w\"> </span><span class=\"c1\"># default; renders on GitHub & Zensical</span>\n</span><span id=\"__span-1-2\"><a id=\"__codelineno-1-2\" name=\"__codelineno-1-2\" href=\"#__codelineno-1-2\"></a>curfew<span class=\"w\"> </span>show<span class=\"w\"> </span>--dot<span class=\"w\"> </span>-o<span class=\"w\"> </span>graph.dot<span class=\"w\"> </span><span class=\"c1\"># GraphViz DOT (if you have GraphViz)</span>\n</span><span id=\"__span-1-3\"><a id=\"__codelineno-1-3\" name=\"__codelineno-1-3\" href=\"#__codelineno-1-3\"></a>curfew<span class=\"w\"> </span>show<span class=\"w\"> </span>--third-party<span class=\"w\"> </span><span class=\"c1\"># include third-party distributions</span>\n</span><span id=\"__span-1-4\"><a id=\"__codelineno-1-4\" name=\"__codelineno-1-4\" href=\"#__codelineno-1-4\"></a>curfew<span class=\"w\"> </span>show<span class=\"w\"> </span>acme_web<span class=\"w\"> </span><span class=\"c1\"># focus: only modules under acme_web</span>\n</span><span id=\"__span-1-5\"><a id=\"__codelineno-1-5\" name=\"__codelineno-1-5\" href=\"#__codelineno-1-5\"></a>curfew<span class=\"w\"> </span>show<span class=\"w\"> </span>--direction<span class=\"w\"> </span>TB<span class=\"w\"> </span><span class=\"c1\"># top-to-bottom layout</span>\n</span></code></pre></div>\n<h2 id=\"curfew-report\"><code>curfew report</code><a class=\"headerlink\" href=\"#curfew-report\" title=\"Permanent link\">¶</a></h2>\n<p>Shows the direct and transitive dependencies and dependents of a module (given\nas a dotted name or a path to a <code>.py</code> file).</p>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-2-1\"><a id=\"__codelineno-2-1\" name=\"__codelineno-2-1\" href=\"#__codelineno-2-1\"></a>curfew<span class=\"w\"> </span>report<span class=\"w\"> </span>curfew.checks.runner\n</span><span id=\"__span-2-2\"><a id=\"__codelineno-2-2\" name=\"__codelineno-2-2\" href=\"#__codelineno-2-2\"></a>curfew<span class=\"w\"> </span>report<span class=\"w\"> </span>src/curfew/cli.py<span class=\"w\"> </span>--output<span class=\"w\"> </span>json\n</span></code></pre></div>\n<h2 id=\"curfew-init\"><code>curfew init</code><a class=\"headerlink\" href=\"#curfew-init\" title=\"Permanent link\">¶</a></h2>\n<p>Scaffolds a <code>[tool.curfew]</code> config by inferring your current first-party modules\nand their existing imports, so adoption is incremental — the generated config\npasses immediately and can be tightened from there.</p>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-3-1\"><a id=\"__codelineno-3-1\" name=\"__codelineno-3-1\" href=\"#__codelineno-3-1\"></a>curfew<span class=\"w\"> </span>init<span class=\"w\"> </span>>><span class=\"w\"> </span>pyproject.toml<span class=\"w\"> </span><span class=\"c1\"># review before committing</span>\n</span></code></pre></div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n \r\n </article>\r\n </div>\r\n \r\n \r\n<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith(\"__tabbed_\"))</script>\r\n </div>\r\n \r\n <button type=\"button\" class=\"md-top md-icon\" data-md-component=\"top\" hidden>\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-circle-arrow-up\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m16 12-4-4-4 4M12 16V8\"/></svg>\r\n Back to top\r\n</button>\r\n \r\n </main>\r\n \r\n <footer class=\"md-footer\">\r\n \r\n \r\n \r\n <nav class=\"md-footer__inner md-grid\" aria-label=\"Footer\" >\r\n \r\n \r\n <a href=\"../why/\" class=\"md-footer__link md-footer__link--prev\" aria-label=\"Previous: Why curfew\">\r\n <div class=\"md-footer__button md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-arrow-left\" viewBox=\"0 0 24 24\"><path d=\"m12 19-7-7 7-7M19 12H5\"/></svg>\r\n </div>\r\n <div class=\"md-footer__title\">\r\n <span class=\"md-footer__direction\">\r\n Previous\r\n </span>\r\n <div class=\"md-ellipsis\">\r\n Why curfew\r\n </div>\r\n </div>\r\n </a>\r\n \r\n \r\n \r\n <a href=\"../configuration/\" class=\"md-footer__link md-footer__link--next\" aria-label=\"Next: Configuration\">\r\n <div class=\"md-footer__title\">\r\n <span class=\"md-footer__direction\">\r\n Next\r\n </span>\r\n <div class=\"md-ellipsis\">\r\n Configuration\r\n </div>\r\n </div>\r\n <div class=\"md-footer__button md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-arrow-right\" viewBox=\"0 0 24 24\"><path d=\"M5 12h14M12 5l7 7-7 7\"/></svg>\r\n </div>\r\n </a>\r\n \r\n </nav>\r\n \r\n \r\n <div class=\"md-footer-meta md-typeset\">\r\n <div class=\"md-footer-meta__inner md-grid\">\r\n <div class=\"md-copyright\">\r\n \r\n <div class=\"md-copyright__highlight\">\r\n Copyright © 2026 OpenAfterHours\r\n </div>\r\n \r\n \r\n Made with\r\n <a href=\"https://zensical.org/\" target=\"_blank\" rel=\"noopener\">\r\n Zensical\r\n </a>\r\n \r\n</div>\r\n \r\n </div>\r\n </div>\r\n</footer>\r\n \r\n </div>\r\n <div class=\"md-dialog\" data-md-component=\"dialog\">\r\n <div class=\"md-dialog__inner md-typeset\"></div>\r\n </div>\r\n \r\n \r\n \r\n \r\n \r\n <script id=\"__config\" type=\"application/json\">{\"annotate\":null,\"base\":\"..\",\"features\":[\"content.code.copy\",\"navigation.footer\",\"navigation.indexes\",\"navigation.sections\",\"navigation.top\",\"search.highlight\",\"toc.follow\"],\"search\":\"../assets/javascripts/workers/search.e2d2d235.min.js\",\"tags\":null,\"translations\":{\"clipboard.copied\":\"Copied to clipboard\",\"clipboard.copy\":\"Copy to clipboard\",\"search.result.more.one\":\"1 more on this page\",\"search.result.more.other\":\"# more on this page\",\"search.result.none\":\"No matching documents\",\"search.result.one\":\"1 matching document\",\"search.result.other\":\"# matching documents\",\"search.result.placeholder\":\"Type to start searching\",\"search.result.term.missing\":\"Missing\",\"select.version\":\"Select version\"},\"version\":null}</script>\r\n \r\n \r\n <script src=\"../assets/javascripts/bundle.6e5f0216.min.js\"></script>\r\n \r\n \r\n </body>\r\n</html>",
|
|
3
|
+
"hash": 13935513871079926015
|
|
4
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": "\r\n<!doctype html>\r\n<html lang=\"en\" class=\"no-js\">\r\n <head>\r\n \r\n <meta charset=\"utf-8\">\r\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\r\n \r\n <meta name=\"description\" content=\"Local-first Python dependency & module-boundary checker. No network, no binaries, zero runtime deps.\">\r\n \r\n \r\n <meta name=\"author\" content=\"OpenAfterHours\">\r\n \r\n \r\n <link rel=\"canonical\" href=\"https://openafterhours.github.io/curfew/why/\">\r\n \r\n \r\n <link rel=\"prev\" href=\"./..\">\r\n \r\n \r\n <link rel=\"next\" href=\"../usage/\">\r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"icon\" href=\"../assets/images/favicon.png\">\r\n <meta name=\"generator\" content=\"zensical-0.0.44\">\r\n \r\n \r\n \r\n <title>Why curfew over tach? - curfew</title>\r\n \r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"stylesheet\" href=\"../assets/stylesheets/modern/main.fba56155.min.css\">\r\n \r\n \r\n \r\n \r\n <link rel=\"stylesheet\" href=\"../assets/stylesheets/modern/palette.dfe2e883.min.css\">\r\n \r\n \r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\r\n <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback\">\r\n <style>:root{--md-text-font:\"Inter\";--md-code-font:\"JetBrains Mono\"}</style>\r\n \r\n \r\n \r\n <script>__md_scope=new URL(\"..\",location),__md_scope.pathname.endsWith(\"/\")||(__md_scope=new URL(__md_scope.pathname+\"/\",location)),__md_hash=e=>[...e].reduce(((e,t)=>(e<<5)-e+t.charCodeAt(0)),0),__md_get=(e,t=localStorage,_=__md_scope)=>JSON.parse(t.getItem(_.pathname+\".\"+e)),__md_set=(e,t,_=localStorage,a=__md_scope)=>{try{_.setItem(a.pathname+\".\"+e,JSON.stringify(t))}catch(e){}},document.documentElement.setAttribute(\"data-platform\",navigator.platform)</script>\r\n \r\n \r\n\r\n \r\n \r\n </head>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <body dir=\"ltr\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\">\r\n \r\n \r\n <input class=\"md-toggle\" data-md-toggle=\"drawer\" type=\"checkbox\" id=\"__drawer\" autocomplete=\"off\">\r\n <input class=\"md-toggle\" data-md-toggle=\"search\" type=\"checkbox\" id=\"__search\" autocomplete=\"off\">\r\n <label class=\"md-overlay\" for=\"__drawer\" aria-label=\"Navigation\"></label>\r\n <div data-md-component=\"skip\">\r\n \r\n \r\n <a href=\"#why-curfew-over-tach\" class=\"md-skip\">\r\n Skip to content\r\n </a>\r\n \r\n </div>\r\n <div data-md-component=\"announce\">\r\n \r\n </div>\r\n \r\n \r\n \r\n\r\n \r\n\r\n<header class=\"md-header md-header--shadow\" data-md-component=\"header\">\r\n <nav class=\"md-header__inner md-grid\" aria-label=\"Header\">\r\n <a href=\"./..\" title=\"curfew\" class=\"md-header__button md-logo\" aria-label=\"curfew\" data-md-component=\"logo\">\r\n \r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\r\n\r\n </a>\r\n <label class=\"md-header__button md-icon\" for=\"__drawer\" aria-label=\"Navigation\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-menu\" viewBox=\"0 0 24 24\"><path d=\"M4 5h16M4 12h16M4 19h16\"/></svg>\r\n </label>\r\n <div class=\"md-header__title\" data-md-component=\"header-title\">\r\n <div class=\"md-header__ellipsis\">\r\n <div class=\"md-header__topic\">\r\n <span class=\"md-ellipsis\">\r\n curfew\r\n </span>\r\n </div>\r\n <div class=\"md-header__topic\" data-md-component=\"header-topic\">\r\n <span class=\"md-ellipsis\">\r\n \r\n Why curfew over tach?\r\n \r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n <form class=\"md-header__option\" data-md-component=\"palette\">\r\n \r\n \r\n \r\n \r\n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to dark mode\" type=\"radio\" name=\"__palette\" id=\"__palette_0\">\r\n \r\n <label class=\"md-header__button md-icon\" title=\"Switch to dark mode\" for=\"__palette_1\" hidden>\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-sun\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"/></svg>\r\n </label>\r\n \r\n \r\n \r\n \r\n \r\n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"slate\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to light mode\" type=\"radio\" name=\"__palette\" id=\"__palette_1\">\r\n \r\n <label class=\"md-header__button md-icon\" title=\"Switch to light mode\" for=\"__palette_0\" hidden>\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-moon\" viewBox=\"0 0 24 24\"><path d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\"/></svg>\r\n </label>\r\n \r\n \r\n</form>\r\n \r\n \r\n \r\n <script>var palette=__md_get(\"__palette\");if(palette&&palette.color){if(\"(prefers-color-scheme)\"===palette.color.media){var media=matchMedia(\"(prefers-color-scheme: light)\"),input=document.querySelector(media.matches?\"[data-md-color-media='(prefers-color-scheme: light)']\":\"[data-md-color-media='(prefers-color-scheme: dark)']\");palette.color.media=input.getAttribute(\"data-md-color-media\"),palette.color.scheme=input.getAttribute(\"data-md-color-scheme\"),palette.color.primary=input.getAttribute(\"data-md-color-primary\"),palette.color.accent=input.getAttribute(\"data-md-color-accent\")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute(\"data-md-color-\"+key,value)}</script>\r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-header__button md-icon\" for=\"__search\" aria-label=\"Search\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-search\" viewBox=\"0 0 24 24\"><path d=\"m21 21-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/></svg>\r\n </label>\r\n <div class=\"md-search\" data-md-component=\"search\" role=\"dialog\" aria-label=\"Search\">\r\n <button type=\"button\" class=\"md-search__button\">\r\n Search\r\n </button>\r\n</div>\r\n \r\n \r\n <div class=\"md-header__source\">\r\n \r\n <a href=\"https://github.com/OpenAfterHours/curfew\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\r\n <div class=\"md-source__icon md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\r\n </div>\r\n <div class=\"md-source__repository\">\r\n OpenAfterHours/curfew\r\n </div>\r\n</a>\r\n \r\n </div>\r\n </nav>\r\n \r\n</header>\r\n \r\n <div class=\"md-container\" data-md-component=\"container\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <main class=\"md-main\" data-md-component=\"main\">\r\n <div class=\"md-main__inner md-grid\">\r\n \r\n \r\n \r\n <div class=\"md-sidebar md-sidebar--primary\" data-md-component=\"sidebar\" data-md-type=\"navigation\" >\r\n <div class=\"md-sidebar__scrollwrap\">\r\n <div class=\"md-sidebar__inner\">\r\n \r\n\r\n\r\n\r\n<nav class=\"md-nav md-nav--primary\" aria-label=\"Navigation\" data-md-level=\"0\">\r\n <label class=\"md-nav__title\" for=\"__drawer\">\r\n <a href=\"./..\" title=\"curfew\" class=\"md-nav__button md-logo\" aria-label=\"curfew\" data-md-component=\"logo\">\r\n \r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\r\n\r\n </a>\r\n curfew\r\n </label>\r\n \r\n <div class=\"md-nav__source\">\r\n <a href=\"https://github.com/OpenAfterHours/curfew\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\r\n <div class=\"md-source__icon md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\r\n </div>\r\n <div class=\"md-source__repository\">\r\n OpenAfterHours/curfew\r\n </div>\r\n</a>\r\n </div>\r\n \r\n <ul class=\"md-nav__list\" data-md-scrollfix>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"./..\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Home\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item md-nav__item--active\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__link md-nav__link--active\" for=\"__toc\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Why curfew\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n </label>\r\n \r\n <a href=\"././\" class=\"md-nav__link md-nav__link--active\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Why curfew\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n \r\n \r\n\r\n\r\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__title\" for=\"__toc\">\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n On this page\r\n </label>\r\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#what-it-checks\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n What it checks\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#how-leakage-detection-works\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n How leakage detection works\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n </ul>\r\n \r\n</nav>\r\n \r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"../usage/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Usage\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"../configuration/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Configuration\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"../graph/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Dependency graph\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n </ul>\r\n</nav>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n \r\n <div class=\"md-sidebar md-sidebar--secondary\" data-md-component=\"sidebar\" data-md-type=\"toc\" >\r\n <div class=\"md-sidebar__scrollwrap\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <input class=\"md-nav__toggle md-toggle\" type=\"checkbox\" id=\"__toc\">\r\n <div class=\"md-sidebar-button__wrapper\">\r\n <label class=\"md-sidebar-button\" for=\"__toc\"></label>\r\n </div>\r\n \r\n \r\n <div class=\"md-sidebar__inner\">\r\n \r\n\r\n\r\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__title\" for=\"__toc\">\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n On this page\r\n </label>\r\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#what-it-checks\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n What it checks\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#how-leakage-detection-works\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n How leakage detection works\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n </ul>\r\n \r\n</nav>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n \r\n <div class=\"md-content\" data-md-component=\"content\">\r\n \r\n <article class=\"md-content__inner md-typeset\">\r\n \r\n \r\n \r\n \r\n\r\n\r\n<h1 id=\"why-curfew-over-tach\">Why curfew over tach?<a class=\"headerlink\" href=\"#why-curfew-over-tach\" title=\"Permanent link\">¶</a></h1>\n<p><a href=\"https://github.com/gauge-sh/tach\"><code>tach</code></a> is excellent, but its graph\nvisualisation has two options that don't fit a locked-down bank/regulatory\nenvironment:</p>\n<ul>\n<li><code>tach show --web</code> <strong>uploads your module structure to a remote web viewer</strong>.</li>\n<li>The DOT output needs the <strong>GraphViz binary installed</strong> to render.</li>\n</ul>\n<p>curfew is <strong>local by construction</strong>:</p>\n<ul>\n<li><strong>No network, ever.</strong> No telemetry, no remote rendering, no \"phone home\".\n This is the headline differentiator.</li>\n<li><strong>Zero runtime dependencies.</strong> <code>uv tool install curfew</code> pulls in nothing — the\n engine and CLI run on the standard library alone. Colour is an optional\n <code>[rich]</code> extra.</li>\n<li><strong>No mandatory binaries.</strong> The default graph output is <strong>Mermaid</strong>, which\n renders in GitHub and MkDocs/Zensical with nothing installed. DOT is offered\n for those who already have GraphViz, but is never required.</li>\n<li><strong>One tool, both checks.</strong> Module boundaries <em>and</em> workspace/dependency\n validation share one import graph.</li>\n</ul>\n<h2 id=\"what-it-checks\">What it checks<a class=\"headerlink\" href=\"#what-it-checks\" title=\"Permanent link\">¶</a></h2>\n<p>The engine builds one static import graph (via <code>ast</code>, never executing your code),\nclassifies every edge, and runs two rule-sets:</p>\n<table>\n<thead>\n<tr>\n<th>Check</th>\n<th>Finds</th>\n<th>Severity</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Module boundary</td>\n<td>a module importing one it isn't allowed to</td>\n<td>error</td>\n</tr>\n<tr>\n<td></td>\n<td>importing a deprecated dependency</td>\n<td>warning</td>\n</tr>\n<tr>\n<td></td>\n<td>bypassing a module's public interface</td>\n<td>error</td>\n</tr>\n<tr>\n<td>External / workspace</td>\n<td>importing an undeclared third-party distribution</td>\n<td>error</td>\n</tr>\n<tr>\n<td></td>\n<td><strong>workspace leakage</strong> (resolves only via a sibling's dep)</td>\n<td>error</td>\n</tr>\n<tr>\n<td></td>\n<td>a declared dependency that is never imported</td>\n<td>warning</td>\n</tr>\n<tr>\n<td></td>\n<td>an import that is neither stdlib, first-party, nor installed</td>\n<td>warning</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"how-leakage-detection-works\">How leakage detection works<a class=\"headerlink\" href=\"#how-leakage-detection-works\" title=\"Permanent link\">¶</a></h2>\n<p>In a uv workspace, every member shares one resolved virtual environment. That\nmakes it easy for member <strong>web</strong> to <code>import requests</code> and have it work — even\nthough only sibling <strong>core</strong> declared <code>requests</code>. The day <strong>core</strong> drops it,\n<strong>web</strong> breaks. curfew flags that as <code>ext.leak</code>: imported, not declared by me,\nbut declared by a sibling. The distribution↔import-name mapping uses\n<code>importlib.metadata.packages_distributions()</code> exclusively, so <code>import yaml</code> is\ncorrectly attributed to <code>PyYAML</code>, <code>sklearn</code> to <code>scikit-learn</code>, and so on.</p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n \r\n </article>\r\n </div>\r\n \r\n \r\n<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith(\"__tabbed_\"))</script>\r\n </div>\r\n \r\n <button type=\"button\" class=\"md-top md-icon\" data-md-component=\"top\" hidden>\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-circle-arrow-up\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m16 12-4-4-4 4M12 16V8\"/></svg>\r\n Back to top\r\n</button>\r\n \r\n </main>\r\n \r\n <footer class=\"md-footer\">\r\n \r\n \r\n \r\n <nav class=\"md-footer__inner md-grid\" aria-label=\"Footer\" >\r\n \r\n \r\n <a href=\"./..\" class=\"md-footer__link md-footer__link--prev\" aria-label=\"Previous: Home\">\r\n <div class=\"md-footer__button md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-arrow-left\" viewBox=\"0 0 24 24\"><path d=\"m12 19-7-7 7-7M19 12H5\"/></svg>\r\n </div>\r\n <div class=\"md-footer__title\">\r\n <span class=\"md-footer__direction\">\r\n Previous\r\n </span>\r\n <div class=\"md-ellipsis\">\r\n Home\r\n </div>\r\n </div>\r\n </a>\r\n \r\n \r\n \r\n <a href=\"../usage/\" class=\"md-footer__link md-footer__link--next\" aria-label=\"Next: Usage\">\r\n <div class=\"md-footer__title\">\r\n <span class=\"md-footer__direction\">\r\n Next\r\n </span>\r\n <div class=\"md-ellipsis\">\r\n Usage\r\n </div>\r\n </div>\r\n <div class=\"md-footer__button md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-arrow-right\" viewBox=\"0 0 24 24\"><path d=\"M5 12h14M12 5l7 7-7 7\"/></svg>\r\n </div>\r\n </a>\r\n \r\n </nav>\r\n \r\n \r\n <div class=\"md-footer-meta md-typeset\">\r\n <div class=\"md-footer-meta__inner md-grid\">\r\n <div class=\"md-copyright\">\r\n \r\n <div class=\"md-copyright__highlight\">\r\n Copyright © 2026 OpenAfterHours\r\n </div>\r\n \r\n \r\n Made with\r\n <a href=\"https://zensical.org/\" target=\"_blank\" rel=\"noopener\">\r\n Zensical\r\n </a>\r\n \r\n</div>\r\n \r\n </div>\r\n </div>\r\n</footer>\r\n \r\n </div>\r\n <div class=\"md-dialog\" data-md-component=\"dialog\">\r\n <div class=\"md-dialog__inner md-typeset\"></div>\r\n </div>\r\n \r\n \r\n \r\n \r\n \r\n <script id=\"__config\" type=\"application/json\">{\"annotate\":null,\"base\":\"..\",\"features\":[\"content.code.copy\",\"navigation.footer\",\"navigation.indexes\",\"navigation.sections\",\"navigation.top\",\"search.highlight\",\"toc.follow\"],\"search\":\"../assets/javascripts/workers/search.e2d2d235.min.js\",\"tags\":null,\"translations\":{\"clipboard.copied\":\"Copied to clipboard\",\"clipboard.copy\":\"Copy to clipboard\",\"search.result.more.one\":\"1 more on this page\",\"search.result.more.other\":\"# more on this page\",\"search.result.none\":\"No matching documents\",\"search.result.one\":\"1 matching document\",\"search.result.other\":\"# matching documents\",\"search.result.placeholder\":\"Type to start searching\",\"search.result.term.missing\":\"Missing\",\"select.version\":\"Select version\"},\"version\":null}</script>\r\n \r\n \r\n <script src=\"../assets/javascripts/bundle.6e5f0216.min.js\"></script>\r\n \r\n \r\n </body>\r\n</html>",
|
|
3
|
+
"hash": 14057790315829513202
|
|
4
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": {
|
|
3
|
+
"meta": {},
|
|
4
|
+
"content": "<h1 id=\"why-curfew-over-tach\">Why curfew over tach?<a class=\"headerlink\" href=\"#why-curfew-over-tach\" title=\"Permanent link\">¶</a></h1>\n<p><a href=\"https://github.com/gauge-sh/tach\"><code>tach</code></a> is excellent, but its graph\nvisualisation has two options that don't fit a locked-down bank/regulatory\nenvironment:</p>\n<ul>\n<li><code>tach show --web</code> <strong>uploads your module structure to a remote web viewer</strong>.</li>\n<li>The DOT output needs the <strong>GraphViz binary installed</strong> to render.</li>\n</ul>\n<p>curfew is <strong>local by construction</strong>:</p>\n<ul>\n<li><strong>No network, ever.</strong> No telemetry, no remote rendering, no \"phone home\".\n This is the headline differentiator.</li>\n<li><strong>Zero runtime dependencies.</strong> <code>uv tool install curfew</code> pulls in nothing — the\n engine and CLI run on the standard library alone. Colour is an optional\n <code>[rich]</code> extra.</li>\n<li><strong>No mandatory binaries.</strong> The default graph output is <strong>Mermaid</strong>, which\n renders in GitHub and MkDocs/Zensical with nothing installed. DOT is offered\n for those who already have GraphViz, but is never required.</li>\n<li><strong>One tool, both checks.</strong> Module boundaries <em>and</em> workspace/dependency\n validation share one import graph.</li>\n</ul>\n<h2 id=\"what-it-checks\">What it checks<a class=\"headerlink\" href=\"#what-it-checks\" title=\"Permanent link\">¶</a></h2>\n<p>The engine builds one static import graph (via <code>ast</code>, never executing your code),\nclassifies every edge, and runs two rule-sets:</p>\n<table>\n<thead>\n<tr>\n<th>Check</th>\n<th>Finds</th>\n<th>Severity</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Module boundary</td>\n<td>a module importing one it isn't allowed to</td>\n<td>error</td>\n</tr>\n<tr>\n<td></td>\n<td>importing a deprecated dependency</td>\n<td>warning</td>\n</tr>\n<tr>\n<td></td>\n<td>bypassing a module's public interface</td>\n<td>error</td>\n</tr>\n<tr>\n<td>External / workspace</td>\n<td>importing an undeclared third-party distribution</td>\n<td>error</td>\n</tr>\n<tr>\n<td></td>\n<td><strong>workspace leakage</strong> (resolves only via a sibling's dep)</td>\n<td>error</td>\n</tr>\n<tr>\n<td></td>\n<td>a declared dependency that is never imported</td>\n<td>warning</td>\n</tr>\n<tr>\n<td></td>\n<td>an import that is neither stdlib, first-party, nor installed</td>\n<td>warning</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"how-leakage-detection-works\">How leakage detection works<a class=\"headerlink\" href=\"#how-leakage-detection-works\" title=\"Permanent link\">¶</a></h2>\n<p>In a uv workspace, every member shares one resolved virtual environment. That\nmakes it easy for member <strong>web</strong> to <code>import requests</code> and have it work — even\nthough only sibling <strong>core</strong> declared <code>requests</code>. The day <strong>core</strong> drops it,\n<strong>web</strong> breaks. curfew flags that as <code>ext.leak</code>: imported, not declared by me,\nbut declared by a sibling. The distribution↔import-name mapping uses\n<code>importlib.metadata.packages_distributions()</code> exclusively, so <code>import yaml</code> is\ncorrectly attributed to <code>PyYAML</code>, <code>sklearn</code> to <code>scikit-learn</code>, and so on.</p>",
|
|
5
|
+
"search": [
|
|
6
|
+
{
|
|
7
|
+
"location": null,
|
|
8
|
+
"level": 1,
|
|
9
|
+
"title": "Why curfew over tach?",
|
|
10
|
+
"text": "<p><code>tach</code> is excellent, but its graph visualisation has two options that don't fit a locked-down bank/regulatory environment:</p> <ul> <li><code>tach show --web</code> uploads your module structure to a remote web viewer.</li> <li>The DOT output needs the GraphViz binary installed to render.</li> </ul> <p>curfew is local by construction:</p> <ul> <li>No network, ever. No telemetry, no remote rendering, no \"phone home\". This is the headline differentiator.</li> <li>Zero runtime dependencies. <code>uv tool install curfew</code> pulls in nothing — the engine and CLI run on the standard library alone. Colour is an optional <code>[rich]</code> extra.</li> <li>No mandatory binaries. The default graph output is Mermaid, which renders in GitHub and MkDocs/Zensical with nothing installed. DOT is offered for those who already have GraphViz, but is never required.</li> <li>One tool, both checks. Module boundaries and workspace/dependency validation share one import graph.</li> </ul>",
|
|
11
|
+
"path": [],
|
|
12
|
+
"tags": []
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"location": "what-it-checks",
|
|
16
|
+
"level": 2,
|
|
17
|
+
"title": "What it checks",
|
|
18
|
+
"text": "<p>The engine builds one static import graph (via <code>ast</code>, never executing your code), classifies every edge, and runs two rule-sets:</p> Check Finds Severity Module boundary a module importing one it isn't allowed to error importing a deprecated dependency warning bypassing a module's public interface error External / workspace importing an undeclared third-party distribution error workspace leakage (resolves only via a sibling's dep) error a declared dependency that is never imported warning an import that is neither stdlib, first-party, nor installed warning",
|
|
19
|
+
"path": [],
|
|
20
|
+
"tags": []
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"location": "how-leakage-detection-works",
|
|
24
|
+
"level": 2,
|
|
25
|
+
"title": "How leakage detection works",
|
|
26
|
+
"text": "<p>In a uv workspace, every member shares one resolved virtual environment. That makes it easy for member web to <code>import requests</code> and have it work — even though only sibling core declared <code>requests</code>. The day core drops it, web breaks. curfew flags that as <code>ext.leak</code>: imported, not declared by me, but declared by a sibling. The distribution↔import-name mapping uses <code>importlib.metadata.packages_distributions()</code> exclusively, so <code>import yaml</code> is correctly attributed to <code>PyYAML</code>, <code>sklearn</code> to <code>scikit-learn</code>, and so on.</p>",
|
|
27
|
+
"path": [],
|
|
28
|
+
"tags": []
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"title": "Why curfew over tach?",
|
|
32
|
+
"toc": [
|
|
33
|
+
{
|
|
34
|
+
"title": "Why curfew over tach?",
|
|
35
|
+
"content": "Why curfew over tach?",
|
|
36
|
+
"id": "why-curfew-over-tach",
|
|
37
|
+
"url": "#why-curfew-over-tach",
|
|
38
|
+
"children": [
|
|
39
|
+
{
|
|
40
|
+
"title": "What it checks",
|
|
41
|
+
"content": "What it checks",
|
|
42
|
+
"id": "what-it-checks",
|
|
43
|
+
"url": "#what-it-checks",
|
|
44
|
+
"children": [],
|
|
45
|
+
"level": 2
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"title": "How leakage detection works",
|
|
49
|
+
"content": "How leakage detection works",
|
|
50
|
+
"id": "how-leakage-detection-works",
|
|
51
|
+
"url": "#how-leakage-detection-works",
|
|
52
|
+
"children": [],
|
|
53
|
+
"level": 2
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"level": 1
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"hash": 6954734319592295309
|
|
61
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": "\r\n<!doctype html>\r\n<html lang=\"en\" class=\"no-js\">\r\n <head>\r\n \r\n <meta charset=\"utf-8\">\r\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\r\n \r\n <meta name=\"description\" content=\"Local-first Python dependency & module-boundary checker. No network, no binaries, zero runtime deps.\">\r\n \r\n \r\n <meta name=\"author\" content=\"OpenAfterHours\">\r\n \r\n \r\n <link rel=\"canonical\" href=\"https://openafterhours.github.io/curfew/configuration/\">\r\n \r\n \r\n <link rel=\"prev\" href=\"../usage/\">\r\n \r\n \r\n <link rel=\"next\" href=\"../graph/\">\r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"icon\" href=\"../assets/images/favicon.png\">\r\n <meta name=\"generator\" content=\"zensical-0.0.44\">\r\n \r\n \r\n \r\n <title>Configuration - curfew</title>\r\n \r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"stylesheet\" href=\"../assets/stylesheets/modern/main.fba56155.min.css\">\r\n \r\n \r\n \r\n \r\n <link rel=\"stylesheet\" href=\"../assets/stylesheets/modern/palette.dfe2e883.min.css\">\r\n \r\n \r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\r\n <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback\">\r\n <style>:root{--md-text-font:\"Inter\";--md-code-font:\"JetBrains Mono\"}</style>\r\n \r\n \r\n \r\n <script>__md_scope=new URL(\"..\",location),__md_scope.pathname.endsWith(\"/\")||(__md_scope=new URL(__md_scope.pathname+\"/\",location)),__md_hash=e=>[...e].reduce(((e,t)=>(e<<5)-e+t.charCodeAt(0)),0),__md_get=(e,t=localStorage,_=__md_scope)=>JSON.parse(t.getItem(_.pathname+\".\"+e)),__md_set=(e,t,_=localStorage,a=__md_scope)=>{try{_.setItem(a.pathname+\".\"+e,JSON.stringify(t))}catch(e){}},document.documentElement.setAttribute(\"data-platform\",navigator.platform)</script>\r\n \r\n \r\n\r\n \r\n \r\n </head>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <body dir=\"ltr\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\">\r\n \r\n \r\n <input class=\"md-toggle\" data-md-toggle=\"drawer\" type=\"checkbox\" id=\"__drawer\" autocomplete=\"off\">\r\n <input class=\"md-toggle\" data-md-toggle=\"search\" type=\"checkbox\" id=\"__search\" autocomplete=\"off\">\r\n <label class=\"md-overlay\" for=\"__drawer\" aria-label=\"Navigation\"></label>\r\n <div data-md-component=\"skip\">\r\n \r\n \r\n <a href=\"#configuration\" class=\"md-skip\">\r\n Skip to content\r\n </a>\r\n \r\n </div>\r\n <div data-md-component=\"announce\">\r\n \r\n </div>\r\n \r\n \r\n \r\n\r\n \r\n\r\n<header class=\"md-header md-header--shadow\" data-md-component=\"header\">\r\n <nav class=\"md-header__inner md-grid\" aria-label=\"Header\">\r\n <a href=\"./..\" title=\"curfew\" class=\"md-header__button md-logo\" aria-label=\"curfew\" data-md-component=\"logo\">\r\n \r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\r\n\r\n </a>\r\n <label class=\"md-header__button md-icon\" for=\"__drawer\" aria-label=\"Navigation\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-menu\" viewBox=\"0 0 24 24\"><path d=\"M4 5h16M4 12h16M4 19h16\"/></svg>\r\n </label>\r\n <div class=\"md-header__title\" data-md-component=\"header-title\">\r\n <div class=\"md-header__ellipsis\">\r\n <div class=\"md-header__topic\">\r\n <span class=\"md-ellipsis\">\r\n curfew\r\n </span>\r\n </div>\r\n <div class=\"md-header__topic\" data-md-component=\"header-topic\">\r\n <span class=\"md-ellipsis\">\r\n \r\n Configuration\r\n \r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n <form class=\"md-header__option\" data-md-component=\"palette\">\r\n \r\n \r\n \r\n \r\n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to dark mode\" type=\"radio\" name=\"__palette\" id=\"__palette_0\">\r\n \r\n <label class=\"md-header__button md-icon\" title=\"Switch to dark mode\" for=\"__palette_1\" hidden>\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-sun\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"/></svg>\r\n </label>\r\n \r\n \r\n \r\n \r\n \r\n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"slate\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to light mode\" type=\"radio\" name=\"__palette\" id=\"__palette_1\">\r\n \r\n <label class=\"md-header__button md-icon\" title=\"Switch to light mode\" for=\"__palette_0\" hidden>\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-moon\" viewBox=\"0 0 24 24\"><path d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\"/></svg>\r\n </label>\r\n \r\n \r\n</form>\r\n \r\n \r\n \r\n <script>var palette=__md_get(\"__palette\");if(palette&&palette.color){if(\"(prefers-color-scheme)\"===palette.color.media){var media=matchMedia(\"(prefers-color-scheme: light)\"),input=document.querySelector(media.matches?\"[data-md-color-media='(prefers-color-scheme: light)']\":\"[data-md-color-media='(prefers-color-scheme: dark)']\");palette.color.media=input.getAttribute(\"data-md-color-media\"),palette.color.scheme=input.getAttribute(\"data-md-color-scheme\"),palette.color.primary=input.getAttribute(\"data-md-color-primary\"),palette.color.accent=input.getAttribute(\"data-md-color-accent\")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute(\"data-md-color-\"+key,value)}</script>\r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-header__button md-icon\" for=\"__search\" aria-label=\"Search\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-search\" viewBox=\"0 0 24 24\"><path d=\"m21 21-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/></svg>\r\n </label>\r\n <div class=\"md-search\" data-md-component=\"search\" role=\"dialog\" aria-label=\"Search\">\r\n <button type=\"button\" class=\"md-search__button\">\r\n Search\r\n </button>\r\n</div>\r\n \r\n \r\n <div class=\"md-header__source\">\r\n \r\n <a href=\"https://github.com/OpenAfterHours/curfew\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\r\n <div class=\"md-source__icon md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\r\n </div>\r\n <div class=\"md-source__repository\">\r\n OpenAfterHours/curfew\r\n </div>\r\n</a>\r\n \r\n </div>\r\n </nav>\r\n \r\n</header>\r\n \r\n <div class=\"md-container\" data-md-component=\"container\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <main class=\"md-main\" data-md-component=\"main\">\r\n <div class=\"md-main__inner md-grid\">\r\n \r\n \r\n \r\n <div class=\"md-sidebar md-sidebar--primary\" data-md-component=\"sidebar\" data-md-type=\"navigation\" >\r\n <div class=\"md-sidebar__scrollwrap\">\r\n <div class=\"md-sidebar__inner\">\r\n \r\n\r\n\r\n\r\n<nav class=\"md-nav md-nav--primary\" aria-label=\"Navigation\" data-md-level=\"0\">\r\n <label class=\"md-nav__title\" for=\"__drawer\">\r\n <a href=\"./..\" title=\"curfew\" class=\"md-nav__button md-logo\" aria-label=\"curfew\" data-md-component=\"logo\">\r\n \r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\r\n\r\n </a>\r\n curfew\r\n </label>\r\n \r\n <div class=\"md-nav__source\">\r\n <a href=\"https://github.com/OpenAfterHours/curfew\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\r\n <div class=\"md-source__icon md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\r\n </div>\r\n <div class=\"md-source__repository\">\r\n OpenAfterHours/curfew\r\n </div>\r\n</a>\r\n </div>\r\n \r\n <ul class=\"md-nav__list\" data-md-scrollfix>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"./..\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Home\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"../why/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Why curfew\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"../usage/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Usage\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item md-nav__item--active\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__link md-nav__link--active\" for=\"__toc\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Configuration\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n </label>\r\n \r\n <a href=\"././\" class=\"md-nav__link md-nav__link--active\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Configuration\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n \r\n \r\n\r\n\r\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__title\" for=\"__toc\">\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n On this page\r\n </label>\r\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#module-patterns\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n Module patterns\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#boundary-semantics\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n Boundary semantics\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#a-note-on-curfew-dogfooding-itself\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n A note on curfew dogfooding itself\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n </ul>\r\n \r\n</nav>\r\n \r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"../graph/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Dependency graph\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n </ul>\r\n</nav>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n \r\n <div class=\"md-sidebar md-sidebar--secondary\" data-md-component=\"sidebar\" data-md-type=\"toc\" >\r\n <div class=\"md-sidebar__scrollwrap\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <input class=\"md-nav__toggle md-toggle\" type=\"checkbox\" id=\"__toc\">\r\n <div class=\"md-sidebar-button__wrapper\">\r\n <label class=\"md-sidebar-button\" for=\"__toc\"></label>\r\n </div>\r\n \r\n \r\n <div class=\"md-sidebar__inner\">\r\n \r\n\r\n\r\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__title\" for=\"__toc\">\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n On this page\r\n </label>\r\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#module-patterns\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n Module patterns\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#boundary-semantics\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n Boundary semantics\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#a-note-on-curfew-dogfooding-itself\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n A note on curfew dogfooding itself\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n </ul>\r\n \r\n</nav>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n \r\n <div class=\"md-content\" data-md-component=\"content\">\r\n \r\n <article class=\"md-content__inner md-typeset\">\r\n \r\n \r\n \r\n \r\n\r\n\r\n<h1 id=\"configuration\">Configuration<a class=\"headerlink\" href=\"#configuration\" title=\"Permanent link\">¶</a></h1>\n<p>Configuration lives in a <code>[tool.curfew]</code> table in <code>pyproject.toml</code> (uv-native).\nAn optional standalone <code>curfew.toml</code> is deep-merged on top of it (top-level keys,\nno <code>[tool.curfew]</code> prefix).</p>\n<div class=\"language-toml highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"k\">[tool.curfew]</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a><span class=\"n\">source_roots</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">"src"</span><span class=\"p\">]</span><span class=\"w\"> </span><span class=\"c1\"># applies to the workspace root package</span>\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a><span class=\"n\">default_deny</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"kc\">false</span><span class=\"w\"> </span><span class=\"c1\"># true => every first-party module must have a rule</span>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a><span class=\"n\">known_first_party</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[]</span><span class=\"w\"> </span><span class=\"c1\"># escape hatch for code generated outside source roots</span>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a><span class=\"n\">ignore_modules</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[]</span><span class=\"w\"> </span><span class=\"c1\"># import names never flagged as unresolved</span>\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a>\n</span><span id=\"__span-0-7\"><a id=\"__codelineno-0-7\" name=\"__codelineno-0-7\" href=\"#__codelineno-0-7\"></a><span class=\"k\">[tool.curfew.modules.</span><span class=\"s2\">"rwa_calc.core"</span><span class=\"k\">]</span>\n</span><span id=\"__span-0-8\"><a id=\"__codelineno-0-8\" name=\"__codelineno-0-8\" href=\"#__codelineno-0-8\"></a><span class=\"n\">depends_on</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[]</span><span class=\"w\"> </span><span class=\"c1\"># leaf — may import nothing first-party</span>\n</span><span id=\"__span-0-9\"><a id=\"__codelineno-0-9\" name=\"__codelineno-0-9\" href=\"#__codelineno-0-9\"></a><span class=\"n\">interface</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">"api"</span><span class=\"p\">]</span><span class=\"w\"> </span><span class=\"c1\"># only rwa_calc.core.api is public</span>\n</span><span id=\"__span-0-10\"><a id=\"__codelineno-0-10\" name=\"__codelineno-0-10\" href=\"#__codelineno-0-10\"></a><span class=\"n\">interface_enforced</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"kc\">true</span><span class=\"w\"> </span><span class=\"c1\"># defaults to true when an interface is declared</span>\n</span><span id=\"__span-0-11\"><a id=\"__codelineno-0-11\" name=\"__codelineno-0-11\" href=\"#__codelineno-0-11\"></a>\n</span><span id=\"__span-0-12\"><a id=\"__codelineno-0-12\" name=\"__codelineno-0-12\" href=\"#__codelineno-0-12\"></a><span class=\"k\">[tool.curfew.modules.</span><span class=\"s2\">"rwa_calc.io"</span><span class=\"k\">]</span>\n</span><span id=\"__span-0-13\"><a id=\"__codelineno-0-13\" name=\"__codelineno-0-13\" href=\"#__codelineno-0-13\"></a><span class=\"n\">depends_on</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">"rwa_calc.core"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"s2\">"rwa_calc.types"</span><span class=\"p\">]</span>\n</span><span id=\"__span-0-14\"><a id=\"__codelineno-0-14\" name=\"__codelineno-0-14\" href=\"#__codelineno-0-14\"></a><span class=\"n\">deprecated_on</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">"rwa_calc.legacy"</span><span class=\"p\">]</span><span class=\"w\"> </span><span class=\"c1\"># allowed but reported as a warning (burndown)</span>\n</span><span id=\"__span-0-15\"><a id=\"__codelineno-0-15\" name=\"__codelineno-0-15\" href=\"#__codelineno-0-15\"></a>\n</span><span id=\"__span-0-16\"><a id=\"__codelineno-0-16\" name=\"__codelineno-0-16\" href=\"#__codelineno-0-16\"></a><span class=\"k\">[tool.curfew.external]</span>\n</span><span id=\"__span-0-17\"><a id=\"__codelineno-0-17\" name=\"__codelineno-0-17\" href=\"#__codelineno-0-17\"></a><span class=\"n\">ignore</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">"setuptools"</span><span class=\"p\">]</span><span class=\"w\"> </span><span class=\"c1\"># never flag as undeclared</span>\n</span><span id=\"__span-0-18\"><a id=\"__codelineno-0-18\" name=\"__codelineno-0-18\" href=\"#__codelineno-0-18\"></a><span class=\"n\">ignore_unused</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[]</span><span class=\"w\"> </span><span class=\"c1\"># never flag as unused</span>\n</span><span id=\"__span-0-19\"><a id=\"__codelineno-0-19\" name=\"__codelineno-0-19\" href=\"#__codelineno-0-19\"></a>\n</span><span id=\"__span-0-20\"><a id=\"__codelineno-0-20\" name=\"__codelineno-0-20\" href=\"#__codelineno-0-20\"></a><span class=\"k\">[tool.curfew.check]</span>\n</span><span id=\"__span-0-21\"><a id=\"__codelineno-0-21\" name=\"__codelineno-0-21\" href=\"#__codelineno-0-21\"></a><span class=\"n\">boundaries</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"kc\">true</span>\n</span><span id=\"__span-0-22\"><a id=\"__codelineno-0-22\" name=\"__codelineno-0-22\" href=\"#__codelineno-0-22\"></a><span class=\"n\">externals</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"kc\">true</span>\n</span></code></pre></div>\n<h2 id=\"module-patterns\">Module patterns<a class=\"headerlink\" href=\"#module-patterns\" title=\"Permanent link\">¶</a></h2>\n<p>A pattern matches a module by <strong>prefix</strong>, guarded at a dot boundary so\n<code>rwa_calc.io</code> never matches <code>rwa_calc.iolib</code>:</p>\n<table>\n<thead>\n<tr>\n<th>Pattern</th>\n<th>Matches</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>pkg.core</code></td>\n<td><code>pkg.core</code> and everything under it (subtree)</td>\n</tr>\n<tr>\n<td><code>pkg.core.*</code></td>\n<td>same as above (explicit subtree sugar)</td>\n</tr>\n<tr>\n<td><code>pkg.core!</code></td>\n<td>only <code>pkg.core</code> exactly</td>\n</tr>\n</tbody>\n</table>\n<p>When several rules match, the <strong>longest prefix wins</strong> (an exact rule beats a\nsubtree rule on a tie).</p>\n<h2 id=\"boundary-semantics\">Boundary semantics<a class=\"headerlink\" href=\"#boundary-semantics\" title=\"Permanent link\">¶</a></h2>\n<ul>\n<li>A module that <strong>has</strong> a rule is default-deny: it may import only its\n <code>depends_on</code> targets plus its own subtree.</li>\n<li>A module with <strong>no</strong> rule is unrestricted — so you can adopt curfew\n incrementally. Set <code>default_deny = true</code> to require a rule on every first-party\n module (ungoverned modules may then only import within their own package).</li>\n<li>A <strong>public interface</strong> lists the names a package exposes. Importing a strict\n descendant that isn't in the interface — or a <code>from pkg import name</code> whose name\n isn't listed — is a bypass (error).</li>\n<li>A <strong>deprecated</strong> dependency is allowed but reported as a warning, so you can\n burn it down over time.</li>\n</ul>\n<h2 id=\"a-note-on-curfew-dogfooding-itself\">A note on curfew dogfooding itself<a class=\"headerlink\" href=\"#a-note-on-curfew-dogfooding-itself\" title=\"Permanent link\">¶</a></h2>\n<p>curfew's own <code>pyproject.toml</code> carries a <code>[tool.curfew]</code> block that documents and\nenforces its layering: <code>model</code> and <code>errors</code> are leaves; the engine layers build\nup; <code>cli</code> is the orchestrator. <code>curfew check</code> runs against curfew in CI.</p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n \r\n </article>\r\n </div>\r\n \r\n \r\n<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith(\"__tabbed_\"))</script>\r\n </div>\r\n \r\n <button type=\"button\" class=\"md-top md-icon\" data-md-component=\"top\" hidden>\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-circle-arrow-up\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m16 12-4-4-4 4M12 16V8\"/></svg>\r\n Back to top\r\n</button>\r\n \r\n </main>\r\n \r\n <footer class=\"md-footer\">\r\n \r\n \r\n \r\n <nav class=\"md-footer__inner md-grid\" aria-label=\"Footer\" >\r\n \r\n \r\n <a href=\"../usage/\" class=\"md-footer__link md-footer__link--prev\" aria-label=\"Previous: Usage\">\r\n <div class=\"md-footer__button md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-arrow-left\" viewBox=\"0 0 24 24\"><path d=\"m12 19-7-7 7-7M19 12H5\"/></svg>\r\n </div>\r\n <div class=\"md-footer__title\">\r\n <span class=\"md-footer__direction\">\r\n Previous\r\n </span>\r\n <div class=\"md-ellipsis\">\r\n Usage\r\n </div>\r\n </div>\r\n </a>\r\n \r\n \r\n \r\n <a href=\"../graph/\" class=\"md-footer__link md-footer__link--next\" aria-label=\"Next: Dependency graph\">\r\n <div class=\"md-footer__title\">\r\n <span class=\"md-footer__direction\">\r\n Next\r\n </span>\r\n <div class=\"md-ellipsis\">\r\n Dependency graph\r\n </div>\r\n </div>\r\n <div class=\"md-footer__button md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-arrow-right\" viewBox=\"0 0 24 24\"><path d=\"M5 12h14M12 5l7 7-7 7\"/></svg>\r\n </div>\r\n </a>\r\n \r\n </nav>\r\n \r\n \r\n <div class=\"md-footer-meta md-typeset\">\r\n <div class=\"md-footer-meta__inner md-grid\">\r\n <div class=\"md-copyright\">\r\n \r\n <div class=\"md-copyright__highlight\">\r\n Copyright © 2026 OpenAfterHours\r\n </div>\r\n \r\n \r\n Made with\r\n <a href=\"https://zensical.org/\" target=\"_blank\" rel=\"noopener\">\r\n Zensical\r\n </a>\r\n \r\n</div>\r\n \r\n </div>\r\n </div>\r\n</footer>\r\n \r\n </div>\r\n <div class=\"md-dialog\" data-md-component=\"dialog\">\r\n <div class=\"md-dialog__inner md-typeset\"></div>\r\n </div>\r\n \r\n \r\n \r\n \r\n \r\n <script id=\"__config\" type=\"application/json\">{\"annotate\":null,\"base\":\"..\",\"features\":[\"content.code.copy\",\"navigation.footer\",\"navigation.indexes\",\"navigation.sections\",\"navigation.top\",\"search.highlight\",\"toc.follow\"],\"search\":\"../assets/javascripts/workers/search.e2d2d235.min.js\",\"tags\":null,\"translations\":{\"clipboard.copied\":\"Copied to clipboard\",\"clipboard.copy\":\"Copy to clipboard\",\"search.result.more.one\":\"1 more on this page\",\"search.result.more.other\":\"# more on this page\",\"search.result.none\":\"No matching documents\",\"search.result.one\":\"1 matching document\",\"search.result.other\":\"# matching documents\",\"search.result.placeholder\":\"Type to start searching\",\"search.result.term.missing\":\"Missing\",\"select.version\":\"Select version\"},\"version\":null}</script>\r\n \r\n \r\n <script src=\"../assets/javascripts/bundle.6e5f0216.min.js\"></script>\r\n \r\n \r\n </body>\r\n</html>",
|
|
3
|
+
"hash": 10791003594825420270
|
|
4
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": {
|
|
3
|
+
"meta": {},
|
|
4
|
+
"content": "<h1 id=\"configuration\">Configuration<a class=\"headerlink\" href=\"#configuration\" title=\"Permanent link\">¶</a></h1>\n<p>Configuration lives in a <code>[tool.curfew]</code> table in <code>pyproject.toml</code> (uv-native).\nAn optional standalone <code>curfew.toml</code> is deep-merged on top of it (top-level keys,\nno <code>[tool.curfew]</code> prefix).</p>\n<div class=\"language-toml highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"k\">[tool.curfew]</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a><span class=\"n\">source_roots</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">"src"</span><span class=\"p\">]</span><span class=\"w\"> </span><span class=\"c1\"># applies to the workspace root package</span>\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a><span class=\"n\">default_deny</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"kc\">false</span><span class=\"w\"> </span><span class=\"c1\"># true => every first-party module must have a rule</span>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a><span class=\"n\">known_first_party</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[]</span><span class=\"w\"> </span><span class=\"c1\"># escape hatch for code generated outside source roots</span>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a><span class=\"n\">ignore_modules</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[]</span><span class=\"w\"> </span><span class=\"c1\"># import names never flagged as unresolved</span>\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a>\n</span><span id=\"__span-0-7\"><a id=\"__codelineno-0-7\" name=\"__codelineno-0-7\" href=\"#__codelineno-0-7\"></a><span class=\"k\">[tool.curfew.modules.</span><span class=\"s2\">"rwa_calc.core"</span><span class=\"k\">]</span>\n</span><span id=\"__span-0-8\"><a id=\"__codelineno-0-8\" name=\"__codelineno-0-8\" href=\"#__codelineno-0-8\"></a><span class=\"n\">depends_on</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[]</span><span class=\"w\"> </span><span class=\"c1\"># leaf — may import nothing first-party</span>\n</span><span id=\"__span-0-9\"><a id=\"__codelineno-0-9\" name=\"__codelineno-0-9\" href=\"#__codelineno-0-9\"></a><span class=\"n\">interface</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">"api"</span><span class=\"p\">]</span><span class=\"w\"> </span><span class=\"c1\"># only rwa_calc.core.api is public</span>\n</span><span id=\"__span-0-10\"><a id=\"__codelineno-0-10\" name=\"__codelineno-0-10\" href=\"#__codelineno-0-10\"></a><span class=\"n\">interface_enforced</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"kc\">true</span><span class=\"w\"> </span><span class=\"c1\"># defaults to true when an interface is declared</span>\n</span><span id=\"__span-0-11\"><a id=\"__codelineno-0-11\" name=\"__codelineno-0-11\" href=\"#__codelineno-0-11\"></a>\n</span><span id=\"__span-0-12\"><a id=\"__codelineno-0-12\" name=\"__codelineno-0-12\" href=\"#__codelineno-0-12\"></a><span class=\"k\">[tool.curfew.modules.</span><span class=\"s2\">"rwa_calc.io"</span><span class=\"k\">]</span>\n</span><span id=\"__span-0-13\"><a id=\"__codelineno-0-13\" name=\"__codelineno-0-13\" href=\"#__codelineno-0-13\"></a><span class=\"n\">depends_on</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">"rwa_calc.core"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"s2\">"rwa_calc.types"</span><span class=\"p\">]</span>\n</span><span id=\"__span-0-14\"><a id=\"__codelineno-0-14\" name=\"__codelineno-0-14\" href=\"#__codelineno-0-14\"></a><span class=\"n\">deprecated_on</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">"rwa_calc.legacy"</span><span class=\"p\">]</span><span class=\"w\"> </span><span class=\"c1\"># allowed but reported as a warning (burndown)</span>\n</span><span id=\"__span-0-15\"><a id=\"__codelineno-0-15\" name=\"__codelineno-0-15\" href=\"#__codelineno-0-15\"></a>\n</span><span id=\"__span-0-16\"><a id=\"__codelineno-0-16\" name=\"__codelineno-0-16\" href=\"#__codelineno-0-16\"></a><span class=\"k\">[tool.curfew.external]</span>\n</span><span id=\"__span-0-17\"><a id=\"__codelineno-0-17\" name=\"__codelineno-0-17\" href=\"#__codelineno-0-17\"></a><span class=\"n\">ignore</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">"setuptools"</span><span class=\"p\">]</span><span class=\"w\"> </span><span class=\"c1\"># never flag as undeclared</span>\n</span><span id=\"__span-0-18\"><a id=\"__codelineno-0-18\" name=\"__codelineno-0-18\" href=\"#__codelineno-0-18\"></a><span class=\"n\">ignore_unused</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[]</span><span class=\"w\"> </span><span class=\"c1\"># never flag as unused</span>\n</span><span id=\"__span-0-19\"><a id=\"__codelineno-0-19\" name=\"__codelineno-0-19\" href=\"#__codelineno-0-19\"></a>\n</span><span id=\"__span-0-20\"><a id=\"__codelineno-0-20\" name=\"__codelineno-0-20\" href=\"#__codelineno-0-20\"></a><span class=\"k\">[tool.curfew.check]</span>\n</span><span id=\"__span-0-21\"><a id=\"__codelineno-0-21\" name=\"__codelineno-0-21\" href=\"#__codelineno-0-21\"></a><span class=\"n\">boundaries</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"kc\">true</span>\n</span><span id=\"__span-0-22\"><a id=\"__codelineno-0-22\" name=\"__codelineno-0-22\" href=\"#__codelineno-0-22\"></a><span class=\"n\">externals</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"kc\">true</span>\n</span></code></pre></div>\n<h2 id=\"module-patterns\">Module patterns<a class=\"headerlink\" href=\"#module-patterns\" title=\"Permanent link\">¶</a></h2>\n<p>A pattern matches a module by <strong>prefix</strong>, guarded at a dot boundary so\n<code>rwa_calc.io</code> never matches <code>rwa_calc.iolib</code>:</p>\n<table>\n<thead>\n<tr>\n<th>Pattern</th>\n<th>Matches</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>pkg.core</code></td>\n<td><code>pkg.core</code> and everything under it (subtree)</td>\n</tr>\n<tr>\n<td><code>pkg.core.*</code></td>\n<td>same as above (explicit subtree sugar)</td>\n</tr>\n<tr>\n<td><code>pkg.core!</code></td>\n<td>only <code>pkg.core</code> exactly</td>\n</tr>\n</tbody>\n</table>\n<p>When several rules match, the <strong>longest prefix wins</strong> (an exact rule beats a\nsubtree rule on a tie).</p>\n<h2 id=\"boundary-semantics\">Boundary semantics<a class=\"headerlink\" href=\"#boundary-semantics\" title=\"Permanent link\">¶</a></h2>\n<ul>\n<li>A module that <strong>has</strong> a rule is default-deny: it may import only its\n <code>depends_on</code> targets plus its own subtree.</li>\n<li>A module with <strong>no</strong> rule is unrestricted — so you can adopt curfew\n incrementally. Set <code>default_deny = true</code> to require a rule on every first-party\n module (ungoverned modules may then only import within their own package).</li>\n<li>A <strong>public interface</strong> lists the names a package exposes. Importing a strict\n descendant that isn't in the interface — or a <code>from pkg import name</code> whose name\n isn't listed — is a bypass (error).</li>\n<li>A <strong>deprecated</strong> dependency is allowed but reported as a warning, so you can\n burn it down over time.</li>\n</ul>\n<h2 id=\"a-note-on-curfew-dogfooding-itself\">A note on curfew dogfooding itself<a class=\"headerlink\" href=\"#a-note-on-curfew-dogfooding-itself\" title=\"Permanent link\">¶</a></h2>\n<p>curfew's own <code>pyproject.toml</code> carries a <code>[tool.curfew]</code> block that documents and\nenforces its layering: <code>model</code> and <code>errors</code> are leaves; the engine layers build\nup; <code>cli</code> is the orchestrator. <code>curfew check</code> runs against curfew in CI.</p>",
|
|
5
|
+
"search": [
|
|
6
|
+
{
|
|
7
|
+
"location": null,
|
|
8
|
+
"level": 1,
|
|
9
|
+
"title": "Configuration",
|
|
10
|
+
"text": "<p>Configuration lives in a <code>[tool.curfew]</code> table in <code>pyproject.toml</code> (uv-native). An optional standalone <code>curfew.toml</code> is deep-merged on top of it (top-level keys, no <code>[tool.curfew]</code> prefix).</p> <pre><code>[tool.curfew]\nsource_roots = [\"src\"] # applies to the workspace root package\ndefault_deny = false # true => every first-party module must have a rule\nknown_first_party = [] # escape hatch for code generated outside source roots\nignore_modules = [] # import names never flagged as unresolved\n\n[tool.curfew.modules.\"rwa_calc.core\"]\ndepends_on = [] # leaf — may import nothing first-party\ninterface = [\"api\"] # only rwa_calc.core.api is public\ninterface_enforced = true # defaults to true when an interface is declared\n\n[tool.curfew.modules.\"rwa_calc.io\"]\ndepends_on = [\"rwa_calc.core\", \"rwa_calc.types\"]\ndeprecated_on = [\"rwa_calc.legacy\"] # allowed but reported as a warning (burndown)\n\n[tool.curfew.external]\nignore = [\"setuptools\"] # never flag as undeclared\nignore_unused = [] # never flag as unused\n\n[tool.curfew.check]\nboundaries = true\nexternals = true\n</code></pre>",
|
|
11
|
+
"path": [],
|
|
12
|
+
"tags": []
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"location": "module-patterns",
|
|
16
|
+
"level": 2,
|
|
17
|
+
"title": "Module patterns",
|
|
18
|
+
"text": "<p>A pattern matches a module by prefix, guarded at a dot boundary so <code>rwa_calc.io</code> never matches <code>rwa_calc.iolib</code>:</p> Pattern Matches <code>pkg.core</code> <code>pkg.core</code> and everything under it (subtree) <code>pkg.core.*</code> same as above (explicit subtree sugar) <code>pkg.core!</code> only <code>pkg.core</code> exactly <p>When several rules match, the longest prefix wins (an exact rule beats a subtree rule on a tie).</p>",
|
|
19
|
+
"path": [],
|
|
20
|
+
"tags": []
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"location": "boundary-semantics",
|
|
24
|
+
"level": 2,
|
|
25
|
+
"title": "Boundary semantics",
|
|
26
|
+
"text": "<ul> <li>A module that has a rule is default-deny: it may import only its <code>depends_on</code> targets plus its own subtree.</li> <li>A module with no rule is unrestricted — so you can adopt curfew incrementally. Set <code>default_deny = true</code> to require a rule on every first-party module (ungoverned modules may then only import within their own package).</li> <li>A public interface lists the names a package exposes. Importing a strict descendant that isn't in the interface — or a <code>from pkg import name</code> whose name isn't listed — is a bypass (error).</li> <li>A deprecated dependency is allowed but reported as a warning, so you can burn it down over time.</li> </ul>",
|
|
27
|
+
"path": [],
|
|
28
|
+
"tags": []
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"location": "a-note-on-curfew-dogfooding-itself",
|
|
32
|
+
"level": 2,
|
|
33
|
+
"title": "A note on curfew dogfooding itself",
|
|
34
|
+
"text": "<p>curfew's own <code>pyproject.toml</code> carries a <code>[tool.curfew]</code> block that documents and enforces its layering: <code>model</code> and <code>errors</code> are leaves; the engine layers build up; <code>cli</code> is the orchestrator. <code>curfew check</code> runs against curfew in CI.</p>",
|
|
35
|
+
"path": [],
|
|
36
|
+
"tags": []
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"title": "Configuration",
|
|
40
|
+
"toc": [
|
|
41
|
+
{
|
|
42
|
+
"title": "Configuration",
|
|
43
|
+
"content": "Configuration",
|
|
44
|
+
"id": "configuration",
|
|
45
|
+
"url": "#configuration",
|
|
46
|
+
"children": [
|
|
47
|
+
{
|
|
48
|
+
"title": "Module patterns",
|
|
49
|
+
"content": "Module patterns",
|
|
50
|
+
"id": "module-patterns",
|
|
51
|
+
"url": "#module-patterns",
|
|
52
|
+
"children": [],
|
|
53
|
+
"level": 2
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"title": "Boundary semantics",
|
|
57
|
+
"content": "Boundary semantics",
|
|
58
|
+
"id": "boundary-semantics",
|
|
59
|
+
"url": "#boundary-semantics",
|
|
60
|
+
"children": [],
|
|
61
|
+
"level": 2
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"title": "A note on curfew dogfooding itself",
|
|
65
|
+
"content": "A note on curfew dogfooding itself",
|
|
66
|
+
"id": "a-note-on-curfew-dogfooding-itself",
|
|
67
|
+
"url": "#a-note-on-curfew-dogfooding-itself",
|
|
68
|
+
"children": [],
|
|
69
|
+
"level": 2
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"level": 1
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"hash": 17798955438144269877
|
|
77
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": "\r\n<!doctype html>\r\n<html lang=\"en\" class=\"no-js\">\r\n <head>\r\n \r\n <meta charset=\"utf-8\">\r\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\r\n \r\n <meta name=\"description\" content=\"Local-first Python dependency & module-boundary checker. No network, no binaries, zero runtime deps.\">\r\n \r\n \r\n <meta name=\"author\" content=\"OpenAfterHours\">\r\n \r\n \r\n <link rel=\"canonical\" href=\"https://openafterhours.github.io/curfew/\">\r\n \r\n \r\n \r\n <link rel=\"next\" href=\"./why/\">\r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"icon\" href=\"./assets/images/favicon.png\">\r\n <meta name=\"generator\" content=\"zensical-0.0.44\">\r\n \r\n \r\n \r\n <title>curfew - curfew</title>\r\n \r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"stylesheet\" href=\"./assets/stylesheets/modern/main.fba56155.min.css\">\r\n \r\n \r\n \r\n \r\n <link rel=\"stylesheet\" href=\"./assets/stylesheets/modern/palette.dfe2e883.min.css\">\r\n \r\n \r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\r\n <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback\">\r\n <style>:root{--md-text-font:\"Inter\";--md-code-font:\"JetBrains Mono\"}</style>\r\n \r\n \r\n \r\n <script>__md_scope=new URL(\".\",location),__md_scope.pathname.endsWith(\"/\")||(__md_scope=new URL(__md_scope.pathname+\"/\",location)),__md_hash=e=>[...e].reduce(((e,t)=>(e<<5)-e+t.charCodeAt(0)),0),__md_get=(e,t=localStorage,_=__md_scope)=>JSON.parse(t.getItem(_.pathname+\".\"+e)),__md_set=(e,t,_=localStorage,a=__md_scope)=>{try{_.setItem(a.pathname+\".\"+e,JSON.stringify(t))}catch(e){}},document.documentElement.setAttribute(\"data-platform\",navigator.platform)</script>\r\n \r\n \r\n\r\n \r\n \r\n </head>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <body dir=\"ltr\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\">\r\n \r\n \r\n <input class=\"md-toggle\" data-md-toggle=\"drawer\" type=\"checkbox\" id=\"__drawer\" autocomplete=\"off\">\r\n <input class=\"md-toggle\" data-md-toggle=\"search\" type=\"checkbox\" id=\"__search\" autocomplete=\"off\">\r\n <label class=\"md-overlay\" for=\"__drawer\" aria-label=\"Navigation\"></label>\r\n <div data-md-component=\"skip\">\r\n \r\n \r\n <a href=\"#curfew\" class=\"md-skip\">\r\n Skip to content\r\n </a>\r\n \r\n </div>\r\n <div data-md-component=\"announce\">\r\n \r\n </div>\r\n \r\n \r\n \r\n\r\n \r\n\r\n<header class=\"md-header md-header--shadow\" data-md-component=\"header\">\r\n <nav class=\"md-header__inner md-grid\" aria-label=\"Header\">\r\n <a href=\"\" title=\"curfew\" class=\"md-header__button md-logo\" aria-label=\"curfew\" data-md-component=\"logo\">\r\n \r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\r\n\r\n </a>\r\n <label class=\"md-header__button md-icon\" for=\"__drawer\" aria-label=\"Navigation\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-menu\" viewBox=\"0 0 24 24\"><path d=\"M4 5h16M4 12h16M4 19h16\"/></svg>\r\n </label>\r\n <div class=\"md-header__title\" data-md-component=\"header-title\">\r\n <div class=\"md-header__ellipsis\">\r\n <div class=\"md-header__topic\">\r\n <span class=\"md-ellipsis\">\r\n curfew\r\n </span>\r\n </div>\r\n <div class=\"md-header__topic\" data-md-component=\"header-topic\">\r\n <span class=\"md-ellipsis\">\r\n \r\n curfew\r\n \r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n <form class=\"md-header__option\" data-md-component=\"palette\">\r\n \r\n \r\n \r\n \r\n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to dark mode\" type=\"radio\" name=\"__palette\" id=\"__palette_0\">\r\n \r\n <label class=\"md-header__button md-icon\" title=\"Switch to dark mode\" for=\"__palette_1\" hidden>\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-sun\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"/></svg>\r\n </label>\r\n \r\n \r\n \r\n \r\n \r\n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"slate\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to light mode\" type=\"radio\" name=\"__palette\" id=\"__palette_1\">\r\n \r\n <label class=\"md-header__button md-icon\" title=\"Switch to light mode\" for=\"__palette_0\" hidden>\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-moon\" viewBox=\"0 0 24 24\"><path d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\"/></svg>\r\n </label>\r\n \r\n \r\n</form>\r\n \r\n \r\n \r\n <script>var palette=__md_get(\"__palette\");if(palette&&palette.color){if(\"(prefers-color-scheme)\"===palette.color.media){var media=matchMedia(\"(prefers-color-scheme: light)\"),input=document.querySelector(media.matches?\"[data-md-color-media='(prefers-color-scheme: light)']\":\"[data-md-color-media='(prefers-color-scheme: dark)']\");palette.color.media=input.getAttribute(\"data-md-color-media\"),palette.color.scheme=input.getAttribute(\"data-md-color-scheme\"),palette.color.primary=input.getAttribute(\"data-md-color-primary\"),palette.color.accent=input.getAttribute(\"data-md-color-accent\")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute(\"data-md-color-\"+key,value)}</script>\r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-header__button md-icon\" for=\"__search\" aria-label=\"Search\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-search\" viewBox=\"0 0 24 24\"><path d=\"m21 21-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/></svg>\r\n </label>\r\n <div class=\"md-search\" data-md-component=\"search\" role=\"dialog\" aria-label=\"Search\">\r\n <button type=\"button\" class=\"md-search__button\">\r\n Search\r\n </button>\r\n</div>\r\n \r\n \r\n <div class=\"md-header__source\">\r\n \r\n <a href=\"https://github.com/OpenAfterHours/curfew\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\r\n <div class=\"md-source__icon md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\r\n </div>\r\n <div class=\"md-source__repository\">\r\n OpenAfterHours/curfew\r\n </div>\r\n</a>\r\n \r\n </div>\r\n </nav>\r\n \r\n</header>\r\n \r\n <div class=\"md-container\" data-md-component=\"container\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <main class=\"md-main\" data-md-component=\"main\">\r\n <div class=\"md-main__inner md-grid\">\r\n \r\n \r\n \r\n <div class=\"md-sidebar md-sidebar--primary\" data-md-component=\"sidebar\" data-md-type=\"navigation\" >\r\n <div class=\"md-sidebar__scrollwrap\">\r\n <div class=\"md-sidebar__inner\">\r\n \r\n\r\n\r\n\r\n<nav class=\"md-nav md-nav--primary\" aria-label=\"Navigation\" data-md-level=\"0\">\r\n <label class=\"md-nav__title\" for=\"__drawer\">\r\n <a href=\"\" title=\"curfew\" class=\"md-nav__button md-logo\" aria-label=\"curfew\" data-md-component=\"logo\">\r\n \r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\r\n\r\n </a>\r\n curfew\r\n </label>\r\n \r\n <div class=\"md-nav__source\">\r\n <a href=\"https://github.com/OpenAfterHours/curfew\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\r\n <div class=\"md-source__icon md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\r\n </div>\r\n <div class=\"md-source__repository\">\r\n OpenAfterHours/curfew\r\n </div>\r\n</a>\r\n </div>\r\n \r\n <ul class=\"md-nav__list\" data-md-scrollfix>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item md-nav__item--active\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__link md-nav__link--active\" for=\"__toc\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Home\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n </label>\r\n \r\n <a href=\"\" class=\"md-nav__link md-nav__link--active\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Home\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n \r\n \r\n\r\n\r\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__title\" for=\"__toc\">\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n On this page\r\n </label>\r\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#install\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n Install\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#quick-start\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n Quick start\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n </ul>\r\n \r\n</nav>\r\n \r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"./why/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Why curfew\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"./usage/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Usage\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"./configuration/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Configuration\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"./graph/\" class=\"md-nav__link\">\r\n \r\n \r\n \r\n <span class=\"md-ellipsis\">\r\n \r\n \r\n Dependency graph\r\n\r\n \r\n </span>\r\n \r\n \r\n\r\n </a>\r\n </li>\r\n \r\n\r\n \r\n </ul>\r\n</nav>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n \r\n <div class=\"md-sidebar md-sidebar--secondary\" data-md-component=\"sidebar\" data-md-type=\"toc\" >\r\n <div class=\"md-sidebar__scrollwrap\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <input class=\"md-nav__toggle md-toggle\" type=\"checkbox\" id=\"__toc\">\r\n <div class=\"md-sidebar-button__wrapper\">\r\n <label class=\"md-sidebar-button\" for=\"__toc\"></label>\r\n </div>\r\n \r\n \r\n <div class=\"md-sidebar__inner\">\r\n \r\n\r\n\r\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\r\n \r\n \r\n \r\n \r\n \r\n \r\n <label class=\"md-nav__title\" for=\"__toc\">\r\n <span class=\"md-nav__icon md-icon\"></span>\r\n On this page\r\n </label>\r\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#install\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n Install\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n <li class=\"md-nav__item\">\r\n <a href=\"#quick-start\" class=\"md-nav__link\">\r\n <span class=\"md-ellipsis\">\r\n <span class=\"md-typeset\">\r\n Quick start\r\n </span>\r\n </span>\r\n </a>\r\n \r\n</li>\r\n \r\n </ul>\r\n \r\n</nav>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n \r\n \r\n <div class=\"md-content\" data-md-component=\"content\">\r\n \r\n <article class=\"md-content__inner md-typeset\">\r\n \r\n \r\n \r\n \r\n\r\n\r\n<h1 id=\"curfew\">curfew<a class=\"headerlink\" href=\"#curfew\" title=\"Permanent link\">¶</a></h1>\n<blockquote>\n<p><em>couvre-feu</em> — \"cover the fire\". An after-hours rule that keeps things within\ntheir bounds. That's exactly what this tool does for your imports.</p>\n</blockquote>\n<p><strong>curfew</strong> is a local-first Python dependency & module-boundary checker — an\nopinionated, fully-offline alternative to <a href=\"https://github.com/gauge-sh/tach\"><code>tach</code></a>.\nFrom a single static import-graph engine it does two jobs:</p>\n<ol>\n<li><strong>Module boundaries</strong> — enforce which first-party modules/packages may import\n which.</li>\n<li><strong>External & workspace validation</strong> — per package, verify every imported\n third-party package is declared, flag declared-but-unused dependencies, and\n detect <strong>uv workspace leakage</strong>.</li>\n</ol>\n<p>It also visualises the dependency graph entirely offline.</p>\n<h2 id=\"install\">Install<a class=\"headerlink\" href=\"#install\" title=\"Permanent link\">¶</a></h2>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a>uv<span class=\"w\"> </span>tool<span class=\"w\"> </span>install<span class=\"w\"> </span>curfew<span class=\"w\"> </span><span class=\"c1\"># zero transitive runtime dependencies</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a>uv<span class=\"w\"> </span>tool<span class=\"w\"> </span>install<span class=\"w\"> </span><span class=\"s2\">"curfew[rich]"</span><span class=\"w\"> </span><span class=\"c1\"># optional colour output</span>\n</span></code></pre></div>\n<h2 id=\"quick-start\">Quick start<a class=\"headerlink\" href=\"#quick-start\" title=\"Permanent link\">¶</a></h2>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a>curfew<span class=\"w\"> </span>check<span class=\"w\"> </span><span class=\"c1\"># run all checks; non-zero exit on any error (CI gate)</span>\n</span><span id=\"__span-1-2\"><a id=\"__codelineno-1-2\" name=\"__codelineno-1-2\" href=\"#__codelineno-1-2\"></a>curfew<span class=\"w\"> </span>show<span class=\"w\"> </span>--mermaid<span class=\"w\"> </span>-o<span class=\"w\"> </span>graph.md<span class=\"w\"> </span><span class=\"c1\"># dependency graph as Mermaid (renders on GitHub)</span>\n</span><span id=\"__span-1-3\"><a id=\"__codelineno-1-3\" name=\"__codelineno-1-3\" href=\"#__codelineno-1-3\"></a>curfew<span class=\"w\"> </span>report<span class=\"w\"> </span>path/to/module.py<span class=\"w\"> </span><span class=\"c1\"># dependencies and dependents of a module</span>\n</span><span id=\"__span-1-4\"><a id=\"__codelineno-1-4\" name=\"__codelineno-1-4\" href=\"#__codelineno-1-4\"></a>curfew<span class=\"w\"> </span>init<span class=\"w\"> </span><span class=\"c1\"># scaffold a [tool.curfew] config</span>\n</span></code></pre></div>\n<div class=\"admonition note\">\n<p class=\"admonition-title\">Run inside your workspace venv</p>\n<p>Workspace-leakage detection relies on the single shared environment that <code>uv</code>\ncreates for a workspace, so run curfew from inside it.</p>\n</div>\n<p>See <a href=\"why/\">Why curfew</a>, <a href=\"usage/\">Usage</a>, <a href=\"configuration/\">Configuration</a>,\nand the dogfooded <a href=\"graph/\">dependency graph</a>.</p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n \r\n </article>\r\n </div>\r\n \r\n \r\n<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith(\"__tabbed_\"))</script>\r\n </div>\r\n \r\n <button type=\"button\" class=\"md-top md-icon\" data-md-component=\"top\" hidden>\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-circle-arrow-up\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m16 12-4-4-4 4M12 16V8\"/></svg>\r\n Back to top\r\n</button>\r\n \r\n </main>\r\n \r\n <footer class=\"md-footer\">\r\n \r\n \r\n \r\n <nav class=\"md-footer__inner md-grid\" aria-label=\"Footer\" >\r\n \r\n \r\n \r\n <a href=\"./why/\" class=\"md-footer__link md-footer__link--next\" aria-label=\"Next: Why curfew\">\r\n <div class=\"md-footer__title\">\r\n <span class=\"md-footer__direction\">\r\n Next\r\n </span>\r\n <div class=\"md-ellipsis\">\r\n Why curfew\r\n </div>\r\n </div>\r\n <div class=\"md-footer__button md-icon\">\r\n \r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-arrow-right\" viewBox=\"0 0 24 24\"><path d=\"M5 12h14M12 5l7 7-7 7\"/></svg>\r\n </div>\r\n </a>\r\n \r\n </nav>\r\n \r\n \r\n <div class=\"md-footer-meta md-typeset\">\r\n <div class=\"md-footer-meta__inner md-grid\">\r\n <div class=\"md-copyright\">\r\n \r\n <div class=\"md-copyright__highlight\">\r\n Copyright © 2026 OpenAfterHours\r\n </div>\r\n \r\n \r\n Made with\r\n <a href=\"https://zensical.org/\" target=\"_blank\" rel=\"noopener\">\r\n Zensical\r\n </a>\r\n \r\n</div>\r\n \r\n </div>\r\n </div>\r\n</footer>\r\n \r\n </div>\r\n <div class=\"md-dialog\" data-md-component=\"dialog\">\r\n <div class=\"md-dialog__inner md-typeset\"></div>\r\n </div>\r\n \r\n \r\n \r\n \r\n \r\n <script id=\"__config\" type=\"application/json\">{\"annotate\":null,\"base\":\".\",\"features\":[\"content.code.copy\",\"navigation.footer\",\"navigation.indexes\",\"navigation.sections\",\"navigation.top\",\"search.highlight\",\"toc.follow\"],\"search\":\"./assets/javascripts/workers/search.e2d2d235.min.js\",\"tags\":null,\"translations\":{\"clipboard.copied\":\"Copied to clipboard\",\"clipboard.copy\":\"Copy to clipboard\",\"search.result.more.one\":\"1 more on this page\",\"search.result.more.other\":\"# more on this page\",\"search.result.none\":\"No matching documents\",\"search.result.one\":\"1 matching document\",\"search.result.other\":\"# matching documents\",\"search.result.placeholder\":\"Type to start searching\",\"search.result.term.missing\":\"Missing\",\"select.version\":\"Select version\"},\"version\":null}</script>\r\n \r\n \r\n <script src=\"./assets/javascripts/bundle.6e5f0216.min.js\"></script>\r\n \r\n \r\n </body>\r\n</html>",
|
|
3
|
+
"hash": 16455186636325965752
|
|
4
|
+
}
|