dotagents-cli 0.6.2__tar.gz → 0.7.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.
Files changed (111) hide show
  1. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/CHANGELOG.md +57 -1
  2. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/PKG-INFO +8 -8
  3. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/README.md +7 -7
  4. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/guide/commands.md +35 -16
  5. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/guide/install.md +1 -1
  6. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/guide/overlays.md +8 -4
  7. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/index.md +2 -1
  8. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/AGENTS.md +58 -15
  9. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/__init__.py +1 -1
  10. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_agents.py +25 -6
  11. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_env.py +22 -2
  12. dotagents_cli-0.7.0/src/dotagents/_ignore.py +146 -0
  13. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_overlays.py +90 -59
  14. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_resources.py +12 -0
  15. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/cli/__init__.py +23 -2
  16. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/cli/env.py +77 -3
  17. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/cli/overlays.py +32 -33
  18. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_cli_main.py +2 -2
  19. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_cmds_discovery.py +38 -0
  20. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_env.py +37 -0
  21. dotagents_cli-0.7.0/tests/test_env_into.py +89 -0
  22. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_fix_overlays.py +59 -20
  23. dotagents_cli-0.7.0/tests/test_ignore.py +58 -0
  24. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_overlays_cli.py +5 -10
  25. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_overlays_command.py +7 -5
  26. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_wire_hooks.py +35 -8
  27. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tools/pyz_smoke.sh +7 -0
  28. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/.gitignore +0 -0
  29. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/LICENSE +0 -0
  30. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/agents.md +0 -0
  31. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/cli.md +0 -0
  32. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/commands.md +0 -0
  33. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/context.md +0 -0
  34. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/env.md +0 -0
  35. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/fs.md +0 -0
  36. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/hooks.md +0 -0
  37. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/merge.md +0 -0
  38. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/overlays.md +0 -0
  39. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/reference.md +0 -0
  40. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/resources.md +0 -0
  41. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/scope.md +0 -0
  42. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/skills.md +0 -0
  43. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/sources.md +0 -0
  44. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/api/wrappers.md +0 -0
  45. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/changelog.md +0 -0
  46. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/guide/authoring.md +0 -0
  47. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/docs/guide/private-sync.md +0 -0
  48. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/install.py +0 -0
  49. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/mkdocs.yml +0 -0
  50. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/pyproject.toml +0 -0
  51. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/__main__.py +0 -0
  52. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_context.py +0 -0
  53. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_fs.py +0 -0
  54. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_hooks.py +0 -0
  55. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_merge.py +0 -0
  56. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/cmds/findings.py +0 -0
  57. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/cmds/launch.py +0 -0
  58. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/hooks/preinvocation_antigravity_context.py +0 -0
  59. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/hooks/pretooluse_codex_env.py +0 -0
  60. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/hooks/sessionstart_codex_context.py +0 -0
  61. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/templates/AGENTS.md +0 -0
  62. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/templates/PROJECT.md +0 -0
  63. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_scope.py +0 -0
  64. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_skills.py +0 -0
  65. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_sources.py +0 -0
  66. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/_wrappers.py +0 -0
  67. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/cli/_common.py +0 -0
  68. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/cli/about.py +0 -0
  69. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/cli/build_pyz.py +0 -0
  70. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/cli/context.py +0 -0
  71. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/cli/init.py +0 -0
  72. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/cli/path.py +0 -0
  73. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/src/dotagents/py.typed +0 -0
  74. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/_helpers.py +0 -0
  75. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/_shell.py +0 -0
  76. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/conftest.py +0 -0
  77. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_about.py +0 -0
  78. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_agents.py +0 -0
  79. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_api_header.py +0 -0
  80. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_audit_leak.py +0 -0
  81. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_build_pyz.py +0 -0
  82. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_cloud_setup.py +0 -0
  83. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_context.py +0 -0
  84. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_env_cli_scope.py +0 -0
  85. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_env_format.py +0 -0
  86. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_env_regressions.py +0 -0
  87. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_env_safety.py +0 -0
  88. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_findings_cmd.py +0 -0
  89. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_fix_adapters.py +0 -0
  90. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_fix_cli.py +0 -0
  91. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_fix_env.py +0 -0
  92. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_fix_hooks.py +0 -0
  93. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_fix_init.py +0 -0
  94. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_fix_packaging.py +0 -0
  95. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_fix_scope_context.py +0 -0
  96. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_fix_skills_adapters.py +0 -0
  97. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_hooks.py +0 -0
  98. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_init_base.py +0 -0
  99. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_last_mile.py +0 -0
  100. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_launch_cmd.py +0 -0
  101. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_mcp.py +0 -0
  102. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_overlay_libs.py +0 -0
  103. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_path_timeout_frontmatter.py +0 -0
  104. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_powershell_env_hook.py +0 -0
  105. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_review_security.py +0 -0
  106. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_scope.py +0 -0
  107. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_sources.py +0 -0
  108. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_sources_uri.py +0 -0
  109. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tests/test_wrappers.py +0 -0
  110. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tools/audit.py +0 -0
  111. {dotagents_cli-0.6.2 → dotagents_cli-0.7.0}/tools/cloud-setup.sh +0 -0
