dotagents-cli 0.7.1__tar.gz → 0.7.3__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 (114) hide show
  1. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/CHANGELOG.md +26 -2
  2. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/PKG-INFO +3 -2
  3. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/README.md +2 -1
  4. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/guide/authoring.md +19 -0
  5. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/guide/commands.md +38 -6
  6. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/guide/install.md +11 -1
  7. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/guide/overlays.md +1 -1
  8. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/AGENTS.md +19 -1
  9. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/__init__.py +1 -1
  10. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_sources.py +60 -6
  11. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/_common.py +4 -1
  12. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_cmds_discovery.py +2 -2
  13. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_context.py +1 -1
  14. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env_regressions.py +6 -6
  15. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env_safety.py +1 -2
  16. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_findings_cmd.py +4 -4
  17. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_adapters.py +1 -1
  18. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_cli.py +1 -1
  19. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_env.py +1 -2
  20. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_hooks.py +1 -1
  21. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_init.py +1 -1
  22. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_overlays.py +2 -2
  23. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_scope_context.py +1 -2
  24. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_skills_adapters.py +2 -2
  25. dotagents_cli-0.7.3/tests/test_git_ssl_revoke.py +102 -0
  26. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_last_mile.py +3 -3
  27. dotagents_cli-0.7.3/tests/test_no_internal_labels.py +231 -0
  28. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_overlays_cli.py +5 -5
  29. dotagents_cli-0.7.1/tests/test_review_security.py → dotagents_cli-0.7.3/tests/test_quoting_injection_safety.py +3 -3
  30. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/.gitignore +0 -0
  31. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/LICENSE +0 -0
  32. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/agents.md +0 -0
  33. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/cli.md +0 -0
  34. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/commands.md +0 -0
  35. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/context.md +0 -0
  36. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/env.md +0 -0
  37. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/fs.md +0 -0
  38. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/hooks.md +0 -0
  39. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/merge.md +0 -0
  40. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/overlays.md +0 -0
  41. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/reference.md +0 -0
  42. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/resources.md +0 -0
  43. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/scope.md +0 -0
  44. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/skills.md +0 -0
  45. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/sources.md +0 -0
  46. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/wrappers.md +0 -0
  47. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/changelog.md +0 -0
  48. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/guide/private-sync.md +0 -0
  49. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/index.md +0 -0
  50. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/install.py +0 -0
  51. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/mkdocs.yml +0 -0
  52. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/pyproject.toml +0 -0
  53. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/__main__.py +0 -0
  54. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_agents.py +0 -0
  55. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_context.py +0 -0
  56. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_env.py +0 -0
  57. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_fs.py +0 -0
  58. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_hooks.py +0 -0
  59. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_ignore.py +0 -0
  60. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_merge.py +0 -0
  61. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/cmds/findings.py +0 -0
  62. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/cmds/launch.py +0 -0
  63. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/hooks/preinvocation_antigravity_context.py +0 -0
  64. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/hooks/pretooluse_codex_env.py +0 -0
  65. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/hooks/sessionstart_codex_context.py +0 -0
  66. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/templates/AGENTS.md +0 -0
  67. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/templates/PROJECT.md +0 -0
  68. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlays.py +0 -0
  69. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_resources.py +0 -0
  70. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_scope.py +0 -0
  71. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_skills.py +0 -0
  72. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_wrappers.py +0 -0
  73. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/__init__.py +0 -0
  74. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/about.py +0 -0
  75. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/build_pyz.py +0 -0
  76. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/context.py +0 -0
  77. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/env.py +0 -0
  78. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/init.py +0 -0
  79. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/overlays.py +0 -0
  80. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/path.py +0 -0
  81. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/py.typed +0 -0
  82. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/_helpers.py +0 -0
  83. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/_shell.py +0 -0
  84. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/conftest.py +0 -0
  85. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_about.py +0 -0
  86. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_agents.py +0 -0
  87. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_api_header.py +0 -0
  88. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_audit_leak.py +0 -0
  89. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_build_pyz.py +0 -0
  90. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_cli_main.py +0 -0
  91. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_cloud_setup.py +0 -0
  92. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env.py +0 -0
  93. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env_cli_scope.py +0 -0
  94. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env_format.py +0 -0
  95. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env_into.py +0 -0
  96. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_packaging.py +0 -0
  97. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_hooks.py +0 -0
  98. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_ignore.py +0 -0
  99. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_init_base.py +0 -0
  100. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_launch_cmd.py +0 -0
  101. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_local_additions.py +0 -0
  102. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_mcp.py +0 -0
  103. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_overlay_libs.py +0 -0
  104. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_overlays_command.py +0 -0
  105. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_path_timeout_frontmatter.py +0 -0
  106. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_powershell_env_hook.py +0 -0
  107. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_scope.py +0 -0
  108. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_sources.py +0 -0
  109. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_sources_uri.py +0 -0
  110. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_wire_hooks.py +0 -0
  111. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_wrappers.py +0 -0
  112. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tools/audit.py +0 -0
  113. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tools/cloud-setup.sh +0 -0
  114. {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tools/pyz_smoke.sh +0 -0
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.3] - 2026-10-04
11
+
12
+ ### Changed
13
+
14
+ - The command guide and the shipped API header now say that every command except
15
+ `env`, built-in or discovered, is served as an MCP tool when `dotagents` starts with
16
+ `DOTAGENTS_MCP=stdio`, and that `AGENT_HELP=1` turns `--help` into a JSON document.
17
+ The authoring guide asks overlays to ship command modules rather than loose
18
+ scripts, and skills to name the tools they use.
19
+
20
+ ## [0.7.2] - 2026-10-03
21
+
22
+ ### Added
23
+
24
+ - `git_ssl_revoke` in `<store>/dotagents/config.toml` sets how the git clones and
25
+ fetches of overlay repos check certificate revocation: `best-effort` (the default)
26
+ skips a revocation list that is missing or unreachable, `false` skips the check, as
27
+ git on Linux does, `true` enforces it. It is passed to every git call, so a stricter
28
+ git configuration elsewhere no longer fails an https clone on Windows, and a clone
29
+ that still fails on revocation says which setting to change. A git too old to know
30
+ `best-effort` (it rejects the value) runs the call again without the setting.
31
+
10
32
  ## [0.7.1] - 2026-10-01
