dotagents-cli 0.3.3__tar.gz → 0.4.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 (84) hide show
  1. dotagents_cli-0.4.0/CHANGELOG.md +726 -0
  2. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/PKG-INFO +8 -8
  3. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/README.md +5 -5
  4. dotagents_cli-0.4.0/docs/api/fs.md +3 -0
  5. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/api/reference.md +4 -3
  6. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/guide/commands.md +93 -24
  7. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/guide/install.md +1 -1
  8. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/guide/overlays.md +28 -11
  9. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/index.md +1 -1
  10. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/mkdocs.yml +1 -1
  11. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/pyproject.toml +3 -3
  12. dotagents_cli-0.4.0/src/dotagents/AGENTS.md +364 -0
  13. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/__init__.py +1 -1
  14. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_agents.py +251 -94
  15. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_context.py +133 -133
  16. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_env.py +212 -74
  17. dotagents_cli-0.4.0/src/dotagents/_fs.py +43 -0
  18. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_hooks.py +58 -24
  19. dotagents_cli-0.4.0/src/dotagents/_merge.py +222 -0
  20. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_overlay/AGENTS.md +5 -3
  21. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_overlay/README.md +16 -9
  22. dotagents_cli-0.4.0/src/dotagents/_overlay/dotagents/DECISIONS.md +24 -0
  23. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_overlay/dotagents/cmds/README.md +12 -9
  24. dotagents_cli-0.4.0/src/dotagents/_overlay/dotagents/cmds/findings.py +579 -0
  25. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_overlay/dotagents/hooks/preinvocation_antigravity_context.py +34 -5
  26. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_overlay/dotagents/hooks/pretooluse_codex_env.py +8 -2
  27. dotagents_cli-0.4.0/src/dotagents/_overlays.py +707 -0
  28. dotagents_cli-0.4.0/src/dotagents/_scope.py +445 -0
  29. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_skills.py +37 -16
  30. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/cli/__init__.py +84 -44
  31. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/cli/_common.py +139 -75
  32. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/cli/build_pyz.py +21 -7
  33. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/cli/context.py +43 -33
  34. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/cli/env.py +109 -31
  35. dotagents_cli-0.4.0/src/dotagents/cli/overlays.py +531 -0
  36. dotagents_cli-0.4.0/tests/_shell.py +50 -0
  37. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_agents.py +3 -1
  38. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_audit_leak.py +13 -6
  39. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_cmds_discovery.py +50 -16
  40. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_context.py +107 -31
  41. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_env.py +190 -18
  42. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_env_format.py +140 -10
  43. dotagents_cli-0.4.0/tests/test_env_safety.py +203 -0
  44. dotagents_cli-0.4.0/tests/test_findings_cmd.py +280 -0
  45. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_hooks.py +27 -0
  46. dotagents_cli-0.4.0/tests/test_last_mile.py +256 -0
  47. dotagents_cli-0.4.0/tests/test_overlays_cli.py +358 -0
  48. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_overlays_command.py +157 -16
  49. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_scope.py +7 -25
  50. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_wire_hooks.py +116 -11
  51. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tools/audit.py +34 -23
  52. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tools/cloud-setup.sh +10 -6
  53. dotagents_cli-0.3.3/CHANGELOG.md +0 -367
  54. dotagents_cli-0.3.3/docs/api/resolve.md +0 -3
  55. dotagents_cli-0.3.3/src/dotagents/AGENTS.md +0 -220
  56. dotagents_cli-0.3.3/src/dotagents/_merge.py +0 -126
  57. dotagents_cli-0.3.3/src/dotagents/_overlay/dotagents/DECISIONS.md +0 -20
  58. dotagents_cli-0.3.3/src/dotagents/_overlays.py +0 -412
  59. dotagents_cli-0.3.3/src/dotagents/_resolve.py +0 -81
  60. dotagents_cli-0.3.3/src/dotagents/_scope.py +0 -265
  61. dotagents_cli-0.3.3/src/dotagents/cli/overlays.py +0 -334
  62. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/.gitignore +0 -0
  63. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/LICENSE +0 -0
  64. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/build.py +0 -0
  65. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/api/agents.md +0 -0
  66. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/api/cli.md +0 -0
  67. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/api/context.md +0 -0
  68. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/api/env.md +0 -0
  69. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/api/hooks.md +0 -0
  70. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/api/merge.md +0 -0
  71. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/api/overlays.md +0 -0
  72. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/api/scope.md +0 -0
  73. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/api/skills.md +0 -0
  74. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/changelog.md +0 -0
  75. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/guide/authoring.md +0 -0
  76. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/docs/guide/private-sync.md +0 -0
  77. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/install.py +0 -0
  78. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/__main__.py +0 -0
  79. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_overlay/CLAUDE.md +0 -0
  80. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/_wrappers.py +0 -0
  81. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/src/dotagents/cli/init.py +0 -0
  82. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_build_pyz.py +0 -0
  83. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_env_cli_scope.py +0 -0
  84. {dotagents_cli-0.3.3 → dotagents_cli-0.4.0}/tests/test_wrappers.py +0 -0
