feat: player-feedback triage (agent.json .feedback.csv -> drop/file) #258
No reviewers
Labels
No labels
Agent
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No assignees
2 participants
Notifications
Total time spent: 4 minutes 34 seconds
Due date
igor
4 minutes 34 seconds
No due date set.
Dependencies
No dependencies set
Reference
joshtronic/igor!258
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/feedback-triage"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Turns porksicle's Google-Form feedback CSV into reviewed work tickets you greenlight — the feedback loop, built to the spec we locked.
Opt-in:
agent.json.feedback.csv(the 2ndagent.jsonconsumer; porksicle's already set).Per tick, one row (
do_feedback_tick): takes the oldest unprocessed CSV row, and ONEclaude_callonAGENT_MODEL_REVIEWreads it — as clearly-fenced untrusted data, never instructions — plus context (recent closed issues, recent commits, the game list), then decides:Agentlabel + unassign; reject = close.Safety: the human label gate means a prompt-injected row can at worst become a ticket you reject — never code. Processed rows are stamped in a local seen-set (
.feedback.seen); nothing is written back to the sheet (the tracker is the status, no status column). Below the Claude health gate (model work). Robust quoted-CSV parsing viapython3.Validated live on your 8 real rows (dry-run, no filing):
28 unit checks;
bash -n+check-syncgreen. No reviewer.To arm: merge this, and the next ticks will start triaging the 8-row backlog (one per tick) into tickets for you to greenlight/reject. The already-worked ones should drop themselves.
Turns the Google-Form feedback CSV into reviewed work tickets. Opt-in by convention: a repo's agent.json .feedback.csv (the second agent.json consumer after auto-merge). - PER-TICK, one row: do_feedback_tick takes the oldest unprocessed CSV row across the analysis set; ONE claude_call on AGENT_MODEL_REVIEW reads it (as fenced UNTRUSTED data) + repo context (recent closed issues, recent commits, the game list) and decides DROP (spam/vague/already-worked) or FILE (real+new). - FILE -> UNLABELED issue assigned to FORGEJO_REVIEWER; greenlight = add Agent label + unassign, reject = close. Human label gate bounds prompt-injection. - DROP latitude is the operator's call (chosen): the model may silently drop confident spam/dupes; the seen-set still records the row. - State: local .feedback.seen (FIFO-capped); nothing written back to the sheet. - Robust CSV parsing via python3 (free text has commas + newlines). - Below the health gate (model work). Verdict is a DECISION:/REASON:/TITLE:/ ===BODY=== sentinel, parsed harness-side. Validated live on porksicle's 8 real rows: correctly DROPPED an already-worked item ('done in #53/#54' from the context) and FILED real bugs + a game idea. 28 unit checks (CSV parse, seen-set, response parse, file payload, tick decision); bash -n + check-sync green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011KXPafSYJPY6XkWHHk1Lfs🤖 Review —
COMMENT(automated)CI for
30613832: successSummary: Adds a player-feedback triage pass (
lib/feedback.sh,bin/lib/feedback-directive.md, wired intobin/tick.shbelow the health gate) that reads a Google-Form CSV fromagent.json.feedback.csv, runs oneclaude_callper tick to DROP/FILE the oldest unprocessed row, and files unlabeled issues for the human to greenlight. Well-scoped (~384 added lines), CI green, no workflow changes, and the security model (untrusted data fenced + human label gate) is sound. I'm landing on COMMENT because of a few things I cannot verify from the diff alone plus one robustness concern.Things I can't verify from the diff (worth a human glance):
claude_callsignature.do_feedback_tickcallsclaude_call "$AGENT_MODEL_REVIEW" "feedback-triage" 4000 "$directive" "$prompt" 0(lib/feedback.sh, in the retry loop). The test stubsclaude_callto ignore all args, so the test passes regardless of whether this arg order/arity matches the realclaude_call. If it doesn't match, the feature breaks at runtime while tests stay green. Please confirm against the real definition.ANALYSIS_REPOS_JSONshape.do_feedback_tickiterates withwhile IFS= read -r repo_line ... done <<<"$ANALYSIS_REPOS_JSON", treating it as newline-delimited JSON objects (one.full_nameper line). The test feeds a single bare object. If the rest of the harness stores this as a JSON array, the loop silently matches nothing and the feature is a no-op. The "validated live on 8 rows (dry-run)" claim suggests it works, but I can't confirm the dry-run exercised this exact path vs. calling functions directly.Robustness concern (not a hard block, but consider):
return 1without marking the row seen — correct for not losing data, but it means a single row that the model persistently can't produce parseable output for will sit at the head forever and block every later row from ever being triaged. Clearing.feedback.seenwon't help since the bad row is still oldest-unseen. Worth a guard or an escape hatch (e.g. a per-row attempt counter that eventually drops-with-log).Minor / description accuracy:
ok/no/eq/hasassertions inbin/test-feedback.sh(3 CSV + 4 seen-set + 1 next + 7 parse + 2 csv_url + 4 file_issue + 6 tick). The tests genuinely exist, so this is a miscount, not a fabricated claim — just fix the number.feedback_parse_responseruns the decision throughtr -dc 'A-Z', so a model line likeDECISION: DROP (spam)collapses toDROPSPAMand fails thecase. Acceptable given the strict directive + 2 retries, but slightly brittle.Positives: good test isolation (all network/forgejo boundaries stubbed, skip-safe on missing tools), proper
jq -n --argpayload construction (no injection), heredoc prompt interpolation is safe (variable values aren't re-evaluated), and the unlabeled-issue + human-greenlight design correctly bounds prompt injection to "a ticket Josh rejects, never code."No blocking defect I can point to with certainty, but enough unverifiable runtime assumptions that I'd want the human to confirm the
claude_callsignature andANALYSIS_REPOS_JSONshape before merge rather than rubber-stamp it.Independent review by the harness on
claude-opus-4-8. The human reviewer is requested once Igor has reviewed; a human still merges.🤖 Review —
COMMENT(automated)CI for
63fc7739: successAdds a player-feedback triage pass:
lib/feedback.sh+ directive + unit tests, wired intobin/tick.shbelow the CEO pass. Well-structured, security-conscious (untrusted-data fencing + human label gate), and unit-tested. My reservations are about integration boundaries I can't verify from the diff and one description inaccuracy — net COMMENT, not a clear block, but I wouldn't rubber-stamp it for autonomous issue-filing without a human sanity-check.What's good
feedback_parse_response), issues are filed UNLABELED + assigned to the human, and code never runs off a feedback row. The directive reinforces this._feedback_fail+ attempt counter capped at 3) is a nice touch so a poison row can't block the queue head forever; tested directly.success.Things a human should verify (stubbed-out in tests, so green CI proves none of them)
claude_call,_fj,forgejo_repo_get_file,feedback_*overrides. So the green suite validates internal logic only, not that the real call signatures match. In particularclaude_call "$AGENT_MODEL_REVIEW" "feedback-triage" 4000 "$directive" "$prompt" 0(lib/feedback.sh,do_feedback_tick) and the_fj GET/POSTshapes can't be confirmed from the diff. Please confirm these match the existing helpers' contracts.ANALYSIS_REPOS_JSONis consumed as newline-delimited JSON objects (while read repo_line; ... jq -r '.full_name' <<<"$repo_line"). The test feeds a single one-line object. If the real env var is a JSON array (single line or pretty-printed), this loop silently processes zero repos. Worth confirming it's NDJSON to match other passes.feedback_file_issue/_fj POST(the actual write path) was not exercised live, only via the payload-shape unit test. The first real FILE will be the first live exercise of the POST.do_ceo_tickat ~line 3622, but the health gate isn't shown. Confirm the gate is above this point.Description accuracy
ok/no/eq/hasassertions inbin/test-feedback.sh. Not a fabrication (work is over-delivered, not missing), but the description and reality disagree; worth fixing so the checklist stays trustworthy.Minor
.feedback.seento re-triage" (the state actually lives under.feedback.seen/.feedback.attemptsindiscretionary-state.json). Harmless, but align the two.feedback_fetch_rowsusescurl -sL(follows redirects) on the.feedback.csvURL. Operator-controlled config, so not a real SSRF vector, but noting it.Nothing here is a concrete correctness/security defect I can pin to a line, so I'm not requesting changes — but the integration assumptions and the unvalidated filing path are exactly the kind of thing the human should glance at before this starts opening real tickets.
Independent review by the harness on
claude-opus-4-8. The human reviewer is requested once Igor has reviewed; a human still merges.