dotagents-cli 0.8.1__tar.gz → 0.8.2__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.8.1 → dotagents_cli-0.8.2}/CHANGELOG.md +23 -1
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/PKG-INFO +1 -1
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/guide/private-sync.md +17 -10
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/AGENTS.md +5 -2
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/__init__.py +1 -1
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_overlay/dotagents/cmds/findings.py +21 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_overlays.py +4 -3
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_scope.py +25 -2
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_wrappers.py +34 -23
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_findings_cmd.py +14 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_fix_scope_context.py +27 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_wrappers.py +29 -3
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/.gitignore +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/LICENSE +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/README.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/agents.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/cli.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/commands.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/context.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/env.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/fs.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/hooks.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/merge.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/overlays.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/reference.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/resources.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/scope.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/skills.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/sources.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/api/wrappers.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/changelog.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/guide/authoring.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/guide/commands.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/guide/install.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/guide/overlays.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/docs/index.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/install.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/mkdocs.yml +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/pyproject.toml +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/__main__.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_agents.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_context.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_env.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_fs.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_hooks.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_ignore.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_launchers.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_merge.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_overlay/dotagents/cmds/launch.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_overlay/dotagents/hooks/preinvocation_antigravity_context.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_overlay/dotagents/hooks/pretooluse_codex_env.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_overlay/dotagents/hooks/sessionstart_codex_context.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_overlay/dotagents/templates/AGENTS.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_overlay/dotagents/templates/PROJECT.md +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_pipwheel.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_pyvenv.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_resources.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_skills.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_sources.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/cli/__init__.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/cli/_common.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/cli/about.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/cli/build_pyz.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/cli/context.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/cli/env.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/cli/init.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/cli/overlays.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/cli/path.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/py.typed +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/_helpers.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/_shell.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/conftest.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_about.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_agents.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_api_header.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_audit_leak.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_build_pyz.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_cli_main.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_cloud_setup.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_cmds_discovery.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_context.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_env.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_env_cli_scope.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_env_format.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_env_into.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_env_regressions.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_env_safety.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_fix_adapters.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_fix_cli.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_fix_env.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_fix_hooks.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_fix_init.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_fix_overlays.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_fix_packaging.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_fix_skills_adapters.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_git_ssl_revoke.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_hooks.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_ignore.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_init_base.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_last_mile.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_launch_cmd.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_launchers.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_local_additions.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_mcp.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_no_internal_labels.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_overlay_libs.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_overlays_cli.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_overlays_command.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_path_timeout_frontmatter.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_pipwheel.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_powershell_env_hook.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_pyvenv_core.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_quoting_injection_safety.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_scope.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_sources.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_sources_uri.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tests/test_wire_hooks.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tools/audit.py +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tools/cloud-setup.sh +0 -0
- {dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/tools/pyz_smoke.sh +0 -0
|
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.2] - 2026-10-25
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `findings` (and `init` / `overlays`) run from inside a project's `.agents`, such as
|
|
15
|
+
`<project>/.agents/findings`, target that project. They used to write a second
|
|
16
|
+
`.agents` inside the store. Deeper than a top-level directory of the user store,
|
|
17
|
+
where no project can be named, they refuse instead of writing there.
|
|
18
|
+
- `findings` keeps a hand-written `INDEX.md`: before the first regeneration it is
|
|
19
|
+
copied to `INDEX.md.handwritten`, with a warning.
|
|
20
|
+
- The routing note above overlay paths no longer suggests `dotagents env` for
|
|
21
|
+
reading an overlay root (it prints the whole environment, secrets included); it
|
|
22
|
+
says to read just the variable.
|
|
23
|
+
|
|
24
|
+
- The `sh` wrapper `init` writes into `<store>/bin/dotagents` works from another
|
|
25
|
+
system that shares the store, such as WSL reading a Windows store. It ran the
|
|
26
|
+
interpreter recorded by `init`, a Windows path there; where that interpreter
|
|
27
|
+
does not run it now takes the store's agents' venv for that OS and machine
|
|
28
|
+
(`.pyvenv/<version>-<os>-<arch>`), else `python3`, and converts a recorded
|
|
29
|
+
`C:/...` pyz path with `wslpath`.
|
|
30
|
+
|
|
10
31
|
## [0.8.1] - 2026-10-21
|
|
11
32
|
|
|
12
33
|
### Added
|
|
@@ -1546,7 +1567,8 @@ Patch release: the PATH/POSIX-conversion fix above (the only change since 0.3.0)
|
|
|
1546
1567
|
affected module sources (`dotagents.cli`, `duho.presets`) to extracted temp files
|
|
1547
1568
|
before dispatch; a no-op for a plain install.
|
|
1548
1569
|
|
|
1549
|
-
[Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.8.
|
|
1570
|
+
[Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.8.2...HEAD
|
|
1571
|
+
[0.8.2]: https://github.com/jose-pr/dotagents/compare/v0.8.1...v0.8.2
|
|
1550
1572
|
[0.8.1]: https://github.com/jose-pr/dotagents/compare/v0.8.0...v0.8.1
|
|
1551
1573
|
[0.8.0]: https://github.com/jose-pr/dotagents/compare/v0.7.6...v0.8.0
|
|
1552
1574
|
[0.7.6]: https://github.com/jose-pr/dotagents/compare/v0.7.5...v0.7.6
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dotagents-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
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
|
|
@@ -11,13 +11,19 @@ overlay is what makes them exist; a plain dotagents has no private-sync surface
|
|
|
11
11
|
|
|
12
12
|
## The idea
|
|
13
13
|
|
|
14
|
-
Your global config store **is** a private git repo
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
project's `.gitignore` already excludes `.agents` per the Leakage rule, so
|
|
14
|
+
Your global config store **is** a private git repo, and its `main` branch is the
|
|
15
|
+
per-user config. Each project's private payload is a **branch** of it, named after
|
|
16
|
+
the project, and the project's `.agents` directory is a git worktree on that branch
|
|
17
|
+
(the project's `.gitignore` already excludes `.agents` per the Leakage rule, so it
|
|
18
18
|
never lands in the public repo). `<name>` defaults to the project's basename, so a
|
|
19
|
-
local checkout and a cloud checkout of the same project resolve to the same
|
|
20
|
-
|
|
19
|
+
local checkout and a cloud checkout of the same project resolve to the same branch.
|
|
20
|
+
|
|
21
|
+
That branch can live elsewhere: `link-project --store-repo self` keeps it on the
|
|
22
|
+
project's own repository (branch `agents`), and `--store-repo <url>` on any other,
|
|
23
|
+
such as a team account's (cloned into `.agents`); `--store-branch` names the branch.
|
|
24
|
+
The choice is recorded in the config repo, so every machine links the project the
|
|
25
|
+
same way. A store kept as a `projects/<name>/` directory, symlinked into the
|
|
26
|
+
project, still works and is kept by a project that already has one.
|
|
21
27
|
|
|
22
28
|
The store's installed overlays can travel two ways:
|
|
23
29
|
|
|
@@ -39,11 +45,12 @@ dotagents overlays add private-sync -g \
|
|
|
39
45
|
--repo "https://github.com/jose-pr/dotagents.git@repo#overlays" # commands + kb + hooks
|
|
40
46
|
|
|
41
47
|
# link-project / sync-project come FROM that overlay -- run `overlays add` first.
|
|
42
|
-
dotagents link-project . #
|
|
48
|
+
dotagents link-project . # check this project's store branch out at .agents
|
|
43
49
|
# (an existing real .agents/ is adopted on the
|
|
44
|
-
# first link
|
|
45
|
-
|
|
46
|
-
dotagents
|
|
50
|
+
# first link)
|
|
51
|
+
dotagents link-project . --store-repo self # ...on the project's own repo
|
|
52
|
+
dotagents link-project . --store-repo <team-repo-url> # ...on another repo
|
|
53
|
+
dotagents sync-project --project . -m "msg" # commit / push the project's branch, then the config
|
|
47
54
|
dotagents sync-project --remote <url> -m init # one-command bootstrap
|
|
48
55
|
```
|
|
49
56
|
|
|
@@ -722,8 +722,11 @@ objects `{"matcher"?: str, "hooks": [{"type": "command", "command": str, ...}]}`
|
|
|
722
722
|
|
|
723
723
|
## `dotagents._wrappers`
|
|
724
724
|
|
|
725
|
-
- `POSIX_TEMPLATE
|
|
726
|
-
|
|
725
|
+
- `POSIX_TEMPLATE` — the `sh` wrapper body, for a `.pyz` (absolute, or relative
|
|
726
|
+
to the wrapper) or `-m dotagents`. It runs the recorded interpreter; where that
|
|
727
|
+
does not run (a Windows store read from WSL) it takes the store's agents' venv
|
|
728
|
+
for this OS and machine (`.pyvenv/*-<os>-<arch>/bin/python`), else `python3`,
|
|
729
|
+
and turns a recorded `C:/...` pyz path into a local one with `wslpath`.
|
|
727
730
|
- `write_wrappers(bin_dir, pyz_path, python=None, *, relative=False) -> list[Path]` —
|
|
728
731
|
`dotagents` (sh) and `dotagents.cmd`, both always, running the `.pyz`.
|
|
729
732
|
- `write_module_wrappers(bin_dir, python=None) -> list[Path]` — both forms running
|
{dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_overlay/dotagents/cmds/findings.py
RENAMED
|
@@ -493,11 +493,32 @@ class FindingsStore:
|
|
|
493
493
|
return "\n".join(lines) + "\n"
|
|
494
494
|
|
|
495
495
|
def write_index(self) -> Path:
|
|
496
|
+
_keep_handwritten(self.index_path, "Regenerated by `dotagents findings`")
|
|
496
497
|
# Atomic: an agent may be reading the index while a change rewrites it.
|
|
497
498
|
write_text_lf(self.index_path, self.render_index(), atomic=True)
|
|
498
499
|
return self.index_path
|
|
499
500
|
|
|
500
501
|
|
|
502
|
+
def _keep_handwritten(index: Path, marker: str) -> None:
|
|
503
|
+
"""Copy an index this command did not write (it lacks ``marker``) to
|
|
504
|
+
``INDEX.md.handwritten`` before it is regenerated, saying so. The backup is
|
|
505
|
+
not ``*.md``, so it is never read as an entry."""
|
|
506
|
+
try:
|
|
507
|
+
text = index.read_text(encoding="utf-8")
|
|
508
|
+
except OSError:
|
|
509
|
+
return
|
|
510
|
+
if marker in text:
|
|
511
|
+
return
|
|
512
|
+
backup = index.with_name(index.name + ".handwritten")
|
|
513
|
+
n = 1
|
|
514
|
+
while backup.exists():
|
|
515
|
+
n += 1
|
|
516
|
+
backup = index.with_name("%s.handwritten-%d" % (index.name, n))
|
|
517
|
+
write_text_lf(backup, text)
|
|
518
|
+
logging.getLogger("dotagents").warning(
|
|
519
|
+
"%s was written by hand; it is kept as %s and regenerated", index, backup.name)
|
|
520
|
+
|
|
521
|
+
|
|
501
522
|
# --------------------------------------------------------------------------- #
|
|
502
523
|
# The command
|
|
503
524
|
# --------------------------------------------------------------------------- #
|
|
@@ -1166,9 +1166,10 @@ def _compose_block(base_text: str, overlays, logger) -> str:
|
|
|
1166
1166
|
#: Emitted once above overlay routing lines that use the per-overlay root vars.
|
|
1167
1167
|
OVERLAY_ROOT_NOTE = (
|
|
1168
1168
|
"Overlay paths below use `$<NAME>_OVERLAY_ROOT` -- an environment variable "
|
|
1169
|
-
"
|
|
1170
|
-
"
|
|
1171
|
-
"before opening the file with a file tool
|
|
1169
|
+
"already set in your shell for each installed overlay (its install dir); read "
|
|
1170
|
+
"just that one (`echo $ENGINEERING_OVERLAY_ROOT`; PowerShell "
|
|
1171
|
+
"`$env:ENGINEERING_OVERLAY_ROOT`) before opening the file with a file tool, "
|
|
1172
|
+
"never the whole environment (`dotagents env` prints secrets)."
|
|
1172
1173
|
)
|
|
1173
1174
|
|
|
1174
1175
|
|
|
@@ -505,6 +505,27 @@ def _project_containing(start: Path) -> Path:
|
|
|
505
505
|
return start
|
|
506
506
|
|
|
507
507
|
|
|
508
|
+
def _outside_store(cwd: Path) -> Path:
|
|
509
|
+
"""``cwd``, or -- when it lies inside a ``.agents`` directory -- the project
|
|
510
|
+
that ``.agents`` belongs to (the parent of the outermost one): a command run
|
|
511
|
+
from ``<project>/.agents/findings`` means that project, never a second
|
|
512
|
+
``.agents`` inside its store. Deeper than a top-level directory of the user
|
|
513
|
+
store (a linked project store reached through its target) there is no
|
|
514
|
+
project to name, so that is refused rather than written into the store."""
|
|
515
|
+
parts = cwd.parts
|
|
516
|
+
if ".agents" not in parts:
|
|
517
|
+
return cwd
|
|
518
|
+
index = parts.index(".agents")
|
|
519
|
+
root = Path(*parts[:index]) if index else cwd
|
|
520
|
+
store = resolve_user_store()
|
|
521
|
+
if _same_path(root / ".agents", store) and len(parts) > index + 2:
|
|
522
|
+
raise SystemExit(
|
|
523
|
+
"error: %s is inside the agents store %s, not a project; run from the "
|
|
524
|
+
"project's root (or pass --dir / --agents-dir)" % (cwd, store)
|
|
525
|
+
)
|
|
526
|
+
return root
|
|
527
|
+
|
|
528
|
+
|
|
508
529
|
def install_project_root() -> Path:
|
|
509
530
|
"""The project root the WRITING commands (``init``, ``overlays``,
|
|
510
531
|
``findings``) target when none is passed explicitly.
|
|
@@ -519,8 +540,10 @@ def install_project_root() -> Path:
|
|
|
519
540
|
|
|
520
541
|
``env`` / ``context`` keep :func:`project_root_default`, where the pin wins
|
|
521
542
|
regardless of the cwd: what they read must stay stable across the
|
|
522
|
-
subdirectories a session's commands run in.
|
|
523
|
-
|
|
543
|
+
subdirectories a session's commands run in.
|
|
544
|
+
|
|
545
|
+
A cwd inside a ``.agents`` counts as its project's root (:func:`_outside_store`)."""
|
|
546
|
+
cwd = _outside_store(Path.cwd())
|
|
524
547
|
for var in ("AGENTS_PROJECT_ROOT", *_HARNESS_PROJECT_ROOT_VARS):
|
|
525
548
|
value = os.environ.get(var)
|
|
526
549
|
if not value:
|
|
@@ -6,20 +6,39 @@ import stat
|
|
|
6
6
|
import sys
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
# The sh wrapper. `{target}` is what the interpreter runs: the pyz (absolute,
|
|
10
|
+
# or `"$d/..."` relative to the wrapper's own directory, so moving the scope dir
|
|
11
|
+
# does not break it) or `-m dotagents` for a plain install. `readlink -f` so a
|
|
12
|
+
# wrapper run through a symlink still finds what is beside the real file.
|
|
13
|
+
#
|
|
14
|
+
# The interpreter recorded is the one of the system that ran `init`. A store
|
|
15
|
+
# shared with another system -- WSL reading a Windows store -- reaches a
|
|
16
|
+
# recorded path that does not run there; the wrapper then takes that system's
|
|
17
|
+
# own: the store's agents' venv for this OS and machine (`.pyvenv/*-<os>-<arch>`,
|
|
18
|
+
# the naming `dotagents._pyvenv` uses), else `python3`, and a recorded
|
|
19
|
+
# `C:/...` pyz path goes through `wslpath`.
|
|
20
|
+
POSIX_TEMPLATE = (
|
|
21
|
+
'#!/bin/sh\n'
|
|
22
|
+
'd=$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")")\n'
|
|
23
|
+
'py="{python}"\n'
|
|
24
|
+
'set -- {target} "$@"\n'
|
|
25
|
+
'if [ ! -x "$py" ]; then\n'
|
|
26
|
+
' case "$(uname -s)" in Darwin) os=darwin ;; *) os=posix ;; esac\n'
|
|
27
|
+
' py=\n'
|
|
28
|
+
' for v in "$d"/../.pyvenv/*-"$os"-"$(uname -m)"/bin/python; do\n'
|
|
29
|
+
' [ -x "$v" ] && py=$v\n'
|
|
30
|
+
' done\n'
|
|
31
|
+
' [ -n "$py" ] || py=$(command -v python3 || command -v python) || {{\n'
|
|
32
|
+
' echo "dotagents: no Python interpreter found" >&2; exit 127; }}\n'
|
|
33
|
+
' case "$1" in [A-Za-z]:/*) if command -v wslpath >/dev/null 2>&1; then\n'
|
|
34
|
+
' p=$(wslpath -u "$1"); shift; set -- "$p" "$@"; fi ;; esac\n'
|
|
35
|
+
'fi\n'
|
|
36
|
+
'exec "$py" "$@"\n'
|
|
18
37
|
)
|
|
19
38
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
39
|
+
|
|
40
|
+
def _posix_wrapper(python: str, target: str) -> str:
|
|
41
|
+
return POSIX_TEMPLATE.format(python=Path(python).as_posix(), target=target)
|
|
23
42
|
|
|
24
43
|
|
|
25
44
|
def _cmd_safe(path: str) -> str:
|
|
@@ -88,7 +107,7 @@ def write_module_wrappers(bin_dir: Path, python: "str | None" = None) -> "list[P
|
|
|
88
107
|
python = python or sys.executable
|
|
89
108
|
sh_path, cmd_path = bin_dir / "dotagents", bin_dir / "dotagents.cmd"
|
|
90
109
|
with open(sh_path, "w", encoding="utf-8", newline="") as f:
|
|
91
|
-
f.write(
|
|
110
|
+
f.write(_posix_wrapper(python, "-m dotagents"))
|
|
92
111
|
if os.name != "nt":
|
|
93
112
|
sh_path.chmod(sh_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
94
113
|
_write_cmd(cmd_path, '"%s" -m dotagents %%*' % _cmd_safe(str(python)))
|
|
@@ -149,17 +168,9 @@ def write_wrappers(
|
|
|
149
168
|
sh_path = bin_dir / "dotagents"
|
|
150
169
|
with open(sh_path, "w", encoding="utf-8", newline="") as f:
|
|
151
170
|
if rel_pyz is not None:
|
|
152
|
-
f.write(
|
|
153
|
-
POSIX_TEMPLATE_REL.format(
|
|
154
|
-
python=Path(python).as_posix(), pyz=rel_pyz.as_posix()
|
|
155
|
-
)
|
|
156
|
-
)
|
|
171
|
+
f.write(_posix_wrapper(python, '"$d/%s"' % rel_pyz.as_posix()))
|
|
157
172
|
else:
|
|
158
|
-
f.write(
|
|
159
|
-
POSIX_TEMPLATE.format(
|
|
160
|
-
python=Path(python).as_posix(), pyz=pyz_path.as_posix()
|
|
161
|
-
)
|
|
162
|
-
)
|
|
173
|
+
f.write(_posix_wrapper(python, '"%s"' % pyz_path.as_posix()))
|
|
163
174
|
if os.name != "nt":
|
|
164
175
|
mode = sh_path.stat().st_mode
|
|
165
176
|
sh_path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
@@ -417,3 +417,17 @@ def test_index_requote_changes_only_the_values_yaml_cannot_read(findings_mod, tm
|
|
|
417
417
|
assert "— [low][api] a: b" in (root / "INDEX.md").read_text(encoding="utf-8")
|
|
418
418
|
# A second pass has nothing left to do.
|
|
419
419
|
assert findings_mod.FindingsStore(root).requote() == []
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def test_a_hand_written_index_is_kept_before_it_is_regenerated(findings_mod, tmp_path):
|
|
423
|
+
root = tmp_path / "findings"
|
|
424
|
+
root.mkdir()
|
|
425
|
+
(root / "INDEX.md").write_text("# My own index\n\n- evidence line\n", encoding="utf-8")
|
|
426
|
+
store = findings_mod.FindingsStore(root)
|
|
427
|
+
store.add("First problem", body="b")
|
|
428
|
+
kept = root / "INDEX.md.handwritten"
|
|
429
|
+
assert kept.read_text(encoding="utf-8") == "# My own index\n\n- evidence line\n"
|
|
430
|
+
assert "Regenerated by `dotagents findings`" in (root / "INDEX.md").read_text(encoding="utf-8")
|
|
431
|
+
store.add("Second problem", body="b") # a generated index is not backed up again
|
|
432
|
+
assert sorted(p.name for p in root.iterdir() if "handwritten" in p.name) == ["INDEX.md.handwritten"]
|
|
433
|
+
assert [f.name for f in store.active()] == ["first-problem", "second-problem"]
|
|
@@ -97,6 +97,33 @@ def test_a_store_that_appears_twice_is_walked_once(tmp_path):
|
|
|
97
97
|
# scope-context-03: a pin the cwd has left is reported by the writing commands
|
|
98
98
|
# --------------------------------------------------------------------------
|
|
99
99
|
|
|
100
|
+
def test_a_cwd_inside_a_project_store_means_that_project(tmp_path, monkeypatch):
|
|
101
|
+
"""`dotagents findings index` run from `<project>/.agents/findings` wrote a
|
|
102
|
+
second `.agents` inside the store; the project is the one holding it."""
|
|
103
|
+
project = tmp_path / "proj"
|
|
104
|
+
(project / ".agents" / "findings").mkdir(parents=True)
|
|
105
|
+
monkeypatch.chdir(project / ".agents" / "findings")
|
|
106
|
+
assert _scope.resolve_scope(False).agents_root == project / ".agents"
|
|
107
|
+
# Pinned elsewhere, the same.
|
|
108
|
+
monkeypatch.setenv("AGENTS_PROJECT_ROOT", str(tmp_path / "other"))
|
|
109
|
+
assert _scope.resolve_scope(False).agents_root == project / ".agents"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_a_cwd_deep_in_the_user_store_is_refused(tmp_path, monkeypatch):
|
|
113
|
+
"""Reached through a linked store's target, the cwd names no project; it is
|
|
114
|
+
refused rather than written into the store as `.agents/.agents`."""
|
|
115
|
+
store = tmp_path / "home" / ".agents"
|
|
116
|
+
monkeypatch.setenv("AGENTS_HOME", str(store))
|
|
117
|
+
deep = store / "projects" / "app" / "findings"
|
|
118
|
+
deep.mkdir(parents=True)
|
|
119
|
+
monkeypatch.chdir(deep)
|
|
120
|
+
with pytest.raises(SystemExit, match="inside the agents store"):
|
|
121
|
+
_scope.resolve_scope(False)
|
|
122
|
+
# A top-level directory of the store is the store itself: the user scope.
|
|
123
|
+
monkeypatch.chdir(store / "projects")
|
|
124
|
+
assert _scope.resolve_scope(False).agents_root == store
|
|
125
|
+
|
|
126
|
+
|
|
100
127
|
def test_writing_outside_the_pinned_root_warns(tmp_path, monkeypatch, caplog):
|
|
101
128
|
proj_a, proj_b = tmp_path / "projA", tmp_path / "projB"
|
|
102
129
|
(proj_a / "sub").mkdir(parents=True)
|
|
@@ -74,8 +74,7 @@ def test_relative_declines_a_pyz_outside_the_scope(tmp_path):
|
|
|
74
74
|
write_wrappers(tmp_path / "scope" / "bin", pyz, relative=True)
|
|
75
75
|
|
|
76
76
|
sh = (tmp_path / "scope" / "bin" / "dotagents").read_text(encoding="utf-8")
|
|
77
|
-
assert "
|
|
78
|
-
assert pyz.as_posix() in sh
|
|
77
|
+
assert 'set -- "%s"' % pyz.as_posix() in sh, "the pyz must be absolute, not a ../ chain"
|
|
79
78
|
|
|
80
79
|
|
|
81
80
|
def test_absolute_by_default(tmp_path):
|
|
@@ -83,7 +82,7 @@ def test_absolute_by_default(tmp_path):
|
|
|
83
82
|
write_wrappers(tmp_path / "bin", pyz)
|
|
84
83
|
sh = (tmp_path / "bin" / "dotagents").read_text(encoding="utf-8")
|
|
85
84
|
assert pyz.as_posix() in sh
|
|
86
|
-
assert "
|
|
85
|
+
assert '"$d/' not in sh, "the pyz path is absolute, not relative to the wrapper"
|
|
87
86
|
|
|
88
87
|
|
|
89
88
|
def test_crlf_in_cmd_and_lf_in_sh(tmp_path):
|
|
@@ -159,6 +158,33 @@ def test_relative_falls_back_when_no_relative_path_exists(tmp_path, monkeypatch)
|
|
|
159
158
|
assert pyz.as_posix() in sh, "must fall back to an absolute path, not crash"
|
|
160
159
|
|
|
161
160
|
|
|
161
|
+
@pytest.mark.skipif(os.name == "nt", reason="the fallback is for a system where the recorded interpreter does not run")
|
|
162
|
+
def test_a_recorded_interpreter_that_does_not_run_falls_back_to_the_store_venv(tmp_path):
|
|
163
|
+
"""A store shared with another system (WSL reading a Windows store): the
|
|
164
|
+
recorded interpreter is not there, so the store's agents' venv for this OS
|
|
165
|
+
and machine runs the pyz."""
|
|
166
|
+
import platform
|
|
167
|
+
|
|
168
|
+
store = tmp_path / "store"
|
|
169
|
+
store.mkdir()
|
|
170
|
+
pyz = store / "dotagents.pyz"
|
|
171
|
+
pyz.write_text(PROBE, encoding="utf-8")
|
|
172
|
+
write_wrappers(store / "bin", pyz, python="C:/Not/Here/python.exe", relative=True)
|
|
173
|
+
os_name = "darwin" if sys.platform == "darwin" else "posix"
|
|
174
|
+
venv_python = store / ".pyvenv" / ("3.99.0-%s-%s" % (os_name, platform.machine())) / "bin" / "python"
|
|
175
|
+
venv_python.parent.mkdir(parents=True)
|
|
176
|
+
venv_python.symlink_to(sys.executable)
|
|
177
|
+
_run_sh(store / "bin" / "dotagents", tmp_path)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@pytest.mark.skipif(os.name == "nt", reason="the fallback is for a system where the recorded interpreter does not run")
|
|
181
|
+
def test_with_no_store_venv_the_fallback_is_python3(tmp_path):
|
|
182
|
+
pyz = tmp_path / "dotagents.pyz"
|
|
183
|
+
pyz.write_text(PROBE, encoding="utf-8")
|
|
184
|
+
write_wrappers(tmp_path / "bin", pyz, python="C:/Not/Here/python.exe")
|
|
185
|
+
_run_sh(tmp_path / "bin" / "dotagents", tmp_path)
|
|
186
|
+
|
|
187
|
+
|
|
162
188
|
def test_module_wrappers_run_the_installed_package(tmp_path):
|
|
163
189
|
r"""A plain install has no pyz: `init` wrote no wrappers at all, and the
|
|
164
190
|
PowerShell hooks called a `<store>\bin\dotagents.cmd` that never existed."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_overlay/dotagents/templates/AGENTS.md
RENAMED
|
File without changes
|
{dotagents_cli-0.8.1 → dotagents_cli-0.8.2}/src/dotagents/_overlay/dotagents/templates/PROJECT.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|