adversarial-friends 0.1.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.
- adversarial_friends-0.1.3/LICENSE +21 -0
- adversarial_friends-0.1.3/PKG-INFO +388 -0
- adversarial_friends-0.1.3/README.md +365 -0
- adversarial_friends-0.1.3/VERSION +1 -0
- adversarial_friends-0.1.3/pyproject.toml +155 -0
- adversarial_friends-0.1.3/setup.cfg +4 -0
- adversarial_friends-0.1.3/src/adversarial_friends/__init__.py +34 -0
- adversarial_friends-0.1.3/src/adversarial_friends/__main__.py +8 -0
- adversarial_friends-0.1.3/src/adversarial_friends/adapters.py +277 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/SKILL.md +215 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/__init__.py +0 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/adapters/agy.toml +46 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/adapters/claude.toml +35 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/adapters/codex.toml +52 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/adapters/ollama.toml +14 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/adapters/opencode.toml +69 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/assumptions.md +21 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/ops.md +19 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/scope.md +19 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/security.md +20 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/spec-vs-reality.md +20 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/testability.md +19 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/references/ledger.md +112 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/references/modes.md +352 -0
- adversarial_friends-0.1.3/src/adversarial_friends/assets/references/troubleshooting.md +153 -0
- adversarial_friends-0.1.3/src/adversarial_friends/ceilings.py +102 -0
- adversarial_friends-0.1.3/src/adversarial_friends/childenv.py +93 -0
- adversarial_friends-0.1.3/src/adversarial_friends/claimschema.py +158 -0
- adversarial_friends-0.1.3/src/adversarial_friends/cli.py +80 -0
- adversarial_friends-0.1.3/src/adversarial_friends/cliargs.py +245 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/__init__.py +0 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/confinement.py +84 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/critique.py +244 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/crossexam.py +485 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/doctor.py +130 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/environment.py +101 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/exits.py +67 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/friends.py +230 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/init.py +99 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/resolve.py +140 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/resume.py +162 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/run.py +468 -0
- adversarial_friends-0.1.3/src/adversarial_friends/commands/runmeta.py +241 -0
- adversarial_friends-0.1.3/src/adversarial_friends/contracts.py +51 -0
- adversarial_friends-0.1.3/src/adversarial_friends/dispatch.py +295 -0
- adversarial_friends-0.1.3/src/adversarial_friends/errors.py +22 -0
- adversarial_friends-0.1.3/src/adversarial_friends/failures.py +168 -0
- adversarial_friends-0.1.3/src/adversarial_friends/http_transport.py +223 -0
- adversarial_friends-0.1.3/src/adversarial_friends/ids.py +41 -0
- adversarial_friends-0.1.3/src/adversarial_friends/isolation.py +161 -0
- adversarial_friends-0.1.3/src/adversarial_friends/judgeprompt.py +192 -0
- adversarial_friends-0.1.3/src/adversarial_friends/ledger.py +132 -0
- adversarial_friends-0.1.3/src/adversarial_friends/merge.py +160 -0
- adversarial_friends-0.1.3/src/adversarial_friends/normalize.py +463 -0
- adversarial_friends-0.1.3/src/adversarial_friends/orchestrator.py +324 -0
- adversarial_friends-0.1.3/src/adversarial_friends/paths.py +23 -0
- adversarial_friends-0.1.3/src/adversarial_friends/presets.py +80 -0
- adversarial_friends-0.1.3/src/adversarial_friends/prompt.py +133 -0
- adversarial_friends-0.1.3/src/adversarial_friends/report.py +333 -0
- adversarial_friends-0.1.3/src/adversarial_friends/resolutions.py +192 -0
- adversarial_friends-0.1.3/src/adversarial_friends/roster.py +175 -0
- adversarial_friends-0.1.3/src/adversarial_friends/rosterfile.py +132 -0
- adversarial_friends-0.1.3/src/adversarial_friends/rounds.py +243 -0
- adversarial_friends-0.1.3/src/adversarial_friends/runstore.py +105 -0
- adversarial_friends-0.1.3/src/adversarial_friends/sandbox.py +361 -0
- adversarial_friends-0.1.3/src/adversarial_friends/spawn.py +450 -0
- adversarial_friends-0.1.3/src/adversarial_friends/trust.py +142 -0
- adversarial_friends-0.1.3/src/adversarial_friends/verdicts.py +353 -0
- adversarial_friends-0.1.3/src/adversarial_friends/verdictschema.py +216 -0
- adversarial_friends-0.1.3/src/adversarial_friends.egg-info/PKG-INFO +388 -0
- adversarial_friends-0.1.3/src/adversarial_friends.egg-info/SOURCES.txt +118 -0
- adversarial_friends-0.1.3/src/adversarial_friends.egg-info/dependency_links.txt +1 -0
- adversarial_friends-0.1.3/src/adversarial_friends.egg-info/entry_points.txt +2 -0
- adversarial_friends-0.1.3/src/adversarial_friends.egg-info/top_level.txt +1 -0
- adversarial_friends-0.1.3/tests/test_abort_reentry.py +27 -0
- adversarial_friends-0.1.3/tests/test_adapters.py +305 -0
- adversarial_friends-0.1.3/tests/test_ceilings.py +60 -0
- adversarial_friends-0.1.3/tests/test_childenv.py +83 -0
- adversarial_friends-0.1.3/tests/test_claimschema.py +202 -0
- adversarial_friends-0.1.3/tests/test_cli_entry.py +41 -0
- adversarial_friends-0.1.3/tests/test_cliargs.py +87 -0
- adversarial_friends-0.1.3/tests/test_confinement_record.py +100 -0
- adversarial_friends-0.1.3/tests/test_discard_consecutive.py +74 -0
- adversarial_friends-0.1.3/tests/test_docs.py +245 -0
- adversarial_friends-0.1.3/tests/test_envelope_fixtures.py +259 -0
- adversarial_friends-0.1.3/tests/test_errors.py +19 -0
- adversarial_friends-0.1.3/tests/test_failures.py +197 -0
- adversarial_friends-0.1.3/tests/test_friend_key.py +29 -0
- adversarial_friends-0.1.3/tests/test_http_transport.py +243 -0
- adversarial_friends-0.1.3/tests/test_ids.py +60 -0
- adversarial_friends-0.1.3/tests/test_isolation.py +380 -0
- adversarial_friends-0.1.3/tests/test_judgeprompt.py +231 -0
- adversarial_friends-0.1.3/tests/test_ledger.py +134 -0
- adversarial_friends-0.1.3/tests/test_merge.py +198 -0
- adversarial_friends-0.1.3/tests/test_normalize.py +452 -0
- adversarial_friends-0.1.3/tests/test_orchestrator.py +214 -0
- adversarial_friends-0.1.3/tests/test_presets.py +81 -0
- adversarial_friends-0.1.3/tests/test_report.py +485 -0
- adversarial_friends-0.1.3/tests/test_resolutions.py +279 -0
- adversarial_friends-0.1.3/tests/test_roster.py +343 -0
- adversarial_friends-0.1.3/tests/test_rosterfile.py +121 -0
- adversarial_friends-0.1.3/tests/test_run_end_to_end_basics.py +391 -0
- adversarial_friends-0.1.3/tests/test_run_end_to_end_crossexam.py +407 -0
- adversarial_friends-0.1.3/tests/test_run_end_to_end_flags.py +238 -0
- adversarial_friends-0.1.3/tests/test_run_end_to_end_gate.py +290 -0
- adversarial_friends-0.1.3/tests/test_run_end_to_end_isolation.py +321 -0
- adversarial_friends-0.1.3/tests/test_run_end_to_end_lenses.py +437 -0
- adversarial_friends-0.1.3/tests/test_run_end_to_end_loop.py +297 -0
- adversarial_friends-0.1.3/tests/test_run_end_to_end_orchestrator.py +349 -0
- adversarial_friends-0.1.3/tests/test_run_end_to_end_roster.py +206 -0
- adversarial_friends-0.1.3/tests/test_run_end_to_end_signals.py +297 -0
- adversarial_friends-0.1.3/tests/test_runstore.py +88 -0
- adversarial_friends-0.1.3/tests/test_sandbox.py +428 -0
- adversarial_friends-0.1.3/tests/test_sandbox_findings.py +119 -0
- adversarial_friends-0.1.3/tests/test_skill_layer.py +74 -0
- adversarial_friends-0.1.3/tests/test_spawn.py +365 -0
- adversarial_friends-0.1.3/tests/test_trust.py +161 -0
- adversarial_friends-0.1.3/tests/test_verdicts.py +256 -0
- adversarial_friends-0.1.3/tests/test_verdicts_lifecycle.py +313 -0
- adversarial_friends-0.1.3/tests/test_verdictschema.py +247 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: adversarial-friends
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Cross-examine specs, plans, and reviews using other agent CLIs as adversarial reviewers
|
|
5
|
+
Author: Tim
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/livingstaccato/adversarial-friends
|
|
8
|
+
Project-URL: Issues, https://github.com/livingstaccato/adversarial-friends/issues
|
|
9
|
+
Keywords: claude,codex,agent,code-review,cli
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
# Adversarial Friends
|
|
27
|
+
|
|
28
|
+
> Hand your spec, plan, or review to **other** agent CLIs — `claude`, `codex`,
|
|
29
|
+
> `agy`, `opencode` — as independent adversarial reviewers, then merge their
|
|
30
|
+
> critiques into one ranked findings report.
|
|
31
|
+
|
|
32
|
+
[](pyproject.toml)
|
|
33
|
+
[](pyproject.toml)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
[](tests/)
|
|
36
|
+
|
|
37
|
+
It automates a workflow you may already do by hand: run a review, paste the
|
|
38
|
+
findings into a different model, ask whether they hold up, carry the argument
|
|
39
|
+
back. Doing that manually means holding a claim ledger in your head. This
|
|
40
|
+
keeps the ledger on disk.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 📋 Contents
|
|
45
|
+
|
|
46
|
+
- [Why more than one model](#-why-more-than-one-model)
|
|
47
|
+
- [Install](#-install)
|
|
48
|
+
- [Quickstart](#-quickstart)
|
|
49
|
+
- [How it works](#-how-it-works)
|
|
50
|
+
- [Lenses](#-lenses)
|
|
51
|
+
- [What you get back](#-what-you-get-back)
|
|
52
|
+
- [What's implemented](#-whats-implemented)
|
|
53
|
+
- [Documentation](#-documentation)
|
|
54
|
+
- [Development](#-development)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 🎯 Why more than one model
|
|
59
|
+
|
|
60
|
+
A single reviewer produces confident prose. Several reviewers produce claims
|
|
61
|
+
that **can be compared** — and the disagreements are where the real problems
|
|
62
|
+
are.
|
|
63
|
+
|
|
64
|
+
This tool's own design spec was built exactly this way:
|
|
65
|
+
|
|
66
|
+
| Reviewer | Result |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `codex` | 17 findings |
|
|
69
|
+
| `claude` | 15 findings, plus one marked `unproven` — *"lens leaks attribution"* |
|
|
70
|
+
| `agy` | independently reproduced two of `claude`'s findings, **and** caught a shared-worktree race neither of the other two flagged |
|
|
71
|
+
|
|
72
|
+
That `unproven` claim was later confirmed and fixed. No single reviewer's pass
|
|
73
|
+
would have surfaced all of it — see the [revision history in the design
|
|
74
|
+
spec](docs/superpowers/specs/2026-08-22-adversarial-friends-design.md#19-revision-history)
|
|
75
|
+
for the full account.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 📦 Install
|
|
80
|
+
|
|
81
|
+
Requires **Python 3.11+** and at least one agent CLI besides the one you're
|
|
82
|
+
running under. The runner itself is **stdlib-only** — zero runtime
|
|
83
|
+
dependencies.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
uv tool install git+https://github.com/livingstaccato/adversarial-friends
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
<details>
|
|
90
|
+
<summary>Other install methods</summary>
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# From a local checkout
|
|
94
|
+
git clone https://github.com/livingstaccato/adversarial-friends
|
|
95
|
+
cd adversarial-friends
|
|
96
|
+
uv tool install .
|
|
97
|
+
|
|
98
|
+
# Without installing at all
|
|
99
|
+
python -m adversarial_friends doctor
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
</details>
|
|
103
|
+
|
|
104
|
+
Then confirm what's actually available:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
afriend doctor
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
agy found schema=True readonly=True effort=native /Users/you/.local/bin/agy
|
|
112
|
+
claude found schema=True readonly=True effort=native /Users/you/.local/bin/claude
|
|
113
|
+
codex found schema=True readonly=True effort=native /opt/homebrew/bin/codex
|
|
114
|
+
opencode found schema=False readonly=False effort=unverified /Users/you/.opencode/bin/opencode
|
|
115
|
+
ollama found schema=False readonly=False effort=none http://127.0.0.1:11434/api/generate
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
For `ollama`, `found` means a reachable endpoint rather than a binary on
|
|
119
|
+
`PATH`; it shows `unreachable` when no server is listening.
|
|
120
|
+
|
|
121
|
+
`doctor` reports what each friend can genuinely **enforce** — schema
|
|
122
|
+
validation, a real read-only mode, a verifiable effort level — rather than
|
|
123
|
+
what it claims to support. `opencode` showing `readonly=False` is not a bug;
|
|
124
|
+
it has no read-only mode, so the tool says so instead of pretending.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 🚀 Quickstart
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
afriend run docs/my-design.md --mode report
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
It prints one thing — the run directory. Read `report.md` inside it:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
cat "$(afriend run docs/my-design.md --mode report)/report.md"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Pick your reviewers and lenses explicitly:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
afriend run spec.md --friend codex:security --friend claude:ops
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
A third slot picks the model — required for `ollama`, which has no default:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
afriend run spec.md --friend ollama:security:qwen3:0.6b
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
> ⚠️ `--friend` **replaces** discovery rather than adding to it. One
|
|
153
|
+
> `--friend` flag means a one-friend run — which cannot cross-examine
|
|
154
|
+
> anything. The tool records that as a downgrade in `run.json` and
|
|
155
|
+
> `report.md` rather than letting it look like a full review.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## ⚙️ How it works
|
|
160
|
+
|
|
161
|
+

|
|
162
|
+
|
|
163
|
+
Every friend gets its **own** prompt built from its **own** lens, runs in its
|
|
164
|
+
**own** isolated directory, in its **own** process group:
|
|
165
|
+
|
|
166
|
+
| Stage | What happens |
|
|
167
|
+
|---|---|
|
|
168
|
+
| 🔍 **Resolve** | Discover agent CLIs on `PATH`, round-robin a lens to each |
|
|
169
|
+
| ✍️ **Prompt** | Build a per-friend prompt: shared contract header + that friend's lens prose + the artifact |
|
|
170
|
+
| 🔒 **Isolate** | Friends with a real read-only mode get a private `git worktree` from one shared snapshot. A CLI with no read-only mode is confined by the OS instead (`sandbox-exec` / `bwrap`) — or refused |
|
|
171
|
+
| ⚡ **Dispatch** | Parallel, one thread per friend, each in its own process group with a kill deadline of `--timeout + 60s` |
|
|
172
|
+
| 🧩 **Normalize** | Unwrap the CLI's own JSON envelope, strip ANSI, recover the payload, validate against the claim schema |
|
|
173
|
+
| 🔗 **Merge** | Exact-merge identical claims into aliases — accumulating origins so corroboration survives |
|
|
174
|
+
| 📄 **Report** | Rank findings, render `report.md`, write the append-only ledger |
|
|
175
|
+
|
|
176
|
+
The snapshot includes **untracked** files (`git stash create` omits them), and
|
|
177
|
+
the working tree is never touched — a friend reviewing your repo can't see a
|
|
178
|
+
half-staged index or scribble on your checkout.
|
|
179
|
+
|
|
180
|
+
<details>
|
|
181
|
+
<summary>Full run flow, step by step</summary>
|
|
182
|
+
|
|
183
|
+

|
|
184
|
+
|
|
185
|
+
</details>
|
|
186
|
+
|
|
187
|
+
### Corroboration is the point
|
|
188
|
+
|
|
189
|
+
Two friends independently reaching the same conclusion is the strongest signal
|
|
190
|
+
this tool produces, so deduplication is built to never destroy it:
|
|
191
|
+
|
|
192
|
+

|
|
193
|
+
|
|
194
|
+
Dedup is **deliberately** exact-match — whitespace and case only. Two friends
|
|
195
|
+
describing one defect in different words produce two claims, which costs a
|
|
196
|
+
round. Guessing at equivalence would corrupt the ledger, which is worse.
|
|
197
|
+
|
|
198
|
+
### Claim states in cross-examination
|
|
199
|
+
|
|
200
|
+
Every claim `--mode crossexam` produces ends in one of eight states. Two of
|
|
201
|
+
them — `deadlocked` and `settled-upheld` — deliberately need a human, and the
|
|
202
|
+
report says so rather than quietly resolving them.
|
|
203
|
+
|
|
204
|
+

|
|
205
|
+
|
|
206
|
+
### The gate loop
|
|
207
|
+
|
|
208
|
+
`--mode gate` is the one that fails a build, and clearing it is a
|
|
209
|
+
back-and-forth rather than a single command:
|
|
210
|
+
|
|
211
|
+

|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 🔬 Lenses
|
|
216
|
+
|
|
217
|
+
A lens is prose, not a config string. Its text is injected into that friend's
|
|
218
|
+
prompt, so it shapes what the friend actually looks for.
|
|
219
|
+
|
|
220
|
+
| Lens | Default scope | Requires a failure scenario |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `assumptions` | doc | ✅ |
|
|
223
|
+
| `security` | repo | ✅ |
|
|
224
|
+
| `ops` | repo | ✅ |
|
|
225
|
+
| `testability` | repo | ✅ |
|
|
226
|
+
| `spec-vs-reality` | repo | ✅ |
|
|
227
|
+
| `scope` | doc | ❌ — advisory only |
|
|
228
|
+
|
|
229
|
+
`scope` is the one lens that doesn't demand a concrete failure scenario;
|
|
230
|
+
"this is more than you need" is a legitimate finding without one. Claims from
|
|
231
|
+
it are marked *(advisory)* in the report so they never carry the same weight
|
|
232
|
+
as a reproducible defect.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 📂 What you get back
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
<run-dir>/
|
|
240
|
+
├── report.md ← ranked findings, corroboration, downgrades
|
|
241
|
+
├── run.json ← machine-readable: friends, statuses, downgrades
|
|
242
|
+
├── claims.jsonl ← append-only ledger: claims, aliases
|
|
243
|
+
├── artifact/ ← frozen copy of what was reviewed, hashed
|
|
244
|
+
└── round-1/
|
|
245
|
+
├── <friend>.prompt ← exactly what this friend was asked
|
|
246
|
+
├── <friend>.raw ← its unmodified stdout
|
|
247
|
+
├── <friend>.err ← its stderr (always present, even when empty)
|
|
248
|
+
└── <friend>.meta ← argv, exit code, duration, timeout, orphan status
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Runs land under `${XDG_STATE_HOME:-~/.local/state}/adversarial-friends/runs/`,
|
|
252
|
+
or wherever `--out` points.
|
|
253
|
+
|
|
254
|
+
Everything a friend was asked and everything it said is on disk. When a run
|
|
255
|
+
comes back thin, that's what you read — not a guess.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## ✅ What's implemented
|
|
260
|
+
|
|
261
|
+
**All four modes run.**
|
|
262
|
+
|
|
263
|
+
| Mode | What it does |
|
|
264
|
+
|---|---|
|
|
265
|
+
| `report` | One round. Every friend critiques in parallel; claims merge into one ranked report. |
|
|
266
|
+
| `crossexam` | Then friends judge the claims they did not write, blind, until each settles or deadlocks. |
|
|
267
|
+
| `gate` | Then every non-advisory claim that did not clear needs an explicit resolution — this is the one that fails a build. |
|
|
268
|
+
| `loop` | Repeats until two consecutive rounds surface nothing new. |
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
afriend run docs/design.md --mode crossexam
|
|
272
|
+
afriend run docs/design.md --mode gate # exit 1 while anything blocks
|
|
273
|
+
afriend resolve <run-id> --claim c-0001@1 \
|
|
274
|
+
--disposition fixed --evidence src/auth.py:38
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Disagreement is the output rather than a problem: two judges who still
|
|
278
|
+
disagree at `--max-rounds` leave the claim `deadlocked`, and the report
|
|
279
|
+
quotes both sides verbatim instead of resolving it by majority.
|
|
280
|
+
|
|
281
|
+
A resolution is an **attestation**, and the tool says so. It cannot know a
|
|
282
|
+
defect is gone — only whether the location you named actually changed since
|
|
283
|
+
the run started. A fix that landed outside the reviewed artifact is fine; a
|
|
284
|
+
location it cannot reconstruct is recorded as `unverifiable` rather than
|
|
285
|
+
waved through. The one thing it refuses is `--disposition fixed` naming a
|
|
286
|
+
location that did not change.
|
|
287
|
+
|
|
288
|
+
Deduplication is judgment the runner declines to fake. `--merge exact`
|
|
289
|
+
(the default) merges only identical claims and always finishes unaided;
|
|
290
|
+
`--merge orchestrator` stops with exit `10`, writes the claims to
|
|
291
|
+
`REQUEST.json`, and waits for you to say which are duplicates:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
afriend run docs/design.md --merge orchestrator # exit 10, writes REQUEST.json
|
|
295
|
+
# ...fill in the merges, save as RESPONSE.json...
|
|
296
|
+
afriend run --resume <run-id> # round 1 is not re-run
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Tired of `--friend` flags? `afriend init` writes a roster from what is
|
|
300
|
+
actually installed, and `~/.config/adversarial-friends/roster.toml` is picked
|
|
301
|
+
up automatically. A repo-local roster never is — a cloned repo does not get
|
|
302
|
+
to choose who reviews it (§13).
|
|
303
|
+
|
|
304
|
+
The same halt serves unparseable output (§14.2): repair is a pure
|
|
305
|
+
transformation with no model call, so when it fails the runner asks you to
|
|
306
|
+
read the raw text rather than discarding whatever the friend found.
|
|
307
|
+
|
|
308
|
+
**There is no `--max-spend-usd`.** A dollar cap needs per-CLI cost reporting
|
|
309
|
+
nobody has captured, and a flag that silently never fires is worse than none
|
|
310
|
+
— you would set it and believe you were protected. Use `--max-calls`, which
|
|
311
|
+
is derived from your roster and actually enforced.
|
|
312
|
+
|
|
313
|
+
| Friend | Status |
|
|
314
|
+
|---|---|
|
|
315
|
+
| `claude` | ✅ ships |
|
|
316
|
+
| `codex` | ✅ ships |
|
|
317
|
+
| `agy` | ✅ ships |
|
|
318
|
+
| `opencode` | ✅ ships — no read-only mode, reported honestly |
|
|
319
|
+
| `ollama` | ✅ ships — local models over HTTP, no schema/read-only to enforce; needs an explicit model |
|
|
320
|
+
|
|
321
|
+
There is no `gemini` adapter: the `gemini` CLI returns an ineligible-tier
|
|
322
|
+
error on the individual free tier, and Google's own supported path from there
|
|
323
|
+
is Antigravity — which is `agy`.
|
|
324
|
+
|
|
325
|
+
### Exit codes
|
|
326
|
+
|
|
327
|
+
| Code | Meaning |
|
|
328
|
+
|---|---|
|
|
329
|
+
| `0` | at least one friend produced a usable critique |
|
|
330
|
+
| `1` | ran, but every dispatched friend failed |
|
|
331
|
+
| `2` | usage error — bad flag, unknown CLI, unimplemented mode |
|
|
332
|
+
| `3` | no usable friends found at all |
|
|
333
|
+
| `128+N` | aborted by signal N — isolation torn down, friends killed |
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 📚 Documentation
|
|
338
|
+
|
|
339
|
+
| Where | What |
|
|
340
|
+
|---|---|
|
|
341
|
+
| [docs/](docs/README.md) | Documentation index |
|
|
342
|
+
| [SKILL.md](src/adversarial_friends/assets/SKILL.md) | The skill itself — when it fires, how to read its output |
|
|
343
|
+
| [modes.md](src/adversarial_friends/assets/references/modes.md) | `report`, `crossexam`, `gate`, `loop` — and which are real |
|
|
344
|
+
| [ledger.md](src/adversarial_friends/assets/references/ledger.md) | Claim, verdict, alias, and resolution records |
|
|
345
|
+
| [troubleshooting.md](src/adversarial_friends/assets/references/troubleshooting.md) | Verified CLI traps, empty reports, timeouts |
|
|
346
|
+
| [architecture/](docs/architecture/README.md) | Diagrams and their sources |
|
|
347
|
+
| [design spec](docs/superpowers/specs/2026-08-22-adversarial-friends-design.md) | The full design, including the adversarial review that produced it |
|
|
348
|
+
|
|
349
|
+
### Using it as a skill or plugin
|
|
350
|
+
|
|
351
|
+
The skill payload ships **inside the wheel** as package data, and is mirrored
|
|
352
|
+
under [`plugins/`](plugins/) for loaders that can't install a Python package:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Claude Code
|
|
356
|
+
/plugin marketplace add /path/to/adversarial-friends/plugins
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
The skill invokes `afriend`, so the package must be installed for it to work —
|
|
360
|
+
`afriend doctor` is the check.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## 🛠 Development
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
make install # uv sync
|
|
368
|
+
make test # pytest — 365 tests
|
|
369
|
+
make quality # lint + type-check + every sync gate + tests
|
|
370
|
+
make diagrams # re-render docs/architecture/*.puml
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`make quality` runs exactly what CI runs. Two gates catch drift that is
|
|
374
|
+
otherwise silent:
|
|
375
|
+
|
|
376
|
+
- **`plugin-sync`** — `src/adversarial_friends/assets/` is canonical; the
|
|
377
|
+
`plugins/` tree is a byte-identical mirror. Edit assets, then
|
|
378
|
+
`make plugin-sync-copy`.
|
|
379
|
+
- **`version-sync`** — `VERSION` must match the `version` field in every
|
|
380
|
+
plugin manifest.
|
|
381
|
+
|
|
382
|
+
See [AGENTS.md](AGENTS.md) for repository layout and conventions.
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## 📄 License
|
|
387
|
+
|
|
388
|
+
MIT — see [LICENSE](LICENSE).
|