Overlays
dotagents._overlays
Overlay support: copy an overlay's files in, and collect what it contributes
to the managed AGENTS.md block.
An overlay is a directory (optionally carrying an overlay.toml manifest) whose
files install to the same relative path in the destination. Two manifest keys are
read here:
routing— lines appended to the core's "Load on demand" list.rules— overlay-relative paths to markdown files whose- **…bullet blocks are appended to "Always-on rules".
Dependency resolution (requires) is still deferred to a future
dotagents overlays subcommand.
DEFAULT_PRIORITY = 500
module-attribute
SETUP_SCRIPT_NAMES = ('setup.py', 'setup')
module-attribute
apply_overlay(overlay_dir, dest, dry_run)
Copy overlay_dir's files into dest (create-if-absent; never clobber
an existing file). Returns (copied, skipped) counts and per-file log lines.
Source code in src/dotagents/_overlays.py
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | |
find_setup_script(overlay_dir)
Return the overlay's setup script, or None if it ships none.
Prefers the OS-agnostic setup.py over the legacy extensionless setup
when an overlay ships both (SETUP_SCRIPT_NAMES is in preference order).
The installed overlay dir is what should be handed in: setup runs against
the copy under <scope>/overlays/<name>/ with that as its cwd, so a script
can reference its own sibling files by relative path.
Source code in src/dotagents/_overlays.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
install_overlay_dir(overlay_dir, dest_overlay_dir, dry_run)
Install an overlay as a directory under <scope>/overlays/<name>/ (the
"install model = overlay-dirs" decision): the overlay is the discoverable unit.
Copies every file the overlay ships (minus its manifest / caches -- see
overlay_files) into dest_overlay_dir, create-if-absent so re-adding an
overlay never clobbers a file the user hand-edited inside the installed copy
(additive/no-clobber, mirroring apply_overlay). The overlay's own
overlay.toml is copied too so a later sync/list can re-read its manifest
from the installed copy. Returns (copied, skipped) counts and log lines.
Source code in src/dotagents/_overlays.py
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | |
merge_overlay_rules(agents_md, overlay_dir, dry_run, logger)
Fold one overlay's D59 routing + rules into an already-installed AGENTS.md's managed block, in place (additive).
install composes these into the base text before first write; an incremental
overlays add instead re-composes the existing installed file's managed block.
Extracts the current block (between the dotagents markers), runs the same
_compose_block fold used at install time over just that block text, and writes
the result back between the markers -- content outside the block is never
touched. A no-op (returns False) when the overlay contributes nothing, the file
is absent, or it carries no managed block.
Source code in src/dotagents/_overlays.py
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 | |
overlay_files(overlay_dir)
Files an overlay installs (everything except its manifest / caches).
Source code in src/dotagents/_overlays.py
254 255 256 257 258 259 260 | |
overlay_sort_key(overlay_dir)
The (priority, name) merge-order key for one overlay (plan 02 / D68).
Lower priority (default DEFAULT_PRIORITY, 500) sorts earlier; a numerically
higher-priority overlay therefore lands later in the merged AGENTS.md block and
wins on conflict -- the same "lower sorts earlier / higher wins" convention
_context.py uses for its own priority ordering. name is the stable tiebreaker
for equal-priority overlays, keyed off the manifest name (falling back to the
directory name) so output is deterministic regardless of input order.
Source code in src/dotagents/_overlays.py
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | |
read_manifest(overlay_dir)
Parse overlay.toml, returning at least name/routing/rules/priority.
A missing or unreadable manifest yields empty contributions -- an overlay is allowed to be just a directory of files.
Source code in src/dotagents/_overlays.py
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | |
recompose_overlay_block(agents_md, base_block, overlay_dirs, dry_run, logger)
Rebuild AGENTS.md's managed block from the pristine base over ALL installed
overlays in (priority, name) order (plan 02 / D68), in place.
This is what makes single-overlays add positioning correct: a lone add merges
one overlay, but its rules must land in the right slot relative to overlays
already present in the block. Incremental append (merge_overlay_rules) can only
tack the newcomer on at the end, so a high-priority overlay added after a
low-priority one would wrongly sort last. Recomposing from the base each time
sidesteps that: base_block is the freshly-read base overlay's managed block (no
overlay content), overlay_dirs is every installed overlay in the scope, and
_compose_block folds them in sorted order -- so the block is a pure function of
which overlays are installed, never when each was added.
Only the managed block (between the dotagents markers) is rewritten; content outside the markers is untouched. Returns True if the file changed, False on a no-op (nothing to merge, file/markers absent, or block already correct).
Source code in src/dotagents/_overlays.py
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 | |
rule_blocks(overlay_dir, rel_paths)
Extract - **… bullet blocks from each referenced markdown file.
Returns (blocks, warnings). Only the leading run of bullets is taken: a rules
file may carry explanatory prose under a ## heading (engineering/rules.md
documents why its rules are not in the base), and that must not be merged
into the core. A path that does not exist is reported, not fatal -- a broken
optional overlay must never block the base config landing.
Source code in src/dotagents/_overlays.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | |
run_overlay_setup(overlay_dir, name, *, agents_dir, dry_run, logger)
Run an overlay's idempotent setup script if it ships one.
Returns the script's exit code, or None when the overlay has no setup
script (nothing to run -- not an error). Presence of setup.py (preferred,
OS-agnostic) or the legacy setup at the overlay root is the opt-in; the
runner never second-guesses a script the user chose to install (see the
overlay-authoring contract).
Idempotency is the overlay author's contract: the script must be safe to
run on every add/sync (check-then-act). The runner only invokes it.
Invocation (pure stdlib subprocess, the same style the private-sync overlay's sync hook uses):
- cwd = the installed overlay dir, so the script sees its own files.
- env carries
AGENTS_HOME= the resolved store path (D58 configurable store), so the script never hardcodes~/.agents. It also getsAGENTS_OVERLAY_DIR= its own installed dir for convenience. The oldDOTAGENTS_AGENTS_DIR/DOTAGENTS_OVERLAY_DIRare also set for back-compat this release (removable next), so existing setup scripts keep working. - a
.pyscript runs under the current interpreter; an extensionlesssetupruns directly, or viashon Windows where it isn't executable.
A non-zero exit is surfaced (returned) so the caller can raise a clear error
-- never a silent skip. Never prints DOTAGENTS_* values (Leakage).
Source code in src/dotagents/_overlays.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
sort_overlays_by_priority(overlay_dirs)
Return overlay_dirs sorted by (priority, name) (plan 02 / D68).
Deterministic regardless of the caller's order: install/overlays add pass
overlays in add-invocation order and overlays sync in alphabetical discovery
order, but the merged block must not depend on either -- a high-priority
overlay's lines must always land last. See overlay_sort_key for the convention.
Source code in src/dotagents/_overlays.py
214 215 216 217 218 219 220 221 222 | |