dotagents-cli 0.7.0__tar.gz → 0.7.1__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 (112) hide show
  1. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/CHANGELOG.md +12 -1
  2. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/PKG-INFO +1 -1
  3. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/guide/commands.md +5 -1
  4. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/guide/overlays.md +11 -2
  5. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/AGENTS.md +9 -2
  6. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/__init__.py +1 -1
  7. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_context.py +47 -3
  8. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_overlay/dotagents/templates/AGENTS.md +6 -0
  9. dotagents_cli-0.7.1/tests/test_local_additions.py +86 -0
  10. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/.gitignore +0 -0
  11. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/LICENSE +0 -0
  12. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/README.md +0 -0
  13. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/agents.md +0 -0
  14. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/cli.md +0 -0
  15. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/commands.md +0 -0
  16. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/context.md +0 -0
  17. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/env.md +0 -0
  18. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/fs.md +0 -0
  19. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/hooks.md +0 -0
  20. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/merge.md +0 -0
  21. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/overlays.md +0 -0
  22. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/reference.md +0 -0
  23. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/resources.md +0 -0
  24. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/scope.md +0 -0
  25. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/skills.md +0 -0
  26. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/sources.md +0 -0
  27. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/api/wrappers.md +0 -0
  28. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/changelog.md +0 -0
  29. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/guide/authoring.md +0 -0
  30. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/guide/install.md +0 -0
  31. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/guide/private-sync.md +0 -0
  32. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/docs/index.md +0 -0
  33. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/install.py +0 -0
  34. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/mkdocs.yml +0 -0
  35. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/pyproject.toml +0 -0
  36. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/__main__.py +0 -0
  37. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_agents.py +0 -0
  38. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_env.py +0 -0
  39. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_fs.py +0 -0
  40. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_hooks.py +0 -0
  41. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_ignore.py +0 -0
  42. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_merge.py +0 -0
  43. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_overlay/dotagents/cmds/findings.py +0 -0
  44. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_overlay/dotagents/cmds/launch.py +0 -0
  45. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_overlay/dotagents/hooks/preinvocation_antigravity_context.py +0 -0
  46. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_overlay/dotagents/hooks/pretooluse_codex_env.py +0 -0
  47. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_overlay/dotagents/hooks/sessionstart_codex_context.py +0 -0
  48. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_overlay/dotagents/templates/PROJECT.md +0 -0
  49. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_overlays.py +0 -0
  50. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_resources.py +0 -0
  51. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_scope.py +0 -0
  52. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_skills.py +0 -0
  53. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_sources.py +0 -0
  54. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/_wrappers.py +0 -0
  55. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/cli/__init__.py +0 -0
  56. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/cli/_common.py +0 -0
  57. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/cli/about.py +0 -0
  58. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/cli/build_pyz.py +0 -0
  59. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/cli/context.py +0 -0
  60. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/cli/env.py +0 -0
  61. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/cli/init.py +0 -0
  62. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/cli/overlays.py +0 -0
  63. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/cli/path.py +0 -0
  64. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/src/dotagents/py.typed +0 -0
  65. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/_helpers.py +0 -0
  66. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/_shell.py +0 -0
  67. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/conftest.py +0 -0
  68. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_about.py +0 -0
  69. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_agents.py +0 -0
  70. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_api_header.py +0 -0
  71. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_audit_leak.py +0 -0
  72. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_build_pyz.py +0 -0
  73. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_cli_main.py +0 -0
  74. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_cloud_setup.py +0 -0
  75. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_cmds_discovery.py +0 -0
  76. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_context.py +0 -0
  77. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_env.py +0 -0
  78. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_env_cli_scope.py +0 -0
  79. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_env_format.py +0 -0
  80. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_env_into.py +0 -0
  81. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_env_regressions.py +0 -0
  82. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_env_safety.py +0 -0
  83. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_findings_cmd.py +0 -0
  84. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_fix_adapters.py +0 -0
  85. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_fix_cli.py +0 -0
  86. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_fix_env.py +0 -0
  87. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_fix_hooks.py +0 -0
  88. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_fix_init.py +0 -0
  89. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_fix_overlays.py +0 -0
  90. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_fix_packaging.py +0 -0
  91. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_fix_scope_context.py +0 -0
  92. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_fix_skills_adapters.py +0 -0
  93. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_hooks.py +0 -0
  94. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_ignore.py +0 -0
  95. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_init_base.py +0 -0
  96. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_last_mile.py +0 -0
  97. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_launch_cmd.py +0 -0
  98. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_mcp.py +0 -0
  99. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_overlay_libs.py +0 -0
  100. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_overlays_cli.py +0 -0
  101. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_overlays_command.py +0 -0
  102. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_path_timeout_frontmatter.py +0 -0
  103. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_powershell_env_hook.py +0 -0
  104. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_review_security.py +0 -0
  105. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_scope.py +0 -0
  106. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_sources.py +0 -0
  107. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_sources_uri.py +0 -0
  108. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_wire_hooks.py +0 -0
  109. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tests/test_wrappers.py +0 -0
  110. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tools/audit.py +0 -0
  111. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tools/cloud-setup.sh +0 -0
  112. {dotagents_cli-0.7.0 → dotagents_cli-0.7.1}/tools/pyz_smoke.sh +0 -0
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.1] - 2026-10-01
11
+
12
+ ### Added
13
+
14
+ - Your own additions to an installed overlay's file live at the same path under the
15
+ store root (`~/.agents/kb/RUST.md` for the rust overlay's `kb/RUST.md`, or
16
+ `<project>/.agents/kb/RUST.md`), where `overlays sync` never touches them. The
17
+ base `AGENTS.md` tells agents to read them alongside the overlay's file, and
18
+ `dotagents context` lists the ones it finds (JSON: `local_additions`).
19
+
10
20
  ## [0.7.0] - 2026-10-01
