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.
Files changed (120) hide show
  1. adversarial_friends-0.1.3/LICENSE +21 -0
  2. adversarial_friends-0.1.3/PKG-INFO +388 -0
  3. adversarial_friends-0.1.3/README.md +365 -0
  4. adversarial_friends-0.1.3/VERSION +1 -0
  5. adversarial_friends-0.1.3/pyproject.toml +155 -0
  6. adversarial_friends-0.1.3/setup.cfg +4 -0
  7. adversarial_friends-0.1.3/src/adversarial_friends/__init__.py +34 -0
  8. adversarial_friends-0.1.3/src/adversarial_friends/__main__.py +8 -0
  9. adversarial_friends-0.1.3/src/adversarial_friends/adapters.py +277 -0
  10. adversarial_friends-0.1.3/src/adversarial_friends/assets/SKILL.md +215 -0
  11. adversarial_friends-0.1.3/src/adversarial_friends/assets/__init__.py +0 -0
  12. adversarial_friends-0.1.3/src/adversarial_friends/assets/adapters/agy.toml +46 -0
  13. adversarial_friends-0.1.3/src/adversarial_friends/assets/adapters/claude.toml +35 -0
  14. adversarial_friends-0.1.3/src/adversarial_friends/assets/adapters/codex.toml +52 -0
  15. adversarial_friends-0.1.3/src/adversarial_friends/assets/adapters/ollama.toml +14 -0
  16. adversarial_friends-0.1.3/src/adversarial_friends/assets/adapters/opencode.toml +69 -0
  17. adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/assumptions.md +21 -0
  18. adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/ops.md +19 -0
  19. adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/scope.md +19 -0
  20. adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/security.md +20 -0
  21. adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/spec-vs-reality.md +20 -0
  22. adversarial_friends-0.1.3/src/adversarial_friends/assets/lenses/testability.md +19 -0
  23. adversarial_friends-0.1.3/src/adversarial_friends/assets/references/ledger.md +112 -0
  24. adversarial_friends-0.1.3/src/adversarial_friends/assets/references/modes.md +352 -0
  25. adversarial_friends-0.1.3/src/adversarial_friends/assets/references/troubleshooting.md +153 -0
  26. adversarial_friends-0.1.3/src/adversarial_friends/ceilings.py +102 -0
  27. adversarial_friends-0.1.3/src/adversarial_friends/childenv.py +93 -0
  28. adversarial_friends-0.1.3/src/adversarial_friends/claimschema.py +158 -0
  29. adversarial_friends-0.1.3/src/adversarial_friends/cli.py +80 -0
  30. adversarial_friends-0.1.3/src/adversarial_friends/cliargs.py +245 -0
  31. adversarial_friends-0.1.3/src/adversarial_friends/commands/__init__.py +0 -0
  32. adversarial_friends-0.1.3/src/adversarial_friends/commands/confinement.py +84 -0
  33. adversarial_friends-0.1.3/src/adversarial_friends/commands/critique.py +244 -0
  34. adversarial_friends-0.1.3/src/adversarial_friends/commands/crossexam.py +485 -0
  35. adversarial_friends-0.1.3/src/adversarial_friends/commands/doctor.py +130 -0
  36. adversarial_friends-0.1.3/src/adversarial_friends/commands/environment.py +101 -0
  37. adversarial_friends-0.1.3/src/adversarial_friends/commands/exits.py +67 -0
  38. adversarial_friends-0.1.3/src/adversarial_friends/commands/friends.py +230 -0
  39. adversarial_friends-0.1.3/src/adversarial_friends/commands/init.py +99 -0
  40. adversarial_friends-0.1.3/src/adversarial_friends/commands/resolve.py +140 -0
  41. adversarial_friends-0.1.3/src/adversarial_friends/commands/resume.py +162 -0
  42. adversarial_friends-0.1.3/src/adversarial_friends/commands/run.py +468 -0
  43. adversarial_friends-0.1.3/src/adversarial_friends/commands/runmeta.py +241 -0
  44. adversarial_friends-0.1.3/src/adversarial_friends/contracts.py +51 -0
  45. adversarial_friends-0.1.3/src/adversarial_friends/dispatch.py +295 -0
  46. adversarial_friends-0.1.3/src/adversarial_friends/errors.py +22 -0
  47. adversarial_friends-0.1.3/src/adversarial_friends/failures.py +168 -0
  48. adversarial_friends-0.1.3/src/adversarial_friends/http_transport.py +223 -0
  49. adversarial_friends-0.1.3/src/adversarial_friends/ids.py +41 -0
  50. adversarial_friends-0.1.3/src/adversarial_friends/isolation.py +161 -0
  51. adversarial_friends-0.1.3/src/adversarial_friends/judgeprompt.py +192 -0
  52. adversarial_friends-0.1.3/src/adversarial_friends/ledger.py +132 -0
  53. adversarial_friends-0.1.3/src/adversarial_friends/merge.py +160 -0
  54. adversarial_friends-0.1.3/src/adversarial_friends/normalize.py +463 -0
  55. adversarial_friends-0.1.3/src/adversarial_friends/orchestrator.py +324 -0
  56. adversarial_friends-0.1.3/src/adversarial_friends/paths.py +23 -0
  57. adversarial_friends-0.1.3/src/adversarial_friends/presets.py +80 -0
  58. adversarial_friends-0.1.3/src/adversarial_friends/prompt.py +133 -0
  59. adversarial_friends-0.1.3/src/adversarial_friends/report.py +333 -0
  60. adversarial_friends-0.1.3/src/adversarial_friends/resolutions.py +192 -0
  61. adversarial_friends-0.1.3/src/adversarial_friends/roster.py +175 -0
  62. adversarial_friends-0.1.3/src/adversarial_friends/rosterfile.py +132 -0
  63. adversarial_friends-0.1.3/src/adversarial_friends/rounds.py +243 -0
  64. adversarial_friends-0.1.3/src/adversarial_friends/runstore.py +105 -0
  65. adversarial_friends-0.1.3/src/adversarial_friends/sandbox.py +361 -0
  66. adversarial_friends-0.1.3/src/adversarial_friends/spawn.py +450 -0
  67. adversarial_friends-0.1.3/src/adversarial_friends/trust.py +142 -0
  68. adversarial_friends-0.1.3/src/adversarial_friends/verdicts.py +353 -0
  69. adversarial_friends-0.1.3/src/adversarial_friends/verdictschema.py +216 -0
  70. adversarial_friends-0.1.3/src/adversarial_friends.egg-info/PKG-INFO +388 -0
  71. adversarial_friends-0.1.3/src/adversarial_friends.egg-info/SOURCES.txt +118 -0
  72. adversarial_friends-0.1.3/src/adversarial_friends.egg-info/dependency_links.txt +1 -0
  73. adversarial_friends-0.1.3/src/adversarial_friends.egg-info/entry_points.txt +2 -0
  74. adversarial_friends-0.1.3/src/adversarial_friends.egg-info/top_level.txt +1 -0
  75. adversarial_friends-0.1.3/tests/test_abort_reentry.py +27 -0
  76. adversarial_friends-0.1.3/tests/test_adapters.py +305 -0
  77. adversarial_friends-0.1.3/tests/test_ceilings.py +60 -0
  78. adversarial_friends-0.1.3/tests/test_childenv.py +83 -0
  79. adversarial_friends-0.1.3/tests/test_claimschema.py +202 -0
  80. adversarial_friends-0.1.3/tests/test_cli_entry.py +41 -0
  81. adversarial_friends-0.1.3/tests/test_cliargs.py +87 -0
  82. adversarial_friends-0.1.3/tests/test_confinement_record.py +100 -0
  83. adversarial_friends-0.1.3/tests/test_discard_consecutive.py +74 -0
  84. adversarial_friends-0.1.3/tests/test_docs.py +245 -0
  85. adversarial_friends-0.1.3/tests/test_envelope_fixtures.py +259 -0
  86. adversarial_friends-0.1.3/tests/test_errors.py +19 -0
  87. adversarial_friends-0.1.3/tests/test_failures.py +197 -0
  88. adversarial_friends-0.1.3/tests/test_friend_key.py +29 -0
  89. adversarial_friends-0.1.3/tests/test_http_transport.py +243 -0
  90. adversarial_friends-0.1.3/tests/test_ids.py +60 -0
  91. adversarial_friends-0.1.3/tests/test_isolation.py +380 -0
  92. adversarial_friends-0.1.3/tests/test_judgeprompt.py +231 -0
  93. adversarial_friends-0.1.3/tests/test_ledger.py +134 -0
  94. adversarial_friends-0.1.3/tests/test_merge.py +198 -0
  95. adversarial_friends-0.1.3/tests/test_normalize.py +452 -0
  96. adversarial_friends-0.1.3/tests/test_orchestrator.py +214 -0
  97. adversarial_friends-0.1.3/tests/test_presets.py +81 -0
  98. adversarial_friends-0.1.3/tests/test_report.py +485 -0
  99. adversarial_friends-0.1.3/tests/test_resolutions.py +279 -0
  100. adversarial_friends-0.1.3/tests/test_roster.py +343 -0
  101. adversarial_friends-0.1.3/tests/test_rosterfile.py +121 -0
  102. adversarial_friends-0.1.3/tests/test_run_end_to_end_basics.py +391 -0
  103. adversarial_friends-0.1.3/tests/test_run_end_to_end_crossexam.py +407 -0
  104. adversarial_friends-0.1.3/tests/test_run_end_to_end_flags.py +238 -0
  105. adversarial_friends-0.1.3/tests/test_run_end_to_end_gate.py +290 -0
  106. adversarial_friends-0.1.3/tests/test_run_end_to_end_isolation.py +321 -0
  107. adversarial_friends-0.1.3/tests/test_run_end_to_end_lenses.py +437 -0
  108. adversarial_friends-0.1.3/tests/test_run_end_to_end_loop.py +297 -0
  109. adversarial_friends-0.1.3/tests/test_run_end_to_end_orchestrator.py +349 -0
  110. adversarial_friends-0.1.3/tests/test_run_end_to_end_roster.py +206 -0
  111. adversarial_friends-0.1.3/tests/test_run_end_to_end_signals.py +297 -0
  112. adversarial_friends-0.1.3/tests/test_runstore.py +88 -0
  113. adversarial_friends-0.1.3/tests/test_sandbox.py +428 -0
  114. adversarial_friends-0.1.3/tests/test_sandbox_findings.py +119 -0
  115. adversarial_friends-0.1.3/tests/test_skill_layer.py +74 -0
  116. adversarial_friends-0.1.3/tests/test_spawn.py +365 -0
  117. adversarial_friends-0.1.3/tests/test_trust.py +161 -0
  118. adversarial_friends-0.1.3/tests/test_verdicts.py +256 -0
  119. adversarial_friends-0.1.3/tests/test_verdicts_lifecycle.py +313 -0
  120. 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
+ ![adversarial-friends](https://raw.githubusercontent.com/livingstaccato/adversarial-friends/main/docs/images/brand/adversarial-friends-banner.png)
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
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](pyproject.toml)
33
+ [![Dependencies](https://img.shields.io/badge/runtime%20deps-none-brightgreen)](pyproject.toml)
34
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
35
+ [![Tests](https://img.shields.io/badge/tests-365-brightgreen)](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
+ ![module architecture](https://raw.githubusercontent.com/livingstaccato/adversarial-friends/main/docs/architecture/components.png)
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
+ ![run flow](https://raw.githubusercontent.com/livingstaccato/adversarial-friends/main/docs/architecture/run-flow.png)
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
+ ![claim lifecycle](https://raw.githubusercontent.com/livingstaccato/adversarial-friends/main/docs/architecture/claim-lifecycle.png)
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
+ ![crossexam states](https://raw.githubusercontent.com/livingstaccato/adversarial-friends/main/docs/architecture/crossexam-states.png)
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
+ ![gate workflow](https://raw.githubusercontent.com/livingstaccato/adversarial-friends/main/docs/architecture/gate-workflow.png)
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).