@@ -7,6 +7,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.0] - 2026-10-01
11
+
12
+ ### Changed
13
+
14
+ - `overlays sync`, and `overlays add` of an overlay already installed, make the
15
+ install exactly what the source ships: changed files are replaced and files the
16
+ source does not ship are removed, including ones edited or added in the install
17
+ (each backed up under `<store>/install_backup/` first). What the `.gitignore` /
18
+ `.ignore` at the overlay's root match (read as git reads a `.gitignore`; one in a
19
+ subdirectory is an ordinary file), and tool caches, are the install's own and left
20
+ alone. Previously an edited file was kept unless `--overwrite`.
21
+ - `--prune` (on `sync`, and now on `add`) clears what `.gitignore` / `.ignore`
22
+ protect and the tool caches too, leaving exactly the upstream files.
23
+
24
+ ### Fixed
25
+
26
+ - A command module whose class declares no `_parsername_` is named by its
27
+ class in kebab case again (`LeakCheck` -> `dotagents leak-check`). Since 0.6.2
28
+ (duho 0.6.0) such a command was only reachable as `dotagents LeakCheck`.
29
+ - The Claude Code CwdChanged hook keeps one `AGENTS_PROJECT_ROOT` pin in
30
+ `$CLAUDE_ENV_FILE`, replacing the previous one, instead of appending a line on
31
+ every `cd` (the file is inlined into every Bash command). Other lines in the file
32
+ are kept. Re-run `dotagents init -g` to update an installed hook.
33
+
34
+ ### Removed
35
+
36
+ - `overlays sync --overwrite`: replacing is what `sync` always does now.
37
+
38
+ ## [0.6.3] - 2026-09-30
39
+
40
+ ### Added
41
+
42
+ - `env --into FILE` writes the output into FILE as the one block `env` owns
43
+ there, replacing the block an earlier run wrote and keeping every other line.
44
+ - With `--diff --format export`, a `PATH` that is the caller's own with entries
45
+ put in front is written as `export PATH='<entries>'"${PATH:+:$PATH}"`, so it
46
+ stays short and keeps what the sourcing shell has.
47
+ - When `dotagents` runs from a `.pyz`, `env` exports `AGENTS_PYLIB`, the path
48
+ of that archive. The libraries it bundles (duho, pathlib_next) import from
49
+ it, so an overlay built on duho works even when the interpreter in
50
+ `AGENTS_PYTHON` has no duho installed. An installed `dotagents` sets nothing:
51
+ its libraries are already importable from `AGENTS_PYTHON`. The archive is
52
+ never put on `PYTHONPATH`, where it would shadow a project's own copy; a
53
+ consumer adds it to `sys.path` itself when its import fails.
54
+
55
+ ### Fixed
56
+
57
+ - The Claude Code SessionStart hook no longer appends the whole environment to
58
+ `$CLAUDE_ENV_FILE` every time it runs (startup, resume, compact, clear). Claude
59
+ Code inlines that file into every Bash command, and once it passed about 8 KB,
60
+ Git for Windows' bash truncated the command and every Bash call failed with an
61
+ unterminated quote. The hook now uses `env --into`, which keeps one block.
62
+ Re-run `dotagents init -g` to update an installed hook.
63
+
10
64
  ## [0.6.2] - 2026-09-28
11
65
 
12
66
  ### Added
@@ -1308,7 +1362,9 @@ Patch release: the PATH/POSIX-conversion fix above (the only change since 0.3.0)
1308
1362
  affected module sources (`dotagents.cli`, `duho.presets`) to extracted temp files
1309
1363
  before dispatch; a no-op for a plain install.
1310
1364
 
