fix(agents): continue rule installation after target errors

This commit is contained in:
Ryan Yin
2026-09-05 13:34:13 +08:00
parent a3bbb70508
commit 26717b613b
3 changed files with 39 additions and 5 deletions
+8
View File
@@ -10,6 +10,7 @@ The primary workflow is to symlink files from here into each agent runtime/confi
- `AGENTS.md`: global baseline rules for coding agents.
- `evals/global-rules.md`: behavioral scenarios for validating changes to the global rules.
- `install-rules.py`: installs the baseline by creating symlinks in supported agent config dirs.
- `test_install_rules.py`: regression tests for file preservation and independent target handling.
- `install-cli.md`: curated CLI install/update command snippets.
- `install-skills.md`: curated `npx skills` command snippets.
@@ -38,6 +39,7 @@ Current targets:
Behavior:
- Each target is handled independently.
- Target errors are reported while remaining targets are attempted; any error yields a nonzero exit.
- Missing destination directories are skipped.
- Existing regular files are preserved as `.bak` backups (numbered when a backup already exists).
- Destination links are replaced atomically; a failed link creation leaves the destination intact.
@@ -49,6 +51,12 @@ the global rules source and is not installed by this script.
Auto-approval controls tool prompting. The global rules still define task authorization, safety, and
secret handling.
Run installer regression tests without changing installed rules:
```bash
python3 -B -m unittest discover -s agents -p test_install_rules.py
```
## About `install-cli.md` and `install-skills.md`
Use them as snippet libraries:
+14 -5
View File
@@ -47,12 +47,21 @@ def main() -> int:
opencode_dir = xdg_config_home / "opencode"
claude_dir = Path("~/.claude").expanduser()
agents_dir = Path("~/.agents").expanduser()
install_one(codex_dir, agents_file, "AGENTS.md")
install_one(opencode_dir, agents_file, "AGENTS.md")
install_one(claude_dir, agents_file, "CLAUDE.md")
install_one(agents_dir, agents_file, "AGENTS.md")
targets = (
(codex_dir, "AGENTS.md"),
(opencode_dir, "AGENTS.md"),
(claude_dir, "CLAUDE.md"),
(agents_dir, "AGENTS.md"),
)
failed = False
for target_dir, target_name in targets:
try:
install_one(target_dir, agents_file, target_name)
except OSError as error:
print(f"failed {target_dir / target_name}: {error}", file=sys.stderr)
failed = True
return 0
return 1 if failed else 0
if __name__ == "__main__":
+17
View File
@@ -1,4 +1,5 @@
import importlib.util
import io
import tempfile
import unittest
from pathlib import Path
@@ -13,6 +14,22 @@ spec.loader.exec_module(installer)
class InstallRulesTests(unittest.TestCase):
def test_target_failure_does_not_stop_remaining_targets(self):
with (
patch.object(
installer, "install_one", side_effect=[PermissionError("denied"), None, None, None]
) as install,
patch("sys.stderr", new_callable=io.StringIO) as stderr,
):
self.assertEqual(installer.main(), 1)
self.assertEqual(install.call_count, 4)
self.assertIn("denied", stderr.getvalue())
def test_successful_targets_return_success(self):
with patch.object(installer, "install_one") as install:
self.assertEqual(installer.main(), 0)
self.assertEqual(install.call_count, 4)
def test_existing_file_is_backed_up_without_overwriting_backup(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)