11
21
 
12
22
  ### Changed
@@ -1362,7 +1372,8 @@ Patch release: the PATH/POSIX-conversion fix above (the only change since 0.3.0)
1362
1372
  affected module sources (`dotagents.cli`, `duho.presets`) to extracted temp files
1363
1373
  before dispatch; a no-op for a plain install.
1364
1374
 
1365
- [Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.7.0...HEAD
1375
+ [Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.7.1...HEAD
1376
+ [0.7.1]: https://github.com/jose-pr/dotagents/compare/v0.7.0...v0.7.1
1366
1377
  [0.7.0]: https://github.com/jose-pr/dotagents/compare/v0.6.3...v0.7.0
1367
1378
  [0.6.3]: https://github.com/jose-pr/dotagents/compare/v0.6.2...v0.6.3
1368
1379
  [0.6.2]: https://github.com/jose-pr/dotagents/compare/v0.6.1...v0.6.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: dotagents-cli
3
- Version: 0.7.0
3
+ Version: 0.7.1
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
@@ -216,7 +216,11 @@ Assembles the effective context an agent should load — the overlay `CONTEXT.md
216
216
  and the project root's own `AGENTS.md` / `AGENTS.local.md` — minus whatever the harness already loads by itself (for Claude Code, whatever its
217
217
  `CLAUDE.md` files really `@`-include) — and prints it to **stdout** by default
218
218
  (POSIX convention); pass a path to write a file, or `--write-agent` to merge it into
219
- each agent's own instruction file.
219
+ each agent's own instruction file. It ends with the skills on offer and, when there are
220
+ any, your **local additions to overlay files** — a store file at the same path as an
221
+ overlay's (`~/.agents/kb/RUST.md` for the rust overlay's `kb/RUST.md`; see
222
+ [Overlays](overlays.md)); `--format json` carries both as their own keys (`skills`,
223
+ `local_additions`).
220
224
 
221
225
  ```bash
222
226
  dotagents context # print the active agent's context to stdout
@@ -79,8 +79,17 @@ up under `<store>/install_backup/` first. What the `.gitignore` / `.ignore` at t
79
79
  overlay's root match (an ignore file in a subdirectory is just a file it ships), and
80
80
  tool caches, belong to the install (a setup script's output, local
81
81
  state) and are left alone; `--prune` clears them too. A `requires` added upstream is
82
- installed. An installed overlay is upstream's copy, not a place for your edits: keep
83
- those in your own files (a `kb/` file, `AGENTS.local.md`).
82
+ installed. An installed overlay is upstream's copy, not a place for your edits.
83
+
84
+ ### Your additions to an overlay's files
85
+
86
+ Put your own text for an overlay file at the **same path under the store root**: an
87
+ addition to the rust overlay's `kb/RUST.md` goes in `~/.agents/kb/RUST.md` (or
88
+ `<project>/.agents/kb/RUST.md` for one project). `sync` never touches it, and agents
89
+ read it alongside the overlay's file, winning on conflict: the base `AGENTS.md` tells
90
+ them to, and `dotagents context` lists every addition it finds (for each `.md` an
91
+ overlay ships in a subdirectory other than `skills/`). Private names, local tool paths
92
+ and links into your own notes belong there, never in the overlay.
84
93
 
85
94
  Removing an overlay deletes only its directory and unpublishes only the skills **it**
86
95
  published (a copy the user edited is left alone). Its lines in `AGENTS.md`'s managed
@@ -433,7 +433,8 @@ API:
433
433
  `Overlay.sort_key`, then contract-A `AGENTS.md` (every store and the project root)
434
434
  and `AGENTS.local.md` (project store and project root), each prefixed
435
435
  `<!-- Source: <path> -->`. Then a skills listing (`- **name** (`<SKILL.md>`):
436
- description`, from each skill's leading frontmatter; a project skill wins).
436
+ description`, from each skill's leading frontmatter; a project skill wins), then
437
+ a "Local additions to overlay files" section when `local_additions` finds any.
437
438
  `<PROJECT_ROOT>` and `<NAME_OVERLAY_ROOT>` placeholders expand; with
438
439
  `expand_vars` so do `$NAME_OVERLAY_ROOT` / `${NAME_OVERLAY_ROOT}`. `inline`
439
440
  appends the on-demand `.md` files the sources reference, each resolved against
@@ -441,7 +442,13 @@ API:
441
442
  system source never into the project).
442
443
  - `assemble_context_data(agent, scope, *, inline=False, expand_vars=True) -> dict`
443
444
  — `{"agent", "harness", "sources", "context", "skills": [{"name",
444
- "description", "path"}]}`; `context` without the skills listing.
445
+ "description", "path"}], "local_additions": [{"overlay_file", "local": [...]}]}`;
446
+ `context` without the skills and local-additions listings.
447
+ - `local_additions(scope) -> list[tuple[Path, list[Path]]]` — the user's own
448
+ additions to installed overlay files: for each `.md` an overlay in `scope.overlays`
449
+ ships in a subdirectory other than `skills/`, every store's file at the same
450
+ relative path (`<store>/kb/RUST.md` for the overlay's `kb/RUST.md`), in
451
+ `scope.stores` order. A store's root files never match.
445
452
 
446
453
  ## `dotagents._agents`
447
454
 
@@ -1,3 +1,3 @@
1
1
  """dotagents: installable CLI for the dotagents agent-config payload."""
2
2
 
3
- __version__ = "0.7.0"
3
+ __version__ = "0.7.1"
@@ -309,6 +309,44 @@ def _get_skills_listing(scope: _scope.Scope) -> str:
309
309
  return "\n\n## Available Skills (Opt-in)\n" + "\n".join(lines) + "\n"
310
310
 
311
311
 
312
+ def local_additions(scope: _scope.Scope) -> "list[tuple[Path, list[Path]]]":
313
+ """The user's own additions to installed overlay files: for each ``.md``
314
+ an overlay ships in a subdirectory (``kb/``, ``flows/``, ``rules/`` ... --
315
+ not ``skills/``, whose published copies sit at the same path in a store),
316
+ every store's file at the same relative path (``~/.agents/kb/RUST.md`` for
317
+ an overlay's ``kb/RUST.md``), in store order. An overlay file is upstream's
318
+ copy, replaced by ``overlays sync``; the addition is where the user's own
319
+ text lives. A store's root files (``AGENTS.md``, ``env.py``) never match:
320
+ only a file in a subdirectory counts."""
321
+ stores = list(scope.stores)
322
+ out: "list[tuple[Path, list[Path]]]" = []
323
+ for overlay in scope.overlays:
324
+ for path in overlay.files():
325
+ rel = path.relative_to(overlay.path)
326
+ if path.suffix.lower() != ".md" or len(rel.parts) < 2 or rel.parts[0] == "skills":
327
+ continue
328
+ found = [store / rel for store in stores if (store / rel).is_file()]
329
+ if found:
330
+ out.append((path, found))
331
+ return out
332
+
333
+
334
+ def _get_local_additions_listing(scope: _scope.Scope) -> str:
335
+ """The local additions (:func:`local_additions`) as a markdown section, or ''."""
336
+ additions = local_additions(scope)
337
+ if not additions:
338
+ return ""
339
+ lines = [
340
+ "- `%s` -- also read %s" % (path, ", ".join("`%s`" % p for p in found))
341
+ for path, found in additions
342
+ ]
343
+ return (
344
+ "\n\n## Local additions to overlay files\n"
345
+ "The user's own additions: read each with the overlay file it extends; it wins on conflict.\n"
346
+ + "\n".join(lines) + "\n"
347
+ )
348
+
349
+
312
350
  def _resolve_and_filter_sources(
313
351
  agent: _agents.Agent, scope: _scope.Scope
314
352
  ) -> "tuple[list[tuple[str, Path, Path | None]], list[Path]]":
@@ -424,6 +462,7 @@ def assemble_context(
424
462
  if not source_paths:
425
463
  return ""
426
464
  text += _get_skills_listing(scope)
465
+ text += _get_local_additions_listing(scope)
427
466
  return text
428
467
 
429
468
 
@@ -440,11 +479,12 @@ def assemble_context_data(
440
479
  "sources": [<absolute source path>, ...], # after harness subtraction
441
480
  "context": <assembled markdown text, minus the skills listing>,
442
481
  "skills": [{"name": ..., "description": ..., "path": <SKILL.md>}, ...],
482
+ "local_additions": [{"overlay_file": ..., "local": [<path>, ...]}, ...],
443
483
  }
444
484
  ``context`` is the same assembled(+inlined, with ``inline=True``) text the
445
- markdown format emits, but WITHOUT the skills listing appended -- skills are
446
- their own structured field so a consumer can render them separately and
447
- keep the opt-in distinction."""
485
+ markdown format emits, but WITHOUT the skills and local-additions listings
486
+ appended -- both are their own structured fields so a consumer can render
487
+ them separately."""
448
488
  text, source_paths = _assemble(agent, scope, inline, expand_vars)
449
489
 
450
490
  skills = [
@@ -457,4 +497,8 @@ def assemble_context_data(
457
497
  "sources": source_paths,
458
498
  "context": text,
459
499
  "skills": skills,
500
+ "local_additions": [
501
+ {"overlay_file": str(path), "local": [str(p) for p in found]}
502
+ for path, found in local_additions(scope)
503
+ ],
460
504
  }
@@ -16,6 +16,12 @@ Startup: annotate that you read `{{AGENTS_MD}}`.
16
16
  config). These files say what lives there and how to work in it, so reading them
17
17
  first is cheaper than rediscovering it from the source. How `.agents/` is
18
18
  populated — real directory, symlink, committed or not — is yours to decide.
19
+ - **Local additions to overlay files**: an overlay file (`$<NAME>_OVERLAY_ROOT/<path>`)
20
+ is upstream's copy, and `dotagents overlays sync` replaces it. The user's own
21
+ additions live at the same `<path>` under this config directory and the project's
22
+ `.agents/`: when you read an overlay file, read those too — they extend it and win on
23
+ conflict (`dotagents context` lists the ones it finds). Never write user-specific text
24
+ into an overlay file; put it there.
19
25
  - **Global-config misses**: if these instructions caused a mistake or rework, or you
20
26
  have an improvement idea, record it and move on — don't edit the config:
21
27
  `dotagents findings add -g "<one line>" -b "<what happened, evidence>"`. Read the
@@ -0,0 +1,86 @@
1
+ """The user's own additions to installed overlay files: a store file at the
2
+ same relative path as an overlay's (``~/.agents/kb/RUST.md`` for the rust
3
+ overlay's ``kb/RUST.md``). `overlays sync` replaces the overlay's copy, so this
4
+ is where the user's text lives; `dotagents context` lists what it finds, and
5
+ the base AGENTS.md tells every agent to read them."""
6
+ import json
7
+
8
+ from dotagents import _agents, _context
9
+ from dotagents._resources import BASE_PROJECT_TEMPLATE, BASE_ROOT, base_agents_text
10
+ from dotagents._scope import Scope
11
+
12
+
13
+ def _write(path, text="x\n"):
14
+ path.parent.mkdir(parents=True, exist_ok=True)
15
+ path.write_text(text, encoding="utf-8")
16
+
17
+
18
+ def _world(tmp_path):
19
+ user = tmp_path / "user"
20
+ project = tmp_path / "proj"
21
+ rust = user / "overlays" / "rust"
22
+ _write(rust / "overlay.toml", 'name = "rust"\n')
23
+ _write(rust / "CONTEXT.md", "RUST-CONTEXT\n")
24
+ _write(rust / "kb" / "RUST.md", "upstream rust\n")
25
+ _write(rust / "kb" / "NOTES.md", "no addition for this one\n")
26
+ _write(rust / "flows" / "deep" / "STEP.md", "upstream step\n")
27
+ _write(rust / "skills" / "rs" / "SKILL.md", "---\nname: rs\ndescription: d\n---\n")
28
+ _write(rust / "kb" / "data.json", "{}\n")
29
+ _write(rust / "AGENTS.md", "an overlay-root file\n")
30
+ _write(user / "AGENTS.md", "# user\n")
31
+ _write(user / "kb" / "RUST.md", "my own note\n")
32
+ _write(user / "flows" / "deep" / "STEP.md", "my step\n")
33
+ _write(user / "skills" / "rs" / "SKILL.md", "---\nname: rs\ndescription: d\n---\n") # a published copy
34
+ _write(user / "kb" / "data.json", "{}\n")
35
+ _write(project / ".agents" / "kb" / "RUST.md", "a project note\n")
36
+ return user, project
37
+
38
+
39
+ def _scope(user, project, global_scope=False):
40
+ return Scope.of(agents_dir=user, project_root=project, global_scope=global_scope)
41
+
42
+
43
+ def test_additions_are_matched_by_relative_path_in_every_store(tmp_path):
44
+ user, project = _world(tmp_path)
45
+ found = {p.relative_to(user / "overlays" / "rust").as_posix(): [q for q in qs]
46
+ for p, qs in _context.local_additions(_scope(user, project))}
47
+ assert sorted(found) == ["flows/deep/STEP.md", "kb/RUST.md"], (
48
+ "only .md files in a subdirectory, never skills/ (published copies) or the root"
49
+ )
50
+ assert found["kb/RUST.md"] == [user / "kb" / "RUST.md", project / ".agents" / "kb" / "RUST.md"]
51
+ user_only = {p.name for p, _ in _context.local_additions(_scope(user, project, True))}
52
+ assert user_only == {"RUST.md", "STEP.md"}
53
+
54
+
55
+ def test_context_lists_them_and_json_carries_them(tmp_path):
56
+ user, project = _world(tmp_path)
57
+ codex = _agents.CodexAgent()
58
+ text = _context.assemble_context(codex, _scope(user, project))
59
+ assert "## Local additions to overlay files" in text
60
+ assert str(user / "kb" / "RUST.md") in text and str(project / ".agents" / "kb" / "RUST.md") in text
61
+ assert "my own note" not in text, "listed, not inlined"
62
+ data = _context.assemble_context_data(codex, _scope(user, project))
63
+ assert "Local additions" not in data["context"]
64
+ entries = {e["overlay_file"]: e["local"] for e in data["local_additions"]}
65
+ assert entries[str(user / "overlays" / "rust" / "kb" / "RUST.md")] == [
66
+ str(user / "kb" / "RUST.md"), str(project / ".agents" / "kb" / "RUST.md"),
67
+ ]
68
+ json.dumps(data)
69
+
70
+
71
+ def test_no_section_without_additions(tmp_path):
72
+ user = tmp_path / "user"
73
+ _write(user / "overlays" / "rust" / "overlay.toml", 'name = "rust"\n')
74
+ _write(user / "overlays" / "rust" / "CONTEXT.md", "RUST-CONTEXT\n")
75
+ _write(user / "overlays" / "rust" / "kb" / "RUST.md", "upstream\n")
76
+ _write(user / "AGENTS.md", "# user\n")
77
+ text = _context.assemble_context(_agents.CodexAgent(), Scope.of(agents_dir=user, global_scope=True))
78
+ assert "RUST-CONTEXT" in text and "Local additions" not in text
79
+
80
+
81
+ def test_the_base_rule_is_in_the_user_block_only(tmp_path):
82
+ user_block = base_agents_text(BASE_ROOT, tmp_path)
83
+ assert "**Local additions to overlay files**" in user_block
84
+ assert "`dotagents context` lists the ones it finds" in user_block
85
+ project = (BASE_ROOT / BASE_PROJECT_TEMPLATE).read_text(encoding="utf-8")
86
+ assert "Local additions" not in project, "a project session reads the user block too"
File without changes
File without changes
File without changes
File without changes
File without changes