drybag 0.7.1__py3-none-any.whl
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.
- drybag/__init__.py +9 -0
- drybag/__main__.py +3 -0
- drybag/areas.py +114 -0
- drybag/attach/__init__.py +18 -0
- drybag/attach/api.py +75 -0
- drybag/attach/binding.py +497 -0
- drybag/attach/check.py +91 -0
- drybag/attach/checkout.py +167 -0
- drybag/attach/commands.py +180 -0
- drybag/attach/doctor.py +639 -0
- drybag/attach/exclude.py +432 -0
- drybag/attach/hooks.py +270 -0
- drybag/attach/items.py +85 -0
- drybag/attach/permissions.py +308 -0
- drybag/attach/render.py +353 -0
- drybag/attach/rules.py +269 -0
- drybag/attach/tags.py +51 -0
- drybag/attach/write.py +1396 -0
- drybag/binding.py +207 -0
- drybag/cli.py +176 -0
- drybag/command.py +99 -0
- drybag/config/__init__.py +5 -0
- drybag/config/classes.py +220 -0
- drybag/config/layout.py +23 -0
- drybag/config/loader.py +692 -0
- drybag/config/machine.py +75 -0
- drybag/config/overlay.py +44 -0
- drybag/config/overlay_layers.py +191 -0
- drybag/config/owner.py +247 -0
- drybag/config/paths.py +302 -0
- drybag/config/plates.py +161 -0
- drybag/config/reading.py +234 -0
- drybag/config/schema.py +161 -0
- drybag/config/session.py +397 -0
- drybag/docs/__init__.py +5 -0
- drybag/docs/api.py +44 -0
- drybag/docs/commands.py +148 -0
- drybag/docs/hygiene.py +195 -0
- drybag/docs/plans.py +492 -0
- drybag/docs/themes.py +153 -0
- drybag/docs/trail.py +392 -0
- drybag/doctor/__init__.py +26 -0
- drybag/doctor/api.py +67 -0
- drybag/doctor/checks.py +775 -0
- drybag/doctor/commands.py +81 -0
- drybag/doctor/model.py +201 -0
- drybag/doctor/registry.py +174 -0
- drybag/errors.py +37 -0
- drybag/findings.py +56 -0
- drybag/fsops.py +866 -0
- drybag/gitenv.py +631 -0
- drybag/grammar.py +45 -0
- drybag/guards/__init__.py +5 -0
- drybag/guards/api.py +42 -0
- drybag/guards/commands.py +189 -0
- drybag/guards/commit.py +574 -0
- drybag/guards/githooks.py +275 -0
- drybag/harnesses.py +321 -0
- drybag/hints/__init__.py +6 -0
- drybag/hints/bashscan.py +782 -0
- drybag/hints/bgcleanup.py +679 -0
- drybag/hints/hooks.py +119 -0
- drybag/hints/hygiene.py +126 -0
- drybag/hints/pipes.py +76 -0
- drybag/hints/runners.py +103 -0
- drybag/hooks/__init__.py +1 -0
- drybag/hooks/api.py +186 -0
- drybag/hooks/commands.py +89 -0
- drybag/hooks/dispatch.py +210 -0
- drybag/hooks/registry.py +30 -0
- drybag/hooks/sink.py +201 -0
- drybag/jsonobject.py +145 -0
- drybag/memory/__init__.py +1 -0
- drybag/memory/answers.py +61 -0
- drybag/memory/api.py +133 -0
- drybag/memory/bundles.py +271 -0
- drybag/memory/commands.py +373 -0
- drybag/memory/composed.py +155 -0
- drybag/memory/doctor.py +215 -0
- drybag/memory/hooks.py +109 -0
- drybag/memory/index.py +560 -0
- drybag/memory/inventory.py +86 -0
- drybag/memory/notes.py +463 -0
- drybag/memory/reviewed.py +93 -0
- drybag/memory/store.py +657 -0
- drybag/memory/trust.py +46 -0
- drybag/overlay/__init__.py +1 -0
- drybag/overlay/api.py +83 -0
- drybag/overlay/commands.py +51 -0
- drybag/overlay/create.py +360 -0
- drybag/overlay/doctor.py +224 -0
- drybag/overlay/identity.py +103 -0
- drybag/overlay/layout.py +61 -0
- drybag/overlay/requires.py +63 -0
- drybag/overlay/sync.py +37 -0
- drybag/overlay/template.py +78 -0
- drybag/overlay/upgrade.py +98 -0
- drybag/presets/__init__.py +74 -0
- drybag/presets/recommended.toml +104 -0
- drybag/printed.py +86 -0
- drybag/processes.py +105 -0
- drybag/prose.py +175 -0
- drybag/release/__init__.py +2 -0
- drybag/release/api.py +20 -0
- drybag/release/pins.py +15 -0
- drybag/result.py +13 -0
- drybag/runner.py +141 -0
- drybag/scaffold/__init__.py +62 -0
- drybag/scaffold/engine.py +187 -0
- drybag/scaffold/entries.py +76 -0
- drybag/scaffold/manifest.py +171 -0
- drybag/scaffold/model.py +80 -0
- drybag/scaffold/regions.py +110 -0
- drybag/scaffold/report.py +30 -0
- drybag/semver.py +125 -0
- drybag/setup/__init__.py +7 -0
- drybag/setup/api.py +38 -0
- drybag/setup/commands.py +414 -0
- drybag/setup/machine.py +209 -0
- drybag/setup/restore.py +299 -0
- drybag/setup/run.py +683 -0
- drybag/sync/__init__.py +8 -0
- drybag/sync/doctor.py +161 -0
- drybag/sync/git.py +170 -0
- drybag/sync/hooks.py +46 -0
- drybag/sync/job.py +954 -0
- drybag/sync/rules.py +357 -0
- drybag/sync/scan.py +162 -0
- drybag/sync/state.py +342 -0
- drybag/sync/trigger.py +139 -0
- drybag/templates/__init__.py +1 -0
- drybag/templates/overlay/.github/dependabot.yml +15 -0
- drybag/templates/overlay/.github/workflows/scan.yml +58 -0
- drybag/templates/overlay/.gitignore +6 -0
- drybag/templates/overlay/.pre-commit-config.yaml +10 -0
- drybag/templates/overlay/README.md +56 -0
- drybag/templates/overlay/common/claude/hooks.json +3 -0
- drybag/templates/overlay/common/claude/permissions.json +3 -0
- drybag/templates/overlay/common/codex/common.rules +2 -0
- drybag/templates/overlay/common/memory/_README.md +15 -0
- drybag/templates/overlay/drybag-overlay.toml +2 -0
- drybag/templates/overlay/projects/README.md +11 -0
- drybag/tomlout.py +207 -0
- drybag/trust/__init__.py +7 -0
- drybag/trust/api.py +49 -0
- drybag/trust/boundary.py +132 -0
- drybag/trust/record.py +208 -0
- drybag-0.7.1.dist-info/METADATA +477 -0
- drybag-0.7.1.dist-info/RECORD +152 -0
- drybag-0.7.1.dist-info/WHEEL +4 -0
- drybag-0.7.1.dist-info/entry_points.txt +3 -0
- drybag-0.7.1.dist-info/licenses/LICENSE +21 -0
drybag/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""drybag: private layers for coding agents."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.7.1"
|
|
4
|
+
|
|
5
|
+
# The public repository, spelled once — the install hints derive from it. `scripts/release.py
|
|
6
|
+
# check` reads `__version__` through a regex anchored on that name alone, and does not see these
|
|
7
|
+
# two lines.
|
|
8
|
+
REPOSITORY_SLUG = "dry-bag/drybag"
|
|
9
|
+
REPOSITORY_URL = f"https://github.com/{REPOSITORY_SLUG}"
|
drybag/__main__.py
ADDED
drybag/areas.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Areas discovered by name: the half `drybag.cli`, `drybag.hooks.registry` and
|
|
2
|
+
`drybag.doctor.registry` share.
|
|
3
|
+
|
|
4
|
+
Three submodules are discovered, each by the module that reads it: an area's `commands.py` gives
|
|
5
|
+
it a CLI group, its `hooks.py` hook handlers, and its `doctor.py` rows in `drybag doctor`'s
|
|
6
|
+
report. The CLI frame and the hook registry import through `area_modules`, so an area that
|
|
7
|
+
fails to import fails them; the report imports through `area_imports`, which hands that failure
|
|
8
|
+
over in the area's place, because the report must outlive any one area's broken code.
|
|
9
|
+
|
|
10
|
+
A leaf module on purpose. `cli.py` imports every area through this discovery, so an area that
|
|
11
|
+
imported back into `cli.py` would constrain what the frame may ever import; the aliases and
|
|
12
|
+
the loop live here instead, and `cli` re-exports the aliases.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import importlib
|
|
19
|
+
import pkgutil
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from types import ModuleType
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
import drybag
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
SubParsers = argparse._SubParsersAction[argparse.ArgumentParser]
|
|
29
|
+
else:
|
|
30
|
+
# Generic only in typeshed: subscripting the class at runtime raises TypeError.
|
|
31
|
+
SubParsers = argparse._SubParsersAction
|
|
32
|
+
|
|
33
|
+
Registrar = Callable[[SubParsers], None]
|
|
34
|
+
|
|
35
|
+
# The sides, by the first part of a module's path under `src/drybag/`; a module neither side
|
|
36
|
+
# lists is the core's. CONTRIBUTING.md, "Areas", says what each holds and the rules between them,
|
|
37
|
+
# and `tests/boundaries/test_delivery.py` holds those rules. They read code, so they cannot see
|
|
38
|
+
# `templates/overlay/`, data only the `overlay` area reads, which sits in the core.
|
|
39
|
+
DELIVERY_AREAS: frozenset[str] = frozenset(
|
|
40
|
+
{
|
|
41
|
+
"attach",
|
|
42
|
+
"hints",
|
|
43
|
+
"memory",
|
|
44
|
+
"overlay",
|
|
45
|
+
"setup",
|
|
46
|
+
"sync",
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
PACK_PACKAGES: frozenset[str] = frozenset(
|
|
50
|
+
{
|
|
51
|
+
"docs",
|
|
52
|
+
"guards",
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
CORE_AREAS: frozenset[str] = frozenset(
|
|
56
|
+
{
|
|
57
|
+
"doctor",
|
|
58
|
+
"hooks",
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _has_submodule(area: str, submodule: str) -> bool:
|
|
64
|
+
"""A filesystem probe: it imports only the `<area>.<submodule>` modules that exist, instead
|
|
65
|
+
of importing every area package merely to test whether one of them does.
|
|
66
|
+
|
|
67
|
+
That saving is latent rather than realised on today's frame: `main()` runs
|
|
68
|
+
`discover_registrars()` to build the full argparse parser before argparse can dispatch to
|
|
69
|
+
`hook`, and building that parser already imports `drybag.config`, `drybag.config.schema`,
|
|
70
|
+
`drybag.presets`, `drybag.release` and `drybag.release.pins` — so by the time this
|
|
71
|
+
probe runs for `hooks`, those packages are already loaded. The probe would only pay off if
|
|
72
|
+
discovery ever registered just the area named on the command line instead of the full set.
|
|
73
|
+
"""
|
|
74
|
+
for root in drybag.__path__:
|
|
75
|
+
directory = Path(root) / area
|
|
76
|
+
if (directory / f"{submodule}.py").is_file():
|
|
77
|
+
return True
|
|
78
|
+
if (directory / submodule / "__init__.py").is_file():
|
|
79
|
+
return True
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _qualified(submodule: str) -> list[str]:
|
|
84
|
+
"""The qualified name of each `drybag.<area>.<submodule>` that exists, in area-name
|
|
85
|
+
order: the one probe both importers below read."""
|
|
86
|
+
return [
|
|
87
|
+
f"drybag.{entry.name}.{submodule}"
|
|
88
|
+
for entry in sorted(pkgutil.iter_modules(drybag.__path__), key=lambda m: m.name)
|
|
89
|
+
if entry.ispkg and _has_submodule(entry.name, submodule)
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def area_modules(submodule: str) -> list[ModuleType]:
|
|
94
|
+
"""Every `drybag.<area>.<submodule>` that exists, in area-name order, no shared registry."""
|
|
95
|
+
return [importlib.import_module(name) for name in _qualified(submodule)]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def area_imports(submodule: str) -> list[tuple[str, ModuleType | Exception]]:
|
|
99
|
+
"""Every `drybag.<area>.<submodule>` that exists, in area-name order, each under its
|
|
100
|
+
qualified name with the module, or with the exception its import raised in the module's place.
|
|
101
|
+
|
|
102
|
+
For a reader that must outlive an area's broken code: `doctor`'s report, which is what a user
|
|
103
|
+
has left when everything else is broken, so one area's `doctor.py` that raises on import must
|
|
104
|
+
cost that area's rows and not the report. The failure is handed over rather than judged here,
|
|
105
|
+
because what it costs is the reader's to say. `Exception` and not `BaseException`, as for a
|
|
106
|
+
check that raises: a `KeyboardInterrupt` during discovery stops the command.
|
|
107
|
+
"""
|
|
108
|
+
found: list[tuple[str, ModuleType | Exception]] = []
|
|
109
|
+
for name in _qualified(submodule):
|
|
110
|
+
try:
|
|
111
|
+
found.append((name, importlib.import_module(name)))
|
|
112
|
+
except Exception as exc: # an area's broken import is handed over, never raised here
|
|
113
|
+
found.append((name, exc))
|
|
114
|
+
return found
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Bind a repository to the machine owner's private overlay, and take back what binding wrote.
|
|
2
|
+
|
|
3
|
+
An area, discovered by name: `commands.py` gives it the `attach` and `detach` groups and
|
|
4
|
+
`api.py` is what another area may import. `detach` takes back what `attach` wrote and leaves the
|
|
5
|
+
overlay's record of the binding, `projects/<name>/project.toml`, where it is. It writes nothing
|
|
6
|
+
a repository chose — the overlay root comes from the machine file, the store is that overlay's
|
|
7
|
+
own directory for this project, and what the overlay grants is shown on one screen rather than
|
|
8
|
+
asked about.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# The command that binds this checkout to its share of the overlay, which every remedy of this area
|
|
12
|
+
# names, so a reader is never sent to two spellings of one command. No `--store`: the store is
|
|
13
|
+
# derived from the machine-recorded overlay, and a placeholder an agent runs as written exits 2.
|
|
14
|
+
# Here and not in `doctor.py` because `hooks.py` names it too, and discovery imports `hooks.py`
|
|
15
|
+
# unguarded for every hook: an edge to the report's module would let a defect there stop every
|
|
16
|
+
# hook, and load the report on each one. Not annotated `Final`: an area's `__init__.py` imports
|
|
17
|
+
# nothing, `typing` included (`tests/test_surfaces.py`).
|
|
18
|
+
ATTACH_COMMAND = "drybag attach"
|
drybag/attach/api.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""The attach area's import surface: everything another area may import from it.
|
|
2
|
+
|
|
3
|
+
The list is chosen from what consumers outside this area actually reach for, and every name on
|
|
4
|
+
it carries the reason beside it — a surface that survives a trim with no explanation is what
|
|
5
|
+
made the trim necessary. An area that needs something absent from this list grows it
|
|
6
|
+
deliberately, in a commit that says which area and why — it does not import a private module of
|
|
7
|
+
this area.
|
|
8
|
+
|
|
9
|
+
No module under `src/` outside this area imports from here. `doctor` was the one consuming area,
|
|
10
|
+
and its `attached` row is now this area's own (`attach/doctor.py`), which reaches this area's
|
|
11
|
+
modules directly. What is left is what the suite
|
|
12
|
+
reads from outside the area:
|
|
13
|
+
|
|
14
|
+
- the ledger (`ledger`) and `AttachLedger`, because `ledger` returns it: a return
|
|
15
|
+
type absent from this list is a value a consumer can hold and cannot declare, and
|
|
16
|
+
`tests/test_surfaces.py` derives that rule rather than restating it; `Rendered`, the type of
|
|
17
|
+
its `render`, for the same rule;
|
|
18
|
+
- the binding (`read_binding`, `Binding`). `Binding.state` is one of `memory.api`'s
|
|
19
|
+
`BINDING_STATES`: the vocabulary and the one classifier that decides between its members are
|
|
20
|
+
the memory area's, so this area publishes neither a copy of the set nor its own members;
|
|
21
|
+
- the file an earlier release merged the overlay's hook entries into (`LOCAL_SETTINGS`), which
|
|
22
|
+
the doctor area's tests write those entries into;
|
|
23
|
+
- `attach` and `Attached`, which `setup --restore` runs on each checkout the overlay's projects
|
|
24
|
+
file lists (`setup.restore`), and the type it returns, with `Carried`, the type of two of its
|
|
25
|
+
fields (`added` and `removed`), which `tests/test_surfaces.py` derives that a surface carries.
|
|
26
|
+
|
|
27
|
+
**Trimmed when the `attached` row moved into this area: `overlay_entries`.** It was published for
|
|
28
|
+
`doctor`'s provenance column, and went once `attach` merged no hook entry.
|
|
29
|
+
|
|
30
|
+
**`setup` was not a consumer of `check`, and still is not.** This docstring used to say "`setup`
|
|
31
|
+
runs `attach --check` and reads the same `Result`"; `setup` imports nothing of `check`, and
|
|
32
|
+
`attach --check` is this area's own command module calling its own `attach.check`. (`setup
|
|
33
|
+
--restore` does import `attach` itself, which is the bullet above.) The
|
|
34
|
+
permission-diff half of the area — `check`, `diff_permissions` and the `PermissionDiff` it
|
|
35
|
+
returns — was published on the strength of that sentence and left when the sentence did.
|
|
36
|
+
|
|
37
|
+
**Trimmed once, and back for `setup --restore`: `attach` and `Attached`; `detach` and
|
|
38
|
+
`Detached` are trimmed again since the `uninstall` that ran `detach` was deleted.** They were
|
|
39
|
+
published against a second sentence of the same kind — "`tests/test_install_path.py`, the
|
|
40
|
+
walkthrough that runs the four packages in the order a person does, is the other consumer, and it
|
|
41
|
+
drives `attach` and
|
|
42
|
+
`detach`, whose returns are `Attached` and `Detached`". That walkthrough drives the argument
|
|
43
|
+
parser: `step("attach", "--store", ..., "--machine", ...)`, which is the point of it, because the
|
|
44
|
+
commands' argv wiring is what it exists to exercise. It imports two surfaces,
|
|
45
|
+
`drybag.doctor.api` and `drybag.memory.api`, and reaches no name on this list. The two verbs
|
|
46
|
+
are this area's own command module's, and their results are read where they are returned.
|
|
47
|
+
|
|
48
|
+
The claim is the same shape as the one this docstring corrected two paragraphs up, and as the
|
|
49
|
+
one `setup/api.py` corrected about its own `setup` and `SetupReport` — three of them now, all
|
|
50
|
+
naming a test that runs a command rather than a module that imports a name. A published surface
|
|
51
|
+
argued from prose is a surface nobody measured: the first count of this list's importers
|
|
52
|
+
covered `src/`, `scripts/` and `tests/` and never read the sentence, and the sentence is what
|
|
53
|
+
kept the names.
|
|
54
|
+
|
|
55
|
+
The ledger's place is the core's (`binding.LEDGER_FILE`, beside the binding id), because `doctor`
|
|
56
|
+
reads it too; nothing outside delivery imports this area but through the crossings pinned in
|
|
57
|
+
`tests/boundaries/test_delivery.py`.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
from drybag.attach.binding import Binding, read_binding
|
|
61
|
+
from drybag.attach.permissions import LOCAL_SETTINGS
|
|
62
|
+
from drybag.attach.render import Rendered
|
|
63
|
+
from drybag.attach.write import Attached, AttachLedger, Carried, attach, ledger
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
"LOCAL_SETTINGS",
|
|
67
|
+
"AttachLedger",
|
|
68
|
+
"Attached",
|
|
69
|
+
"Binding",
|
|
70
|
+
"Carried",
|
|
71
|
+
"Rendered",
|
|
72
|
+
"attach",
|
|
73
|
+
"ledger",
|
|
74
|
+
"read_binding",
|
|
75
|
+
]
|