11
33
 
12
34
  ### Added
@@ -1357,7 +1379,7 @@ Patch release: the PATH/POSIX-conversion fix above (the only change since 0.3.0)
1357
1379
 
1358
1380
  ### Changed
1359
1381
 
1360
- - Migrate the CLI to `duho>=0.3.3` (was `>=0.1.1`). duho's Plan-13 `Args`/`Cmd`
1382
+ - Migrate the CLI to `duho>=0.3.3` (was `>=0.1.1`). duho's `Args`/`Cmd`
1361
1383
  split means commands are now `class X(LoggingArgs, Cmd)` with a `__call__` entrypoint
1362
1384
  (was a bare `LoggingArgs` with `__run__`) and the umbrella root is
1363
1385
  `class Dotagents(LoggingArgs, Cli)`. Field declarations (annotation + help string +
@@ -1372,7 +1394,9 @@ Patch release: the PATH/POSIX-conversion fix above (the only change since 0.3.0)
1372
1394
  affected module sources (`dotagents.cli`, `duho.presets`) to extracted temp files
1373
1395
  before dispatch; a no-op for a plain install.
1374
1396
 
1375
- [Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.7.1...HEAD
1397
+ [Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.7.3...HEAD
1398
+ [0.7.3]: https://github.com/jose-pr/dotagents/compare/v0.7.2...v0.7.3
1399
+ [0.7.2]: https://github.com/jose-pr/dotagents/compare/v0.7.1...v0.7.2
1376
1400
  [0.7.1]: https://github.com/jose-pr/dotagents/compare/v0.7.0...v0.7.1
1377
1401
  [0.7.0]: https://github.com/jose-pr/dotagents/compare/v0.6.3...v0.7.0
1378
1402
  [0.6.3]: https://github.com/jose-pr/dotagents/compare/v0.6.2...v0.6.3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: dotagents-cli
3
- Version: 0.7.1
3
+ Version: 0.7.3
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
@@ -270,7 +270,8 @@ config miss is a **finding** — `dotagents findings add -g` for the user store
270
270
  triage closes each with its resolution, which is the record. How you keep any
271
271
  decisions beyond that is up to you. A store's `dotagents/` directory exists only for
272
272
  your own command modules (`dotagents/cmds/`), which you create when you add your first
273
- one, and for `dotagents/config.toml`, which records an `init --from` base. This
273
+ one, and for `dotagents/config.toml`, which records an `init --from` base and,
274
+ if you set it, `git_ssl_revoke` (how git checks certificate revocation on Windows). This
274
275
  repo follows the same rule: its working material lives in an **untracked** `.agents/`,
275
276
  never committed — so what's public here is only the CLI, the base overlay, and the
276
277
  opt-in overlays. If you fork, keep the tracked surface free of personal paths and
@@ -220,7 +220,8 @@ config miss is a **finding** — `dotagents findings add -g` for the user store
220
220
  triage closes each with its resolution, which is the record. How you keep any
221
221
  decisions beyond that is up to you. A store's `dotagents/` directory exists only for
222
222
  your own command modules (`dotagents/cmds/`), which you create when you add your first
223
- one, and for `dotagents/config.toml`, which records an `init --from` base. This
223
+ one, and for `dotagents/config.toml`, which records an `init --from` base and,
224
+ if you set it, `git_ssl_revoke` (how git checks certificate revocation on Windows). This
224
225
  repo follows the same rule: its working material lives in an **untracked** `.agents/`,
225
226
  never committed — so what's public here is only the CLI, the base overlay, and the
226
227
  opt-in overlays. If you fork, keep the tracked surface free of personal paths and
@@ -149,7 +149,26 @@ parser cannot be built. The bundled `findings` and `launch` are ordinary command
149
149
  modules discovered from the package, and the private-sync overlay ships
150
150
  `link-project` / `sync-project` from its own `cmds/` the same way.
151
151
 
152
+ **Prefer a command module to a loose script.** A script under `tools/` is something
153
+ an agent has to find, quote and spawn. A command module is discovered, shows up in
154
+ `dotagents --help`, and is served as an MCP tool with a typed schema when `dotagents`
155
+ runs with `DOTAGENTS_MCP=stdio` (see
156
+ [Commands → As tools for an agent](commands.md#as-tools-for-an-agent)). Write it to be
157
+ a good tool:
158
+
159
+ - the logic lives in the command class — `__call__` and its methods — not in one
160
+ function that `__call__` forwards every field to;
161
+ - each field's help says what the value is, since it becomes the schema's description;
162
+ - the output is short: one line for a change, the smallest useful part for a read,
163
+ `--json` for a program;
164
+ - it never prompts, and a command that deletes or publishes needs an explicit flag.
165
+
152
166
  ## Skills
153
167
 
154
168
  Put `skills/<skill-name>/` directories in your overlay to publish shared skills into
155
169
  the scope on install. See [Overlays → Skills](overlays.md#skills).
170
+
171
+ A skill that relies on commands says so, in its own text: which `dotagents` commands
172
+ it uses, that they are available as MCP tools (`dotagents.<command>`) when the
173
+ server is registered, and the command-line form to fall back to when it is not. An
174
+ agent reading the skill should not have to discover either.
@@ -59,6 +59,38 @@ into `<store>/dotagents/cmds/` (create the dir — `init` does not) and it becom
59
59
  subcommand, no registration needed; see
60
60
  [Authoring → Custom commands](authoring.md#custom-commands).
61
61
 
62
+ ## As tools for an agent
63
+
64
+ Every command above except `env`, and every discovered one, is also an **MCP tool**.
65
+ Start `dotagents` with `DOTAGENTS_MCP=stdio` in its environment and it serves that
66
+ command set over stdio instead of running a command:
67
+
68
+ ```sh
69
+ DOTAGENTS_MCP=stdio dotagents # register this as a stdio MCP server in your agent
70
+ ```
71
+
72
+ - One tool per leaf command, named with dots: `dotagents.findings.add`,
73
+ `dotagents.overlays.list`, and for an overlay's commands `dotagents.<command>`.
74
+ - A tool's input schema is generated from the command's fields; its result is what
75
+ the command prints.
76
+ - The command set is discovered once, when the server starts, for the directory it
77
+ starts in. Restart it after installing an overlay or changing project.
78
+
79
+ Use it for a command an agent calls many times in a session — a findings queue, a
80
+ plan's progress, a lookup. A tool call skips the interpreter start-up and the
81
+ command discovery that every `dotagents <command>` process pays, and its arguments
82
+ are typed values, not strings quoted through a shell.
83
+
84
+ `env` is not served: it prints resolved values, which can be secrets. Everything
85
+ else is, including the commands that change the store (`init`, `overlays add`,
86
+ `overlays remove`). Register the server only where the agent is meant to be able to
87
+ run them.
88
+
89
+ For a machine-readable description without a server, set `AGENT_HELP=1` and ask for
90
+ `--help`: `dotagents findings --help` then prints one JSON document for that command
91
+ tree. It is complete and long; a single subcommand's plain `--help` is shorter when
92
+ that is all that is needed.
93
+
62
94
  ## init
63
95
 
64
96
  See [Install](install.md) for the full walkthrough. In brief:
@@ -501,13 +533,13 @@ There is no `dotagents audit`. The dotagents source repo has its own
501
533
  it is not a validator for an installed `~/.agents` and is deliberately not shipped
502
534
  in the package or the `.pyz`.
503
535
 
504
- ### Personal pre-push scanning (not in this repo)
536
+ ### Pre-push scanning (an overlay's command)
505
537
 
506
- Scanning a repo for personal leaks before publishing it — machine paths, private
507
- plan names, `.agents/` refs, agent-session trailers in commit messages — enforces
508
- personal conventions rather than dotagents' own mechanism, so no such tool is
509
- shipped here. Keep one as a discovered command module in your own private
510
- `<scope>/dotagents/cmds/` and run it locally before a push.
538
+ Scanning a repo for leaks before publishing it — machine paths, private plan names,
539
+ `.agents/` refs, agent-session trailers in commit messages — enforces a convention
540
+ rather than dotagents' own mechanism, so the package ships no such command. The
541
+ opt-in `engineering` overlay does: `dotagents leak-check <repo>`. Its patterns for
542
+ what is personal to you stay in a machine-local file, never in a repository.
511
543
 
512
544
  ## link-project / sync-project
513
545
 
@@ -101,7 +101,17 @@ python -m dotagents build-pyz --out dist/dotagents.pyz # or build it (needs a
101
101
  [Authoring → Custom commands](authoring.md#custom-commands)).
102
102
  - `dotagents/config.toml` — only after `init --from`: records that base, so a later
103
103
  `init` and every `overlays add` / `remove` / `sync` compose over it. Delete its
104
- `base` line to return to the bundled base.
104
+ `base` line to return to the bundled base. You may add one setting by hand:
105
+ `git_ssl_revoke` — how strictly the git clones of overlay repos check that the
106
+ server's certificate is not revoked. `best-effort` (the default) skips a
107
+ revocation list that is missing or unreachable; `false` skips the check, as git
108
+ on Linux always does; `true` enforces it. It matters on Windows, where git
109
+ checks revocation and an https clone otherwise fails when the list cannot be
110
+ fetched; a project store's setting wins over the user store's.
111
+
112
+ ```toml
113
+ git_ssl_revoke = "false"
114
+ ```
105
115
 
106
116
  ## What runs, and from where
107
117
 
@@ -44,7 +44,7 @@ or `--repo <checkout>/overlays` for a local checkout of the branch.
44
44
 
45
45
  | Overlay | What it carries |
46
46
  | --- | --- |
47
- | `engineering` | One opinionated engineering process. `rules/ENGINEERING.md` — always-on discipline merged into `AGENTS.md` (commit shape, release-tag consent, benchmark-as-evidence, token discipline, draft follow-ups). `flows/` — `PLAN.md` (a strong model writes precise, autonomous plans), `EXEC.md` (a cheaper model executes them without re-deriving context), `REVIEW.md` (file-threaded multi-agent plan review), `REPO.md` (the repo standard); `kb/MODELS.md` (executor/model selection). `references/` — language-neutral repo-file templates (README, CHANGELOG, LICENSE, `.gitignore`, docs-index, the plan template). `tools/` — `summarize_run` (command → verdict) and `compare_bench` (diff benchmark JSONs). The language overlays require it. |
47
+ | `engineering` | One opinionated engineering process. `rules/ENGINEERING.md` — always-on discipline merged into `AGENTS.md` (commit shape, release-tag consent, benchmark-as-evidence, token discipline, draft follow-ups). `flows/` — `PLAN.md` (a strong model writes precise, autonomous plans), `EXEC.md` (a cheaper model executes them without re-deriving context), `REVIEW.md` (file-threaded multi-agent plan review), `REPO.md` (the repo standard); `kb/MODELS.md` (executor/model selection). `references/` — language-neutral repo-file templates (README, CHANGELOG, LICENSE, `.gitignore`, docs-index, the plan template). `cmds/` — `plans`, `leak-check`, `eol`, `summarize-run` (command → verdict) and `compare-bench` (diff benchmark JSONs), each a `dotagents` command. The language overlays require it. |
48
48
  | `python`, `node`, `rust` | Per-language `kb/` conventions + manifest templates + CI workflow templates. |
49
49
  | `release` | A host-agnostic release helper: an agent-driven commit-plan loop plus tag/CI monitoring across GitHub (`gh`) and GitLab. |
50
50
  | `private-sync` | The one-private-repo, per-project `.agents` model (`kb/PRIVATE_SYNC.md` + cloud hooks) — see [Private sync](private-sync.md). |
@@ -44,6 +44,16 @@ and an error saying a command is required, and exits 2.
44
44
  | `about [--json]` | `dotagents-cli <version>`, then `<distribution> <version>` per bundled (`.pyz`) or installed package. |
45
45
  | `build-pyz [--out P] [--python SHEBANG] [--duho-version V] [--pathlib-next-version V] [--extras a,b]` | Build a zipapp from a source checkout; errors elsewhere. |
46
46
 
47
+ **Every command but `env` is also an MCP tool.** With `DOTAGENTS_MCP=stdio` in its
48
+ environment, `dotagents` serves the command set — built-in, bundled and discovered —
49
+ over stdio instead of running one: a tool per leaf command, named with dots
50
+ (`dotagents.findings.add`, `dotagents.overlays.list`), its input schema generated from
51
+ the command's fields, its result the command's output. `env` is not served: it prints
52
+ resolved, possibly secret values. The set is fixed when the server starts, for the
53
+ scope it starts in. With `AGENT_HELP=1`, `--help` prints one
54
+ JSON document (`duho/agent-help@1`) for the command tree it is asked about, in place
55
+ of the human text.
56
+
47
57
  Scope flags: `-g` / `--global` selects the user store; `--agents-dir D` overrides the
48
58
  store the command resolves to (the project store for `init` / `overlays` / `findings`
49
59
  without `-g`, the user store otherwise). For `env`, `context` and `launch`, `-g` means
@@ -306,7 +316,15 @@ a directory of overlays or a registry file (default: the root); for a **source**
306
316
  - `redact(text) -> str` — URL userinfo removed, query values masked.
307
317
  - `uri_path_class()` — `pathlib_next.uri.UriPath`, or `None` without the `uri` extra.
308
318
  - `url_scheme(location) -> str` — the lower-cased scheme.
309
- - `class SourceCache(root, logger=None)` — `repo_dir(location, ref=None) -> Path`
319
+ - `GIT_SSL_REVOKE_KEY = "git_ssl_revoke"`, `GIT_SSL_REVOKE_VALUES = ("best-effort",
320
+ "true", "false")`, `GIT_SSL_REVOKE_DEFAULT = "best-effort"`;
321
+ `git_ssl_revoke(*stores) -> str` — the first store config (`config.toml`) that sets
322
+ the key, project store first; another value raises `SourceError` naming the file.
323
+ - `class SourceCache(root, logger=None, ssl_revoke="best-effort")` — every git call
324
+ runs as `git -c http.schannelCheckRevoke=<ssl_revoke> ...` (Git for Windows'
325
+ Schannel checks revocation; git with OpenSSL ignores it), and a failure that looks
326
+ like a revocation check names the key. A git that rejects the value as a bad
327
+ boolean (older than `best-effort`) is asked again without it. `repo_dir(location, ref=None) -> Path`
310
328
  (`<root>/<slug>-<hash>`); `checkout(spec) -> Path` (clone or fetch, once per
311
329
  process; a failed fetch of an existing checkout is a warning);
312
330
  `materialize(spec) -> Path` (a `url` spec: `file://` in place, anything else
@@ -1,3 +1,3 @@
1
1
  """dotagents: installable CLI for the dotagents agent-config payload."""
2
2
 
3
- __version__ = "0.7.1"
3
+ __version__ = "0.7.3"
@@ -214,6 +214,48 @@ def url_scheme(location: str) -> str:
214
214
  return location.split("://", 1)[0].lower()
215
215
 
216
216
 
217
+ #: The store-config key (``<store>/dotagents/config.toml``) for git's
218
+ #: certificate-revocation check on https clones and fetches, passed to every
219
+ #: git call as ``http.schannelCheckRevoke`` -- Git for Windows' Schannel
220
+ #: backend checks revocation; git elsewhere (OpenSSL) checks no CRLs and
221
+ #: ignores the setting. ``best-effort`` (the default, stated explicitly so a
222
+ #: stricter git config cannot fail dotagents' clones) skips a CRL that is
223
+ #: missing or unreachable; ``false`` skips the check, as on Linux; ``true``
224
+ #: enforces it.
225
+ GIT_SSL_REVOKE_KEY = "git_ssl_revoke"
226
+ GIT_SSL_REVOKE_VALUES = ("best-effort", "true", "false")
227
+ GIT_SSL_REVOKE_DEFAULT = "best-effort"
228
+
229
+
230
+ def git_ssl_revoke(*stores: "Optional[Path]") -> str:
231
+ """The first ``git_ssl_revoke`` set in ``stores``' config (the project
232
+ store before the user store), else the default. Any other value raises
233
+ :class:`SourceError` naming the file."""
234
+ from dotagents.cli._common import STORE_CONFIG, read_store_config
235
+
236
+ for store in stores:
237
+ if store is None:
238
+ continue
239
+ value = read_store_config(store).get(GIT_SSL_REVOKE_KEY)
240
+ if value is None:
241
+ continue
242
+ normalized = value.strip().lower()
243
+ if normalized not in GIT_SSL_REVOKE_VALUES:
244
+ raise SourceError(
245
+ "error: %s = %r in %s: expected one of %s"
246
+ % (GIT_SSL_REVOKE_KEY, value, Path(store) / STORE_CONFIG, ", ".join(GIT_SSL_REVOKE_VALUES))
247
+ )
248
+ return normalized
249
+ return GIT_SSL_REVOKE_DEFAULT
250
+
251
+
252
+ #: What a revocation-check failure looks like in git's stderr (Schannel).
253
+ _REVOCATION_FAILURE = re.compile(r"revocation|CRYPT_E_|SEC_E_UNTRUSTED", re.IGNORECASE)
254
+ #: A git too old to know ``best-effort`` reads it as a broken boolean and
255
+ #: fails every https call: the call is made again without the setting.
256
+ _UNKNOWN_REVOKE_VALUE = re.compile(r"bad boolean config value .* for 'http\.schannelcheckrevoke'", re.IGNORECASE)
257
+
258
+
217
259
  class SourceCache(object):
218
260
  """What the commands fetch, under ``root``: git clones, one per repository
219
261
  location AND ref (two registry entries naming the same repository at
@@ -223,9 +265,10 @@ class SourceCache(object):
223
265
  (a remote directory synced down, a remote file copied), each refreshed
224
266
  once per process."""
225
267
 
226
- def __init__(self, root: Path, logger: Any = None):
268
+ def __init__(self, root: Path, logger: Any = None, ssl_revoke: str = GIT_SSL_REVOKE_DEFAULT):
227
269
  self.root = Path(root)
228
270
  self.logger = logger
271
+ self.ssl_revoke = ssl_revoke # git's http.schannelCheckRevoke (GIT_SSL_REVOKE_KEY)
229
272
  self._fresh: "set[str]" = set() # locations fetched during this process
230
273
 
231
274
  # --- pathlib_next paths ----------------------------------------------
@@ -314,17 +357,28 @@ class SourceCache(object):
314
357
  def _git(self, args: "list[str]", cwd: "Optional[Path]", *, check: bool = True) -> "subprocess.CompletedProcess":
315
358
  env = dict(os.environ)
316
359
  env["GIT_TERMINAL_PROMPT"] = "0" # never hang on a credential prompt
360
+ setting = ["-c", "http.schannelCheckRevoke=%s" % self.ssl_revoke]
317
361
  try:
318
362
  proc = subprocess.run(
319
- ["git", *args], cwd=str(cwd) if cwd else None,
320
- capture_output=True, text=True, env=env,
363
+ ["git", *setting, *args], cwd=str(cwd) if cwd else None, capture_output=True, text=True, env=env,
321
364
  )
365
+ if proc.returncode != 0 and _UNKNOWN_REVOKE_VALUE.search(proc.stderr or ""):
366
+ # An older git (no best-effort): its own default applies.
367
+ proc = subprocess.run(
368
+ ["git", *args], cwd=str(cwd) if cwd else None, capture_output=True, text=True, env=env,
369
+ )
322
370
  except OSError as exc:
323
371
  raise SourceError("error: git is needed for a git overlay source: %s" % exc)
324
372
  if check and proc.returncode != 0:
373
+ output = proc.stderr.strip() or proc.stdout.strip()
374
+ hint = ""
375
+ if _REVOCATION_FAILURE.search(output):
376
+ hint = (
377
+ "\n(the certificate's revocation check failed: set %s = \"false\" in"
378
+ " <store>/dotagents/config.toml to skip it, as git does on Linux)" % GIT_SSL_REVOKE_KEY
379
+ )
325
380
  raise SourceError(
326
- "error: git %s failed (exit %d): %s"
327
- % (args[0], proc.returncode, redact(proc.stderr.strip() or proc.stdout.strip()))
381
+ "error: git %s failed (exit %d): %s%s" % (args[0], proc.returncode, redact(output), hint)
328
382
  )
329
383
  return proc
330
384
 
@@ -934,4 +988,4 @@ def resolve(
934
988
  ordered += [Spec("dir", str(p)) for p in registry_files(*stores)]
935
989
  if not ordered and not allow_empty:
936
990
  raise SourceError(NO_SOURCE_MESSAGE)
937
- return CompositeSource(ordered, SourceCache(cache_root, logger))
991
+ return CompositeSource(ordered, SourceCache(cache_root, logger, ssl_revoke=git_ssl_revoke(*stores)))
@@ -324,7 +324,10 @@ def _resolve_from(
324
324
  if cache_root is None:
325
325
  cache_root = resolve_user_store(None) / ".cache" / "overlays"
326
326
  try:
327
- target = _sources.locate(spec, _sources.SourceCache(Path(cache_root), logger))
327
+ cache = _sources.SourceCache(
328
+ Path(cache_root), logger, ssl_revoke=_sources.git_ssl_revoke(resolve_user_store(None))
329
+ )
330
+ target = _sources.locate(spec, cache)
328
331
  except SystemExit as exc:
329
332
  message = str(exc.code if exc.code is not None else exc)
330
333
  if message.startswith("error: "):
@@ -297,8 +297,8 @@ def test_bad_source_is_skipped_not_fatal(monkeypatch, tmp_path):
297
297
  # at import time must not crash: discovery runs before EVERY invocation,
298
298
  # including `env`/`context` inside the SessionStart hooks, so one typo in a
299
299
  # personal cmds module used to take the whole session's env and context
300
- # down (review 2026-09-09). duho propagates SyntaxError on purpose; the
301
- # dotagents layer is where it must stop.
300
+ # down. duho propagates SyntaxError on purpose; the dotagents layer is
301
+ # where it must stop.
302
302
  good = tmp_path / "good"
303
303
  _write(good / "toy.py", TOY)
304
304
  broken = tmp_path / "broken"
@@ -116,7 +116,7 @@ def test_harness_loads_subtracts_user_agents_md(layout, monkeypatch):
116
116
 
117
117
  # Fresh install, no include yet: NOTHING loads the store's AGENTS.md, so
118
118
  # `context` must emit it (the old static assumption dropped it here, and
119
- # the base rules never reached a session -- review 2026-09-09, 1.5).
119
+ # the base rules never reached a session at all).
120
120
  text = _context.assemble_context(claude, S(dotagents_dir, project_root, True))
121
121
  assert "# User rules" in text
122
122
 
@@ -1,9 +1,9 @@
1
- """Env-assembly regressions from the 2026-09-23 review, each a repro turned
2
- into a test: malformed env.py output, relative PATH entries, a session whose
3
- project root is the home directory, and the PowerShell rendering of
4
- typographic quotes and non-ASCII values through the real loader pipe.
1
+ """Env-assembly regressions, each a repro turned into a test: malformed
2
+ env.py output, relative PATH entries, a session whose project root is the
3
+ home directory, and the PowerShell rendering of typographic quotes and
4
+ non-ASCII values through the real loader pipe.
5
5
 
6
- A case the product still gets wrong is a strict xfail naming the review
6
+ A case the product still gets wrong is a strict xfail naming the known
7
7
  issue, so fixing it turns the test into an XPASS failure until the marker
8
8
  goes. tmp dirs only, no network.
9
9
  """
@@ -22,7 +22,7 @@ from dotagents._scope import Scope
22
22
 
23
23
  def _open(issue, why, condition=True):
24
24
  return pytest.mark.xfail(
25
- condition, strict=True, reason="open (review 2026-09-23 %s): %s" % (issue, why)
25
+ condition, strict=True, reason="open (%s): %s" % (issue, why)
26
26
  )
27
27
 
28
28
 
@@ -1,6 +1,5 @@
1
1
  """Env-chain safety: which files run, how a failed source is reported, and what
2
- bash itself adds to a sourced file's output (review 2026-09-09, findings 1.6 and
3
- the env/scope items).
2
+ bash itself adds to a sourced file's output.
4
3
 
5
4
  tmp dirs only, no network. Run from repo root: ``python -m pytest tests/``.
6
5
  """
@@ -284,13 +284,13 @@ def test_subcommands_are_nested_not_module_level(findings_mod):
284
284
 
285
285
 
286
286
  # --------------------------------------------------------------------------- #
287
- # Encodings and reserved names (review 2026-09-23). Cases the module still
288
- # gets wrong are strict xfails naming the issue: a fix flips them to XPASS
289
- # failures until the marker goes.
287
+ # Encodings and reserved names. Cases the module still gets wrong are strict
288
+ # xfails naming the issue: a fix flips them to XPASS failures until the
289
+ # marker goes.
290
290
  # --------------------------------------------------------------------------- #
291
291
 
292
292
  def _open(issue, why):
293
- return pytest.mark.xfail(strict=True, reason="open (review 2026-09-23 %s): %s" % (issue, why))
293
+ return pytest.mark.xfail(strict=True, reason="open (%s): %s" % (issue, why))
294
294
 
295
295
 
296
296
  def test_cmd_body_from_stdin_is_read_as_utf8_bytes(findings_mod, tmp_path, monkeypatch, capsys):
@@ -1,4 +1,4 @@
1
- """Adapter fixes from the 2026-09-23 review: adapters are told the scope,
1
+ """Adapter fixes: adapters are told the scope,
2
2
  Claude honours CLAUDE_CONFIG_DIR, `init` writes the store's AGENTS.md itself,
3
3
  global harness configs are left alone by a project init, Cursor's rule file,
4
4
  Claude's import parsing, identity from runtime signals only, include dedupe.
@@ -1,4 +1,4 @@
1
- """Regression tests for the CLI review fixes: command discovery that survives a
1
+ """Regression tests for CLI fixes: command discovery that survives a
2
2
  command whose parser cannot be built, the `findings` queue's encoding, naming
3
3
  and move-never-delete rules, and `launch`'s exit code, context file and
4
4
  cmd.exe argument handling.
@@ -1,5 +1,4 @@
1
- """Regression tests for the env assembly and rendering review fixes
2
- (review 2026-09-23, "Env assembly and rendering").
1
+ """Regression tests for env assembly and rendering fixes.
3
2
 
4
3
  tmp dirs only, no network. Run from repo root: ``python -m pytest tests/``.
5
4
  """
@@ -1,4 +1,4 @@
1
- """Hook fixes from the 2026-09-23 review: the PowerShell handlers' Git Bash
1
+ """Hook fixes: the PowerShell handlers' Git Bash
2
2
  gate, UTF-8 through the PowerShell pipe, the PreToolUse matcher, merging that
3
3
  keeps the user's keys, symlink-safe and non-ASCII-safe wrappers.
4
4
 
@@ -1,4 +1,4 @@
1
- """Regression tests for the 2026-09-23 review's `init` / `launch` fixes:
1
+ """Regression tests for `init` / `launch` fixes:
2
2
 
3
3
  - `init --from` takes the documented URI, git and checkout forms (it crashed
4
4
  on every remote scheme and on a checkout root);
@@ -1,5 +1,5 @@
1
- """Regression tests for the 2026-09-23 review's "Overlays, sources and skills"
2
- findings: typed source errors, registry parsing, credential handling, the spec
1
+ """Regression tests for "Overlays, sources and skills" fixes: typed source
2
+ errors, registry parsing, credential handling, the spec
3
3
  grammar, manifest validation, overlay names, install records, and the
4
4
  add/remove/sync lifecycle.
5
5
 
@@ -1,5 +1,4 @@
1
- """Regressions for the scope-resolution and context-assembly review fixes
2
- (2026-09-23 review, "Scope resolution and context assembly").
1
+ """Regressions for the scope-resolution and context-assembly fixes.
3
2
 
4
3
  tmp dirs only. The user store is redirected with `$AGENTS_HOME`, the system
5
4
  store with `$AGENTS_SYSTEM_ROOT` (a path that does not exist), and the project
@@ -1,5 +1,5 @@
1
- """Regression tests for the 2026-09-23 review's skills, adapter and hook-cost
2
- findings: Claude's per-skill links are refreshed and pruned, `overlays
1
+ """Regression tests for skills, adapter and hook-cost fixes: Claude's
2
+ per-skill links are refreshed and pruned, `overlays
3
3
  add/sync/remove` keep a wired Claude skills dir current, project-scope skill
4
4
  links warn when git would commit them, pi gets a pointer to a project store,
5
5
  and the env loaders reuse a cached diff while the inputs are unchanged.
@@ -0,0 +1,102 @@
1
+ """`git_ssl_revoke` in `<store>/dotagents/config.toml`: git's
2
+ certificate-revocation check for overlay clones and fetches. Git for Windows'
3
+ Schannel backend fails an https clone whose CRL cannot be fetched; git with
4
+ OpenSSL (Linux) checks no CRLs. dotagents passes the setting to every git call
5
+ as `http.schannelCheckRevoke`, `best-effort` unless the store says otherwise."""
6
+ import subprocess
7
+
8
+ import pytest
9
+
10
+ from dotagents import _sources
11
+ from dotagents.cli._common import STORE_CONFIG
12
+
13
+
14
+ def _config(store, text):
15
+ path = store / STORE_CONFIG
16
+ path.parent.mkdir(parents=True, exist_ok=True)
17
+ path.write_text(text, encoding="utf-8")
18
+
19
+
20
+ @pytest.fixture()
21
+ def argv(monkeypatch):
22
+ seen = []
23
+
24
+ def run(cmd, **kwargs):
25
+ seen.append(cmd)
26
+ return subprocess.CompletedProcess(cmd, 0, "", "")
27
+
28
+ monkeypatch.setattr(_sources.subprocess, "run", run)
29
+ return seen
30
+
31
+
32
+ def test_every_git_call_carries_the_setting_best_effort_by_default(tmp_path, argv):
33
+ cache = _sources.SourceCache(tmp_path / "cache")
34
+ cache._git(["ls-remote", "https://example.invalid/r.git"], None)
35
+ assert argv[-1][:3] == ["git", "-c", "http.schannelCheckRevoke=best-effort"]
36
+ _sources.SourceCache(tmp_path / "cache", ssl_revoke="false")._git(["fetch"], None)
37
+ assert argv[-1][:3] == ["git", "-c", "http.schannelCheckRevoke=false"]
38
+
39
+
40
+ def test_the_store_config_chooses_project_before_user(tmp_path):
41
+ user, project = tmp_path / "user", tmp_path / "project"
42
+ assert _sources.git_ssl_revoke(project, user) == "best-effort"
43
+ _config(user, 'git_ssl_revoke = "false"\n')
44
+ assert _sources.git_ssl_revoke(None, user) == "false"
45
+ _config(project, 'git_ssl_revoke = "TRUE"\n')
46
+ assert _sources.git_ssl_revoke(project, user) == "true"
47
+
48
+
49
+ def test_an_unknown_value_names_the_file(tmp_path):
50
+ _config(tmp_path, 'git_ssl_revoke = "maybe"\n')
51
+ with pytest.raises(_sources.SourceError, match="git_ssl_revoke = 'maybe'.*best-effort, true, false"):
52
+ _sources.git_ssl_revoke(tmp_path)
53
+
54
+
55
+ def test_a_revocation_failure_names_the_setting(tmp_path, monkeypatch):
56
+ stderr = ("fatal: unable to access 'https://example.invalid/r.git/': schannel: next InitializeSecurityContext "
57
+ "failed: CRYPT_E_NO_REVOCATION_CHECK (0x80092012) - The revocation function was unable to check "
58
+ "revocation for the certificate.")
59
+ monkeypatch.setattr(_sources.subprocess, "run",
60
+ lambda cmd, **kw: subprocess.CompletedProcess(cmd, 128, "", stderr))
61
+ with pytest.raises(_sources.SourceError) as caught:
62
+ _sources.SourceCache(tmp_path)._git(["clone", "https://example.invalid/r.git"], None)
63
+ message = str(caught.value)
64
+ assert "CRYPT_E_NO_REVOCATION_CHECK" in message and 'git_ssl_revoke = "false"' in message
65
+
66
+
67
+ def test_a_git_without_best_effort_runs_again_without_it(tmp_path, monkeypatch):
68
+ seen = []
69
+
70
+ def run(cmd, **kwargs):
71
+ seen.append(cmd)
72
+ if "http.schannelCheckRevoke=best-effort" in cmd:
73
+ return subprocess.CompletedProcess(cmd, 128, "", "fatal: bad boolean config value 'best-effort' for "
74
+ "'http.schannelcheckrevoke'\n")
75
+ return subprocess.CompletedProcess(cmd, 0, "ok", "")
76
+
77
+ monkeypatch.setattr(_sources.subprocess, "run", run)
78
+ proc = _sources.SourceCache(tmp_path)._git(["ls-remote", "https://example.invalid/r.git"], None)
79
+ assert proc.returncode == 0 and len(seen) == 2
80
+ assert seen[1] == ["git", "ls-remote", "https://example.invalid/r.git"]
81
+
82
+
83
+ def test_any_other_failure_has_no_hint(tmp_path, monkeypatch):
84
+ monkeypatch.setattr(_sources.subprocess, "run",
85
+ lambda cmd, **kw: subprocess.CompletedProcess(cmd, 128, "", "fatal: repository not found"))
86
+ with pytest.raises(_sources.SourceError) as caught:
87
+ _sources.SourceCache(tmp_path)._git(["clone", "https://example.invalid/r.git"], None)
88
+ assert "git_ssl_revoke" not in str(caught.value)
89
+
90
+
91
+ @pytest.mark.skipif(not __import__("shutil").which("git"), reason="needs git on PATH")
92
+ def test_git_accepts_the_option_on_a_local_clone(tmp_path):
93
+ """The option reaches a real git without breaking a clone (git ignores
94
+ it off Schannel; on Windows it is honoured)."""
95
+ work = tmp_path / "work"
96
+ work.mkdir()
97
+ for args in (["init", "-q", "-b", "main"], ["-c", "user.email=t@example.invalid", "-c", "user.name=t",
98
+ "commit", "-q", "--allow-empty", "-m", "x"]):
99
+ subprocess.run(["git", *args], cwd=str(work), check=True, capture_output=True)
100
+ cache = _sources.SourceCache(tmp_path / "cache", ssl_revoke="false")
101
+ cache._git(["clone", "-q", str(work), str(tmp_path / "copy")], None)
102
+ assert (tmp_path / "copy" / ".git").is_dir()
@@ -1,8 +1,8 @@
1
1
  """The last mile: does the config `init` writes actually reach a harness?
2
2
 
3
- Review 2026-09-09 (finding 1.5): `init` wrote `<store>/CLAUDE.md`, which Claude
4
- Code never reads, and `context` subtracted the store's AGENTS.md as "already
5
- loaded" on a static assumption. These tests pin the fix -- the include `init`
3
+ `init` wrote `<store>/CLAUDE.md`, which Claude Code never reads, and
4
+ `context` subtracted the store's AGENTS.md as "already loaded" on a static
5
+ assumption. These tests pin the fix -- the include `init`
6
6
  writes where Claude reads it, the live include-based subtraction, and
7
7
  `--write-agent` landing in the harness's real file as a managed block -- plus
8
8
  the managed-block merge rules it relies on.