1311
- [Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.6.2...HEAD
1365
+ [Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.7.0...HEAD
1366
+ [0.7.0]: https://github.com/jose-pr/dotagents/compare/v0.6.3...v0.7.0
1367
+ [0.6.3]: https://github.com/jose-pr/dotagents/compare/v0.6.2...v0.6.3
1312
1368
  [0.6.2]: https://github.com/jose-pr/dotagents/compare/v0.6.1...v0.6.2
1313
1369
  [0.6.1]: https://github.com/jose-pr/dotagents/compare/v0.6.0...v0.6.1
1314
1370
  [0.6.0]: https://github.com/jose-pr/dotagents/compare/v0.5.1...v0.6.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: dotagents-cli
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: The dotagents CLI: install and manage a portable ~/.agents config for AI coding agents
5
5
  Project-URL: Homepage, https://github.com/jose-pr/dotagents/
6
6
  Project-URL: Repository, https://github.com/jose-pr/dotagents
@@ -203,14 +203,14 @@ installed first, its `routing`/`rules` merge into `AGENTS.md`'s managed block, a
203
203
  `skills/<name>/` are symlinked (or `--copy`'d, for Windows / no-symlink) into the
204
204
  store's shared `skills/` so every agent sees the same skills.
205
205
 
206
- - `add` and `sync` never clobber a file you hand-edited inside an installed overlay:
207
- a file that differs from the source is kept and reported. A fresh `add` whose setup
208
- script fails is rolled back.
206
+ - `add` and `sync` keep an installed overlay exactly as its source ships it: changed
207
+ files are replaced and files the source does not ship are removed, anything edited
208
+ there backed up under `<store>/install_backup/` first. What the `.gitignore` /
209
+ `.ignore` at the overlay's root match (setup output, local state) and tool caches are left
210
+ alone; `--prune` clears them too. A fresh `add` whose setup script fails is rolled
211
+ back.
209
212
  - `sync` refreshes each overlay from the repo it was installed from (`--repo` replaces
210
- that source), removes files the source dropped that you never edited, installs a
211
- `requires` added upstream, and re-merges the managed block. `--overwrite` also
212
- replaces edited files and `--prune` removes edited files the source dropped; both
213
- back each file up under `<store>/install_backup/` first.
213
+ that source), installs a `requires` added upstream, and re-merges the managed block.
214
214
  - `remove` deletes the overlay's directory, unpublishes only the skills **it**
215
215
  published, and recomposes `AGENTS.md`'s managed block over the overlays that remain,
216
216
  so its rules and routing leave with it. It refuses an overlay another installed
@@ -153,14 +153,14 @@ installed first, its `routing`/`rules` merge into `AGENTS.md`'s managed block, a
153
153
  `skills/<name>/` are symlinked (or `--copy`'d, for Windows / no-symlink) into the
154
154
  store's shared `skills/` so every agent sees the same skills.
155
155
 
156
- - `add` and `sync` never clobber a file you hand-edited inside an installed overlay:
157
- a file that differs from the source is kept and reported. A fresh `add` whose setup
158
- script fails is rolled back.
156
+ - `add` and `sync` keep an installed overlay exactly as its source ships it: changed
157
+ files are replaced and files the source does not ship are removed, anything edited
158
+ there backed up under `<store>/install_backup/` first. What the `.gitignore` /
159
+ `.ignore` at the overlay's root match (setup output, local state) and tool caches are left
160
+ alone; `--prune` clears them too. A fresh `add` whose setup script fails is rolled
161
+ back.
159
162
  - `sync` refreshes each overlay from the repo it was installed from (`--repo` replaces
160
- that source), removes files the source dropped that you never edited, installs a
161
- `requires` added upstream, and re-merges the managed block. `--overwrite` also
162
- replaces edited files and `--prune` removes edited files the source dropped; both
163
- back each file up under `<store>/install_backup/` first.
163
+ that source), installs a `requires` added upstream, and re-merges the managed block.
164
164
  - `remove` deletes the overlay's directory, unpublishes only the skills **it**
165
165
  published, and recomposes `AGENTS.md`'s managed block over the overlays that remain,
166
166
  so its rules and routing leave with it. It refuses an overlay another installed
@@ -87,8 +87,8 @@ the store's, else `dotagents` on `PATH`.
87
87
 
88
88
  | Hook | Command | Why |
89
89
  | --- | --- | --- |
90
- | `SessionStart` | appends `dotagents env --diff --format export` to `$CLAUDE_ENV_FILE`, then runs `dotagents context` | Claude sources `$CLAUDE_ENV_FILE` before each Bash command, so the env layers reach every command in the session; and it injects the hook's **stdout into the session context**, which is how the assembled context reaches the model. |
91
- | `CwdChanged` | when the new directory has a `.agents/`, appends `export AGENTS_PROJECT_ROOT=<it>` to `$CLAUDE_ENV_FILE`; then prints the directory's `AGENTS.md` if there is one | Re-pins the project root after a `cd` into another project, and surfaces that directory's `AGENTS.md`. |
90
+ | `SessionStart` | writes `dotagents env --diff --format export` into `$CLAUDE_ENV_FILE` (`--into`: one block, replaced on every run; `PATH` as a prefix on the shell's own), then runs `dotagents context` | Claude sources `$CLAUDE_ENV_FILE` before each Bash command, so the env layers reach every command in the session; and it injects the hook's **stdout into the session context**, which is how the assembled context reaches the model. |
91
+ | `CwdChanged` | when the new directory has a `.agents/`, writes `export AGENTS_PROJECT_ROOT=<it>` into `$CLAUDE_ENV_FILE`, replacing the pin an earlier `cd` wrote; then prints the directory's `AGENTS.md` if there is one | Re-pins the project root after a `cd` into another project, and surfaces that directory's `AGENTS.md`. |
92
92
 
93
93
  On Windows each event gets a second, PowerShell-native handler (`shell:
94
94
  "powershell"`) that runs only when Claude Code would find no Git Bash, so a machine
@@ -105,11 +105,16 @@ without a prompt on each one requires `permissionDecision: "allow"`, which skips
105
105
  approval prompt (your deny and ask rules still apply). It is off by default, and a
106
106
  plain `init` removes one an earlier release wired.
107
107
 
108
- The env half **appends** (`>>`) and is guarded by `[ -n "$CLAUDE_ENV_FILE" ]`, both
109
- per the [hooks docs](https://code.claude.com/docs/en/hooks#persist-environment-variables):
110
- other hooks write to the same file, so `>` would discard their variables, and an
111
- unguarded redirect would create a file literally named `""` where the variable is
112
- unset. `$CLAUDE_ENV_FILE` exists only inside SessionStart/Setup/CwdChanged/FileChanged
108
+ The env half never truncates the file and is guarded by `[ -n "$CLAUDE_ENV_FILE" ]`
109
+ (see the [hooks docs](https://code.claude.com/docs/en/hooks#persist-environment-variables)):
110
+ another hook may write to the same file, so each hook replaces only its own part --
111
+ SessionStart its `env --into` block, CwdChanged its one `AGENTS_PROJECT_ROOT` line --
112
+ and keeps every other line. Appending instead grew the file on every re-run (resume,
113
+ compact, clear) and every `cd`, and Claude Code inlines it into each Bash command, which
114
+ Git for Windows' bash cuts off at about 8 KB. Each run starts from a fresh environment
115
+ (the hooks docs: previously sourced env files are not loaded in the hook's process), so
116
+ SessionStart recomputes the whole diff: a value changed since the last run is updated,
117
+ and one no env layer sets any more drops out. `$CLAUDE_ENV_FILE` exists only inside SessionStart/Setup/CwdChanged/FileChanged
113
118
  hook processes — it is absent from the session's own shell, so checking for it with
114
119
  `env | grep` proves nothing.
115
120
 
@@ -163,7 +168,7 @@ Manages opt-in overlays **by name**. See [Overlays](overlays.md) for the full mo
163
168
  dotagents overlays add python engineering # install into the scope, publish skills, merge rules/routing
164
169
  dotagents overlays list # installed (discovered) + available from the repos
165
170
  dotagents overlays sync 'py*' # refresh installed overlays matching a glob
166
- dotagents overlays sync --overwrite # also replace installed files you edited (backed up)
171
+ dotagents overlays sync --prune # also clear what .gitignore/.ignore protect: exactly upstream
167
172
  dotagents overlays remove python # delete the overlay dir, unpublish its skills, un-merge its rules
168
173
  dotagents overlays show python # describe one: manifest, requires, setup, skills, source (--json)
169
174
  ```
@@ -184,13 +189,17 @@ dotagents overlays show python # describe one: manifest, requires, s
184
189
  it as done.
185
190
  - **`sync`** refreshes each installed overlay from the repo it was installed from
186
191
  (recorded in `<overlay>/.dotagents-install.json`); `--repo` replaces that recorded
187
- source for the run. New files land and `overlay.toml` is refreshed; a file that
188
- differs from the source is kept and reported, and `--overwrite` replaces it. A file
189
- the source no longer ships is removed when you never edited it, and kept (reported)
190
- when you did, unless `--prune`. `--overwrite` and `--prune` copy each file to
191
- `<store>/install_backup/<timestamp>/overlays/<name>/` first. A `requires` added
192
- upstream is installed, and a published skill copy you edited is kept unless
193
- `--overwrite`.
192
+ source for the run. The install is made to match the source exactly: new and changed
193
+ files land, `overlay.toml` is refreshed, and files the source does not ship are
194
+ removed. A file you edited (or added) there is copied to
195
+ `<store>/install_backup/<timestamp>/overlays/<name>/` before it is replaced or
196
+ removed. What the `.gitignore` / `.ignore` at the overlay's root match — setup
197
+ output, local state — and tool caches (`__pycache__`, `.venv`, …) are the install's own and
198
+ left alone; `--prune` clears them too, leaving exactly the upstream files. A
199
+ `requires` added upstream is installed, and published skill copies are refreshed.
200
+ `add` of an overlay already installed does the same (with `--prune` too). Keep your
201
+ own additions outside `<store>/overlays/` — a `kb/` file of your own, an
202
+ `AGENTS.local.md` — where a sync never touches them.
194
203
  - **`remove`** deletes the overlay's directory, unpublishes the skills it published
195
204
  (and their links in Claude's skills dir), and recomposes `AGENTS.md`'s managed block over the overlays that remain, so its
196
205
  rules and routing leave with it. It refuses an overlay another installed overlay
@@ -272,6 +281,11 @@ dotagents env --diff --format json # only vars that differ from the caller's e
272
281
  environment, the working directory, the stores, the overlays, every env and `lib`
273
282
  file), for at most five minutes; the per-command env loaders use it. The cache is
274
283
  owner-only, under `<user store>/.cache/env/`.
284
+ - `--into <file>` — write into `<file>` instead of stdout, as the one block this
285
+ command owns there (between `: dotagents-env-begin` / `: dotagents-env-end`):
286
+ an earlier block is replaced, anything else in the file is kept. The SessionStart
287
+ hook writes `$CLAUDE_ENV_FILE` this way, so the file stays one block however often
288
+ the hook runs. `export` format only.
275
289
  - `-g` / `--global` — skip the project-level env files (the store is unaffected).
276
290
  - `--agents-dir <dir>` — user store override for this run.
277
291
 
@@ -307,7 +321,12 @@ form and drop names that are not shell identifiers.
307
321
  overlay, where `NAME` is the overlay's name upper-cased with `-` turned into `_`
308
322
  (`my-ov` → `MY_OV_OVERLAY_ROOT`). That is the same name `context` expands as a
309
323
  `<NAME_OVERLAY_ROOT>` placeholder, so an env file and a context file refer to an
310
- overlay's install dir by one name.
324
+ overlay's install dir by one name. When `dotagents` runs from a `.pyz`,
325
+ `AGENTS_PYLIB` names that archive: the libraries it bundles (duho,
326
+ pathlib_next) import from it, so an overlay built on them appends it to
327
+ `sys.path` when its own import fails. An installed `dotagents` sets nothing,
328
+ since those libraries are already in `AGENTS_PYTHON`'s site-packages. It is
329
+ never put on `PYTHONPATH`, where it would shadow a project's own copy.
311
330
  3. **`PATH`** — every level's `bin/` (each store's overlays, then the store) goes
312
331
  to the front in that order, including ones that do not exist yet, so an `env.py`
313
332
  and every subprocess can call an overlay's helpers by name. The order holds even
@@ -96,7 +96,7 @@ python -m dotagents build-pyz --out dist/dotagents.pyz # or build it (needs a
96
96
  holds the git checkouts and remote sources the repos materialize (the directory carries a
97
97
  `.gitignore` of `*`, so a store kept in git never records them).
98
98
  - `install_backup/<timestamp>/` — what `init --force` replaced, and what
99
- `overlays sync --overwrite` / `--prune` replaced or removed.
99
+ `overlays add` / `sync` replaced or removed that had been edited in an installed overlay.
100
100
  - `dotagents/cmds/` — only if you create it: your own command modules (see
101
101
  [Authoring → Custom commands](authoring.md#custom-commands)).
102
102
  - `dotagents/config.toml` — only after `init --from`: records that base, so a later
@@ -73,10 +73,14 @@ store). Each overlay:
73
73
  reads that dir sees the same skills.
74
74
 
75
75
  `sync` refreshes each overlay from the repo it was installed from (`--repo` replaces
76
- that source for the run), removes files the source dropped that you never edited,
77
- installs a `requires` added upstream, and reports files that differ from the source
78
- instead of replacing them — `--overwrite` replaces them and `--prune` also removes
79
- edited files the source dropped, each backed up under `<store>/install_backup/` first.
76
+ that source for the run) and makes the install exactly the source: changed files are
77
+ replaced and files the source does not ship are removed, anything edited there backed
78
+ up under `<store>/install_backup/` first. What the `.gitignore` / `.ignore` at the
79
+ overlay's root match (an ignore file in a subdirectory is just a file it ships), and
80
+ tool caches, belong to the install (a setup script's output, local
81
+ state) and are left alone; `--prune` clears them too. A `requires` added upstream is
82
+ installed. An installed overlay is upstream's copy, not a place for your edits: keep
83
+ those in your own files (a `kb/` file, `AGENTS.local.md`).
80
84
 
81
85
  Removing an overlay deletes only its directory and unpublishes only the skills **it**
82
86
  published (a copy the user edited is left alone). Its lines in `AGENTS.md`'s managed
@@ -23,7 +23,8 @@ carry the opinions.
23
23
  composable **overlays** you layer in explicitly with `dotagents overlays add
24
24
  <name>`. The overlays in this repo are examples — payloads riding on dotagents,
25
25
  swappable for your own; see [Overlays](guide/overlays.md) for what each ships.
26
- Overlays are additive: they never overwrite a file you have already customized.
26
+ An installed overlay is kept exactly as upstream ships it (what the `.gitignore` /
27
+ `.ignore` at its root protect stays yours), so your own changes live in your own files.
27
28
  - **Two scopes.** Config installs into a **user** store (`~/.agents`, configurable)
28
29
  or a **project** store (`<project>/.agents`). Overlays, skills, commands, and env
29
30
  files all resolve across the same scope precedence.
@@ -31,14 +31,14 @@ and an error saying a command is required, and exits 2.
31
31
  | Command | Contract |
32
32
  | --- | --- |
33
33
  | `init [-g] [--dest D] [--from SRC] [--bin-dir D] [--agents a,b] [--no-hooks] [--powershell-env-hook] [--force] [--dry-run]` | Merge the base block into `<store>/AGENTS.md`, write `<store>/bin/dotagents[.cmd]`, and per active agent (`--agents`, else detected + `claude`) write its include and hooks. `--force` replaces `AGENTS.md`'s content, backed up under `<store>/install_backup/<timestamp>/`. `--from` records its base in `<store>/dotagents/config.toml`. |
34
- | `overlays add NAME... [-g] [--repo SPEC]... [--copy] [--no-setup] [--setup-timeout S] [--no-requires] [--dry-run]` | Install each overlay and its `requires` into `<store>/overlays/<name>/`, publish its skills, run its `setup.py`, recompose the managed block. Every name is resolved before anything is written; a source inside `<store>/overlays/` is refused; a fresh install whose setup fails is rolled back. No name: exit 2. |
34
+ | `overlays add NAME... [-g] [--repo SPEC]... [--copy] [--no-setup] [--setup-timeout S] [--no-requires] [--prune] [--dry-run]` | Install each overlay and its `requires` into `<store>/overlays/<name>/` (an installed one is made to match its source again, as `sync` does; `--prune` clears its ignored files too), publish its skills, run its `setup.py`, recompose the managed block. Every name is resolved before anything is written; a source inside `<store>/overlays/` is refused; a fresh install whose setup fails is rolled back. No name: exit 2. |
35
35
  | `overlays remove NAME... [-g] [--force] [--dry-run]` | Delete the overlay dirs, unpublish the skills they published, recompose the block. Refuses an overlay another installed overlay requires unless `--force`. No name: exit 2. |
36
- | `overlays sync [GLOB] [-g] [--repo SPEC]... [--copy] [--overwrite] [--prune] [--no-setup] [--setup-timeout S] [--dry-run]` | Refresh installed overlays from the repo recorded at install (`--repo` replaces it). Files that differ from the source are kept unless `--overwrite`; files the source dropped are removed if unedited, else kept unless `--prune`; replaced/pruned files are backed up under `<store>/install_backup/<timestamp>/overlays/<name>/`. Installs new `requires`. Exits 1 when an overlay's repo cannot be loaded, its source lies inside `<store>/overlays/`, or its setup fails. |
36
+ | `overlays sync [GLOB] [-g] [--repo SPEC]... [--copy] [--prune] [--no-setup] [--setup-timeout S] [--dry-run]` | Refresh installed overlays from the repo recorded at install (`--repo` replaces it): the install is made to match the source exactly: new and changed files land, `overlay.toml` is refreshed, files the source does not ship are removed; a file edited here is backed up first under `<store>/install_backup/<timestamp>/overlays/<name>/`. What the `.gitignore` / `.ignore` at the overlay's root match (setup output, local state) and tool caches are left alone; `--prune` clears them too. Installs new `requires`. Exits 1 when an overlay's repo cannot be loaded, its source lies inside `<store>/overlays/`, or its setup fails. |
37
37
  | `overlays list [-g] [--repo SPEC]... [--json]` | Installed overlays per store (shadowed copies marked, unmet `requires` flagged) and what the repos offer. |
38
38
  | `overlays show NAME [-g] [--repo SPEC]... [--json]` | One overlay: the copy a session would use, else the source's; manifest, setup, skills, file count, recorded source. No name: exit 2. |
39
39
  | `context [OUT] [-g] [--agents a,b] [--format markdown\|system-reminder\|json] [--write-agent] [--inline]` | Print (or write to `OUT`) the assembled context. `--write-agent` merges it into each agent's `context_target` under the project root (not with `-g`). Exits 2 when `--agents` names no known agent. |
40
40
  | `path [-g] [--lib] [--format list\|native\|posix\|json]` | Print the bin dirs `env` puts on `PATH` (`--lib`: the existing lib dirs it puts on `PYTHONPATH`), highest precedence first; runs no env file. `posix` is `:`-joined, MSYS2 form on Windows. |
41
- | `env [-g] [--format F] [--diff] [--cache]` | Print the assembled environment (`--diff`: only what differs from the caller's; `--cache`: replay the previous output while `env_cache_key` holds, up to `ENV_CACHE_TTL`) in format `F`: `auto` (default: the calling shell), `export`/`posix`/`sh`/`bash`, `dotenv`/`env`, `powershell`/`pwsh`/`ps`, `cmd`/`bat`/`batch`, `fish`, `json`, `ini`, `yaml`. |
41
+ | `env [-g] [--format F] [--diff] [--cache] [--into FILE]` | Print the assembled environment (`--diff`: only what differs from the caller's; `--cache`: replay the previous output while `env_cache_key` holds, up to `ENV_CACHE_TTL`; `--into FILE`: write it into FILE as the one block this command owns there) in format `F`: `auto` (default: the calling shell), `export`/`posix`/`sh`/`bash`, `dotenv`/`env`, `powershell`/`pwsh`/`ps`, `cmd`/`bat`/`batch`, `fish`, `json`, `ini`, `yaml`. |
42
42
  | `launch [AGENT] [-g] [--command PROG] [--no-context] [--inline] [--write-agent] [--dry-run] [-- ARGS...]` | Run the agent's CLI with the env applied and the context handed over; the exit code is the harness's (`128+N` on signal N). |
43
43
  | `findings add\|list\|show\|done\|reopen\|remove\|index\|path [-g] [--dir D]` | The findings queue at `<store>/findings/` (`--dir` overrides): one markdown file per finding, `done` moves it to `processed/` with a required resolution, `INDEX.md` regenerated on every change. |
44
44
  | `about [--json]` | `dotagents-cli <version>`, then `<distribution> <version>` per bundled (`.pyz`) or installed package. |
@@ -68,6 +68,10 @@ are skipped.
68
68
  - `class Dotagents(LoggingArgs, Cli)` — the umbrella; field `cmdspath: list[str]`.
69
69
  - `CMDS_PATH_ENV = "AGENTS_CMDS_PATH"`.
70
70
  - Re-exported for command modules: `DotAgentsArgs`, `_write_stdout` (see below).
71
+ - `kebab_name(class_name) -> str` — a discovered command's default name when its
72
+ class declares no `_parsername_`: `LeakCheck` -> `leak-check`, `HTTPServer` ->
73
+ `http-server`, `my_cmd` -> `my-cmd`. `_parsername_` is optional in a command
74
+ module; an explicit one always wins.
71
75
 
72
76
  ## `dotagents.cli._common`
73
77
 
@@ -75,7 +79,8 @@ are skipped.
75
79
  `global_scope: bool = False` (`--global`, `-g`) and
76
80
  `agents_dir: Optional[Path] = None` (`--agents-dir`); `resolve_scope(*,
77
81
  project_root=None) -> Scope` (via `_scope.resolve_scope`). Subclass it alone
78
- (`class X(DotAgentsArgs)`), define `_parsername_` and `__call__(self) -> int`. A
82
+ (`class X(DotAgentsArgs)`), define `__call__(self) -> int` and optionally
83
+ `_parsername_` (default: `kebab_name` of the class, for a discovered module). A
79
84
  subclass may redeclare a field with the same type and default to change its help.
80
85
  - `_write_stdout(text) -> None` — write to stdout as UTF-8 whatever the console's
81
86
  encoding (a bare `print` fails on a non-Latin-1 character under cp1252).
@@ -95,6 +100,23 @@ are skipped.
95
100
  `OVERLAY_ROOT_NOTE` (`dotagents._overlays`), `resolve_user_store`
96
101
  (`dotagents._scope`).
97
102
 
103
+ ## `dotagents._ignore`
104
+
105
+ The `.gitignore` / `.ignore` at the ROOT of a directory, read as git reads a
106
+ `.gitignore` (pure stdlib) -- what an installed overlay keeps as its own when `add`
107
+ / `sync` mirror its source. An ignore file in a subdirectory is an ordinary file;
108
+ nothing above the root is read.
109
+
110
+ - `IGNORE_FILES = (".gitignore", ".ignore")` — read at the root in that order, so
111
+ `.ignore` wins (ripgrep's precedence).
112
+ - `parse(text, base="") -> list` — one file's rules (`base`: its directory):
113
+ comments, `\#` / `\!` escapes, `!` negation, leading/middle `/` anchoring,
114
+ trailing `/` directory-only, `*`, `?`, `[...]`, `**`.
115
+ - `class IgnoreRules(rules=())` — `for_root(root)` reads `root`'s two files;
116
+ `ignored(rel, is_dir=False) -> bool` decides a `/`-joined path (a pattern without
117
+ a slash matches at any depth), a path under an ignored directory staying ignored
118
+ whatever a later `!` says, as in git.
119
+
98
120
  ## `dotagents._resources`
99
121
 
100
122
  The package's own data; library code, importing nothing from `dotagents.cli`.
@@ -108,6 +130,10 @@ The package's own data; library code, importing nothing from `dotagents.cli`.
108
130
  store at `dest`: the project template for a project store when the base has one,
109
131
  else the user template, else `<src>/AGENTS.md`; `{{AGENTS_MD}}` rendered as the
110
132
  absolute POSIX path of `<dest>/AGENTS.md`.
133
+ - `pyz_archive() -> str | None` — the `.pyz` this dotagents is imported from (the
134
+ ancestor of the module's `__file__` that is a file), or `None` for a plain
135
+ install. The archive also holds the vendored duho and pathlib_next; `env`
136
+ exports it as `AGENTS_PYLIB`.
111
137
 
112
138
  ## Commands — `dotagents.cli.init`, `dotagents.cli.overlays`, `dotagents.cli.context`, `dotagents.cli.env`, `dotagents.cli.path`, `dotagents.cli.about`, `dotagents.cli.build_pyz`
113
139
 
@@ -119,6 +145,13 @@ returns the exit code.
119
145
  ("markdown", "system-reminder", "json")`); `env.Env`; `path.PathCmd`
120
146
  (`PATH_FORMATS = ("list", "native", "posix", "json")`); `build_pyz.BuildPyz`;
121
147
  `about.About`.
148
+ - `env`: `write_into(path, text, output_format)` — what `--into` does: the file
149
+ keeps ONE block between `INTO_BEGIN = ": dotagents-env-begin"` and
150
+ `INTO_END = ": dotagents-env-end"` (no-op commands, not comments, so a `; cmd`
151
+ appended to the last line still runs; earlier blocks removed, other lines kept,
152
+ the new block last), written atomically; `INTO_FORMATS = ("export",)`. With
153
+ `--diff --format export`, a `PATH` that is the caller's with entries in front is
154
+ written as `export PATH='<prefix>'"${PATH:+:$PATH}"`.
122
155
  - `about`: `DISTRIBUTION = "dotagents-cli"`, `BUNDLE_FILE = "_bundle.json"` (written
123
156
  by `build-pyz` into the package), `RUNTIME_PACKAGES = ("duho", "pathlib_next",
124
157
  "uritools", "netimps", "requests")` (the only packages a plain install reports);
@@ -187,9 +220,9 @@ returns the exit code.
187
220
  string arrays including multi-line strings, an integer); `None` plus one warning
188
221
  when it is not valid TOML.
189
222
  - `class InstallResult` — what `install_to` did: `written`, `unchanged`,
190
- `kept` (differ from source), `removed` (dropped by the source), `orphans_kept`
191
- (dropped but edited), `backed_up`, `lines`; property `skipped` (= unchanged +
192
- kept). Unpacks as `(written, skipped, lines)`.
223
+ `replaced` (edited here, replaced by the source's file), `removed` (not in the
224
+ source, or cleared by `prune`), `backed_up`, `lines`; property `skipped`
225
+ (= unchanged). Unpacks as `(written, skipped, lines)`.
193
226
  - `class Overlay(path, store=None)` — one overlay directory; `os.PathLike`; equal
194
227
  and hashed by `path`; nothing read at construction.
195
228
  - Class data: `NAME_RE`, `RESERVED_NAMES` (Windows device names),
@@ -235,12 +268,16 @@ returns the exit code.
235
268
  - `rule_blocks(rel_paths) -> tuple[list[str], list[str]]` — `(blocks, warnings)`:
236
269
  each file's leading `- **` bullets up to its next `## ` heading; a path outside
237
270
  the overlay, missing or unreadable is a warning.
238
- - `install_to(dest_overlay_dir, dry_run, overwrite=False, *, prune=False,
239
- backup_root=None, source=None) -> InstallResult` — copy `files()` in; a file
240
- that differs is kept unless `overwrite`; `overlay.toml` always refreshed; a
241
- recorded file the source dropped is removed when unedited, kept unless `prune`
242
- when edited; replaced/pruned files copied under `backup_root` first; writes the
243
- install record (`source`, or the previous one when `None`).
271
+ - `ignore_rules() -> IgnoreRules` — the rules of the `.gitignore` / `.ignore`
272
+ at the overlay's root (`dotagents._ignore`).
273
+ - `install_to(dest_overlay_dir, dry_run, *, prune=False, backup_root=None,
274
+ source=None) -> InstallResult` — make the install exactly the overlay:
275
+ `files()` installed or replaced, `overlay.toml` refreshed, every other file
276
+ removed; what `ignore_rules()` matches, the install record and `SKIP_PARTS`
277
+ caches are never copied, replaced or removed unless `prune` (which clears
278
+ them, without backup); a replaced or removed file that is not what dotagents
279
+ installed is copied under `backup_root` first; writes the install record
280
+ (`source`, or the previous one when `None`).
244
281
  - `OVERLAY_ROOT_NOTE: str` — the line emitted once above overlay routing lines that
245
282
  use `$<NAME>_OVERLAY_ROOT`.
246
283
  - `recompose_overlay_block(agents_md, base_block, overlays, dry_run, logger) -> bool`
@@ -322,7 +359,11 @@ proxy model.
322
359
  user-scope walk `AGENTS_PROJECT_ROOT` is set only when unset. Only when unset:
323
360
  `AGENTS_PYTHON` (`sys.executable`) and one `<NAME>_OVERLAY_ROOT` per
324
361
  `scope.overlays` (a value pinned to the user store's copy is re-pointed when a
325
- project copy shadows it).
362
+ project copy shadows it). `AGENTS_PYLIB` (`_env.PYLIB_VAR`): the `.pyz` when
363
+ dotagents runs from one (`_resources.pyz_archive()`), else not set — where its
364
+ own libraries (duho, pathlib_next) import from by zipimport for an overlay
365
+ built on them. Never added to `PYTHONPATH`: a consumer appends it to
366
+ `sys.path` when its import fails.
326
367
  - `PATH`: every level's `bin` (contract A, not project-root, missing dirs included)
327
368
  at the front in that order, whatever the caller's `PATH` held; empty and
328
369
  cwd-relative inherited entries are dropped.
@@ -631,6 +672,8 @@ Read:
631
672
  - `AGENTS_HARNESS` — the running harness (registry name or harness id), for
632
673
  identity. `AGENTS_PYTHON` — the interpreter for `env.py`. `AGENTS_PYTHONPATH` —
633
674
  the overlay/store `lib` dirs `env` put on `PYTHONPATH`, highest precedence first.
675
+ `AGENTS_PYLIB` — the `.pyz` dotagents runs from (duho and pathlib_next import
676
+ from it), unset for an installed dotagents.
634
677
  - Harness config dirs: `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `PI_CODING_AGENT_DIR`.
635
678
  Harness markers: `CLAUDECODE`, `CLAUDE_CODE_ENTRYPOINT`, `GEMINI_CLI`,
636
679
  `CODEX_SANDBOX*`, `CURSOR_AGENT`, `PI_CODING_AGENT`. Model vars: `ANTHROPIC_MODEL`,
@@ -644,7 +687,7 @@ Read:
644
687
 
645
688
  Emitted by `env` (and applied by `launch`): `AGENTS_HARNESS`, `AGENTS_VENDOR`,
646
689
  `AGENT`, `AGENTS_MODEL`, `AGENTS_HOME`, `AGENTS_PROJECT_ROOT`, `AGENTS_PYTHON`,
647
- `<NAME>_OVERLAY_ROOT`, `PATH`, `AGENTS_PYTHONPATH`, `AGENTS_PROXY` and the mirrored
690
+ `AGENTS_PYLIB` (from a `.pyz` only), `<NAME>_OVERLAY_ROOT`, `PATH`, `AGENTS_PYTHONPATH`, `AGENTS_PROXY` and the mirrored
648
691
  proxy vars, plus whatever the env files set. `AGENTS_AGENT` is never emitted.
649
692
  `launch` also exports `AGENTS_CONTEXT_FILE`.
650
693
 
@@ -1,3 +1,3 @@
1
1
  """dotagents: installable CLI for the dotagents agent-config payload."""
2
2
 
3
- __version__ = "0.6.2"
3
+ __version__ = "0.7.0"
@@ -500,14 +500,21 @@ class ClaudeAgent(Agent):
500
500
  # SessionStart does two things, in one `type: command` hook (SessionStart
501
501
  # supports only command/mcp_tool hooks, and env must run before context):
502
502
  #
503
- # 1. `dotagents env --diff` output is appended to $CLAUDE_ENV_FILE
503
+ # 1. `dotagents env --diff` output goes into $CLAUDE_ENV_FILE
504
504
  # (code.claude.com/docs/en/env-vars + hooks.md "Persist environment
505
505
  # variables"): Claude sources that file before each Bash command. It is
506
506
  # set only inside SessionStart/Setup/CwdChanged/FileChanged hook
507
507
  # processes, never in the session's own environment.
508
- # * APPEND (`>>`), never truncate -- other hooks write to the same file.
508
+ # * `--into`, not `>>`: the block a previous run wrote is REPLACED
509
+ # and anything else in the file (another hook's lines) is kept.
510
+ # SessionStart re-runs on resume, compact and clear; appending
511
+ # grew the file by the whole env each time, and Claude Code inlines
512
+ # it into every Bash command's `bash -c` argument, which Git for
513
+ # Windows' bash truncates at about 8 KB -- every Bash call then
514
+ # failed with an unterminated quote. PATH is written relative to
515
+ # the shell's own ($PATH), which keeps the block small.
509
516
  # * Guard on non-empty (`[ -n "$CLAUDE_ENV_FILE" ]`) -- when unset,
510
- # `>> ""` would create a file literally named "".
517
+ # `--into ""` has nowhere to write.
511
518
  #
512
519
  # 2. `dotagents context`: Claude injects a SessionStart hook's stdout into
513
520
  # the session context.
@@ -534,7 +541,7 @@ class ClaudeAgent(Agent):
534
541
  SESSION_START_COMMAND = (
535
542
  _HOOK_DOTAGENTS
536
543
  + 'if [ -n "$CLAUDE_ENV_FILE" ]; then '
537
- '"$d" env --diff --format export >> "$CLAUDE_ENV_FILE"; '
544
+ '"$d" env --diff --format export --into "$CLAUDE_ENV_FILE"; '
538
545
  "fi; "
539
546
  '"$d" context'
540
547
  )
@@ -546,10 +553,22 @@ class ClaudeAgent(Agent):
546
553
  # `%q` quotes the path for the bash that sources the file: a `'` in a
547
554
  # directory name used to leave an unterminated quote that broke every
548
555
  # later source of $CLAUDE_ENV_FILE (and a crafted name ran commands).
556
+ # The file keeps ONE pin, the latest: an earlier `export
557
+ # AGENTS_PROJECT_ROOT=` line is dropped and every other line (another
558
+ # hook's) kept. Appending grew the file by a line per `cd`, and Claude Code
559
+ # inlines it into every Bash command (see SESSION_START_COMMAND). Pure bash,
560
+ # 3.2-compatible (macOS): no mapfile, no external command, so a `cd` stays
561
+ # instant.
549
562
  CWD_CHANGED_COMMAND = (
550
563
  'if [ -n "$CLAUDE_ENV_FILE" ] && [ -d .agents ]; then '
551
- "printf 'export AGENTS_PROJECT_ROOT=%q\\n' \"$(pwd -W 2>/dev/null || pwd)\" "
552
- '>> "$CLAUDE_ENV_FILE"; fi; '
564
+ "kept=(); "
565
+ 'if [ -f "$CLAUDE_ENV_FILE" ]; then '
566
+ 'while IFS= read -r line || [ -n "$line" ]; do '
567
+ 'case "$line" in "export AGENTS_PROJECT_ROOT="*) ;; *) kept+=("$line") ;; esac; '
568
+ 'done < "$CLAUDE_ENV_FILE"; fi; '
569
+ '{ for line in ${kept[@]+"${kept[@]}"}; do printf \'%s\\n\' "$line"; done; '
570
+ "printf 'export AGENTS_PROJECT_ROOT=%q\\n' \"$(pwd -W 2>/dev/null || pwd)\"; "
571
+ '} > "$CLAUDE_ENV_FILE"; fi; '
553
572
  "[ -f AGENTS.md ] && cat AGENTS.md || true"
554
573
  )
555
574
 
@@ -59,7 +59,10 @@ The identity/proxy model is wired into the output around the file chain:
59
59
  the same set :func:`get_overlay_roots` lists, named by
60
60
  :attr:`_overlays.Overlay.root_var` --
61
61
  the same name ``dotagents context`` expands as a placeholder), so an env
62
- file can reference an overlay's install dir by name.
62
+ file can reference an overlay's install dir by name. ``AGENTS_PYLIB``
63
+ (:data:`PYLIB_VAR`) names the ``.pyz`` when dotagents runs from one --
64
+ where its own libraries (duho, pathlib_next) import from for an overlay
65
+ built on them; never put on ``PYTHONPATH``.
63
66
  * **Proxy** is normalized AFTER the file chain: ``AGENTS_PROXY`` is seeded if
64
67
  unset (from ``AGENTS_WEBFETCH_PROXY_URL`` else the global
65
68
  HTTPS/HTTP/ALL_PROXY, either case); any proxy var that *already exists* is
@@ -94,6 +97,11 @@ from typing import Iterable, Optional
94
97
 
95
98
  from dotagents._scope import Scope, project_root_default
96
99
 
100
+ #: Names the ``.pyz`` dotagents runs from (unset for an installed dotagents,
101
+ #: whose libraries are in ``AGENTS_PYTHON``'s site-packages): an overlay built
102
+ #: on duho appends it to ``sys.path`` when ``import duho`` fails.
103
+ PYLIB_VAR = "AGENTS_PYLIB"
104
+
97
105
 
98
106
  class EnvChanges(dict):
99
107
  """A change set: the vars set (``dict[str, str]``, as before) plus
@@ -1047,6 +1055,17 @@ def get_environment(
1047
1055
  # `sys.executable` is '' or None under an embedding host: then nothing is
1048
1056
  # exported rather than an empty command name.
1049
1057
  _seed("AGENTS_PYTHON", sys.executable)
1058
+ # --- AGENTS_PYLIB --- where dotagents' own libraries (duho, pathlib_next)
1059
+ # import from for an overlay that builds on them. Set only when dotagents
1060
+ # runs from a .pyz: the archive, importable by zipimport. An installed
1061
+ # dotagents has them in AGENTS_PYTHON's site-packages already. Never put on
1062
+ # PYTHONPATH, which would shadow a project's own copy: a consumer appends
1063
+ # it to sys.path when its import fails.
1064
+ from dotagents._resources import pyz_archive
1065
+
1066
+ pylib = pyz_archive()
1067
+ if pylib and osenv.get(PYLIB_VAR) != pylib:
1068
+ _apply({PYLIB_VAR: pylib})
1050
1069
 
1051
1070
  # --- Overlay roots --- one `<NAME>_OVERLAY_ROOT` per installed overlay, the
1052
1071
  # same name `dotagents context` expands as a `<NAME_OVERLAY_ROOT>` placeholder
@@ -1208,10 +1227,11 @@ def env_cache_key(scope: Scope, base_env: "dict[str, str]", *parts: str) -> str:
1208
1227
  import hashlib
1209
1228
 
1210
1229
  from dotagents import __version__
1230
+ from dotagents._resources import pyz_archive
1211
1231
 
1212
1232
  scope = _absolute_scope(scope)
1213
1233
  items = [
1214
- __version__, sys.executable or "", os.getcwd(), scope.level,
1234
+ __version__, sys.executable or "", pyz_archive() or "", os.getcwd(), scope.level,
1215
1235
  str(scope.user_root), str(scope.agents_root), str(scope.project_root),
1216
1236
  str(scope.system_root), *parts,
1217
1237
  ]