@@ -0,0 +1,726 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.4.0] - 2026-09-09
11
+
12
+ ### Security
13
+
14
+ - `env --format export` (the form the SessionStart hook writes into
15
+ `$CLAUDE_ENV_FILE`) now single-quotes every value (`'` → `'\''`, control
16
+ characters via bash's `$'...'`). Values were JSON-quoted, i.e. inside DOUBLE
17
+ quotes, so `$(...)`, backticks and `$VAR` in any env value were executed or
18
+ expanded when the file was sourced, while `\n` and non-ASCII (`\u00e9`)
19
+ arrived as literal escape text.
20
+ - The env chain no longer executes or sources a project's own top-level
21
+ `env.py` / `env`: only `<project>/.agents/*` and the user-local
22
+ `local.env` / `pre.local.env` run at the project levels. Every session start
23
+ ran the chain, so opening a session in a cloned repository with a top-level
24
+ `env.py` was code execution from that checkout.
25
+
26
+ ### Added
27
+
28
+ - feat: **`dotagents findings`** — a per-scope findings queue, shipped as the
29
+ one bundled command module (`_overlay/dotagents/cmds/findings.py`). A
30
+ finding is one markdown file shaped like an agent memory (frontmatter
31
+ `name`/`description`/`status`/`created` for the index line, details in the
32
+ body) under `<scope-root>/findings/`: the project's `.agents/findings/` by
33
+ default, the user store's with `-g`, anywhere with `--dir`. `add` records
34
+ one (`--body`/`--body-file`, `-` = stdin), `list` prints the active ones
35
+ (`--all`, `--processed`, `--json`), `show` prints one (`--json`), `done`
36
+ appends a required `## Resolution` and MOVES the file to `processed/`
37
+ (never deletes), `reopen` moves it back, `remove` deletes a mistaken one,
38
+ `index` regenerates `INDEX.md` (active first, then processed — every
39
+ mutating command rewrites it), `path` prints the queue's location.
40
+ Hand-written notes without a frontmatter are listed too and gain one when
41
+ first rewritten. Files are written LF-only on every platform.
42
+ - chore: `init` no longer copies bundled command modules (`*.py`) from the
43
+ package's `dotagents/cmds/` into the store — only the README. The bundled
44
+ dir is always a discovery source, so a copy added nothing, and being
45
+ create-if-absent it would have pinned the first-installed version of
46
+ `findings` and shadowed every later one. A same-named module dropped into a
47
+ scope's `dotagents/cmds/` still overrides the bundled one.
48
+ - feat: `env` prepends every level's existing **`lib/`** dir to `PYTHONPATH`,
49
+ the way it already prepends every level's `bin/` to `PATH` — overlays first,
50
+ then the store, then the project's `.agents/`, never the project root — and
51
+ does so before the env-file chain, so an `env.py` (and every subprocess that
52
+ inherits the env) can `import` an overlay's `lib/` module. Only dirs that
53
+ exist are added, and `PYTHONPATH` is untouched when there are none. The
54
+ formatter already converts any `*PATH` variable between Windows and POSIX
55
+ forms, so `PYTHONPATH` gets the same treatment as `PATH`.
56
+ - feat: `env` emits one **`<NAME>_OVERLAY_ROOT`** per installed overlay (the
57
+ overlay's install dir), seeded before the env-file chain like the two scope
58
+ roots and, like them, only if unset. `NAME` is the overlay's directory name
59
+ upper-cased with every non-alphanumeric character turned into `_`
60
+ (`my-ov.v2` → `MY_OV_V2_OVERLAY_ROOT`). This is the same name `context`
61
+ already expanded as a `<NAME_OVERLAY_ROOT>` placeholder; the two now share
62
+ one naming function, so an env file and a context file name an overlay's
63
+ install dir identically. The variable is derived from the same normalized
64
+ name `overlays add` installs under (`Overlay.normalize_name`), so the var
65
+ for `overlays/<n>/` is always `Overlay.root_var_for(n)`.
66
+
67
+ ### Changed
68
+
69
+ - The overlay-root note `overlays add` writes above routing lines cites
70
+ `$ENGINEERING_OVERLAY_ROOT` as its example; the docs and CI install the
71
+ `engineering` overlay, which now carries the flows, references, and tools that
72
+ were separate overlays (each is a category dir under it, never `<name>/<name>/`).
73
+ - The contract-A walk is `Scope.paths(*names, include_missing=False)`; the
74
+ `_resolve` module (and its API docs page) is gone, its `LEVEL_NAMES` live in
75
+ `_scope`.
76
+ - The system store is a store like the others. `Scope.system_root`
77
+ (`/etc/agents`, or `$AGENTS_SYSTEM_ROOT`) heads `Scope.stores`, and the
78
+ contract-A walk goes store by store -- system, user, project -- each store's
79
+ overlays first, then the store itself, then the project root; the hardcoded
80
+ `/etc/agents` tier and its odd position (after the user store's overlays,
81
+ before the user store) are gone. A system overlay is shadowed by a same-named
82
+ user or project one. `overlays list` prints one `installed (<level>)` block
83
+ per store that has anything, most specific first.
84
+ - The library walk takes one object. `Scope` now carries the whole picture
85
+ (`user_root`, `project_root`, `stores`, `overlays`, `files(*names)`), and
86
+ `get_file_paths`, `get_environment`, `get_diff`, `resolve_env_files`,
87
+ `get_bin_paths`, `get_lib_paths`, `get_overlay_roots`, `assemble_context` and
88
+ `assemble_context_data` take a `Scope` instead of the
89
+ `(agents_dir, project_root, global_scope)` keyword triple ported from the
90
+ precursor. `Scope.of(agents_dir=, project_root=, global_scope=)` builds one
91
+ from resolved parts. No compatibility form: the CLI is the public surface.
92
+ - **One overlay name, two scopes = one overlay, the project's.** A project
93
+ session uses the user store's overlays AND the project's (`<project>/.agents/
94
+ overlays/`); an overlay installed in both under the same name is the
95
+ project's copy only, which shadows the store's for bin, lib, env files,
96
+ cmds, `CONTEXT.md` and the `<NAME>_OVERLAY_ROOT` var (a store root the
97
+ session had already pinned is re-pointed). `-g` sees the user store alone.
98
+ `Overlay.installed(*stores)` is the one discovery behind the contract-A
99
+ walk, `env`, `context` and `overlays list` / `show`; the `_scope`
100
+ `discover_overlays` pass-through is gone (`Overlay.discover(root)` is the
101
+ single-root form). `overlays list` in a project scope prints both
102
+ `installed (project)` and `installed (user)`, marking shadowed store copies;
103
+ `overlays show` falls back to the user store's copy before the source.
104
+ - Overlay routing lines refer to overlay files through `$<NAME>_OVERLAY_ROOT`
105
+ (the variable `env` exports per installed overlay), not a hard `~/.agents/`
106
+ path; when a merged block carries such lines, `AGENTS.md` gets one line
107
+ above them saying what the token is and how to resolve it. The example
108
+ overlays on the `overlays` branch follow this convention and ship no setup
109
+ scripts any more (PATH, PYTHONPATH and the root var are `env`'s job; an
110
+ overlay's own env belongs in its `env.py`). `tools/cloud-setup.sh` finds
111
+ the private-sync settings snippet inside the installed overlay.
112
+ - The base overlay's findings workflow now goes through the command. The
113
+ managed `AGENTS.md` rule ("Global-config misses"), `dotagents/DECISIONS.md`
114
+ ("How findings become config") and the skeleton README say
115
+ `dotagents findings add -g ...` to record a miss, `list -g` / `show -g` to
116
+ triage and `done -g <name> -r ...` to close one, instead of "drop a note in
117
+ `~/.agents/dotagents/findings/`". With that, the user store's queue is
118
+ **`~/.agents/findings/`** (the command's `-g` default), no longer
119
+ `~/.agents/dotagents/findings/`. An existing queue at the old path keeps
120
+ working with `--dir ~/.agents/dotagents/findings`, or move it once:
121
+ `mv ~/.agents/dotagents/findings ~/.agents/findings`. Re-run `dotagents init`
122
+ to refresh the managed block.
123
+ - refactor: `dotagents._overlays` is now built around an **`Overlay`** class —
124
+ one overlay is one directory, and everything that depends on a single
125
+ overlay is a method or property on it: `.path` / `.name` / `.normalized_name`
126
+ / `.root_var` / `.is_valid` / `.manifest_path`, `.read_manifest()` /
127
+ `.priority` / `.sort_key`, `.find_setup_script()` / `.run_setup(...)`,
128
+ `.files()` / `.rule_blocks(...)` / `.apply_to(...)` / `.install_to(...)` /
129
+ `.merge_rules_into(...)`. The name rules are static methods
130
+ (`Overlay.is_valid_name` / `.normalize_name` / `.root_var_for`), so a caller
131
+ holding only a name uses the same rule as one holding a directory;
132
+ `Overlay.discover(root)` is the single discovery routine that `_scope`,
133
+ the contract-A resolver and `env` all share, and `Overlay.sort_by_priority`
134
+ the single merge order. The module-level functions they replace
135
+ (`read_manifest`, `find_setup_script`, `run_overlay_setup`, `overlay_files`,
136
+ `rule_blocks`, `apply_overlay`, `install_overlay_dir`, `merge_overlay_rules`,
137
+ `overlay_sort_key`, `sort_overlays_by_priority`, `normalize_name`,
138
+ `overlay_root_var`) are gone, as are `_scope.is_valid_overlay_name` and
139
+ `_scope.normalize_overlay_name`. `recompose_overlay_block` stays a module
140
+ function (it works over a set of overlays) and now accepts `Overlay`
141
+ instances or directories. `_overlays` is a private module, so no public
142
+ version signal.
143
+
144
+ - fix: an `env.py` may now print its changes as one JSON object **per line**,
145
+ merged in order (a later line wins), as well as a single object. Each
146
+ overlay's `setup.py` appends its own managed block to the store's `env.py`
147
+ and each block prints its own object, so a store with two such overlays
148
+ (e.g. `net` + `private-sync`) emitted two lines — and the single-object
149
+ reader rejected the whole output, silently dropping both overlays' vars. A
150
+ line that is not a JSON object still voids the whole script's contribution,
151
+ so a half-applied change set is impossible.
152
+ - fix: `init --agents codex` run from a session whose environment already
153
+ carried dotagents' identity vars (which its own env-loader hook exports —
154
+ `AGENT=claude-code` and friends in every command a Claude session runs)
155
+ wrote a Codex env block with no identity at all: the identity stamp never
156
+ overrides a value already present, so every key counted as "already set",
157
+ to Claude's values. When the agent is named explicitly the identity is now
158
+ that agent's, overriding the base env; without an explicit agent a pinned
159
+ value is respected as before.
160
+ - fix: the built `.pyz` lost the help text of the umbrella's own `--cmdspath`
161
+ flag (every subcommand's help survived). The zipapp source-repoint shim
162
+ covered each `dotagents.cli.<x>` command module but not the `dotagents.cli`
163
+ package itself, and it resolved a package's source as `cli.py` instead of
164
+ `cli/__init__.py`, so the umbrella kept its zip-internal `__file__` and duho
165
+ fell back to a bare flag. Both fixed; the top-level help in a `.pyz` now
166
+ matches a plain install.
167
+ - fix: `context`'s `<NAME_OVERLAY_ROOT>` placeholder now also maps `.` (and any
168
+ other non-alphanumeric character) in an overlay name to `_`, not only `-` —
169
+ the name has to be a legal shell variable to be emitted by `env`, and the
170
+ placeholder follows the same rule. Overlays whose names contain only letters,
171
+ digits, `_` and `-` are unaffected.
172
+
173
+ ### Fixed
174
+
175
+ - **One broken command module no longer breaks every `dotagents` call.**
176
+ Discovery skips a source that fails to import for any reason (a
177
+ `SyntaxError`, an exception at import time) with a warning naming it. It
178
+ caught `ImportError` only, and duho propagates the rest on purpose, so a
179
+ typo in `~/.agents/dotagents/cmds/foo.py` made `env`, `context`, `init` and
180
+ even `--version` traceback, i.e. the hooks delivered nothing.
181
+ - **No more temp-directory litter from the `.pyz`.** Everything a zipapp run
182
+ extracts (package data, the repointed module sources) lives under one
183
+ per-process scratch directory removed at exit; a `mkdtemp` per item with no
184
+ cleanup had left 632 `dotagents-*` directories in one machine's `%TEMP%`.
185
+ - `overlays add` installs a source dir spelled `my_overlay` (it normalized to
186
+ `my-overlay` and looked only that up), validates and resolves EVERY name
187
+ against the source before touching anything (`add good bad` used to install
188
+ `good`, run its setup, then fail), installs each manifest's `requires`
189
+ first (transitively; `--no-requires` to skip; a missing requirement warns, a
190
+ cycle errors), publishes skills from the INSTALLED copy (a symlink into the
191
+ source dies with a temporary checkout and could never be matched by
192
+ `remove`), and reports the setup script on a dry run.
193
+ - `overlays remove` normalizes names (`add My_Ov` + `remove My_Ov` said "not
194
+ installed") and recomposes the managed block over the overlays that remain,
195
+ so an overlay's rules and routing leave `AGENTS.md` with it; the warning
196
+ that pointed at the removed `dotagents install` is gone.
197
+ - `overlays sync` honours `--copy` (declared and ignored) and gains
198
+ `--overwrite`, which replaces installed files whose content differs from the
199
+ source; without it a sync never updated an upstream change to an existing
200
+ file.
201
+ - New `overlays show <name>` describes an overlay (installed copy first, else
202
+ the source): description, priority, requires, routing, rules, setup script,
203
+ skills, file count, root var; `--json`.
204
+ - The manifest reader strips trailing `#` comments quote-aware and finds an
205
+ array's closing `]` by scanning, so `routing = ["a"] # note` no longer
206
+ swallows the NEXT array (a rules path became a routing line), an indented
207
+ `]` no longer yields `[]`, `'single-quoted'` strings parse, and
208
+ `priority = 5 # low` is 5, not 500. `description` and `requires` are read.
209
+ - `_compose_block` with a base that has no `## Load on demand` heading appends
210
+ the overlay rules at the end of the block, as its warning always claimed;
211
+ they were dropped.
212
+ - `build-pyz` outside a source checkout is a clear error instead of a
213
+ `FileNotFoundError`.
214
+ - `dotagents`, `overlays` and `findings` invoked with no subcommand print
215
+ their help and exit 2 instead of logging a hint and exiting 0.
216
+ - `--agents-dir X` on the command line is honoured by command discovery, so
217
+ the store the command is about to use is the one whose `cmds/` are found.
218
+ - `findings add` rejects a name already carried by another note's frontmatter
219
+ (`get` matches frontmatter names first, so `done` would have processed the
220
+ wrong file).
221
+ - Two overlay dirs with the same manifest `name` sort deterministically (dir
222
+ name is the final tiebreaker).
223
+ - **SessionStart context is no longer injected twice.** The PowerShell
224
+ variants of Claude's `SessionStart` / `CwdChanged` handlers run only when
225
+ `bash` is not on PATH. Both handlers fire on every session, and on a Windows
226
+ box that has both Git Bash and PowerShell both succeeded, so the same
227
+ payload landed twice at every session start (two identical 100 KB
228
+ payloads, measured).
229
+ - The Codex `PreToolUse` env-loader prefix is quoted correctly: inside
230
+ `"$(...)"` its `\"` were literal quote characters, so `PATH` became
231
+ `".agents/bin:...:<last>"` with the quotes, the project `.agents/bin` was
232
+ never found and the last original entry was broken. The rewritten command
233
+ is now executed in bash by a test.
234
+ - Hook merging keeps a user's hook that shares a matcher-object with an older
235
+ shape of ours (the whole object used to be dropped), and a revised `shell`
236
+ / `matcher` / `commandWindows` / status on an unchanged command text now
237
+ reaches existing users instead of being kept as-is.
238
+ - Claude's skills link is per skill into `<config>/skills/<name>`: linking the
239
+ whole directory failed with "conflict" for anyone who already had their own
240
+ `~/.claude/skills`, so overlay skills never reached them. A same-named skill
241
+ the user placed there stays.
242
+ - Every hook command resolves the store as `$AGENTS_HOME` when set (bash:
243
+ `${AGENTS_HOME:-$HOME/.agents}`), so a custom store gets hooks that can find
244
+ `dotagents`; the PowerShell SessionStart variant prefers the project's own
245
+ `.agents\bin` like the bash one. The PowerShell tool env-loader runs
246
+ `env --diff` instead of re-assigning the whole environment on every call.
247
+ - The bash `CwdChanged` handler re-pins `AGENTS_PROJECT_ROOT` into
248
+ `$CLAUDE_ENV_FILE` when the new directory carries a `.agents/`; the
249
+ SessionStart pin is only-if-unset, so a `cd` into another project used to
250
+ keep the first project's root for the rest of the session.
251
+ - The Antigravity hook pins the project root from `workspacePaths` (cwd and
252
+ `AGENTS_PROJECT_ROOT` of the `dotagents context` spawn), as its docstring
253
+ already claimed, honours `$AGENTS_HOME` when locating `dotagents`, and
254
+ injects nothing when the assembly exits non-zero. Its constant is
255
+ `PREINVOCATION_HOOK_SCRIPT` (it wires a `PreInvocation` hook).
256
+ - Unpublishing an overlay's skills compares file CONTENT, not just names: a
257
+ copy the user had edited (same file set, different bytes) was deleted as
258
+ the overlay's.
259
+ - **The store's rules now reach Claude on a fresh install.** `init` writes the
260
+ `@` include where Claude Code reads it -- `~/.claude/CLAUDE.md` for the user
261
+ store, `<project>/.claude/CLAUDE.md` for a project -- as an appended managed
262
+ block, skipped when the include line is already there by hand. Before, it
263
+ wrote `<store>/CLAUDE.md` (which Claude never reads) while `context`
264
+ subtracted `~/.agents/AGENTS.md` as "already loaded" on a static assumption,
265
+ so nothing delivered the base rules. `context` now subtracts exactly what
266
+ the harness's entry files actually `@`-include (`Agent.loaded_paths`),
267
+ recursively, on this machine.
268
+ - `context` no longer inlines every `.md` file the sources mention. Inlining is
269
+ opt-in (`--inline` / `inline=True`): the base rules say to read those files
270
+ only when a task needs them, and inlining every mention made a 100 KB
271
+ SessionStart payload, most of it a changelog and an API header that
272
+ happened to be named in prose. With `--inline`, sources and harness-loaded
273
+ files are never inlined a second time, and placeholders inside inlined
274
+ files expand.
275
+ - Overlay priority ordering and `<NAME_OVERLAY_ROOT>` placeholder expansion in
276
+ `context` never worked: the resolver labels an overlay entry with the
277
+ overlay's name and the code compared it to the literal `"overlay"`. Every
278
+ installed overlay now gets a placeholder (matching `env`), not only one that
279
+ ships a `CONTEXT.md`.
280
+ - `context --write-agent` merges the context into the harness's own
281
+ instruction file under the PROJECT root -- Claude `.claude/CLAUDE.md`, Codex
282
+ `AGENTS.md`, Gemini `GEMINI.md`, Cursor `.cursorrules`, Copilot
283
+ `.github/copilot-instructions.md`, Antigravity `.agents/rules/dotagents.md`
284
+ -- as a managed `dotagents:context` block that is refreshed in place. It
285
+ used to overwrite a file under the user STORE; for Codex that was
286
+ `<store>/AGENTS.md`, a context source, so every run re-inlined the previous
287
+ run's output. `--format` is validated, and `--write-agent` refuses `--format
288
+ json` or an output path instead of silently ignoring one.
289
+ - Project-scope overlays (`<project>/.agents/overlays/`, where `overlays add`
290
+ installs by default) are now part of the contract-A walk: their `bin`,
291
+ `lib`, env files, `cmds` and `CONTEXT.md` resolve, after the user store's,
292
+ and each gets a `<NAME>_OVERLAY_ROOT` (a project overlay wins a name clash).
293
+ Nothing consumed them before except the `AGENTS.md` recompose.
294
+ - Managed-block markers must be a line of their own: a prose MENTION of the
295
+ markers (the base `AGENTS.md` carries one) was matched as the block, so the
296
+ sentence around it was replaced and a second block appeared. A begin marker
297
+ with no end after it is refused with a clear error instead of gaining a
298
+ second block; a `--from` base without markers is a usage error, not a
299
+ traceback.
300
+ - Every managed file (`AGENTS.md`, the includes, `settings.json`, `hooks.json`,
301
+ context targets) is written LF-only on every platform; `settings.json` /
302
+ `hooks.json` are written atomically and keep non-ASCII values as-is.
303
+ - The user scope is recognized by the configurable store (`$AGENTS_HOME`), not
304
+ the literal `~/.agents`: `init -g` with a custom store used to wire Claude's
305
+ hooks into `<store-parent>/.claude/settings.local.json`, which nothing reads.
306
+ - `CODEX_HOME` no longer marks a running Codex session (it is the user's
307
+ persistent state-dir override, exported from a shell profile); the
308
+ `CODEX_SANDBOX*` vars still do, and `CODEX_HOME` still locates the config.
309
+ - An unknown `--agents` name no longer overrides a pinned identity in `env`.
310
+ - An overlay cannot be named like a contract-A level (`user`, `project`,
311
+ `system`, `project-root`, `default`, `overlay`); its dir name is its level
312
+ label in the walk and would collide with the per-level filename keys.
313
+ - A directory named `env` (a common virtualenv name) is no longer "sourced" as
314
+ an env file; only regular files resolve.
315
+ - Sourcing a plain env file that fails (missing, a directory, a syntax error)
316
+ now contributes nothing and warns, as documented. The bash command was a
317
+ `;` list, so `env -0` ran regardless and reported rc 0 with whatever had
318
+ been assigned before the error.
319
+ - Bash's own `PWD` (in `/c/...` form), `OLDPWD`, `SHLVL` and `MSYSTEM*` are no
320
+ longer reported as a sourced file's changes; a non-UTF-8 byte in a sourced
321
+ value no longer aborts the whole assembly.
322
+ - `env --format auto` returned `cmd` when invoked through the `dotagents.cmd`
323
+ wrapper from PowerShell (the wrapper cannot exec, so `cmd.exe` sits between
324
+ Python and the shell); a `cmd` whose own parent is a shell is now skipped.
325
+ - `env` formats: `fish` escapes backslashes; `cmd` doubles `%` and flattens
326
+ newlines; `yaml` quotes values a reader would type (`true`, `123`, `null`,
327
+ `1e3`, leading indicators); `powershell`/`cmd` no longer mangle a
328
+ forward-slash native path (`C:/a/tools` became `C;A:\tools`); output is
329
+ written as UTF-8 bytes so a non-Latin-1 value cannot crash on a cp1252
330
+ console.
331
+ - `init -g`, `overlays ... -g` and every other `resolve_scope` caller honour
332
+ `$AGENTS_HOME` (the var `env` itself emits) instead of the literal `~/.agents`,
333
+ and `--agents-dir` overrides the store in the project scope too (it was
334
+ silently ignored without `-g`). `resolve_user_store` moved to `_scope`
335
+ (still re-exported from `dotagents.cli`); the `findings` command's local
336
+ workaround is gone.
337
+ - `overlays list` / `overlays add` apply the one overlay-name rule when listing
338
+ a source (`__pycache__`, `2fast`, dotdirs are no longer "available"), and a
339
+ bad source path names whether it came from `--source` or the env var.
340
+ - `findings list` / `show` write UTF-8 bytes, so a description with a
341
+ non-Latin-1 character no longer raises on a cp1252 console.
342
+
343
+ ## [0.3.4] - 2026-08-16
344
+
345
+ ### Changed
346
+
347
+ - chore: raise the dependency floors and scope them to a minor series —
348
+ `duho>=0.5.0,<0.6` (was `>=0.4.0`) and `pathlib_next>=0.9.0,<0.10` (was
349
+ `>=0.8.0`). Both are pre-1.0, where a minor bump is the signal that the
350
+ documented API broke, so the ceiling is what keeps the next one from arriving
351
+ unannounced. Neither floor sits above its `.0` patch: the suite and a CLI
352
+ smoke pass at exactly duho 0.5.0 and pathlib_next 0.9.0, so nothing here needs
353
+ an API added later in either series. The one 0.5.0 behavior change that
354
+ reaches this CLI's surface is list-typed *option* fields taking one value per
355
+ occurrence instead of `nargs="*"` — `--agents` and `--cmdspath` are unaffected,
356
+ since the documented forms are the comma list (`--agents a,b`, split by the
357
+ command itself) and the repeated flag, both of which behave the same either
358
+ way. Exercised end to end against duho 0.5.4 / pathlib_next 0.9.2 on Python
359
+ 3.9 and 3.14. The `[uri]`/`[http]`/`[sftp]`/`[s3]` extras stay unversioned
360
+ passthroughs to `pathlib_next`'s own extras.
361
+ - chore: `build-pyz`'s vendored pins moved with those floors — the `.pyz` now
362
+ bundles duho 0.5.0 and pathlib_next 0.9.0 (was 0.4.0 / 0.8.0), the minimum the
363
+ package claims to support rather than the latest patch. These pins are a
364
+ second copy of the dependency versions and had drifted a full minor series
365
+ behind, so the shipped zipapp bundled versions `pip install dotagents-cli`
366
+ would have refused. The rebuilt `.pyz` keeps full flag/help/positional
367
+ fidelity through the zipapp shim.
368
+
369
+ ## [0.3.3] - 2026-08-16
370
+
371
+ ### Added
372
+
373
+ - feat: `env` and `context` gained **`--agents-dir`** (from the shared
374
+ `DotAgentsArgs` base) to override the store for one run. Their `-g/--global`
375
+ keeps its existing, narrower meaning here — *skip the project-level files* —
376
+ and now says so in `--help`.
377
+
378
+ ### Removed
379
+
380
+ - **`build-pyz` no longer bundles the repo's `tools/`** as `dotagents/_tools`
381
+ inside the built `.pyz`, and the `--tools-dir` flag is gone with it. Nothing
382
+ read `_tools`: the compiled `audit` wrapper and a personal scanner's wrapper that
383
+ shelled out to it no longer exist. Every shipped artifact carried the dead weight while
384
+ `tools/audit.py` claimed it was "not shipped in the `.pyz`" — now true.
385
+ - **`dotagents._sync`** — a `pathlib_next.PathSyncer` wrapper that existed only
386
+ to back the `install` subcommand's backup/copy report. `install` was removed
387
+ in 0.3.x; the module has had no callers since, no tests, and a return
388
+ annotation that disagreed with what it returned. `pathlib_next` remains a
389
+ dependency (duho, and `--from` URI support). Its API-reference page went with
390
+ it.
391
+
392
+ ### Fixed
393
+
394
+ - docs: a cluster of "docs say X, tree does Y" corrections — `tools/audit.py`
395
+ no longer claims to ship as a bundled `audit` command module (it is repo CI
396
+ tooling and there is no `dotagents audit`) and its `--root` help names the
397
+ real default; `install.py`'s usage line drops the removed `install` and the
398
+ never-existing `audit`; the README's `tools/` row says where a personal
399
+ command module actually lives; the API header names the real `_overlays` exports
400
+ (`install_overlay_dir` / `apply_overlay` / `run_overlay_setup`) and the real
401
+ package-data dirs (`_overlay` / `_overlays_src`, never a `skeleton/`); and
402
+ `_merge._extract_block`'s docstring now says it returns the block *including*
403
+ its marker lines, which is what it has always done and what callers rely on.
404
+ - fix: **`stamp_identity` no longer pretends to emit `AGENTS_AGENT`.** The line
405
+ sourced the value from `$AGENTS_AGENT` and only assigned when that same key
406
+ was unset, so it could never emit anything — while the docstring and the
407
+ shipped API header both advertised the var. Line removed; the API header's
408
+ "emitted by the identity/env layer" list now drops both `AGENTS_AGENT` and
409
+ `AGENTS_CODE_SESSION_ID` (the latter was deliberately never emitted) and says
410
+ so explicitly, so nothing branches on a var that never arrives.
411
+ - fix: **`dotagents env` and `dotagents context` now resolve their roots instead
412
+ of hardcoding them.** Both took `Path.cwd()` as the project root and
413
+ `~/.agents` as the user store, so `$AGENTS_PROJECT_ROOT` (or the agent-native
414
+ `$CLAUDE_PROJECT_DIR`) and `$AGENTS_HOME` were ignored by the two commands
415
+ that most needed them — a `SessionStart` hook runs `dotagents context` from
416
+ wherever the session happens to start, so a pinned project root was silently
417
+ dropped and a relocated store was never read. They now use
418
+ `_scope.project_root_default()` and the new
419
+ `dotagents.cli.resolve_user_store()` (`--agents-dir` → `$AGENTS_HOME` →
420
+ legacy `$DOTAGENTS_AGENTS_DIR` → `~/.agents`), matching what the package's own
421
+ `resolve_scope` docstring and command discovery already promised. Behavior is
422
+ unchanged when none of the vars are set.
423
+
424
+ ## [0.3.2] - 2026-07-25
425
+
426
+ ### Added
427
+
428
+ - feat: **`AntigravityAgent`** — context injection for Google's Antigravity
429
+ CLI/IDE (a separate product from Gemini CLI, despite sharing the `~/.gemini/`
430
+ namespace for some files). Antigravity's hooks have no `SessionStart`
431
+ equivalent — only `PreToolUse`/`PostToolUse`/`PreInvocation`/`PostInvocation`/
432
+ `Stop` — so a `PreInvocation` hook gated on `invocationNum == 0` behaves like a
433
+ one-shot context load instead of resending it every model turn. Wires into
434
+ `~/.gemini/config/hooks.json`. No detection marker exists for Antigravity, so
435
+ it's explicit-`--agents antigravity`-only, never auto-detected.
436
+ - feat: **Codex gets a `PreToolUse` env hook**, closing the one gap Codex had
437
+ versus Claude: Codex has no per-session env-persistence mechanism at any hook
438
+ event, so a deployed script prepends a guarded env-loader to every `Bash` tool
439
+ call via `updatedInput.command` — the same rewrite mechanism Claude's own
440
+ `PreToolUse` hook uses, confirmed directly against Codex's docs.
441
+ - feat: **`init` wires a PowerShell `PreToolUse` env hook on Windows**, closing
442
+ a real gap: `$CLAUDE_ENV_FILE` only reaches Claude's *Bash* tool
443
+ (`$env:CLAUDE_ENV_FILE` is empty inside a live PowerShell tool call, confirmed
444
+ directly) — a fresh PowerShell tool call gets none of the SessionStart env.
445
+ The hook prepends a guarded env-loader to a PowerShell tool call's own command,
446
+ shipped as an inline command string (never a `.ps1` file — a script file is
447
+ subject to PowerShell's execution policy, and dotagents has no code-signing
448
+ certificate; the inline form runs even under `Restricted`).
449
+ - feat: **`SessionStart`/`CwdChanged` now register two handlers each** — a
450
+ bash-syntax one and an explicit `shell: "powershell"` one — because Claude's
451
+ own hooks.md says the shell "defaults to bash, or to powershell on Windows
452
+ when Git Bash isn't installed": bash syntax fed to `powershell -Command` on
453
+ such a machine is a hard parse error, silently losing both env and context for
454
+ the whole session.
455
+ - feat: **`DotAgentsArgs`** (`dotagents.cli`, re-exported for overlay-shipped
456
+ commands) — one shared `-g/--global` + `--agents-dir` base class. `init` and
457
+ all four `overlays` subcommands now inherit it instead of independently
458
+ redeclaring the same fields, so scope resolution can't silently drift between
459
+ commands (one previously defaulted `agents_dir` eagerly to `Path.home() /
460
+ ".agents"`; harmless in practice, but needless).
461
+
462
+ ### Fixed
463
+
464
+ - fix: **`dotagents env --format powershell`/`cmd` left an already-POSIX `PATH`
465
+ unconverted**, the mirror of the export/dotenv/fish fix below going the other
466
+ direction. Found live: run from a genuine Windows PowerShell terminal whose
467
+ own inherited `PATH` already held WSL/MSYS-mount-style entries
468
+ (`/mnt/c/Program Files/...`), the emitted `${env:PATH} = '...'` was
469
+ syntactically valid PowerShell but a single opaque colon-joined string, not
470
+ the `;`-split list PowerShell's own PATH lookup needs — every subsequent
471
+ bare-command lookup broke for that session. Also handles the MIXED case
472
+ (dotagents' own native bin dirs prepended onto an already-POSIX inherited
473
+ PATH, one string with both separators at once), caught by sourcing real
474
+ output into a live PowerShell session and watching `git.exe` fail to
475
+ resolve before the second fix. A WSL-only segment with no Windows equivalent
476
+ (`/usr/bin`) is dropped rather than mangled into a broken relative path.
477
+ - fix: **the built `.pyz` degraded `-g` on any discovered command inheriting a
478
+ dotagents-defined base class** (first hit by `DotAgentsArgs` above) — duho's
479
+ AST introspection walks the full MRO for a command's flags, but the zipapp
480
+ source-repoint shim only ever covered built-in command modules, not a base
481
+ class's own module (`dotagents.cli._common`). `--global` degraded to the
482
+ name-derived `--global-scope` and `-g` vanished silently. Fixed, with new CI
483
+ coverage asserting the exact short flag survives a real built pyz.
484
+ - fix: **`harness_loads` relative entries matched by bare filename, not full
485
+ path** — a relative entry like Codex's `"AGENTS.md"` wrongly suppressed ANY
486
+ file sharing that basename anywhere on disk, including the unrelated
487
+ `~/.agents/AGENTS.md` user-store file Codex's harness never reads.
488
+ `dotagents context --agents codex` emitted an empty `sources: []` even with
489
+ real content present. Now resolved against `project_root` and compared by
490
+ full path, like the absolute (`~/`, `/`) forms already were.
491
+ - fix: `dotagents context --format json` crashed with `UnicodeEncodeError` on
492
+ any character outside Latin-1 (a bare `print()` encoding with the console's
493
+ codepage) — the same class of bug already fixed for the markdown path, just
494
+ never covered for JSON.
495
+ - fix: PowerShell format uses `${env:NAME}` (curly-brace form), not the bare
496
+ `$env:NAME` sigil — a handful of real Windows env vars have parens in their
497
+ names (`ProgramFiles(x86)`), and `$env:FOO(X86) = ...` is a PowerShell parse
498
+ error; the curly-brace form is valid for every name.
499
+
500
+ ## [0.3.1] - 2026-07-24
501
+
502
+ ### Fixed
503
+
504
+ - fix: **`dotagents env --format export`/`dotenv`/`fish` now emit a POSIX PATH on
505
+ Windows**, instead of the OS-native `C:\...;C:\...` form. This is the exact
506
+ command the Claude `SessionStart` hook appends into `$CLAUDE_ENV_FILE`, which
507
+ Claude sources before *every* subsequent Bash tool call in the session — an
508
+ unconverted PATH broke command lookup (`git`, `grep`, `head`, `python`, ...) for
509
+ the rest of the session once poisoned, not just once. PATH-shaped values are
510
+ now converted per segment: backslash to forward-slash, `;` to `:`, and a drive
511
+ letter to its MSYS mount point (`C:/...` -> `/c/...` — required for PATH
512
+ *lookups* specifically; slash direction alone does not work in MSYS2/Cygwin
513
+ bash). `PATHEXT` is dropped (no POSIX meaning). A handful of real Windows env
514
+ vars with parentheses in their names (`ProgramFiles(x86)`) are also dropped for
515
+ these formats — `export FOO(X86)=...` is a bash syntax error, not a bad value,
516
+ and aborts sourcing the rest of the file. `powershell`/`cmd`/`json`/`ini`/`yaml`
517
+ are unaffected.
518
+ - fix: a UNC PATH segment (`\\server\share\...`) no longer collapses to a single
519
+ leading slash (`/server/share/...`) during the POSIX conversion above — MSYS
520
+ requires the double-slash UNC root (`//server/share/...`) to resolve it.
521
+
522
+ Patch release: the PATH/POSIX-conversion fix above (the only change since 0.3.0).
523
+
524
+ ## [0.3.0] - 2026-07-24
525
+
526
+ ### Changed
527
+
528
+ - **BREAKING** — `dotagents link` / `dotagents sync` are gone from the CLI. They are
529
+ the private-sync workflow's commands, not dotagents' core, so they moved — together
530
+ with the logic behind them (`src/dotagents/_link.py`) — into the opt-in
531
+ **`private-sync` overlay**, and were renamed to say what they act on:
532
+
533
+ dotagents link-project . # was: dotagents link .
534
+ dotagents sync-project -m "msg" # was: dotagents sync -m "msg"
535
+
536
+ Install the overlay to get them back: `dotagents overlays add private-sync --source
537
+ <overlays-checkout>`. A plain dotagents now ships no private-sync workflow at all;
538
+ its whole command surface is `init` / `build-pyz` / `context` / `env` / `overlays`,
539
+ and everything else is discovered from an overlay or from your own `cmds/` modules.
540
+ `tools/cloud-setup.sh` installs the overlay before linking, so the cloud bootstrap
541
+ is unaffected.
542
+ - The bundled `dotagents/cmds/` directory now ships no command module of its own, but
543
+ `init` still creates it: it is the documented drop-in point for your own commands
544
+ (a `README.md` beside it explains the shape and the precedence rules).
545
+
546
+ ### Added
547
+
548
+ - feat: `init` wires agent hooks and links the shared skills dir, for each active
549
+ agent with a published hook schema (today: Claude and Codex). `--no-hooks` skips it.
550
+
551
+ **Claude** (`~/.claude/settings.json`) gets `SessionStart`, which appends
552
+ `dotagents env --diff --format export` to `$CLAUDE_ENV_FILE` and then runs
553
+ `dotagents context` — Claude sources that file before each Bash command and injects
554
+ the hook's stdout into the session context, so both the env layers and the assembled
555
+ context reach the session automatically. It also gets `CwdChanged`, surfacing a
556
+ directory's `AGENTS.md`. The env redirect **appends** and is guarded against an
557
+ unset variable, both as the hooks docs require.
558
+
559
+ **Codex** (`~/.codex/hooks.json`, or `$CODEX_HOME`) gets `SessionStart` running
560
+ `dotagents context`; its hook JSON is structurally identical to Claude's. We write
561
+ `hooks.json` rather than touching your `config.toml` for hooks.
562
+
563
+ Codex's env arrives differently: it has no `$CLAUDE_ENV_FILE` equivalent, reads no
564
+ `.env` files, and has no event that fires before config load, so
565
+ `dotagents init --agents codex` writes a `# dotagents:begin/end` managed block
566
+ containing `[shell_environment_policy].set` into `config.toml`. **Only on an
567
+ explicit `--agents`** — this edits your main config with values that go stale, so
568
+ auto-detection never triggers it. The block is appended and refreshed in place
569
+ (everything outside the markers is untouched) and `set` merges rather than
570
+ replaces. **The values are a static snapshot: re-run `init` after changing your env
571
+ layers.** Identity vars describe the target agent, so initializing from Claude still
572
+ writes `AGENT = "codex"`; `PATH` is excluded, since `set` overrides per subprocess
573
+ and a baked-in `PATH` would replace the inherited one.
574
+
575
+ The merge is additive and idempotent: unrelated keys and hooks you wrote yourself
576
+ survive verbatim, malformed entries are dropped rather than raising, and re-running
577
+ writes nothing.
578
+
579
+ `init` also links `<scope>/skills/` into the agent's config dir, closing the last
580
+ mile for overlay-published skills — publishing only helps if the agent reads that
581
+ directory. Symlink where the OS permits, copy otherwise (a copy is a snapshot;
582
+ re-run `init` to refresh).
583
+
584
+ - feat: the personal leak scanner (then in `tools/`) now also scans commit messages (current branch history)
585
+ for agent-session trailers/URLs — a `Claude-Session:` trailer or `claude.ai/code/session`
586
+ link — and exits 1 on any hit. The trailer is auto-added by the agent harness and
587
+ exposes a session id in public history if it slips through; the pre-existing tracked-file
588
+ scan didn't cover commit messages. `flows/REPO.md` release discipline documents the check
589
+ and the `git filter-branch --msg-filter` remediation for one that already landed.
590
+ - feat: `tools/cloud-setup.sh` step 5 wires `hooks/settings.snippet.json` into the
591
+ user-level `~/.claude/settings.json` (idempotent JSON merge, preserves existing
592
+ settings/hooks). A fresh cloud container has no settings file and nothing else
593
+ created one, so the SessionStart pull/link and Stop sync-back hooks never ran —
594
+ the private repo went stale and session changes were silently never pushed back.
595
+ `kb/PRIVATE_SYNC.md` documents the auto-wiring (manual merge still applies on
596
+ local machines).
597
+
598
+ ### Fixed
599
+
600
+ - fix: `dotagents link`/`sync` never adopt or copy back a `<project>/.agents` that is
601
+ itself a git checkout (`.git` present — dir, or file for worktrees). A hosted-runner
602
+ session that lists the agents repo as a *source* gets it cloned to
603
+ `<project>/.agents` by the harness; first-link adoption then moved that entire
604
+ checkout — `.git`, foreign proxy remote, session branch — into
605
+ `~/.agents/projects/<name>/`, nesting a repo inside the private repo, which a later
606
+ sync's `git add -A` would push as a bare gitlink (and `sync`'s copy-back had the same
607
+ swallow, with `overwrite=True`). Both paths now log a skip and leave the checkout in
608
+ place; `link --force` keeps an escape hatch that backs the checkout up to
609
+ `.agents.bak*` (git state intact) and links the store.
610
+
611
+ - fix: `dotagents sync` now authenticates the private repo directly against github.com
612
+ when `DOTAGENTS_AGENTS_TOKEN` is set — and on a hosted runner that rewrites github
613
+ traffic to a scoped in-session proxy, bypasses the rewrite — so a **standalone**
614
+ `dotagents sync` no longer 403s. Previously only the private-sync Stop hook worked
615
+ (it sources `_agents-git-auth.sh`); a direct CLI run had no bypass, so its pull failed
616
+ (`could not read Password`) and its push returned HTTP 403 through the proxy. The CLI
617
+ now ports that logic: a per-command `-c` credential helper in a normal environment, or
618
+ an isolated `GIT_CONFIG_GLOBAL` (identity + CA bundle preserved) that skips the rewrite
619
+ when one is active. The token is still read from the environment at auth time and never
620
+ written to `.git/config`.
621
+ - fix: `tools/cloud-setup.sh` no longer lets a single container-start clone failure
622
+ permanently disable the environment. The clone often loses a race with egress/proxy
623
+ readiness; previously it `exit 0`'d on the first failure, skipping the hook-wiring
624
+ step — so the SessionStart hook (which can itself re-clone) was never registered and
625
+ nothing ever recovered. Now the clone retries with backoff (5 attempts), and if it
626
+ still fails the script persists a copy of itself and wires a SessionStart **recovery
627
+ hook** that re-runs the bootstrap next session (egress is up by then); the first
628
+ successful run merges the private-sync hooks and removes the recovery hook.
629
+ - fix: `tools/cloud-setup.sh` also wires that recovery hook when
630
+ `DOTAGENTS_AGENTS_REMOTE` is **unset at setup time**, not only on clone failure.
631
+ Hosted runners often expose the remote/token secrets to session processes but not
632
+ to the setup-script phase, so the first bootstrap had no remote to clone and its
633
+ no-remote branch just `exit 0`'d, leaving nothing to retry — the environment stayed
634
+ dead every session (observed: setup ran the correct one-liner and emitted only the
635
+ banner + `skipping` line, ~154 bytes, no clone). The branch now persists the recovery
636
+ hook like the exhausted-clone path, so the next session — where the secret is present
637
+ — clones and self-removes the hook. A genuinely remote-less environment just re-skips
638
+ each session (idempotent; the hook never duplicates). (Durable fix is still
639
+ to expose the secrets to the Setup Script phase so the first container succeeds.)
640
+ - fix: `.gitignore` templates and `dotagents link` now use a slashless `.agents`
641
+ instead of `.agents/`. `link` creates `.agents` as a *symlink*, which git treats
642
+ as a file, so the directory-only `.agents/` pattern never actually ignored it —
643
+ the link showed up as untracked in every project. `_gitignore_excludes_agents`
644
+ is now symlink-aware (a bare `.agents/` no longer counts as excluding a symlinked
645
+ link, so the WARN fires), and the reference template, REPO.md guidance, and the
646
+ starter `_overlay/AGENTS.md` Leakage rule all recommend `.agents`.
647
+ - docs: recommend `curl … -o file && sh file` over `curl … | sh` for the setup-script
648
+ field (README, `kb/PRIVATE_SYNC.md`, `tools/cloud-setup.sh` header). With a pipe the
649
+ field's exit code is `sh`'s (0 on empty stdin), so a failed fetch at container start
650
+ is silently reported as success; `&&` propagates the fetch failure to the setup log.
651
+
652
+ ### Changed
653
+
654
+ - refactor: move the cloud bootstrap from `overlays/private-sync/hooks/cloud-setup.sh` to
655
+ top-level `tools/cloud-setup.sh` (public, required tooling) so a fresh cloud container
656
+ can fetch-and-run it from the public repo instead of pasting its contents — the web
657
+ environment setup-script field becomes a one-liner
658
+ (`curl -fsSL …/tools/cloud-setup.sh | sh`) that stays current on every container start.
659
+ Docs (README, `kb/PRIVATE_SYNC.md`) updated to the download bootstrap.
660
+ - fix: `tools/cloud-setup.sh` prints `starting`/`done` banners (so a setup-script log
661
+ proves whether it executed — a blank log means the field never invoked it, a config
662
+ issue) and `mkdir -p "$HOME"` before `git config --global` (which fails if HOME isn't
663
+ created yet in some setup contexts).
664
+
665
+ ## [0.2.0] - 2026-07-19
666
+
667
+ ### Changed
668
+
669
+ - chore: migrate the CLI to `duho>=0.3.3` (was `>=0.1.1`). duho's Plan-13 `Args`/`Cmd`
670
+ split means commands are now `class X(LoggingArgs, Cmd)` with a `__call__` entrypoint
671
+ (was a bare `LoggingArgs` with `__run__`) and the umbrella root is
672
+ `class Dotagents(LoggingArgs, Cli)`. Field declarations (annotation + help string +
673
+ flags tuple) are unchanged. Bumped the `build-pyz` vendored `duho` default to 0.3.3.
674
+ - fix: restore full flag/help fidelity in the built `dotagents.pyz` under duho 0.3.3.
675
+ duho discovers each field's flags + help by AST-parsing its module source, and inside
676
+ a zipapp the zip-internal `__file__` isn't readable — degrading `--from` to `--from-`,
677
+ the `link` positional to `--path`, and dropping help text. `cli.main` now repoints the
678
+ affected module sources (`dotagents.cli`, `duho.presets`) to extracted temp files
679
+ before dispatch; a no-op for a plain install.
680
+
681
+ ### Added
682
+
683
+ - feat: private-agents git sync — `dotagents link` symlinks a project's `.agents` to a
684
+ per-project store under the global `~/.agents/projects/<name>` (basename-keyed, so a
685
+ local and a cloud checkout converge on the same store), adopting an existing real
686
+ `.agents/` into an empty store on the first link; `--copy` mirrors it as a real dir
687
+ for no-symlink environments (with automatic fallback), `--force` handles conflicts.
688
+ `dotagents sync` runs `git pull --rebase`/commit/push on the private repo, copies a
689
+ copy-mode project's `.agents` back into its store first (`--project`), and bootstraps
690
+ a fresh repo in one command (`--remote`). Logic in `src/dotagents/_link.py`; the model
691
+ keeps per-user config and every project's private `.agents` in one private repo while
692
+ the public project repos track none of it (the Leakage rule already `.gitignore`s
693
+ `.agents/`).
694
+ - feat: `overlays/private-sync/` overlay — `kb/PRIVATE_SYNC.md` (the model, commands,
695
+ first-time + cloud setup, auth, gotchas) plus `hooks/private-sync-{start,stop}.sh`
696
+ (SessionStart clone/pull + link, Stop sync-back) and a `settings.snippet.json` for
697
+ `~/.claude/settings.json`, so cloud sessions link and sync automatically. Cloud auth is
698
+ a fine-grained PAT via `DOTAGENTS_AGENTS_TOKEN`, wired through a git credential helper
699
+ that reads it from the environment (never persisted to `.git/config`);
700
+ `hooks/_agents-git-auth.sh` auto-detects a hosted-runner `github.com`→in-session-proxy
701
+ `insteadOf` rewrite and bypasses it (isolated git config) so token auth reaches the
702
+ real github.com for a private repo outside the session's scope. `hooks/cloud-setup.sh`
703
+ is a self-contained container-start bootstrap (inlines auth + bypass, so it runs before
704
+ `~/.agents` exists) that clones/pulls the repo, installs the CLI, and links the project
705
+ — for the web environment's setup-script field, solving the first-clone chicken-and-egg
706
+ the SessionStart hook can't.
707
+ - feat: installable `dotagents` CLI package (`src/dotagents/`, built on `duho` for
708
+ the argument surface and `pathlib_next` for copy/URI handling) exposing `init`
709
+ (lay down the neutral base overlay), `install` (base plus opt-in overlays via
710
+ repeatable `--overlays <path>`, copied additively), `audit` (wraps
711
+ `tools/audit_config.py`), and `build-pyz` (vendors pinned `duho`/`pathlib_next`
712
+ via `pip install --target` + `zipapp` into a self-contained, downloadable
713
+ `dotagents.pyz`). `init`'s `AGENTS.md`/`CLAUDE.md` are merged as a
714
+ marker-delimited managed block so re-running never clobbers customizations
715
+ outside the block. `install --bin-dir` writes `dotagents`/`dotagents.cmd`
716
+ wrappers. `install.py` is a thin shim over `dotagents.cli.main()`.
717
+ - Config content is a **neutral base overlay** (`src/dotagents/_overlay/` — the
718
+ `AGENTS.md` scaffolding + design-log convention `init` writes) plus **opt-in
719
+ overlays** (`overlays/<name>/`): `flows` (PLAN/EXEC/REVIEW/REPO + MODELS),
720
+ `recovery`, `references`, `python`/`node`/`rust`, `agents`, `tools`. Each carries
721
+ an `overlay.toml` manifest for a future `dotagents overlays` subcommand.
722
+ - Repo layout: the CLI in `src/dotagents/`, config overlays in `overlays/`, required
723
+ tooling in top-level `tools/` (`audit_config.py` and a personal leak scanner); repo root holds
724
+ the installer, CI, repo-development directives, and the tracked, sanitized
725
+ `.agents/` design log (index + per-decision files) + plans. `audit_config.py` has
726
+ `--repo-hygiene` (scans tracked files for personal/machine-specific leftovers).