@freibergergarcia/phone-a-friend 2.3.1 → 2.6.3
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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +35 -2
- package/commands/curiosity-engine.md +56 -46
- package/commands/phone-a-friend.md +31 -11
- package/commands/phone-a-team.md +171 -32
- package/dist/index.js +1282 -616
- package/package.json +1 -1
- package/skills/curiosity-engine/COMMAND.opencode.md +3 -0
- package/skills/curiosity-engine/SKILL.md +56 -46
- package/skills/phone-a-friend/COMMAND.opencode.md +10 -1
- package/skills/phone-a-friend/SKILL.md +31 -11
package/README.md
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
[](https://github.com/freibergergarcia/phone-a-friend/actions/workflows/ci.yml)
|
|
12
12
|
[](LICENSE)
|
|
13
13
|

|
|
14
|
-
[](https://freibergergarcia.github.io/phone-a-friend/)
|
|
15
14
|
|
|
16
15
|
</div>
|
|
17
16
|
|
|
@@ -136,7 +135,7 @@ phone-a-friend --to codex --prompt "List files that need refactoring" \
|
|
|
136
135
|
--schema '{"type":"object","properties":{"files":{"type":"array","items":{"type":"string"}}},"required":["files"],"additionalProperties":false}'
|
|
137
136
|
```
|
|
138
137
|
|
|
139
|
-
Claude and
|
|
138
|
+
Claude, Codex, and Ollama enforce the schema through their native structured-output surfaces. Gemini and OpenCode CLI use prompt injection (best-effort), with PaF validating built-in verdict envelopes before returning them.
|
|
140
139
|
|
|
141
140
|
### Sessions
|
|
142
141
|
|
|
@@ -201,6 +200,40 @@ phone-a-friend config edit # Open in $EDITOR
|
|
|
201
200
|
|
|
202
201
|
`doctor` reports CLI backends, local backends (Ollama), host integration status (Claude / OpenCode plugin install state), and a summary count. The OpenCode CLI is treated as optional: if you only use Claude Code and don't have OpenCode installed, doctor will not flag that as a degraded state.
|
|
203
202
|
|
|
203
|
+
### Update notifications
|
|
204
|
+
|
|
205
|
+
phone-a-friend checks the npm registry for newer stable releases at most once
|
|
206
|
+
every 24 hours and prints a one-time stderr banner the next time it runs in an
|
|
207
|
+
interactive terminal. The current invocation is never slowed down: the registry
|
|
208
|
+
fetch happens in the background, with results applied on the next run.
|
|
209
|
+
|
|
210
|
+
Sample banner:
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
↑ phone-a-friend X.Y.Z available (current: A.B.C)
|
|
214
|
+
Run: npm install -g @freibergergarcia/phone-a-friend@latest
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The banner is suppressed automatically when:
|
|
218
|
+
- stdout or stderr is not a TTY (piped or redirected output)
|
|
219
|
+
- `CI` is set, or `TERM=dumb`
|
|
220
|
+
- the command uses `--quiet`, `--schema`, `--verdict-json`, or any subcommand-level `--json` flag
|
|
221
|
+
- the same version was already shown within the last 7 days
|
|
222
|
+
|
|
223
|
+
To disable update checks entirely:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# One-off
|
|
227
|
+
PHONE_A_FRIEND_UPDATE_CHECK=false phone-a-friend ...
|
|
228
|
+
|
|
229
|
+
# Permanent
|
|
230
|
+
phone-a-friend config set defaults.update_check false
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The cache lives at `~/.config/phone-a-friend/update-check.json` (or under
|
|
234
|
+
`$XDG_CONFIG_HOME` if set). Run `phone-a-friend doctor` to inspect the current
|
|
235
|
+
state.
|
|
236
|
+
|
|
204
237
|
## Backends
|
|
205
238
|
|
|
206
239
|
| Backend | Type | Streaming |
|
|
@@ -62,12 +62,12 @@ When `RELAY_MODE = direct`, call backend CLIs directly instead of using the
|
|
|
62
62
|
|
|
63
63
|
| Backend | Direct command |
|
|
64
64
|
|---------|---------------|
|
|
65
|
-
| **Codex** | `codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "
|
|
66
|
-
| **Gemini** | `gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "
|
|
67
|
-
| **Ollama** | `curl -s http://localhost:11434/api/chat -H "Content-Type: application/json" -d
|
|
65
|
+
| **Codex** | `codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "$(cat "$PROMPT_FILE")" < /dev/null` |
|
|
66
|
+
| **Gemini** | `gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "$(cat "$PROMPT_FILE")"` |
|
|
67
|
+
| **Ollama** | `PROMPT_JSON="$(jq -Rs . < "$PROMPT_FILE")"; curl -s http://localhost:11434/api/chat -H "Content-Type: application/json" -d "{\"model\":\"<model>\",\"messages\":[{\"role\":\"user\",\"content\":${PROMPT_JSON}}],\"stream\":false}" \| jq -r '.message.content'` |
|
|
68
68
|
|
|
69
|
-
In direct mode,
|
|
70
|
-
template:
|
|
69
|
+
In direct mode, build `PROMPT_FILE` from the relay prompt using this
|
|
70
|
+
template and the quoted-heredoc rule:
|
|
71
71
|
|
|
72
72
|
```
|
|
73
73
|
You are helping another coding agent by reviewing or advising on work in a local repository.
|
|
@@ -123,6 +123,7 @@ If `--backend` value is not `codex`, `gemini`, or `ollama`: report error and sto
|
|
|
123
123
|
|
|
124
124
|
Set:
|
|
125
125
|
- TOPIC = parsed topic string
|
|
126
|
+
- TOPIC_SAFE = TOPIC (untrusted text; never splice it into an inline shell command)
|
|
126
127
|
- MAX_ROUNDS = parsed rounds (default 3, clamped [1, 6])
|
|
127
128
|
- BACKEND = parsed backend (default `codex`)
|
|
128
129
|
- ROUND = 1
|
|
@@ -179,43 +180,48 @@ Display to user:
|
|
|
179
180
|
Claude Code, the OpenCode model name in OpenCode). Pick one that the user
|
|
180
181
|
will recognize.
|
|
181
182
|
|
|
182
|
-
Then relay to backend
|
|
183
|
+
Then relay to backend. First build `PROMPT_FILE` so untrusted text such as
|
|
184
|
+
TOPIC and QUESTION is passed as data, not spliced into an inline shell
|
|
185
|
+
command:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
PROMPT_FILE="$(mktemp)"
|
|
189
|
+
trap 'rm -f "$PROMPT_FILE" "${REPROMPT_FILE:-}"' EXIT
|
|
190
|
+
{
|
|
191
|
+
printf '%s\n' 'You are playing The Curiosity Engine — a structured Q&A rally with another agent.'
|
|
192
|
+
printf 'Topic: %s\n' "$TOPIC_SAFE"
|
|
193
|
+
printf 'Round: 1 of %s\n\n' "$MAX_ROUNDS"
|
|
194
|
+
printf '%s\n' "The orchestrating agent's question for you:"
|
|
195
|
+
printf '%s\n\n' "$QUESTION"
|
|
196
|
+
cat <<'PAF_CURIOSITY_PROMPT_EOF'
|
|
197
|
+
You MUST respond in EXACTLY this format — no exceptions, no extra text:
|
|
198
|
+
|
|
199
|
+
ANSWER: <your answer to the orchestrator's question, 2-4 sentences>
|
|
200
|
+
QUESTION: <a new question for the orchestrator on the same topic, that you are genuinely curious about>
|
|
201
|
+
|
|
202
|
+
Do not add any text before ANSWER: or after the QUESTION line.
|
|
203
|
+
PAF_CURIOSITY_PROMPT_EOF
|
|
204
|
+
} > "$PROMPT_FILE"
|
|
205
|
+
```
|
|
183
206
|
|
|
184
207
|
**Binary mode** (`RELAY_MODE = binary`):
|
|
185
208
|
```bash
|
|
186
|
-
phone-a-friend --to <BACKEND> --repo "$PWD" --sandbox read-only --fast $PAF_NO_DIFF [--model <model>] --prompt "
|
|
209
|
+
phone-a-friend --to <BACKEND> --repo "$PWD" --sandbox read-only --fast $PAF_NO_DIFF [--model <model>] --prompt "$(cat "$PROMPT_FILE")"
|
|
187
210
|
```
|
|
188
211
|
|
|
189
212
|
**Direct mode** (`RELAY_MODE = direct`):
|
|
190
213
|
```bash
|
|
191
214
|
# Codex:
|
|
192
|
-
codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "
|
|
215
|
+
codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "$(cat "$PROMPT_FILE")" < /dev/null
|
|
193
216
|
# Gemini (always include -m):
|
|
194
|
-
gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "
|
|
217
|
+
gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "$(cat "$PROMPT_FILE")"
|
|
195
218
|
# Ollama (use OLLAMA_SELECTED_MODEL from Step 2):
|
|
219
|
+
PROMPT_JSON="$(jq -Rs . < "$PROMPT_FILE")"
|
|
196
220
|
curl -s http://localhost:11434/api/chat -H "Content-Type: application/json" \
|
|
197
|
-
-d
|
|
221
|
+
-d "{\"model\":\"<OLLAMA_SELECTED_MODEL>\",\"messages\":[{\"role\":\"user\",\"content\":${PROMPT_JSON}}],\"stream\":false}" \
|
|
198
222
|
| jq -r '.message.content'
|
|
199
223
|
```
|
|
200
224
|
|
|
201
|
-
Where `<relay-prompt>` is:
|
|
202
|
-
|
|
203
|
-
```
|
|
204
|
-
You are playing The Curiosity Engine — a structured Q&A rally with another agent.
|
|
205
|
-
Topic: <TOPIC>
|
|
206
|
-
Round: 1 of <MAX_ROUNDS>
|
|
207
|
-
|
|
208
|
-
The orchestrating agent's question for you:
|
|
209
|
-
<QUESTION>
|
|
210
|
-
|
|
211
|
-
You MUST respond in EXACTLY this format — no exceptions, no extra text:
|
|
212
|
-
|
|
213
|
-
ANSWER: <your answer to the orchestrator's question, 2-4 sentences>
|
|
214
|
-
QUESTION: <a new question for the orchestrator on the same topic, that you are genuinely curious about>
|
|
215
|
-
|
|
216
|
-
Do not add any text before ANSWER: or after the QUESTION line.
|
|
217
|
-
```
|
|
218
|
-
|
|
219
225
|
## Step 4 — Parse Backend Response
|
|
220
226
|
|
|
221
227
|
If the relay call (binary or direct) produces no output, empty stdout, or a
|
|
@@ -237,35 +243,39 @@ After each relay call, parse the response for `ANSWER:` and `QUESTION:` fields.
|
|
|
237
243
|
|
|
238
244
|
Send one correction relay if `ANSWER:` or `QUESTION:` is missing:
|
|
239
245
|
|
|
246
|
+
First create `REPROMPT_FILE` with a quoted heredoc:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
REPROMPT_FILE="$(mktemp)"
|
|
250
|
+
cat > "$REPROMPT_FILE" <<'PAF_CURIOSITY_REPROMPT_EOF'
|
|
251
|
+
Your previous response did not follow the required format.
|
|
252
|
+
You MUST respond with EXACTLY this structure:
|
|
253
|
+
|
|
254
|
+
ANSWER: <your answer>
|
|
255
|
+
QUESTION: <your question for the orchestrator>
|
|
256
|
+
|
|
257
|
+
No other text. Try again.
|
|
258
|
+
PAF_CURIOSITY_REPROMPT_EOF
|
|
259
|
+
```
|
|
260
|
+
|
|
240
261
|
**Binary mode** (`RELAY_MODE = binary`):
|
|
241
262
|
```bash
|
|
242
|
-
phone-a-friend --to <BACKEND> --repo "$PWD" --sandbox read-only --fast $PAF_NO_DIFF [--model <model>] --prompt "
|
|
263
|
+
phone-a-friend --to <BACKEND> --repo "$PWD" --sandbox read-only --fast $PAF_NO_DIFF [--model <model>] --prompt "$(cat "$REPROMPT_FILE")"
|
|
243
264
|
```
|
|
244
265
|
|
|
245
266
|
**Direct mode** (`RELAY_MODE = direct`):
|
|
246
267
|
```bash
|
|
247
268
|
# Codex:
|
|
248
|
-
codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "
|
|
269
|
+
codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "$(cat "$REPROMPT_FILE")" < /dev/null
|
|
249
270
|
# Gemini:
|
|
250
|
-
gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "
|
|
271
|
+
gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "$(cat "$REPROMPT_FILE")"
|
|
251
272
|
# Ollama:
|
|
273
|
+
REPROMPT_JSON="$(jq -Rs . < "$REPROMPT_FILE")"
|
|
252
274
|
curl -s http://localhost:11434/api/chat -H "Content-Type: application/json" \
|
|
253
|
-
-d
|
|
275
|
+
-d "{\"model\":\"<OLLAMA_SELECTED_MODEL>\",\"messages\":[{\"role\":\"user\",\"content\":${REPROMPT_JSON}}],\"stream\":false}" \
|
|
254
276
|
| jq -r '.message.content'
|
|
255
277
|
```
|
|
256
278
|
|
|
257
|
-
Where `<re-prompt>` is:
|
|
258
|
-
|
|
259
|
-
```
|
|
260
|
-
Your previous response did not follow the required format.
|
|
261
|
-
You MUST respond with EXACTLY this structure:
|
|
262
|
-
|
|
263
|
-
ANSWER: <your answer>
|
|
264
|
-
QUESTION: <your question for the orchestrator>
|
|
265
|
-
|
|
266
|
-
No other text. Try again.
|
|
267
|
-
```
|
|
268
|
-
|
|
269
279
|
Parse again. If still missing `QUESTION:` → end game early. Display:
|
|
270
280
|
```
|
|
271
281
|
⚠️ <BACKEND> broke the chain on round <N> (missing QUESTION: after re-prompt).
|
|
@@ -363,7 +373,7 @@ capability, or debugging requires a pin.
|
|
|
363
373
|
with cache path, expiry, and bypass instructions; no auto-substitution):
|
|
364
374
|
|
|
365
375
|
```bash
|
|
366
|
-
phone-a-friend --to gemini --repo "$PWD" --sandbox read-only --fast $PAF_NO_DIFF --prompt "
|
|
376
|
+
phone-a-friend --to gemini --repo "$PWD" --sandbox read-only --fast $PAF_NO_DIFF --prompt "$(cat "$PROMPT_FILE")"
|
|
367
377
|
```
|
|
368
378
|
|
|
369
379
|
To bypass the cache: `PHONE_A_FRIEND_GEMINI_DEAD_CACHE=false`. Or delete the
|
|
@@ -371,7 +381,7 @@ cache file to clear it.
|
|
|
371
381
|
|
|
372
382
|
**Direct mode** (no PaF wrapper — orchestrator handles retry):
|
|
373
383
|
```bash
|
|
374
|
-
gemini --sandbox --yolo --include-directories "$PWD" --output-format text --prompt "
|
|
384
|
+
gemini --sandbox --yolo --include-directories "$PWD" --output-format text --prompt "$(cat "$PROMPT_FILE")"
|
|
375
385
|
```
|
|
376
386
|
|
|
377
387
|
In direct mode, on capacity/transient errors (429, 500, 503), retry with a
|
|
@@ -35,6 +35,9 @@ into the current conversation.
|
|
|
35
35
|
subcommands (e.g. `phone-a-friend phone-a-team`).
|
|
36
36
|
- `--backend` is a `/phone-a-team` skill argument, not a PaF CLI flag. Do
|
|
37
37
|
not pass `--backend` to `phone-a-friend`.
|
|
38
|
+
- When materializing relay commands, write dynamic prompt/context text into
|
|
39
|
+
temp files using single-quoted heredocs. Do not splice user text, prior
|
|
40
|
+
model output, or conversation context into double-quoted shell arguments.
|
|
38
41
|
- Do NOT dump repo files or git output into `--context-file` or
|
|
39
42
|
`--context-text`. Repo-aware backends read files via `--repo "$PWD"`
|
|
40
43
|
using their own tools. See "Context hygiene" below.
|
|
@@ -61,11 +64,11 @@ When `RELAY_MODE = direct`, call backend CLIs directly instead of using the
|
|
|
61
64
|
|
|
62
65
|
| Backend | Direct command |
|
|
63
66
|
|---------|---------------|
|
|
64
|
-
| **Codex** | `codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "
|
|
65
|
-
| **Gemini** | `gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "
|
|
67
|
+
| **Codex** | `codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "$(cat "$PROMPT_FILE")" < /dev/null` |
|
|
68
|
+
| **Gemini** | `gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "$(cat "$PROMPT_FILE")"` |
|
|
66
69
|
|
|
67
|
-
In direct mode,
|
|
68
|
-
template:
|
|
70
|
+
In direct mode, build `PROMPT_FILE` from prompt + context using this
|
|
71
|
+
template and the quoted-heredoc rule:
|
|
69
72
|
|
|
70
73
|
```
|
|
71
74
|
You are helping another coding agent by reviewing or advising on work in a local repository.
|
|
@@ -168,12 +171,29 @@ I'm working on this task and got the above response. Please review it and return
|
|
|
168
171
|
|
|
169
172
|
**Binary mode** (`RELAY_MODE = binary`):
|
|
170
173
|
```bash
|
|
171
|
-
|
|
174
|
+
RELAY_BIN="$(command -v phone-a-friend)"
|
|
175
|
+
PROMPT_FILE="$(mktemp)"
|
|
176
|
+
CONTEXT_FILE="$(mktemp)"
|
|
177
|
+
trap 'rm -f "$PROMPT_FILE" "$CONTEXT_FILE"' EXIT
|
|
178
|
+
|
|
179
|
+
cat > "$PROMPT_FILE" <<'PAF_PROMPT_EOF'
|
|
180
|
+
<relay-prompt>
|
|
181
|
+
PAF_PROMPT_EOF
|
|
182
|
+
|
|
183
|
+
cat > "$CONTEXT_FILE" <<'PAF_CONTEXT_EOF'
|
|
184
|
+
<context-payload>
|
|
185
|
+
PAF_CONTEXT_EOF
|
|
186
|
+
|
|
187
|
+
"$RELAY_BIN" --to codex --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" --context-file "$CONTEXT_FILE" $PAF_NO_DIFF [--fast] [--session <id>]
|
|
172
188
|
# For gemini, omit --model by default (let auto-routing pick); see "Gemini model selection" below.
|
|
173
189
|
# Do NOT pass --session to gemini — it will error (see "Session continuity" below):
|
|
174
|
-
|
|
190
|
+
"$RELAY_BIN" --to gemini --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" --context-file "$CONTEXT_FILE" $PAF_NO_DIFF [--fast]
|
|
175
191
|
```
|
|
176
192
|
|
|
193
|
+
Use delimiter names that do not appear in the payload. The quoted heredoc
|
|
194
|
+
marker (`<<'PAF_PROMPT_EOF'`) is intentional: it makes shell treat the
|
|
195
|
+
body as data, not executable text.
|
|
196
|
+
|
|
177
197
|
`$PAF_NO_DIFF` comes from the probe in "Diff suppression" above. It
|
|
178
198
|
resolves to `--no-include-diff` on new binaries and an empty string on
|
|
179
199
|
stale binaries (with `PHONE_A_FRIEND_INCLUDE_DIFF=false` exported as
|
|
@@ -185,14 +205,14 @@ I'm working on this task and got the above response. Please review it and return
|
|
|
185
205
|
**Direct mode** (`RELAY_MODE = direct`):
|
|
186
206
|
```bash
|
|
187
207
|
# Codex:
|
|
188
|
-
codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "
|
|
208
|
+
codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "$(cat "$PROMPT_FILE")" < /dev/null
|
|
189
209
|
# Gemini (omit -m for auto-routing; pin only when reproducibility/capability is needed):
|
|
190
|
-
gemini --sandbox --yolo --include-directories "$PWD" --output-format text --prompt "
|
|
210
|
+
gemini --sandbox --yolo --include-directories "$PWD" --output-format text --prompt "$(cat "$PROMPT_FILE")"
|
|
191
211
|
```
|
|
192
212
|
|
|
193
|
-
In direct mode, build
|
|
194
|
-
|
|
195
|
-
`<context-payload>` into the
|
|
213
|
+
In direct mode, build `PROMPT_FILE` from the template in the "Direct call
|
|
214
|
+
reference" section using the same quoted-heredoc rule, substituting
|
|
215
|
+
`<relay-prompt>` and `<context-payload>` into the file body.
|
|
196
216
|
|
|
197
217
|
Note: `--fast`, `--session`, and `--no-include-diff` are PaF CLI flags
|
|
198
218
|
only available in binary mode. Do not append them to direct-mode
|
package/commands/phone-a-team.md
CHANGED
|
@@ -37,19 +37,20 @@ When `RELAY_MODE = direct`, call backend CLIs directly instead of using the
|
|
|
37
37
|
|
|
38
38
|
| Backend | Direct command |
|
|
39
39
|
|---------|---------------|
|
|
40
|
-
| **Codex** | `codex exec -C "$PWD" --skip-git-repo-check --sandbox <mode> "
|
|
41
|
-
| **Gemini** | `gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "
|
|
42
|
-
| **Ollama** | `curl -s http://localhost:11434/api/chat -H "Content-Type: application/json" -d
|
|
40
|
+
| **Codex** | `codex exec -C "$PWD" --skip-git-repo-check --sandbox <mode> "$(cat "$PROMPT_FILE")" < /dev/null` |
|
|
41
|
+
| **Gemini** | `gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "$(cat "$PROMPT_FILE")"` |
|
|
42
|
+
| **Ollama** | `PROMPT_JSON="$(jq -Rs . < "$PROMPT_FILE")"; curl -s http://localhost:11434/api/chat -H "Content-Type: application/json" -d "{\"model\":\"<model>\",\"messages\":[{\"role\":\"user\",\"content\":${PROMPT_JSON}}],\"stream\":false}" \| jq -r '.message.content'` |
|
|
43
43
|
|
|
44
44
|
Sandbox mapping for direct mode:
|
|
45
45
|
- **Codex**: pass the mode string directly (`--sandbox read-only` or
|
|
46
46
|
`--sandbox workspace-write`)
|
|
47
47
|
- **Gemini**: `--sandbox` flag is boolean. Present = sandboxed (read-only).
|
|
48
|
-
|
|
48
|
+
Use `--sandbox` for both read-only and workspace-write; omit it only for
|
|
49
|
+
`danger-full-access`.
|
|
49
50
|
- **Ollama**: no sandbox support. All context must be in the prompt.
|
|
50
51
|
|
|
51
|
-
In direct mode,
|
|
52
|
-
|
|
52
|
+
In direct mode, build `PROMPT_FILE` from prompt + context + diff using this
|
|
53
|
+
template and the quoted-heredoc rule:
|
|
53
54
|
|
|
54
55
|
```
|
|
55
56
|
You are helping another coding agent by reviewing or advising on work in a local repository.
|
|
@@ -113,9 +114,13 @@ matrix and skip rules).
|
|
|
113
114
|
Extract a model name from the task arguments.
|
|
114
115
|
|
|
115
116
|
**Explicit flag (highest priority, all backends):**
|
|
116
|
-
- If `$ARGUMENTS` contains `--model <name>`:
|
|
117
|
-
|
|
118
|
-
-
|
|
117
|
+
- If `$ARGUMENTS` contains `--model <name>`: validate `<name>` against
|
|
118
|
+
`^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`.
|
|
119
|
+
- If invalid (spaces, quotes, backticks, shell metacharacters, or a leading
|
|
120
|
+
punctuation character): abort and ask the user for a safe model name.
|
|
121
|
+
- If valid: set `MODEL_OVERRIDE = <name>` and remove the `--model <name>`
|
|
122
|
+
pair from TASK_DESCRIPTION.
|
|
123
|
+
- This applies to all backends (codex, gemini, ollama, both, all).
|
|
119
124
|
|
|
120
125
|
**Natural language extraction (Ollama only, lower priority):**
|
|
121
126
|
- Only attempt NL extraction when BACKEND is exactly `ollama` and no
|
|
@@ -129,6 +134,9 @@ Extract a model name from the task arguments.
|
|
|
129
134
|
TASK_DESCRIPTION.
|
|
130
135
|
- If the candidate appears inside quotes, backticks, or code blocks, do
|
|
131
136
|
NOT extract (it's an example or reference, not a meta-instruction).
|
|
137
|
+
- Validate extracted names with `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`. If
|
|
138
|
+
the extracted candidate fails validation, abort and ask the user for a
|
|
139
|
+
safe model name.
|
|
132
140
|
|
|
133
141
|
**Examples:**
|
|
134
142
|
- "review this code, use deepseek" (backend=ollama) → extract "deepseek" ✓
|
|
@@ -227,13 +235,14 @@ server has nothing to run — proceeding would always fail.
|
|
|
227
235
|
If models are available, select using this precedence:
|
|
228
236
|
1. If `MODEL_OVERRIDE` is set (from `--model` flag or NL extraction in
|
|
229
237
|
Step 1): set `OLLAMA_SELECTED_MODEL = MODEL_OVERRIDE`. Check if it exists
|
|
230
|
-
in `OLLAMA_AVAILABLE_MODELS`. If not found, **
|
|
231
|
-
|
|
232
|
-
tag variant.") but proceed.
|
|
238
|
+
in `OLLAMA_AVAILABLE_MODELS`. If not found, **abort** and ask the user
|
|
239
|
+
to choose one of the discovered local models.
|
|
233
240
|
2. If no override and `RELAY_MODE = binary`: check config by running
|
|
234
241
|
`phone-a-friend config get backends.ollama.model`. If a value is
|
|
235
|
-
returned,
|
|
236
|
-
`
|
|
242
|
+
returned, validate it against `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`,
|
|
243
|
+
then set `OLLAMA_SELECTED_MODEL` to that value. Validate against
|
|
244
|
+
`OLLAMA_AVAILABLE_MODELS` — if not found, abort and ask the user to
|
|
245
|
+
choose one of the discovered local models.
|
|
237
246
|
If `RELAY_MODE = direct`: skip this step (the binary is not available to
|
|
238
247
|
query config). Fall through to option 3.
|
|
239
248
|
3. If neither override nor config: set `OLLAMA_SELECTED_MODEL` to the first
|
|
@@ -342,6 +351,12 @@ command:
|
|
|
342
351
|
|
|
343
352
|
3. **Each teammate's prompt** must use this template:
|
|
344
353
|
|
|
354
|
+
Shell safety rule: every dynamic prompt/context payload must be written
|
|
355
|
+
to a temp file with a single-quoted heredoc before invoking Bash. Do not
|
|
356
|
+
splice user text, prior model output, or conversation context into
|
|
357
|
+
double-quoted shell arguments. Model names still go through the safe
|
|
358
|
+
model-name validation from Step 1.
|
|
359
|
+
|
|
345
360
|
**Binary mode** (`RELAY_MODE = binary`):
|
|
346
361
|
```
|
|
347
362
|
You are a relay worker. Your ONLY job: run the command below via Bash,
|
|
@@ -350,8 +365,20 @@ command:
|
|
|
350
365
|
|
|
351
366
|
Run this now:
|
|
352
367
|
|
|
353
|
-
|
|
354
|
-
|
|
368
|
+
PROMPT_FILE="$(mktemp)"
|
|
369
|
+
CONTEXT_FILE="$(mktemp)"
|
|
370
|
+
trap 'rm -f "$PROMPT_FILE" "$CONTEXT_FILE"' EXIT
|
|
371
|
+
|
|
372
|
+
cat > "$PROMPT_FILE" <<'PAF_TEAM_PROMPT_EOF'
|
|
373
|
+
<prompt>
|
|
374
|
+
PAF_TEAM_PROMPT_EOF
|
|
375
|
+
|
|
376
|
+
cat > "$CONTEXT_FILE" <<'PAF_TEAM_CONTEXT_EOF'
|
|
377
|
+
<context>
|
|
378
|
+
PAF_TEAM_CONTEXT_EOF
|
|
379
|
+
|
|
380
|
+
phone-a-friend --to <backend> --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" \
|
|
381
|
+
[--context-file "$CONTEXT_FILE"] $PAF_NO_DIFF \
|
|
355
382
|
[--sandbox <mode>] [--model <model>] --fast [--session <SESSION_ID>]
|
|
356
383
|
|
|
357
384
|
Note: for `--to claude`, `--fast` has no effect.
|
|
@@ -439,6 +466,61 @@ is identical — only the execution mechanism changes.
|
|
|
439
466
|
Execute a do-review-decide loop. Maximum MAX_ROUNDS rounds. Stop early if
|
|
440
467
|
converged.
|
|
441
468
|
|
|
469
|
+
### Convergence trace
|
|
470
|
+
|
|
471
|
+
Before the loop starts, initialize an in-memory `CONVERGENCE_TRACE` array.
|
|
472
|
+
This trace is local to the current command run; it is not analytics, tracking,
|
|
473
|
+
or persisted product telemetry.
|
|
474
|
+
|
|
475
|
+
After the REVIEW phase of each round, append a verdict envelope (the exact
|
|
476
|
+
same shape used by `phone-a-friend --verdict-json`). The array index is the
|
|
477
|
+
round number minus one, so do not add a separate `round` property to the
|
|
478
|
+
envelope:
|
|
479
|
+
|
|
480
|
+
```
|
|
481
|
+
CONVERGENCE_TRACE = [] # index = round - 1
|
|
482
|
+
|
|
483
|
+
# Each entry is a verdict envelope:
|
|
484
|
+
{
|
|
485
|
+
"schema_version": 1,
|
|
486
|
+
"verdict": "ship" | "iterate" | "abstain",
|
|
487
|
+
"summary": "<one-sentence synthesis>",
|
|
488
|
+
"findings": [
|
|
489
|
+
{ "severity": "blocker" | "important" | "nit",
|
|
490
|
+
"title": "<headline>",
|
|
491
|
+
"rationale": "<why it matters>",
|
|
492
|
+
"location": "<file or file:line> or null" }
|
|
493
|
+
]
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
The verdict is **derived from severities**: any `blocker` or `important`
|
|
498
|
+
finding => `iterate`; empty findings or only `nit` findings => `ship`;
|
|
499
|
+
`abstain` only when the reviewer cannot make a confident call AND findings
|
|
500
|
+
is empty. This matches `parseVerdict()` in PaF's `src/verdict.ts`. Do not
|
|
501
|
+
contradict the rule (e.g. do not record verdict=ship while listing a
|
|
502
|
+
blocker — that is a malformed envelope).
|
|
503
|
+
|
|
504
|
+
Two ways to source the verdict envelope per round:
|
|
505
|
+
|
|
506
|
+
1. **Lead-judged (default)**: the lead orchestrator runs the rubric in
|
|
507
|
+
Phase 2 REVIEW and emits the envelope based on its own judgment. No
|
|
508
|
+
extra relay call. Cheap, fits text-output rounds, suitable for most
|
|
509
|
+
tasks.
|
|
510
|
+
2. **Backend-judged (optional, file-change rounds)**: when the round
|
|
511
|
+
produced file changes that exist as a git diff, the lead MAY also
|
|
512
|
+
run `phone-a-friend --to <backend> --review --verdict-json` for an
|
|
513
|
+
independent third-party verdict. If used, merge it conservatively with
|
|
514
|
+
the lead-judged envelope: any blocker or important finding from either
|
|
515
|
+
source makes the trace verdict `iterate`, and a backend `ship` verdict
|
|
516
|
+
MUST NOT erase blocker or important findings the lead already found.
|
|
517
|
+
Prefer the backend envelope only when it is stricter than the lead, or
|
|
518
|
+
when the lead abstained and the backend produced a concrete verdict.
|
|
519
|
+
Cap at one verdict-json relay call per round.
|
|
520
|
+
|
|
521
|
+
Both sources produce the same envelope shape, so CONVERGENCE_TRACE is uniform
|
|
522
|
+
either way.
|
|
523
|
+
|
|
442
524
|
### Timing Expectations
|
|
443
525
|
|
|
444
526
|
Different backends have different response times:
|
|
@@ -487,7 +569,19 @@ Delegate the task to the backend via the relay. The lead's job is to
|
|
|
487
569
|
|
|
488
570
|
**Binary mode** (`RELAY_MODE = binary`):
|
|
489
571
|
```bash
|
|
490
|
-
|
|
572
|
+
PROMPT_FILE="$(mktemp)"
|
|
573
|
+
CONTEXT_FILE="$(mktemp)"
|
|
574
|
+
trap 'rm -f "$PROMPT_FILE" "$CONTEXT_FILE"' EXIT
|
|
575
|
+
|
|
576
|
+
cat > "$PROMPT_FILE" <<'PAF_TEAM_PROMPT_EOF'
|
|
577
|
+
<prompt>
|
|
578
|
+
PAF_TEAM_PROMPT_EOF
|
|
579
|
+
|
|
580
|
+
cat > "$CONTEXT_FILE" <<'PAF_TEAM_CONTEXT_EOF'
|
|
581
|
+
<context>
|
|
582
|
+
PAF_TEAM_CONTEXT_EOF
|
|
583
|
+
|
|
584
|
+
phone-a-friend --to <backend> --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" [--context-file "$CONTEXT_FILE"] $PAF_NO_DIFF [--sandbox <mode>] [--model <model>] --fast [--session <SESSION_ID>]
|
|
491
585
|
```
|
|
492
586
|
|
|
493
587
|
Diff inclusion: `$PAF_NO_DIFF` is set by the probe in "Diff inclusion
|
|
@@ -509,21 +603,23 @@ Delegate the task to the backend via the relay. The lead's job is to
|
|
|
509
603
|
**Direct mode** (`RELAY_MODE = direct`):
|
|
510
604
|
```bash
|
|
511
605
|
# Codex:
|
|
512
|
-
codex exec -C "$PWD" --skip-git-repo-check --sandbox <mode> "
|
|
513
|
-
# Gemini (
|
|
514
|
-
gemini [--sandbox] --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "
|
|
606
|
+
codex exec -C "$PWD" --skip-git-repo-check --sandbox <mode> "$(cat "$PROMPT_FILE")" < /dev/null
|
|
607
|
+
# Gemini (`--sandbox` for read-only/workspace-write; omit only for danger-full-access):
|
|
608
|
+
gemini [--sandbox] --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "$(cat "$PROMPT_FILE")"
|
|
515
609
|
# Ollama:
|
|
610
|
+
PROMPT_JSON="$(jq -Rs . < "$PROMPT_FILE")"
|
|
516
611
|
curl -s http://localhost:11434/api/chat -H "Content-Type: application/json" \
|
|
517
|
-
-d
|
|
612
|
+
-d "{\"model\":\"<OLLAMA_SELECTED_MODEL>\",\"messages\":[{\"role\":\"user\",\"content\":${PROMPT_JSON}}],\"stream\":false}" \
|
|
518
613
|
| jq -r '.message.content'
|
|
519
614
|
```
|
|
520
615
|
|
|
521
616
|
Note: `--fast` and `--session` are not available in direct mode. Direct
|
|
522
617
|
mode relay calls are always stateless (each round starts fresh).
|
|
523
618
|
|
|
524
|
-
In direct mode, build
|
|
525
|
-
"
|
|
526
|
-
`git diff HEAD` and append the output to the template's "Git Diff"
|
|
619
|
+
In direct mode, build `PROMPT_FILE` using the template from the "Direct call
|
|
620
|
+
reference" section and the quoted-heredoc rule. If `--include-diff` is used,
|
|
621
|
+
run `git diff HEAD` and append the output to the template's "Git Diff"
|
|
622
|
+
section inside that file.
|
|
527
623
|
|
|
528
624
|
For gemini, omit `--model` by default and let auto-routing pick (see "Gemini model selection" section).
|
|
529
625
|
For ollama, always include `--model` / model field using `OLLAMA_SELECTED_MODEL` from preflight.
|
|
@@ -562,16 +658,47 @@ round 1" on tasks that deserve iteration.
|
|
|
562
658
|
the better output, note the disagreement and rationale for selection.
|
|
563
659
|
- If one backend fails → continue with the successful one, note the failure.
|
|
564
660
|
|
|
661
|
+
#### Phase 2.5: Convergence trace snapshot
|
|
662
|
+
|
|
663
|
+
After REVIEW, append the verdict envelope for this round to
|
|
664
|
+
`CONVERGENCE_TRACE` (see "Convergence trace" above). Display a one-line
|
|
665
|
+
summary to the user before moving to DECIDE:
|
|
666
|
+
|
|
667
|
+
```
|
|
668
|
+
Round N: verdict=<ship|iterate|abstain> | catches: B blocker, I important, X nits
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
(omit zero-count categories: `Round 2: verdict=iterate | catches: 1 important, 2 nits`).
|
|
672
|
+
|
|
673
|
+
**Diminishing-returns warning** (only when comparing round N to round N-1,
|
|
674
|
+
both with verdict=iterate): if round N has equal-or-more findings than
|
|
675
|
+
round N-1 AND blocker+important counts did not decrease, surface a single
|
|
676
|
+
stderr-style line BEFORE the DECIDE phase:
|
|
677
|
+
|
|
678
|
+
```
|
|
679
|
+
Round N may not be making progress: same/more catches than round N-1, no severity decrease. Consider stopping.
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
This is a hint, not a hard stop. The lead may still continue if there is a
|
|
683
|
+
reason (e.g. the round addressed a blocker but introduced an important
|
|
684
|
+
finding). When continuing past the warning, briefly note the rationale in
|
|
685
|
+
the next-round feedback.
|
|
686
|
+
|
|
565
687
|
#### Phase 3: DECIDE
|
|
566
688
|
|
|
567
|
-
Based on the review:
|
|
689
|
+
Based on CONVERGENCE_TRACE[round-1].verdict and the review:
|
|
568
690
|
|
|
569
|
-
- **Converged** (
|
|
691
|
+
- **Converged** (verdict = `ship`): Stop the loop. Execute Step 8
|
|
570
692
|
(Cleanup), then Step 9 (Final Synthesis). Do not iterate further — no
|
|
571
693
|
iterating for its own sake.
|
|
572
|
-
- **Issues found** (
|
|
573
|
-
|
|
574
|
-
|
|
694
|
+
- **Issues found** (verdict = `iterate`): Formulate specific, actionable
|
|
695
|
+
feedback derived from the round's findings (use the `title` and
|
|
696
|
+
`rationale` of each blocker/important entry). Start the next round
|
|
697
|
+
with this feedback incorporated into the prompt.
|
|
698
|
+
- **Inconclusive** (verdict = `abstain`): The reviewer could not make
|
|
699
|
+
a confident call. Surface what's missing (in `summary`) and either
|
|
700
|
+
request that information from the user OR run one more round with a
|
|
701
|
+
more focused prompt. Do not declare convergence on `abstain`.
|
|
575
702
|
- **Backend error** (timeout, crash, unexpected failure): Note the failure.
|
|
576
703
|
If another backend is available, try it. If no backend produced a
|
|
577
704
|
successful result this round: if a previous round had a usable result,
|
|
@@ -753,6 +880,18 @@ a clear synthesis to the user. Include ALL of the following:
|
|
|
753
880
|
convergence.
|
|
754
881
|
5. **Sandbox note**: If `--sandbox workspace-write` was used at any point,
|
|
755
882
|
note it here.
|
|
883
|
+
6. **Convergence retrospective** (from `CONVERGENCE_TRACE`):
|
|
884
|
+
- Find the first round whose verdict is `ship`. Call that one-based round
|
|
885
|
+
number `K` (`CONVERGENCE_TRACE` array index + 1).
|
|
886
|
+
- If `K` exists and `K < MAX_ROUNDS`, append:
|
|
887
|
+
`Hint: this run reached "ship" at round K. Try --max-rounds K next time for a similar task.`
|
|
888
|
+
- If `K` does not exist (no ship-verdict in any round), append:
|
|
889
|
+
`Hint: no round reached "ship" — task may need decomposition, more context, or out-of-band work before re-running.`
|
|
890
|
+
- If a diminishing-returns warning fired during the run, mention it
|
|
891
|
+
here too: `Round X did not show progress over round X-1; if you see
|
|
892
|
+
this pattern again, consider --max-rounds (X-1).`
|
|
893
|
+
- Skip the retrospective when only a single round ran (insufficient
|
|
894
|
+
data to make any recommendation).
|
|
756
895
|
|
|
757
896
|
Format the synthesis clearly. The user should understand at a glance what
|
|
758
897
|
happened and whether the result is complete.
|
|
@@ -861,10 +1000,10 @@ The following precedence determines `OLLAMA_SELECTED_MODEL` during preflight:
|
|
|
861
1000
|
|
|
862
1001
|
1. **`MODEL_OVERRIDE`** (from `--model` flag or NL extraction in Step 1) —
|
|
863
1002
|
highest priority. Validate against `OLLAMA_AVAILABLE_MODELS`. If not
|
|
864
|
-
found,
|
|
1003
|
+
found, abort and ask the user to choose one of the discovered models.
|
|
865
1004
|
2. **Config `backends.ollama.model`** — set via TUI model picker or
|
|
866
|
-
`phone-a-friend config set`. Validate against
|
|
867
|
-
|
|
1005
|
+
`phone-a-friend config set`. Validate against the safe model-name pattern
|
|
1006
|
+
and available models; abort if invalid or unavailable.
|
|
868
1007
|
3. **First model from `/api/tags`** — fallback auto-selection.
|
|
869
1008
|
|
|
870
1009
|
- **Do NOT maintain a model priority list** for Ollama. Unlike Gemini, Ollama
|