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.
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/CHANGELOG.md +26 -2
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/PKG-INFO +3 -2
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/README.md +2 -1
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/guide/authoring.md +19 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/guide/commands.md +38 -6
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/guide/install.md +11 -1
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/guide/overlays.md +1 -1
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/AGENTS.md +19 -1
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/__init__.py +1 -1
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_sources.py +60 -6
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/_common.py +4 -1
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_cmds_discovery.py +2 -2
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_context.py +1 -1
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env_regressions.py +6 -6
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env_safety.py +1 -2
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_findings_cmd.py +4 -4
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_adapters.py +1 -1
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_cli.py +1 -1
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_env.py +1 -2
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_hooks.py +1 -1
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_init.py +1 -1
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_overlays.py +2 -2
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_scope_context.py +1 -2
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_skills_adapters.py +2 -2
- dotagents_cli-0.7.3/tests/test_git_ssl_revoke.py +102 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_last_mile.py +3 -3
- dotagents_cli-0.7.3/tests/test_no_internal_labels.py +231 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_overlays_cli.py +5 -5
- dotagents_cli-0.7.1/tests/test_review_security.py → dotagents_cli-0.7.3/tests/test_quoting_injection_safety.py +3 -3
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/.gitignore +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/LICENSE +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/agents.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/cli.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/commands.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/context.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/env.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/fs.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/hooks.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/merge.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/overlays.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/reference.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/resources.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/scope.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/skills.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/sources.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/api/wrappers.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/changelog.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/guide/private-sync.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/docs/index.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/install.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/mkdocs.yml +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/pyproject.toml +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/__main__.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_agents.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_context.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_env.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_fs.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_hooks.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_ignore.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_merge.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/cmds/findings.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/cmds/launch.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/hooks/preinvocation_antigravity_context.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/hooks/pretooluse_codex_env.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/hooks/sessionstart_codex_context.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/templates/AGENTS.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlay/dotagents/templates/PROJECT.md +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_overlays.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_resources.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_scope.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_skills.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/_wrappers.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/__init__.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/about.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/build_pyz.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/context.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/env.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/init.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/overlays.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/cli/path.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/src/dotagents/py.typed +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/_helpers.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/_shell.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/conftest.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_about.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_agents.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_api_header.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_audit_leak.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_build_pyz.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_cli_main.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_cloud_setup.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env_cli_scope.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env_format.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_env_into.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_fix_packaging.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_hooks.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_ignore.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_init_base.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_launch_cmd.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_local_additions.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_mcp.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_overlay_libs.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_overlays_command.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_path_timeout_frontmatter.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_powershell_env_hook.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_scope.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_sources.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_sources_uri.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_wire_hooks.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tests/test_wrappers.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tools/audit.py +0 -0
- {dotagents_cli-0.7.1 → dotagents_cli-0.7.3}/tools/cloud-setup.sh +0 -0
- {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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
###
|
|
536
|
+
### Pre-push scanning (an overlay's command)
|
|
505
537
|
|
|
506
|
-
Scanning a repo for
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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). `
|
|
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
|
-
- `
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
301
|
-
#
|
|
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
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
288
|
-
#
|
|
289
|
-
#
|
|
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 (
|
|
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
|
|
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
|
|
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,4 +1,4 @@
|
|
|
1
|
-
"""Hook fixes
|
|
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,5 +1,5 @@
|
|
|
1
|
-
"""Regression tests for
|
|
2
|
-
|
|
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
|
|
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
|
|
2
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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.
|