Reviewer — R5 Chain Adversarial Review
Date: 2026-08-25
Reviewed work: Four consecutive Builder passes (R5, R5b, R5c, R5d) on bin/pos-ai-gemini and docs
Approved scope: tmp_request.md R5 — terse prompt + markdown rendering, --last + default session, answer separation + staleness transparency, machine context
TL;DR
Status: ACCEPT_WITH_NOTES
- 21 checklist items assessed: 18 PASS, 1 FAIL (duplicate line in ai.md doc), 1 WARN (chat REPL extra whitespace — cosmetic), 1 NOT-APPLICABLE (chat subcommands that never existed)
- 1 BLOCKING finding: duplicate line in
DOC/howto/ai.md (lines 135–138)
- 0 regressions detected in non-tty output path or pre-R5 behavior
- 0 scope creep into share-lib, menu-lib, cmd_unmount, or Telegram bridge
- 3 unverified items (require
make gen, bash -n, runtime testing — sandbox blocked)
Step 1: Scope compliance
Files in R5 scope (per approved scope):
bin/pos-ai-gemini — ✅ Changed, 311→565 lines
DOC/howto/ai.md — ✅ Changed
DOC/POS.md — ✅ Changed (ai section updated)
completions/pos.bash — ✅ Changed (GEN: block, --full --last added)
DOC/AGENT_Context_Project.md — ✅ Changed (line-count resync only)
Out-of-scope files also modified in working tree (from concurrent work, NOT caused by R5):
bin/pos-docker-vbox, bin/pos-share-nfs-client, bin/pos-share-smb-client — R1/R3/R6 work
lib/common.sh — R4 confirm convention
DOC/DEV.md, DOC/howto/docker.md, DOC/howto/share.md — doc sync for R3/R4/R6
These are concurrent uncommitted changes, not scope creep by R5.
| Verdict |
PASS |
| Rationale |
All R5-scope files are modified; no R5-authored changes found in out-of-scope files |
Step 2: Default system prompt (R5)
| Check |
Verdict |
Evidence |
| Terse clause present |
PASS |
Line 23: "Be extremely terse: lead with the exact command(s) to run; one-line explanations max; no greetings, no closing offers, no essays." |
| Troubleshooting clause present |
PASS |
Line 23: "diagnose it from that and lead with the fix command(s)." |
| Machine context appended dynamically |
PASS |
cmd_ask lines 411–414: mc="$(machine_context)"; [ -n "$mc" ] && mc=" $mc"; system="$DEFAULT_SYSTEM_PROMPT$mc" |
Step 3: --system replaces wholesale
| Verdict |
PASS |
| Evidence |
Lines 410–415: system="$SYSTEM_PROMPT" then if [ -z "$system" ] && [ "$FULL_MODE" -eq 0 ]; then .... When --system sets SYSTEM_PROMPT, the condition [ -z "$system" ] is false → built-in prompt + machine context are never assembled. |
Step 4: --full drops built-in
| Verdict |
PASS |
| Evidence |
Line 411: if [ -z "$system" ] && [ "$FULL_MODE" -eq 0 ]. With FULL_MODE=1, the entire block is skipped → system stays as SYSTEM_PROMPT which defaults to "". No built-in or machine context injected. |
Step 5: --last resolves + injects + announces
| Verdict |
PASS |
| Evidence |
newest_pos_log() (lines 131–138) finds newest non-pos.log, verifies non-empty. cmd_ask (lines 398–403): calls last_log_annotate for stderr info, last_log_context for the tail, appends [last command output:] block. last_log_annotate (lines 171–185): prints [i] attaching last pos output — <basename> (<age>) + preview line to stderr. |
Step 6: Staleness >60min warning
| Verdict |
PASS |
| Evidence |
Line 182: if [ "$age_s" -gt "$LAST_LOG_STALE_SECS" ] (LAST_LOG_SECS=3600). Prints [!] that log is %s old and may not match your current problem... to stderr with pipe-fresh hint. |
Step 7: Session "default" persists
| Verdict |
PASS |
| Evidence |
Line 16: SESSION="default" (was SESSION="" in baseline). session_load() / session_save() no longer gated on [ -n "$SESSION" ] in ask/chat — always writes to default.json. |
Step 8: --session overrides default
| Verdict |
PASS |
| Evidence |
Flag parser lines 534–535: --session) SESSION="$2"; shift 2. Overrides the default before any session operation. |
Step 9: Tty answer separation
| Verdict |
PASS |
| Evidence |
render_markdown tty path (line 318): printf '\n%s\n' "$rendered" — exactly one leading blank line + content + one trailing newline. Non-tty path (lines 262–264): printf '%s\n' "$text" — raw bytes, zero added newlines. |
Step 10: Markdown renderer coverage
| Verdict |
PASS |
| Evidence |
Fenced blocks: line 280 toggle + line 281 dimmed indent. Inline code: lines 291–295 → yellow. Bold **…**: lines 298–302. Bold __…__: lines 305–309. Headers #{1,4}: lines 282–285 → bold cyan, # stripped. HR ---/***___: line 287 → thin rule. List markers: no-op (passed through as-is, which is correct). glow: lines 313–314, command -v glow >/dev/null 2>&1 — opportunistic. |
Step 11: Machine context fallback chain
| Verdict |
PASS |
| Evidence |
machine_context() lines 337–386: hostnamectl first (lines 340–356), os-release fallback for distro (lines 359–368), uname fallback for kernel+arch (lines 370–371). [ -n "$out" ] || return 0 on line 384 — clause omitted when all fail. mc_clean() strips ANSI, control chars, collapses whitespace. |
Step 12: Non-tty stdout byte-identical
| Verdict |
PASS |
| Evidence |
Old baseline cmd_ask: printf '%s\n' "$out". New cmd_ask: render_markdown "$out" where non-tty path is printf '%s\n' "$text". Byte-equivalent output format. The system prompt change affects what the model generates but not the print format — opt-in behavioral change within scope. |
Step 13: Chat REPL commands intact
| Verdict |
NOT-APPLICABLE |
| Rationale |
Checklist references /history, /export, /help — these never existed in either baseline or current code. Old code only had /reset and q/Q/quit/exit. Current code preserves both exactly (lines 441–446). No regression. |
Step 14: Telegram listener bridge untouched
| Verdict |
PASS |
| Evidence |
git diff --name-only shows no changes to communication listener/sender files. Telegram bridge calls pos ai gemini ask — the non-tty stdout path is byte-identical (check 12). |
Step 15: Pipe stdin still works
| Verdict |
PASS |
| Evidence |
Lines 392–394: elif [ ! -t 0 ]; then prompt="$(cat)". Identical to baseline. |
Step 16: POS_FLAGS complete
| Verdict |
PASS |
| Evidence |
Line 5: # POS_FLAGS: --model --session --system --full --last. All five flags from checklist present. Completions line 6 matches: _pos_flags[ai-gemini]="--model --session --system --full --last". |
Step 17: make gen idempotency
| Verdict |
UNVERIFIED |
| Rationale |
Cannot run make gen in sandbox. Static evidence: completions posflags block (line 6) matches POS_FLAGS header; filetable row updated to 565 lines (confirmed by wc -l). The docker-vbox, nfs-client, smb-client, and completions line-count rows in the GEN block reflect concurrent work, not R5 drift. Strong inference: gen output would be byte-identical, but UNVERIFIED without running it. |
Step 18: glow deps guard
| Verdict |
PASS |
| Evidence |
Line 313: if command -v glow >/dev/null 2>&1 — standard pattern, never errors when absent, falls back to awk renderer. No other reference to glow in the file. |
Step 19: Lint convention compliance
| Verdict |
PASS |
| Evidence |
Shebang: line 1 #!/usr/bin/env bash. Strict mode: line 2 set -euo pipefail. Help dispatch: line 529 -h|--help) usage ;; in flag parse loop (before subcommand dispatch). No new external deps required (glow is optional). |
Step 20: --last scoped to ask
| Verdict |
PASS |
| Evidence |
Lines 554–556: if [ "$LAST_MODE" -eq 1 ] && [ "${cmd:-}" != "ask" ]; then err "--last only applies to 'pos ai gemini ask'". Hard error for any other subcommand. |
Step 21: Error→stderr, answer→stdout
| Verdict |
PASS |
| Evidence |
err() from common.sh → stderr. warn() at line 450 → stderr. gemini_generate error echo at line 246 → >&2. last_log_annotate all output → >&2. render_markdown → stdout. API errors → stderr + exit 1. |
Findings
Finding 1 — Duplicate line in DOC/howto/ai.md
- Severity: REQUIRED
- Evidence: Lines 135–138 in
DOC/howto/ai.md:
The diff confirms: the new line was appended but the old identical line was not removed.
- Relevant files/lines:
DOC/howto/ai.md:135-138
- Approved scope reference: R5 doc updates to ai.md
- Why it matters: Duplicate text is a doc bug — readers see the same bullet twice. Trivial to fix (delete one).
Finding 2 — Chat REPL uses raw SYSTEM_PROMPT (no built-in default)
- Severity: NOTE
- Evidence:
cmd_chat line 449: gemini_generate "$model" "$contents" "$SYSTEM_PROMPT" — passes the raw SYSTEM_PROMPT var. When no --system is given, this is "" (empty), meaning chat gets NO system instruction — not even the terse prompt. This is consistent with the scope ("chat keeps its neutral behavior, only --system applies") and with ai.md line 39: "chat keeps its neutral behavior (only --system applies)."
- Why it matters: Not a defect — explicitly documented behavior. But worth noting that chat and ask have asymmetric prompt treatment.
- Severity: SUGGESTED
- Evidence:
cmd_chat lines 458–460: printf '\n'; render_markdown "$answer"; printf '\n\n'. On a tty, render_markdown emits \n<rendered>\n, so total after > prompt close is: \n (from printf) + \n (render_markdown leading) + content + \n (render_markdown trailing) + \n\n (printf). This produces 2 leading blank lines and 3 trailing blank lines — more visual whitespace than the baseline's printf '\n%s\n\n'.
- Why it matters: Cosmetic. The extra separation is arguably better for interactive readability. Non-blocking.
Verification verified
- ✅ Default system prompt has terse + troubleshooting clauses (line 23)
- ✅ Machine context function implements hostnamectl → os-release → uname fallback chain (lines 337–386)
- ✅ --system prevents built-in assembly (line 411 condition)
- ✅ --full prevents built-in assembly (line 411 FULL_MODE check)
- ✅ --last injects
[last command output:] block (lines 398–403)
- ✅ --last announces to stderr with basename + age + preview (lines 171–185)
- ✅ Staleness warning at >60min (line 182, LAST_LOG_SECS=3600)
- ✅ Session default persists (SESSION="default", no conditional on load/save)
- ✅ Tty rendering: glow opportunistic + awk fallback, non-tty raw (lines 260–318)
- ✅ POS_FLAGS header has all 5 flags (line 5)
- ✅ Completions match POS_FLAGS (line 6)
- ✅ Filetable line count accurate (565 matches wc -l)
- ✅ --last scoped to ask only (lines 554–556)
- ✅ Errors to stderr throughout
- ✅ Pipe stdin preserved (lines 392–394)
- ✅ Telegram bridge path untouched (no file changes)
- ✅ Conventions: shebang, strict mode, help dispatch order
Verification unverified
make gen byte-idempotency (sandbox cannot execute make)
bash -n syntax check (sandbox cannot execute bash on non-git commands)
- Runtime test:
--last with actual dispatcher logs, staleness warning timing, glow rendering
Scope compliance
- In-scope confirmed: All R5/R5b/R5c/R5d features implemented in
bin/pos-ai-gemini + docs
- Out-of-scope found: None authored by R5. Working tree contains concurrent changes from R1/R3/R4/R6 work (separate feature branches mixed into working tree before commit)
- No regressions: Non-tty output byte-equivalent, chat REPL preserved, stdin pipe preserved, Telegram bridge untouched
Remaining uncertainty
- Whether
make gen output is byte-identical to committed GEN blocks — strong inference says yes (flag set matches header, line counts match), but unverified.
- Whether the awk markdown renderer handles all edge cases at runtime (nested bold inside code blocks, unclosed fences, etc.) — static review shows correct structure but edge-case behavior is runtime-only.
Recommended next agent
Builder
Reason: One REQUIRED finding (duplicate doc line in DOC/howto/ai.md) is a trivial fix within approved scope — delete lines 137–138. The builder can resolve this and then the chain is ready for commit.
Changes made by Reviewer
None — read-only review.