v0.0.203
251 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b835d53d83 |
add /anneal + pullfrog-reviewer named subagent + Build self-review polish (#550)
* cherry-pick updated /anneal command from billing branch + add as Claude Code slash command mirrors origin/billing:.cursor/commands/anneal.md (commit 4f389a8f) into both .cursor/commands/ and .claude/commands/ so the parallel-lens annealing prompt is available in both editors. content is identical between the two files. * anneal: drop REVIEW.md pointer, surface-agnostic dispatch wording, fix modes.ts self-review contradictions Anneal pass over the /anneal slash command and the Build-mode self-review step: - Drop REVIEW.md references in both anneal.md copies. The file does not exist on the Claude Code surface (only .cursor/commands/), and its contents (correctness/security/impact framing) directly contradict the prescribed single-lens, no-pre-shaping discipline. - Replace "Task tool calls" with surface-agnostic "parallel subagent calls" so the meta-prompt does not couple to either CLI's tool naming. - Hedge the "verify via web search" instruction to acknowledge subagents may not have web search available. - modes.ts: drop "and the changed files" — the same step's don't-list forbids handing subagents a curated reading list (in-file contradiction). - modes.ts: restore the "skim only, don't pre-review" warning that the long-form treats as load-bearing. - modes.ts: drop "NO MCP tools" — overbroad; the actual safety property is captured by "no writes, no shell commands, no side effects". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * anneal: two-round self-anneal of /anneal + modes.ts self-review Expand the multi-lens parallel-review protocol with fixes surfaced by running /anneal on this branch twice. Material additions: /anneal canonical (.claude/commands/anneal.md + .cursor mirror): - promote orientation-vs-defect-hunting distinction to a load-bearing framing in the opening paragraphs - add an empty-target early exit ("nothing to anneal" stop) at §1 - spell out the read-only constraint with the no-op-if-reverted test, and forbid recursive subagent dispatch (incl. agentic MCP tools) - add cleanup-and-debt sub-categories (env vars, feature flags, dangling symbols), supply-chain, test-integrity lenses to the catalog - §1 lens-count rule: explicit trivial/typical/high-risk tiers; "treat as typical" tiebreaker for the unsure case - §2 example uses bare `git diff <primary-branch>` to capture uncommitted edits (three-dot syntax is committed-only) - §5 targeted-follow-up cross-references the fresh-eyes carve-out in Delegation discipline - final-message format spells out coverage shape, findings-table shape, dry-run fix-plan branch, and plan/doc summary branch - stopping criteria distinguish "trivial" from "small / low-risk" action/modes.ts Build mode step 4 (self-review one-pass anneal): - empty-diff early exit; "step 4 mandatory whenever there is a diff" resolves the prior contradiction with the always-runs assertion - lens count by risk (2-3 typical / 4 high-risk single-round-cap / exactly 1 trivial) with separate Tiebreaker - expand swap-in lens menu (research-validated assumptions, security, user-journey, ops, integration, test integrity, supply chain, performance, holistic) so the catalog is a starting menu, not a closed set - rename `cleanup & scope` to `diff hygiene` to avoid colliding with the canonical's broader `cleanup & debt` - delegation discipline bulletized (don't lens-review yourself, don't summarize, don't curate, don't pre-shape, don't mention other lenses); independence rationale stated inline - explicit research-discipline reminder for any lens that touches external contracts (web search, quote URLs) - comment block enumerates deliberate omissions vs the canonical (dry-run, severity categorization, read-only shell) and the deliberate scope decision (sibling diff-producing modes stay solo) action/modes.ts Review + IncrementalReview subagent-dispatch wording: - propagate the no-recursive-dispatch rule (was missing) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * add set_plan/get_plan + restructure Review/IncrementalReview as parallel-subagent orchestrators Build mode's self-review and Review/IncrementalReview now follow the multi-lens parallel-subagent fan-out pattern from the canonical /anneal protocol. New set_plan/get_plan MCP tools (orchestrator-only) persist the implementation plan in tool state so the self-review's plan-adherence lens can verify the diff against the original intent rather than reconstructing it post-hoc. Subagent "read-only / no further dispatch" is currently enforced via prompt prose only — neither claude-code's --disallowedTools nor opencode's per-agent tools allowlist is configured to scope subagent MCP access. Documented as a deferred ~30-50 LOC follow-up in the modes.ts header comment. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * revert Review/IncrementalReview mode prompts to main; keep Build self-review changes E2e testing on this branch only exercised the trivial-1-lens path for Review (preview repo had only docs PRs). Multi-lens Review fan-out was never directly validated against a real code PR. Splitting the Review/IncrementalReview restructure to its own branch (review-mode-orchestrator, draft PR #555) pending focused validation. Keep on this branch: - set_plan/get_plan MCP tools - Build mode multi-lens self-review (Test 3 directly validated 2-subagent parallel fan-out on a 2-file diff) - /anneal command updates (.claude/ and .cursor/ mirrors) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * require plan parameter when selecting Build mode Adds an arktype .narrow on SelectModeParams that rejects select_mode({mode:"Build"}) unless a non-empty 'plan' string is also provided. When valid, the plan is stored into ctx.toolState.plan at mode-selection time, so step 4's plan-adherence lens always has a comparison target. This closes the e2e finding that agents never reached for set_plan on their own (5 of 6 runs in production). Build mode prompt updated to reflect that plan is already populated at mode selection; set_plan remains as the mid-task replan tool. Other modes are unaffected. Validation surfaces the error to the agent with a descriptive message including the path ('plan') and recovery instructions, so a failing call is recoverable on the next turn rather than a hard fail. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * move Build-mode plan-required check from arktype .narrow to execute() arktype .narrow predicates aren't JSON-Schema serializable — FastMCP's toJsonSchema() emitted a {code: "predicate", predicate: Function} object instead of a serialized schema. Effect: agents couldn't see select_mode in their tool list (verified by 5 consecutive runs across two models silently bypassing select_mode entirely after the prior commit). Fix: keep the param schema clean (.narrow removed) and check selectedMode.name === "Build" && !params.plan in the execute() body, returning a structured error response. The agent now sees select_mode normally, gets a clear actionable error if it forgets the plan, and can recover on the next turn by retrying with the plan included. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * flip lens architecture: Build = single fresh-eyes subagent, Review/IncrementalReview = multi-lens Build mode self-review previously fanned out 1-4 lenses on the agent's own diff. The bias-mitigation argument for fan-out is weaker for self-review than for reviewing someone else's PR — the orchestrator just wrote the code, so what matters is one fresh-eyes subagent that doesn't share the implementation context, not breadth across parallel angles. Build now dispatches exactly one subagent that gets the original user request and the diff and evaluates whether the diff fulfills the request. Review and IncrementalReview now use the multi-lens orchestrator pattern (triage → parallel read-only fan-out → aggregate → draft comments → submit). For someone else's PR, parallel lenses (correctness, security, research-validated, user-journey, etc.) provide breadth that a single subagent can't carry coherently. Was previously parked on the review-mode-orchestrator branch (PR #555). Removes set_plan/get_plan MCP tools, ToolState.plan field, and the plan parameter on select_mode. Validated end-to-end that those didn't cause agents to actually use plan tracking (5 of 6 e2e runs skipped them); the original user request from the prompt body is the source of truth and the orchestrator already has it. Drops timeout test plan-param workaround that was added for the prior validation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * split Review/IncrementalReview multi-lens back out to review-mode-orchestrator branch The multi-lens orchestrator restructure for Review/IncrementalReview was bundled into this branch in commit e964ae0c, but it hasn't been validated against a real code-heavy PR (the e2e exercised it only on docs PRs). Splitting it back out keeps this branch focused on the validated half — Build → single fresh-eyes subagent — and lets the Review changes ship in a focused PR (#555 reopened). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * anneal: fix Build prompt contract bugs found by 3-lens review Major fixes: - checkout_pr returns the field as `base`, not `baseRef` (per checkout.ts:611-616). The prompt was telling agents to read `result.baseRef` which would be undefined. - The base-ref fallback "after fetching" is unreachable via the `git` MCP tool (it blocks `fetch` per AUTH_REQUIRED_REDIRECT). Now names `git_fetch` explicitly. - Boundary-tag wrapping for the user request had no escape rule for input that contains the literal close marker, and no fallback for an empty request. Both are now documented with a nonce-suffix mitigation. - PR reference updated #555 → #557 (the active PR for the multi-lens review-mode-orchestrator branch; #555 was closed after the rebase). Minor fixes: - Retry predicate tightened: "errors out (tool error) or returns an empty body", not "returns nothing usable" (which is unfalsifiable and lets an orchestrator declare any output not-usable to skip review). - Subagent read-only constraints rephrased as prescriptive ("MUST NOT call") rather than descriptive ("you have only"), since on inheriting runtimes the subagent does in fact have access to write tools and the constraint is prompt-only. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * anneal round 2: tighten Build prompt edge cases (workflow_dispatch, base-ref, footer-strip, skip marker) Cross-lens findings from holistic + user-journey + research-validated lenses: - workflow_dispatch + empty diff: report_progress silently no-ops when there's no parent issue/PR. Now also call set_output with a "no-op" summary so the user gets surfacable feedback. - base-ref resolution: clarified `base` from checkout_pr is a bare ref name, added explicit `git remote show origin` path for repos whose primary is not `main` (master, trunk, etc.). - bare `git diff` description: tightened from "shows working tree" to "shows unstaged working-tree changes" — bare diff misses staged changes too, not just committed ones. - prompt-body stripping: explicitly call out the leading `> ` blockquote prefix (added by the *YOUR TASK* section formatting) and the entire Pullfrog footer block, not just one example link. - boundary-tag nonce: always-on now, not conditional on detecting a close marker. Cost is one random short string; failure mode (prompt injection if input contains literal close marker) is silent. - subagent-skip marker: structured `Self-review: SKIPPED (subagent error: ...)` on its own commit-message line, so the gap is greppable. Header comment also documents: - AddressReviews/Fix/Task asymmetry (deliberately deferred) - Subagent-runtime-fence deferred fix must explicitly deny Skill / agentic MCP tools, not just destructive tools (claude-code blocks recursive Task spawn but not alternative dispatch paths). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * anneal round 3: targeted re-review of round-2 changes catches real regressions Round 2's "fixes" introduced two real bugs that round 3's targeted correctness re-review caught: CRITICAL (fixed): tier-3 base-ref resolution used `git remote show origin`, which requires network auth — the MCP `git` tool runs commands through plain spawn() without auth, so this hangs on private repos. Replaced with `git symbolic-ref refs/remotes/origin/HEAD` (local symref, no network), which actions/checkout populates. MAJOR (fixed): the eventInstructions fallback was incoherent — the agent has no separately-addressable eventInstructions field; whatever it received in *YOUR TASK* is its only input. Removed the misleading reference. MAJOR (fixed): per-line `> ` strip was ambiguous, could destructively flatten user-pasted markdown blockquotes. Now: "strip exactly one leading `> ` per line". MAJOR (fixed): tier-1 base-ref preferred bare `<base>` over `origin/<base>`, which fails on the rare alreadyOnBranch path in checkout_pr where the local ref isn't re-created. Now prefers `origin/<base>` (always populated post-fetch). MINOR (fixed): footer-strip anchor was `<sup>`/`<picture>`, both of which appear in legitimate user content (footnotes, etc.). Switched to the PULLFROG_DIVIDER sentinel which is purpose-built for this. MAJOR (acknowledged, partial fix): 4-hex nonce is theatrical security; bumped to 8 hex and explicitly noted it's a typo-guard, not a security boundary, and that the structural fix (separate task() argument) is the real solution. REJECTED (verified false positive): subagent claimed `set_output` is not registered for workflow_dispatch. Verified at action/utils/payload.ts:118 — workflow_dispatch from `gh workflow run` resolves to trigger:"unknown", which IS standalone, which IS registered with set_output. E2e logs from prior tests confirm agents successfully call pullfrog_set_output on workflow_dispatch runs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * anneal round 4: drop broken symbolic-ref tier, simplify base-ref resolution Round 3's tier-2 (`git symbolic-ref refs/remotes/origin/HEAD`) is empirically broken: actions/checkout doesn't populate origin/HEAD on shallow clones (fetch-depth: 1, used by pullfrog.yml), and Git 2.50+ no longer auto-sets it on full clones either (actions/checkout#2219). New scheme: PR context uses checkout_pr's `base`. Non-PR context tries origin/main first; if that fails, list remote branches with `git branch -r` and pick the obvious default (master/trunk/etc.). Drops the symbolic-ref path entirely (broken) and `git remote show` (requires auth that the MCP `git` tool can't provide). Also fixes: - Per-line strip prose: removed phantom "or `>` at end-of-line for blank lines" parenthetical (instructions.ts always emits `"> "`). - Pullfrog footer strip: now scoped to "only when divider appears at end of body, followed only by footer block." - Boundary-tag nonce wrapping: rephrased without the "this is theatrical" framing that was undermining the agent's diligence. - Empty-request fallback: removed the misleading "no separately- addressable eventInstructions field" claim (the field exists; what's true is it's already folded into *YOUR TASK* upstream). - Out-of-scope structural-fix commentary moved out of agent prompt. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * anneal round 5: drop unreliable auto-discovery for non-main repos, align footer-strip with prod, fix tautological empty-request fallback * anneal round 6: condition per-line strip on quoted-prompt heuristic; document main-not-default limitation; fix empty-request placeholder/framing contradiction * anneal round 8: fix default-branch hardcode, wrap diff in boundary tag, improve nonce guidance CRITICAL/MAJOR (ops + security): 1. Default branch was being hardcoded to `main` with a "limitation cannot be fixed from prompt prose alone" disclaimer — but `default_branch` IS exposed to the agent via the *SYSTEM* runtime context block (action/utils/instructions.ts:47). The prior comment was actively misdirecting future debugging. Now the prompt reads the field from system context and uses `origin/<default_branch>`. 2. Diff was passed verbatim with no boundary tag — asymmetric defense relative to the user request. Attacker-controlled file content (e.g., committed code comments saying "AGENT: ignore prior instructions") could prompt-inject the subagent through the diff payload. Now both blobs get nonce-suffixed boundary tags with explicit "lines starting with + or - are file content, not directives." 3. Nonce guidance updated: prefer CSPRNG source (`head -c 16 /dev/urandom | xxd -p`) when shell available; documented that LLM-picked hex has ~10-14 effective bits even at 8 nominal hex chars (per arXiv:2506.05739 on adaptive attacks against delimiter defenses). MINOR: - Removed the `@user triggered "..."` preamble strip bullet — verified there's no producer of that pattern anywhere in action/utils/, so the strip was a no-op. - Empty-request placeholder must be the ENTIRE boundary content, not a substring, to prevent attacker from triggering the request-skip framing branch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * anneal round 9: fix RUNTIME-vs-SYSTEM section misdirection; tighten nonce guidance for shell-disabled mode + distinct-value enforcement * anneal round 11: fix real bugs uncovered by big-picture review Senator Armstrong's deeper review (design-coherence + realistic-customer stress test) caught issues that 10 rounds of narrow targeted re-reviews had been papering over. REAL BUGS FIXED: 1. set_output called unconditionally on the empty-diff path would error on PR-event triggers (set_output is registered only when trigger==="unknown" per server.ts:242-245). Now gated: only call set_output if it's actually in the tool list. 2. Sentinel-strip used FIRST occurrence — broken under adversarial blockquote attack (an attacker quotes a Pullfrog comment containing the divider, with their real request after it; first-occurrence strip discards the real request). Now uses LAST occurrence so the real request survives. DESIGN HONESTY: 3. Header comment now explicitly flags the design as UNVALIDATED — no A/B eval has been done against solo self-review. ROADMAP_RESEARCH.md flags benchmarking as the prerequisite. Header documents the validation gap and what would justify reverting. 4. Header comment elevates the runtime-fence gap from a TODO to a SECURITY GAP that must ship before the prompt protocol can be considered production-hardened. Ordering: runtime fence FIRST, prompt protocol SECOND. SIMPLIFICATIONS (per senior-engineer review): 5. Dropped the second nonce on the diff — the diff is the artifact under review; suspicious instruction-shaped lines in commits are exactly what the subagent should flag, not something to fence off. 6. Dropped CSPRNG-vs-LLM-fallback branching prose — just "16+ hex chars, use /dev/urandom if shell available, otherwise pick." 7. Dropped the regenerate-if-collide rule (vanishingly unlikely with 16 hex chars, costs tokens to enforce). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * anneal round 12: revert round-11 regressions (sentinel-strip, set_output gate, diff nonce) Round 12's sharper review caught three regressions round 11 introduced: 1. Sentinel-strip last-occurrence was strictly worse than first-occurrence for the common "user references a prior Pullfrog comment" case. The adversarial-quote scenario it was defending against is contrived (an attacker can put hostile payload anywhere; strip discipline doesn't change attack surface). Reverted to first-occurrence to align with canonical stripExistingFooter() and avoid silently swallowing user reference context. 2. set_output "gate" via "if it's in your tool list" relied on tool introspection that LLMs cannot reliably perform. Replaced with: just call report_progress; document the workflow_dispatch limitation as acceptable (job log is feedback-of-last-resort) rather than asking the agent to conditional-call a tool that may not exist. 3. Diff was de-nonced in round 11 on the assumption runtime fence ships first, but until that runtime fence lands the plain label is forgeable (committed file content can include "--- END DIFF ---" + injection). Restored nonce wrapping. The cost is one extra hex string; the benefit is real until runtime fence ships. Also added explicit caveat on the self-attested skip marker: the proper fix is MCP-layer dispatch-counting, not commit-message annotation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ruthless cut: revert Build self-review elaboration to compact form main already had subagent dispatch (4 compact lines). This branch added 70+ lines of elaboration — header warnings, base-ref dance, footer-strip rules, nonce- suffixed boundary tags, retry-once skip markers, delegation-discipline list — all predicated on a runtime fence that doesn't exist and validation that never ran. Senior-engineer review (round 11) explicitly recommended cutting; ROADMAP_RESEARCH flags A/B benchmarking as the prerequisite for this design. Net change vs main now matches what the user actually asked for: - drop the optional plan step (and its "follow the plan" / Notes references) - subagent receives the original user request alongside the diff, evaluated against base ref, with explicit no-further-dispatch constraint Everything else reverts to main's prose. ~10 lines net change instead of 70+. * anneal round 13: tighten self-review prompt inputs to runtime-resolvable values Two underspecified inputs flagged by parallel holistic + mechanics review: 1. "the original user request" is empty for non-@pullfrog-tagged auto-triggers (sync, check_suite, opened, etc.); only YOUR TASK is reliably present in the assembled prompt across all event types. Replace. 2. "base ref (PR base or repo default branch)" requires the agent to resolve and fetch the default branch on non-PR runs (origin/<default> typically not fetched). Drop the elaboration — bare git diff captures all changes at step-3 time since step 2 doesn't commit. Aligns with 3ed2c55a's ruthless-cut philosophy: less elaboration, not more. Verified in round 14: YOUR TASK is the literal section header in instructions.ts (buildTaskSection); bare git diff scope is correct. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * restore plan step to Build mode prompt The plan step was removed alongside the MCP-contract plan-required work, but the user only wanted it gone from the MCP contract, not from the prompt itself. Restores step 1 (plan), the "follow the plan" build sub-bullet, the trailing Notes section, and renumbers learningsStep back to 6. Made-with: Cursor * add pullfrog-reviewer named subagent; standardize review fence to non-mutative+non-recursive Defines a constrained `pullfrog-reviewer` named subagent for the Build mode self-review and /anneal lens dispatch, with a single source of truth in action/agents/reviewer.ts (allowed tools, denied mutating MCP tools, system prompt). Enforcement: - opencode: real fence via agent.pullfrog-reviewer block in buildSecurityConfig — denies edit/bash/task and globs each mutating pullfrog_* MCP tool to false. - claude-code: forward-looking only. Per-agent disallowedTools is upstream-broken (anthropics/claude-agent-sdk-typescript#172, open as of latest update Mar 2026 — subagent child processes still see and can call disallowed tools, including Task). The --agents JSON is defined anyway so the fence becomes real when upstream fixes #172; until then the prompt prose constraint is the actual fence. The PreToolUse hook workaround that does enforce is out of scope. Read-only MCP tools (get_*, list_*) intentionally remain enabled so the reviewer can pull PR/issue/check context without dispatching state changes. Both modes.ts Build self-review and the two anneal.md files now share the same "non-mutative + non-recursive" framing — file reads, grep, search, web search/fetch, read-only shell, and read-only MCP queries allowed; writes, state-changing MCP, and nested subagent dispatch denied. Resolves the previous inconsistency where /anneal allowed read-only shell and Build self-review banned all shell. Made-with: Cursor * Build self-review: pass build-phase failure summary to reviewer subagent Adds an instruction in step 4's dispatch: along with YOUR TASK and git diff, pass a tight plain-text summary of any lint/typecheck/test failures fixed during build (what broke, root cause, the fix) — or "no build-phase failures" if clean. Goal: let the reviewer check that fixes addressed root causes rather than suppressed symptoms (e.g., editing a test to make it pass instead of fixing the bug). Implemented as agent self-summarization rather than piping raw build output to avoid context flooding — typecheck/test output can be hundreds to thousands of lines per failure. The agent has the failure trail in its own conversation history and summarizes from memory; the reviewer sees a few lines per failure, not raw stderr. Caveat: this is a plausible-but-unvalidated quality improvement. The mechanical justification (signal already produced, currently not passed on) is real; "this catches more bugs" is a hypothesis that will need actual run data to confirm. Downside is bounded (reviewer gets slightly more context, no behavior change if the summary is empty or ignored). Made-with: Cursor * Build self-review: distill /anneal delegation + research discipline into dispatch instructions Lifts the codified learnings from /anneal's "Delegation discipline" and "Research discipline" sections into Build mode step 4. These rules are about how-to-prompt the reviewer (not about parallelism), so they transfer losslessly to single-agent dispatch and address bias modes the prior prompt was silent on: - Don't summarize what you implemented (biases toward shape-validation) - Don't curate a reading list (your curation is itself a lens) - Don't pre-shape output with severity/category (leaks hypotheses) - Don't defect-hunt in parallel (reintroduces the implementation bias the subagent is meant to mitigate) - For diffs touching third-party API contracts / SDK semantics / framework directives / DB engine specifics, instruct the reviewer to verify load-bearing claims via web search and quote URLs rather than trust training data Restructures step 4 from one paragraph into three (constraints, inputs, discipline) plus a final review-and-commit paragraph for readability. These are validated learnings from many anneal rounds, not theoretical best practices — they're the single substantive piece this branch was missing. Made-with: Cursor * pullfrog-reviewer: drop MCP deny-list, rely on prose constraint Per-PR-review feedback: hand-maintaining MUTATING_MCP_TOOLS against action/mcp/server.ts was fragile — a future mutating tool added to the MCP server without updating this list would silently grant write access to the reviewer. Inverting to an allowlist or adding a structural test both keep the drift problem. Drop the list and all per-agent runtime denies (claude disallowedTools, opencode tools/permission map). Strengthen REVIEWER_SYSTEM_PROMPT to spell out the categories of state-changing MCP tools by example and explicitly tell the model to apply the no-op-if-reverted invariant to tools added after the prompt was written — the rule is the invariant, not the enumeration. Keep the named subagent so the prompt is reliably injected. Update modes.ts and both anneal.md copies to drop the runtime-enforces-where-supported claim. Co-authored-by: Cursor <cursoragent@cursor.com> * pullfrog-reviewer: fix description to allow read-only shell The description field was overstating the constraint as 'must not shell', but the system prompt explicitly allows read-only commands like git diff, git log, cat, ls. Align description with the actual contract. Co-authored-by: Cursor <cursoragent@cursor.com> * restructure Review/IncrementalReview as multi-lens parallel-subagent orchestrators For someone else's PR, parallel lenses (correctness, security, research-validated claims, user-journey, etc.) provide breadth across angles that a single subagent can't carry coherently. The orchestrator does triage → parallel read-only subagent fan-out → aggregate → draft comments → submit. Lens count by risk: 1 lens for trivial PRs, 2-3 for typical, 4 for high-risk surfaces (billing, auth, migrations). This branch contains ONLY the Review/IncrementalReview multi-lens prompts. Build mode keeps its single-fresh-eyes-subagent shape (different problem — orchestrator just wrote the code; bias-mitigation comes from one subagent that doesn't share the implementation context). The Build changes ship in a separate PR (self-review-subagents → main). Pending validation against a real code-heavy PR before merge — e2e on a docs-only preview repo only exercised the trivial-1-lens path. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Review/IncrementalReview: dispatch fan-out via reviewfrog named subagent The fan-out steps previously said "launch one read-only subagent per lens" without naming the subagent. That bypassed the only enforcement layer the named subagent provides: a baked-in system prompt that restates the non-mutative + non-recursive contract regardless of what the orchestrator sends. Both modes now dispatch via REVIEWER_AGENT_NAME (matching Build mode's self-review wiring) and restate the constraint inline so the rule is present twice. * rename pullfrog-reviewer → reviewfrog Mechanical rename of the named subagent. Constant names (REVIEWER_AGENT_NAME, REVIEWER_SYSTEM_PROMPT) and file paths (action/agents/reviewer.ts) stay as-is — only the agent identifier string and prose references in anneal.md and code comments change. * modes/anneal: trivial PRs skip review entirely; lens count is judgment, not table; allow subsystem lenses Three coupled changes to Review/IncrementalReview/Build self-review and the canonical /anneal command: 1. Trivial-skip: trivial diffs (single-line, formatting/comment-only, doc typo, low-risk dep bump, no behavior change) skip the fan-out / self-review entirely. Build mode skips its self-review subagent; Review submits a bare "Reviewed — no issues found." without dispatching lenses; IncrementalReview takes the existing non-substantive submit path. Tiebreaker on uncertainty: treat as non-trivial. 2. Drop prescriptive lens counts. Replaces "2-3 typical / 4 high-risk cap / 1 trivial" with judgment-based guidance: pick as many lenses as the target has distinct surfaces of risk worth investigating independently; one is sometimes enough; bias toward more (and toward follow-up rounds in /anneal) for high-stakes subsystems; 5+ is a smell that lenses are overlapping rather than covering distinct ground. 3. Subsystem lenses. Adds an explicit second flavor of lens — domain-scoped frames like "the auth lens", "the billing lens", "the schema-migration lens" — alongside the existing themed lenses (correctness, security, user-journey, etc.). Stack themed + subsystem freely. modes.ts and anneal.md (.cursor/ + .claude/, kept byte-identical) move together so the canonical pattern doc and the orchestrator prompt agree on the protocol. * add SessionLabeler so parallel subagent log lines are differentiable When the orchestrator dispatches multiple `reviewfrog` subagents in a single assistant turn (the parallel fan-out the multi-lens prompt now requires), their tool_use / tool_result / text events arrive on opencode's NDJSON stream tagged with distinct `sessionID`s but go through a single `[Pullfrog]` log prefix. Result: log readers can't attribute which lens issued which tool call, making CI logs unreadable for any review with 2+ lenses. SessionLabeler: - Binds the first-seen sessionID to "orchestrator" and subsequent new sessionIDs to FIFO-popped lens labels seeded from task tool_use inputs. - Derives labels from `lens: <name>` markers in the dispatch prompt, the Task `description` field, the `subagent_type`, or `subagent#N` fallback. - Keeps state local to a single runOpenCode invocation. Wiring: - opencode.ts: every event handler (init, message, text, tool_use, tool_result) now looks up the per-event label and prefixes log output via formatWithLabel(). Subagent finalOutput/token-reset paths gated on ORCHESTRATOR_LABEL so child sessions can't clobber parent state. - claude.ts: claude rolls subagent activity into a single tool_result block (no per-event session_id), so it gets a minimal "» dispatching subagent: <label>" log line on Task tool_use as the only attribution. - modes.ts (Review + IncrementalReview): orchestrator instructed to set the Task `description` to the lens name, since that's what the labeler reads when no explicit `lens:` marker is in the prompt. Tests: 18 unit tests covering label derivation, FIFO binding, interleaved sessions, fallback paths, and a realistic four-lens parallel fan-out simulation. Full action test suite stays green (400 passing). This is the pre-flight instrumentation that the multi-lens validation runs depend on — without it, post-hoc log analysis can't tell two subagents apart. * log subagent dispatch + finish at info level for per-lens visibility OpenCode's runtime currently encapsulates subagent execution inside the `task` tool — subagent-internal tool_use/tool_result events do not surface on the parent's NDJSON stream. The SessionLabeler I added in 0c4647f4 therefore can't actually differentiate concurrent subagent log lines (there are no concurrent log lines on the parent stream to differentiate). What CAN be observed on the parent stream is the dispatch and the result of each `task` tool call. This patch surfaces both at info level: » dispatching subagent: lens:security (subagent_type=reviewfrog) ... » subagent finished: lens:security (15.3s, status=completed) — ... Without this, a 4-lens parallel fan-out looks like 4 dispatches in close succession followed by a long quiet gap and then an aggregation turn — you can't see when each lens finished or how the durations overlapped. With it, parallel execution is visible from the timestamps on the "finished" lines. The dispatched label comes from SessionLabeler.recordTaskDispatch (so both lines share the same lens identity). taskDispatchInfo maps callID to {label, startedAt} so the matching tool_result can compute duration and emit the finished line. Also added a defensive comment on the SessionLabeler instantiation documenting that the per-event session-prefix path is currently dormant in the opencode runtime, but kept in place so attribution flips on automatically if/when opencode begins streaming subagent sessions. * fix subagent-finished log: hybrid exact+FIFO callID matching opencode does not consistently surface a tool_result callID matching the originating tool_use callID for the `task` tool, so the previous exact-match-only finish line never fired. Now we: - Dual-index task dispatches by callID AND in a FIFO queue. - Track non-task callIDs so we can identify "unrecognised callID" results as likely-task-with-mismatched-id. - On tool_result, exact-match first; fall back to FIFO when the output looks like a subagent reply (>300 chars) and the callID is unknown. - Flush leftover dispatches at run end with an "(inferred at run-end)" suffix so the gap is visible if subagent results arrive entirely off the tool_result event path (e.g. inlined into the next assistant message). * fix subagent-finished log: move run-end flush to post-subprocess block Investigation on T3 + finish-log-validation runs revealed two real issues with my prior attempt: 1. The `result` event handler is dead — opencode never emits a `result`-typed event over its NDJSON stream, so the inferred-at-run-end flush I had placed there never fired. Move the flush to right after `runSubprocess` returns where it actually executes. 2. The FIFO heuristic was too strict — the >300-char output check excluded short or empty outputs that opencode's `task` tool_result appears to carry (the subagent's full reply seems to arrive via a separate channel, not the result event itself). Drop the size check; rely solely on `knownNonTaskCallIDs` to keep genuinely-non-task tool_results from popping a pending task. Net effect: every `task` tool dispatch gets a matching `» subagent finished` line in the logs, either from the FIFO fallback during the run or from the run-end flush as a backstop. * modes/anneal: anchor lens calibration in worked examples The prior trivial-skip definition ("single-line fix, formatting-only, …") was anchored on diff size, but real-world risk is anchored on diff *shape*: a 5000-line lockfile regen IS trivial, and a 1-line SQL operator flip in a billing path is NOT. The prior lens-count guidance ("there's no fixed count, bias toward more for high-stakes subsystems") gave the agent no concrete shapes to anchor against, so runs varied between under-pick (4 generic lenses on a billing PR) and over-pick (5 overlapping themed lenses on a refactor). This commit hardens both: - Trivial definition gets explicit "looks trivial but isn't" anti-patterns: SQL operator flips, money/tax/timeout constants, feature-flag defaults, comparison operator changes, semantic 1-liners buried in whitespace, public-API renames, new direct deps. Skip lists get explicit "size doesn't matter" calibration for lockfile regens and mechanical renames. - Lens count gets a worked-example ladder: 1 lens (refactor / new test file / isolated fix), 2-3 lenses (typical features), 4-5 lenses (high-stakes subsystem touches), 6+ is a smell. - Subsystem lenses get an explicit recommendation to lead over generic themed equivalents for high-stakes domains, with the reasoning: domain framing primes the subagent for domain-specific failure modes (double-charges, refund races, dispute flows) the generic lens misses. Mirrored byte-identical into both anneal.md copies; modes.ts updates all three review surfaces (Build self-review, Review triage, IncrementalReview triage). * fix harness false-failure when Review submits without todowrite Review and IncrementalReview prompts explicitly forbid calling report_progress (the review IS the durable record). The post-run harness in action/utils/run.ts errors with "agent completed without reporting progress" when toolState.wasUpdated is false at exit. Until now, the only path that set wasUpdated for these modes was the todoTracker's debounced publish — which only fires if the agent happens to call todowrite during the run. Adversarial run on PR #16 (misleading-trivial billing tweak) hit exactly this case: agent went straight from triage → fan-out → review submission with no todowrite calls, and the harness reported failure even though the substantive review was successfully submitted with two inline comments. Fix: create_pull_request_review now marks wasUpdated=true (and finalSummaryWritten=true) on every terminal path — successful submit, empty-content skip, and all-comments-dropped skip. Submitting a review is unambiguously a "done" signal in these modes. Found via adversarial testing of the multi-lens orchestrator on a 1-line tax constant change. Logged in /tmp/pullfrog-validation/v3/. * fix harness false-failure when Review submits without todowrite (correctly) Replaces the prior fix (acc2bd65) which set wasUpdated=true inside create_pull_request_review. That approach worked for the harness check but broke the orphan-comment cleanup: with wasUpdated=true and finalSummaryWritten=true, the (!wasUpdated || trackerWasLastWriter) condition in main.ts evaluated false and the "Leaping into action" progress comment was left behind on every Review run — the exact behavior the cleanup logic was designed to prevent (see plans/review_progress_comment_cleanup_b0120f6c.plan.md). Correct fix: change the harness check in action/utils/run.ts to recognize a submitted PR review as an alternate completion signal alongside wasUpdated. wasUpdated stays false on purpose so cleanup deletes the orphan, but the run no longer false-fails when the agent followed the Review-mode contract (submit a review, never call report_progress). The bug was discovered during adversarial testing of PR #16 (misleading-trivial billing tweak) where the agent went straight from triage → fan-out → review submission without using todowrite, causing the harness to error even though the substantive review (a CAUTION blocking review with two inline comments catching a 10x tax cut) was successfully posted. * fix harness false-failure for Review modes (mode-based carve-out) Replaces the prior carve-out (4c0f69aa) which gated on toolState.review.id. That worked for runs where the review tool actually populated the toolState (validation-2 succeeded), but failed for runs that took a slightly different path where the assignment didn't propagate visibly to handleAgentResult — even when the review verifiably posted to GitHub. Found this empirically: PR #19 (pure mechanical rename across 20 files) opened with the prior fix in place, the agent picked exactly one impact lens (correct calibration!), confirmed no stale references, submitted "Reviewed — no issues found." successfully (visible in GitHub API), and the harness STILL errored with "agent completed without reporting progress." Same SHA, same branch, same code as validation-2 which passed. The toolState.review.id check turns out not to be reliably visible from the run.ts handler in all paths. Better fix: gate on toolState.selectedMode. Review and IncrementalReview modes are designed to never call report_progress (the review is the durable record, and IncrementalReview's non-substantive path produces no artifact at all by design). The harness completion check makes no sense for these modes — skip it entirely. The agent's clean subprocess exit is the completion signal. This also handles edge cases the previous fix missed: IncrementalReview's non-substantive path (no review submitted by design) and any future Review-flow shape that doesn't end at create_pull_request_review. * ci: trigger Test run to validate models-live timeout/concurrency changes * ci: prune passthrough models from live smoke matrix openrouter/* aliases and keyed opencode/* aliases are routing-layer wrappers around models we already smoke-test directly. running every passthrough burns CI minutes (~30 min/run) without catching anything the direct smoke doesn't — slug drift is already covered by the models-catalog job. keep one canary per routing layer (openrouter/claude-sonnet, opencode/claude-sonnet) to validate auth + tool-call translation. free opencode models stay in the matrix since they're unique to the provider. INCLUDE_ALL_PASSTHROUGHS=1 bypasses the prune for full validation. matrix size: 37 → 20 jobs. * fix isRateLimited false-positive on UUIDs/timestamps containing 429 The bare "429" substring pattern was matching MCP session IDs (e.g. `...-4429-...`) and microsecond timestamps in agent stdout, sending transient failures down the 60s rate-limit retry path. With the new 4-minute per-step CI timeout, that backoff plus a slow retry pushed the step past its budget and timed out. Switch to regex patterns and gate the numeric code on `\b429\b` so word boundaries prevent the substring false-match. Verified locally that the UUID `97287d2f-ae1d-4429-8627-73e2454e80ca` and timestamp `02:04:50.9429654` no longer match while real `HTTP 429` / `"status":429` strings still do. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Colin McDonnell <colinmcd94@gmail.com> Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> |
||
|
|
c6a757424c |
Stop hook + learnings reflection via post-run loop (#515) (#548)
* add stop hook + learnings reflection to post-run loop (#515) stop hook (#515): repo-configured script that runs after the agent finishes. non-zero exit resumes the agent with the hook output as guidance; persistent failure (3 attempts) marks the run failed. the dirty-tree and stop-hook gates share a single retry loop so a fix + push happen in one turn. learnings reflection: per Colin, the learnings step baked into mode checklists rarely fires — the agent stays focused on the task and the meta-ask falls through. the post-run loop now delivers a dedicated one-shot --continue turn asking the agent to call update_learnings if relevant, nothing else competing for attention. reflection doesn't consume the gate-retry budget; if it dirties the tree, the next loop iteration catches it via the dirty-tree gate. plumbing: Repo.stopScript column + migration, zod schema, run-context api, AgentSettings UI. RepoSettings.stopScript threads through to AgentRunContext and into each agent harness. subprocess-dependent logic lives in action/agents/postRun.ts to keep action/agents/shared.ts lean — shared.ts is reachable from pullfrog/internal, and pulling node:child_process through it leaks into root tsc (which uses bundler resolution, not NodeNext). * fix: preserve successful run when reflection turn fails The post-run reflection turn (update_learnings nudge) is a best-effort one-shot; its failure must not flip a successful run to failed. Prior code overwrote `result` with the reflection's return value, so a model API error during reflection caused the whole run to be reported as failed even though the gated work had already completed cleanly. Now: save the pre-reflection result, and if reflection returns `success: false`, log a warning, restore the prior success, and exit without re-invoking the gates (re-running a freshly-green stop hook risks a flaky false-positive failure). Adds action/agents/postRun.test.ts covering the reflection path — previously uncovered. * fix: surface both stop-hook stdout and stderr to the agent The `(stderr || stdout)` heuristic in executeStopHook dropped stdout entirely whenever stderr had any content. Scripts that emit a benign warning to stderr and the actionable error to stdout (common for wrapper scripts) starved the agent of the information it needed to fix the issue. Now concatenate both streams (stderr first, stdout second, skipping empty ones) before truncation. This keeps stdout's tail — usually where summaries and totals live — intact under the 4096-char cap. * test: lock in the core post-run retry + reflection invariants PR #548's test plan ships four manual verification scenarios. Convert three to vitest coverage, catching regressions on the hottest code paths: - persistent stop hook failure exhausts MAX_POST_RUN_RETRIES and surfaces as AgentResult.error with both the retry count and the verbatim hook output (so the GitHub-comment rendering stays actionable). - every gate retry is fed the hook output as the resume prompt. - usage aggregates across the initial run plus every retry (billing relies on this). - reflection turn still fires when no stop hook is configured and the tree is clean. Manual item remaining is the full UI round-trip of the settings form, which is out of scope for unit tests. * test: cover executeStopHook soft-fail and truncation invariants Three paths the PR documents but previously had no regression gates: - timeout (SPAWN_TIMEOUT_CODE) and activity-timeout (SPAWN_ACTIVITY_TIMEOUT_CODE) must return null, not a failure. a hook that times out is an infra problem; retrying with an agent turn risks an infinite loop. - spawn errors (ENOENT from a typoed binary, etc.) take the same soft-fail path for the same reason. - oversize hook output is truncated to the last 4096 chars with a "truncated" marker, keeping the tail (where summaries live) and protecting the 65535-char GitHub-comment budget downstream. Regression targets — a refactor that accidentally surfaces an infra failure as a gate failure, or blows the comment budget, will now fail loudly in CI. * test: cover soft-fail, no-resume, and short-circuit invariants Three more documented behaviors that previously had no regression gates: - dirty-tree-only is a soft-fail: persistent uncommitted changes log and warn but DO NOT flip the run to failed. a regression that started surfacing this as AgentResult.error would break every run that leaves a test fixture untracked. - canResume=false + stop hook failure still surfaces the hook failure as AgentResult.error. the retry budget is zero so "N retry attempts" is correctly omitted from the message, but the run still reports WHY it failed rather than silently reporting success. - initial result with success=false short-circuits the loop: no gate checks, no reflection, no resume calls. the original agent error flows through verbatim for clean triage. Also reset mockedSpawn in beforeEach so test state doesn't leak between cases. * test: lock in the reflection-dirties-tree → dirty-tree-gate path The PR description claims: "if the reflection turn dirties the tree, the loop picks that up on the next iteration via the normal dirty-tree gate." There was no regression gate on this invariant. Without it, a refactor that moved the reflection out of the retry loop (e.g., into a one-shot post-loop call) would silently bypass the commit-before-you-finish contract whenever the agent misbehaves during reflection — uncommitted changes would ship as part of the run's "success" state. The test sequences three getGitStatus returns (clean → dirty → clean) and asserts two resume calls: REFLECTION first, then UNCOMMITTED CHANGES with the dirtying file in the prompt. * fix: preserve pre-reflection task output when reflection succeeds the reflection turn's reply ("done" or "updated learnings with N bullets") is a meta-ask, not a task summary. before this fix, result = reflectionResult clobbered the original task's output on the returned AgentResult, so downstream consumers (handleAgentResult's fallback path when toolState is empty, programmatic callers of main()) saw the reflection's trivial reply instead of the real summary. spread reflectionResult to inherit fields subsequent gate retries need (e.g. the new sessionId claude emits per --resume invocation), but keep the pre-reflection output verbatim. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: fall back to reflection's output when pre-reflection output is empty the prior fix used `??` which only fell through on null/undefined. runs that communicate exclusively through MCP tools (e.g. report_progress) and emit no plain text leave result.output = "", which `??` preserved as-is — dropping the reflection's reply and leaving handleAgentResult's fallback path with nothing to show. switch to `||` so empty-string pre-reflection output yields the reflection's output instead of ""; non-empty task output still wins as intended. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test: drop reflection-failure-skips-hook test (over-specified control flow) the test pinned the literal `break` in the post-reflection failure branch with stopScript=null, asserting only that getGitStatus was called once. that's not a behavior contract — a reasonable refactor (e.g. `continue` to re-check gates with explicit flake guards) would fail this test even though the new behavior would be fine. the "does not flip a successful run to failed" test already covers the only thing callers depend on. * test: drop low-value mock-driven tests from postRun - "fires the reflection turn when no stop hook is configured" — fully subsumed by the output-preservation test (asserts task output survives, which is only possible if reflection fired). - "uses stdout alone" / "uses stderr alone" — pin format trivia (`filter(Boolean).join`) that LLMs ignore. - "returns empty output (not undefined) when both streams are empty" — guards a TS-impossible case; every consumer uses `output || "(no output)"`. - "returns null on activity-timeout" — duplicate of the timeout test; same `return null` branch with a different constant. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Colin McDonnell <colinmcd94@gmail.com> |
||
|
|
57f54e37c5 |
add bundled git-archaeology skill, auto-installed for opencode and claude (#565)
* add bundled git-archaeology skill, auto-installed for opencode and claude ships a SKILL.md teaching agents the underused git history primitives (pickaxe -S/-G, -L for function/line ranges, --reverse blame, deleted-file recovery) so they stop scrolling git log -p when blame comes up empty. introduces a lightweight bundled-skill path alongside the existing addSkill (npx skills add) flow used for external skills like agent-browser. SKILL.md is inlined into dist/cli.mjs via esbuild's text loader and written to <home>/.agents/skills/<name>/SKILL.md at runtime — no network, no version drift, no per-run install cost. * fix: register vitest plugin to load .md as text for bundled-skill tests * fix: drop vite type import from vitest plugin (vite isn't a direct dep) * fix: load bundled skills via readFileSync so source mode works esbuild's text loader only applies to the npm-bundled dist/cli.mjs path. the preview / oss path runs cli.ts directly with node (PULLFROG_FORCE_LOCAL_CLI=1 in runCli.ts#runLocalCli), where node has no idea how to import .md files — ERR_UNKNOWN_FILE_EXTENSION crashes the action before any agent starts. switch to runtime readFileSync that checks both candidate locations: - source mode: <actionRoot>/skills/<name>/SKILL.md (relative to utils/skills.ts) - bundled mode: <distDir>/skills/<name>/SKILL.md (esbuild copies the tree) drops the no-longer-needed esbuild text loader, vitest .md plugin, and ambient *.md type declaration. wiki/skills.md updated with the why. * fix: write bundled skills to per-agent dirs so claude actually registers them |
||
|
|
3bacf01e48 |
bump model registry for deepseek v4, kimi k2.6, claude opus 4.7 (#554)
* bump model registry for deepseek v4, kimi k2.6, claude opus 4.7
deepseek released v4 (pro/flash) on 2026-04-24 as a generational replacement
for v3-era reasoner/chat. deepseek will fully retire deepseek-chat and
deepseek-reasoner on 2026-07-24 — both already route server-side to v4-flash.
introduce deepseek-pro (preferred) and deepseek-flash slugs and mark the
legacy aliases deprecated via fallback so existing users transparently
upgrade. mirror on the openrouter side.
also bump moonshotai/kimi to k2.6 (from k2.5, 2026-04-21 release) and bump
the anthropic claude-opus openrouter resolves to 4.7 (we'd already moved the
native side to claude-opus-4-7 but openrouter resolves still pointed at 4.6).
update OSS_PROXY_MODEL fallback and stale doc reference accordingly.
snapshot regenerated; all 111 catalog tests + 66 unit tests pass.
* walk fallback chain when resolving the OSS proxy model
the OSS proxy path in run-context/route.ts read alias.openRouterResolve
directly, bypassing the fallback chain. so an OSS repo configured with
deepseek/deepseek-reasoner kept proxying to openrouter/deepseek/deepseek-v3.2
instead of resolving through the new fallback to openrouter/deepseek-v4-pro.
that worked today (v3.2 routes server-side to V4-Flash) but breaks when
deepseek and openrouter retire v3.2 alongside the 2026-07-24 deprecation.
extract the chain walk into a private resolveTerminalAlias helper and add
resolveOpenRouterModel that mirrors resolveCliModel but returns
openRouterResolve. fallback semantics now apply uniformly across both
runtime resolution paths.
* hide deprecated aliases from model selector dropdowns
aliases with a fallback (currently deepseek-reasoner / deepseek-chat /
openrouter/deepseek-chat) should not be selectable from the model dropdown
or the interactive cli model picker — they're a transition path, not a
choice. but if a repo already has a deprecated slug stored in the db, the
selector trigger still resolves it against the full alias registry so the
display name renders correctly until the user opens the menu and picks a
new model.
verified manually: deepseek submenu shows pro+flash only, openrouter submenu
shows pro+flash but no chat, and a deprecated stored value still renders
its full display name in the trigger.
* ci: run models-live on PRs that touch resolution files
Previously the per-alias smoke matrix only fired on push-to-main, so
resolution-affecting PRs (this one included) shipped without ever
exercising the agent harness against the real provider for each alias.
Loosen the gate on the `aliases` step in the `changes` job to fire
whenever the `models` paths-filter matches (action/models.ts,
action/package.json, action/agents/**) — same set that already drives
the comment about "resolution-affecting files". `models-live` itself
is unchanged: it still keys on a non-empty matrix.
`models-catalog` stays gated to main-push intentionally — its existing
comment justifies that (transient upstream catalog drift shouldn't
block PRs).
* relabel codex aliases as GPT, bump to 5.5 family, add gpt-pro
OpenAI retired the "-codex" model suffix on 2026-07-23 (gpt-5.3-codex,
gpt-5.1-codex-mini, gpt-5.2-codex et al all shut down) and unified the
codex+gpt lines into a single family at gpt-5.4. Per OpenAI's own
deprecation table, every "-codex" substitute is plain gpt-5.x — no
future Codex-suffixed frontier models are coming.
Keep the existing slugs for DB stability (no migration needed) but roll
displayName + resolve forward across openai, opencode, and openrouter:
- openai/gpt-codex → "GPT" → openai/gpt-5.5
- openai/gpt-codex-mini → "GPT Mini" → openai/gpt-5.4-mini
- openai/gpt-pro (new) → "GPT Pro" → openai/gpt-5.5-pro
Same relabel + new gpt-pro slug for opencode/* and openrouter/*.
gpt-5.5 (and gpt-5.5-pro) hit the OpenAI public API on 2026-04-24,
day after launch — both are live on OpenRouter as well.
There's no gpt-5.5-mini yet (analysts speculate late June – mid August
based on the gpt-5.4-mini cycle), so "GPT Mini" stays at gpt-5.4-mini
for now; one-line bump when the smaller variant ships.
Also pick up unrelated upstream catalog drift in the snapshot
(xai/grok-4.3 released 2026-05-01, openrouter/poolside laguna).
* deprecate gpt-codex aliases, mint gpt/gpt-pro/gpt-mini, render terminal alias in UI
The previous commit relabeled gpt-codex/gpt-codex-mini in place ("GPT" /
"GPT Mini") so a single slug carried two different identities. That worked
but was self-contradictory: the slug name no longer described the model.
Switch to the same shape we use for the deepseek V3→V4 transition:
- Mint new live slugs: openai/gpt, openai/gpt-pro, openai/gpt-mini
(mirrored on opencode/* and openrouter/*)
- Restore honest deprecated state on gpt-codex/gpt-codex-mini —
displayName "GPT Codex" / "GPT Codex Mini", original 5.3-codex /
5.1-codex-mini resolves, fallback set to the new gpt / gpt-mini slugs
- resolveCliModel + resolveOpenRouterModel walk the chain (existing
machinery), so DB rows holding "openai/gpt-codex" transparently route
to gpt-5.5 with no migration
UI render contract: display sites resolve to the *terminal* alias so a
deprecated stored slug shows the model the user is actually running, not
the historical name. Three call sites updated:
- components/ModelSelector.tsx (dropdown trigger label + provider label)
- action/utils/buildPullfrogFooter.ts (PR-comment "Using `X`" footer)
- action/commands/init.ts ("using model X" startup line)
Promoted internal resolveTerminalAlias → exported resolveDisplayAlias so
all three sites use the same primitive (also re-exported from external.ts
+ internal/index.ts so the Next.js app can import it).
Selectable lists (dropdown options, init picker) still filter on
!a.fallback so deprecated slugs never appear as fresh choices — only
deprecated stored values render.
wiki/model-resolution.md: replaced the muddled "slug names outlive
product names" bullet with a clear decision table for in-place bump
(generational, e.g. Opus 4.6 → 4.7) vs. deprecate+replace (vendor
restructures, e.g. codex → unified GPT, deepseek V3 → V4). Documents
the UI render contract too.
models-live CI matrix will smoke-test all 6 new slugs (gpt, gpt-pro,
gpt-mini × openai/opencode/openrouter) plus the 6 deprecated codex slugs
(which resolve through fallback to the same terminal targets) — 12 jobs
total against real provider APIs.
* wiki: slugs are evergreen, resolves are versioned
Document the slug-naming rule explicitly so future entries don't repeat
the deepseek-chat/deepseek-reasoner mistake (mirroring an upstream's
versioned/product-line-specific ID into the slug). Slugs should track
brand-style tier names that survive major version bumps; embedding
versions is the resolve string's job.
|
||
|
|
55c95e6f50 |
Fix Node 24 action bootstrap fallback (#556)
* Fix Node 24 action bootstrap fallback Resolve the published CLI launcher through PATH so runners missing a sibling Node 24 npx can still start, and make post cleanup recognize prefixed leaping comments. * Bump Pullfrog action package version Ensure the Node 24 bootstrap and post-cleanup fixes publish to npm and move the v0 action tag. * Walk PATH for corepack and npx in action bootstrap ensureActionDependencies and runPackageCli now resolve corepack/npx through PATH the same way as the npx-via-PATH fix, so Node 24 runner pools missing either sibling can still bootstrap. Also adds a Zod-mirror settings helper for the preview-556 repo and documents the per-PR settings workflow. * log when corepack PATH fallback is used |
||
|
|
f662b1a0c8 |
unify per-run token + cost accounting + persist to WorkflowRun (#547)
* unify per-run token + cost accounting across agents every agent harness now logs the same 5-column (or 6 with cost) table and populates the same AgentUsage contract, regardless of agent or upstream provider. previously OpenCode and the Claude fallback path emitted a 3-col table whose "Input Tokens" was actually only the non-cached delta, silently dropping cache read/write — real runs were being reported at ~0.4% of their true input (e.g. one baseline showed Input=30 while step_finish events summed to cache_read=724,753). changes: - add logTokenTable helper in action/agents/shared.ts with stable columns: Input | Cache Read | Cache Write | Output | Total | Cost ($). cost column renders only when a value is known. - action/agents/opencode.ts: accumulate step_finish.part.tokens AND step_finish.part.cost (sourced from models.dev inside opencode — confirmed working across Anthropic, OpenAI, Google, xAI, DeepSeek, Moonshot, and OpenRouter). drop the event.stats.total_tokens fallback since that payload has no cache breakdown. - action/agents/claude.ts: success-path now treats input_tokens as the non-cached field (matching OpenCode semantics), carries cache_read_input_tokens / cache_creation_input_tokens separately, and captures total_cost_usd from the final result event. the per-message fallback accumulator now captures cache fields too so it's no longer lossy when the result event never fires. - formatUsageSummary gains a Cost ($) column that matches the stdout table row-for-row; missing values render as "—". - scripts/token-usage.ts parses all three historical formats (new 5-col, legacy 4-col Claude success, legacy 3-col lossy) and explicitly flags the lossy runs instead of averaging misleading values. validation (pnpm play --local, identical "say hello" prompt): agent+model Input CacheR CacheW Output Total Cost OpenCode + Anthropic Sonnet 4.6 4 41,177 20,735 129 62,045 $0.0921 Claude CLI + Anthropic Sonnet 4.6 9 80,133 11,611 389 92,142 $0.0766 OpenCode + OpenAI codex-mini 10,893 46,976 0 606 58,475 $0.0059 OpenCode + Google Gemini 3 Flash — — — — — $0.0114 OpenCode + xAI Grok 4 Fast — — — — — $0.0035 OpenCode + DeepSeek Chat 18,854 0 0 1 18,855 $0.0053 OpenCode + Moonshot Kimi K2.5 — — — — — $0.0106 OpenCode + OpenRouter→Anthropic — — — — — $0.0617 OpenCode + OpenRouter→OpenAI — — — — — $0.0038 * isolate play.ts from developer gitconfig play.ts is a CI-emulator but inherits the developer's user- and system-scope gitconfig. a common local convenience — url."git@github.com:".insteadOf "https://github.com/" to force SSH auth — gets applied at read time on every git call inside the temp repo, causing `git remote get-url --push origin` to return an SSH URL instead of the stored HTTPS one. pullfrog_push_branch's validatePushDestination (correctly) treats that as tampering and blocks the push. the agent then burns the full MAX_COMMIT_RETRIES budget trying workarounds that can't beat a user-scope insteadOf rule, turning a trivial "say hello" run into a 1.35M-token session. point GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM at /dev/null inside run() so the play process and its spawned agent see the same empty gitconfig that a real CI runner would. CI has no rewrites, so this is a no-op there; dev machines get CI-identical git state. SSH client config (~/.ssh/config and keys) is separate from gitconfig and is unaffected, so setupTestRepo's SSH clone still works locally. setupGit only writes --local scope, so nothing downstream depends on user-scope values. verification: with the scratch repo cleaned up and this isolation in place, OpenCode + Anthropic on the same "say hello" prompt goes from 1,349,654 tokens / $2.00+ to 62,045 tokens / $0.0921 — no retry loop, no push blocks. * persist aggregated token + cost usage to WorkflowRun AgentUsage has been memory-only — rendered into the GitHub step summary and then discarded when the runner tears down. that made questions like "avg cost per customer per day" require log-spelunking. persist it: - add Int? columns for inputTokens / outputTokens / cacheReadTokens / cacheWriteTokens and a Decimal? costUsd column on workflow_runs. Int4's 2.1B ceiling is ~200x larger than any realistic run so BigInt would be overkill. costUsd uses the same default Decimal precision as existing money columns (accounts.usageUsd, proxy_keys.hwmUsage). - extend PATCH /api/workflow-run/[runId] to accept the new numeric fields alongside the existing artifact strings. per-field type validation ensures the allowlist stays scalar-safe and rejects negative / non-finite values. - generalize patchWorkflowRunFields in the action so it accepts a mixed string/number payload, and add an aggregateUsage(entries) helper that sums per-agent AgentUsage records into a single patch. - call the reporter from main.ts's outer finally block, gated on toolContext. this is the shared cleanup path that every agent implementation flows through — claude.ts, opencode.ts, and any future harness all push their AgentUsage into toolState.usageEntries via the same line 468, so one finally-block call covers them all. running in finally also means partial usage gets persisted even when the agent errored out mid-run. * anneal token + cost accounting follow-up polish from a review pass: - aggregate usage across commit-retry iterations inside each agent harness. previously runClaude / runOpenCode returned only the final retry's usage, so any run that hit the dirty-tree retry loop under-counted tokens and cost in both the stdout table and the WorkflowRun row. added a shared mergeAgentUsage helper in agents/shared.ts; both harnesses now fold each iteration's usage into a running total and return the sum. - scripts/token-usage.ts now handles the unified format with or without the Cost ($) column. previously the int-only number regex rejected decimals and the 5-cell length check rejected 6-cell rows, so logs from post-cost-tracking runs fell through to "no token table". the parser now accepts both 5- and 6-cell unified rows, splits int vs decimal cells, and averages reported Cost alongside the tokens. - PATCH /api/workflow-run/[runId] now rejects INT field values above INT4_MAX (2_147_483_647) so a malformed payload gets a clean 400 instead of propagating a Prisma error. also defends against a compromised runner sending a deliberately huge value. - clarifying comments: opencode.ts documents that step_finish.part.cost is a per-step delta (empirically verified), main.ts explains that toolState.usageEntries already carries merged per-retry usage so aggregateUsage just sums entries (one per agent.run()). - tests for aggregateUsage and mergeAgentUsage — 12 new cases covering empty / partial / multi-agent inputs and the "keep undefined" semantic that prevents spurious zeros from being persisted. - drop `as number` cast in logTokenTable — narrow via const instead. * anneal: clamp INT overflow + guarantee mergeAgentUsage immutability second review pass surfaced two defensive gaps: - a single token field exceeding INT4_MAX would pass the client but be rejected by the server's per-field validator, writing a partial row with some NULLs where sums belonged. clamp in aggregateUsage so the wire payload is always self-consistent across all numeric columns, with a loud warning so the clamp doesn't silently swallow weirdness. - mergeAgentUsage's single-sided branches returned the input reference. callers treat AgentUsage as immutable but future callers might not; always return a fresh shallow copy instead. two new tests guarantee the no-mutation-leak property. no behavior change in the happy path — INT4_MAX is ~200x the largest realistic per-run token count. * anneal: resilient usage persistence + cross-platform null device third review pass surfaced three small issues: - main.ts finally block: writeGitHubUsageSummaryToFile throwing would skip the WorkflowRun usage PATCH. both are independent best-effort cleanup tasks — wrap the former in catch so a filesystem failure doesn't block DB persistence. - AgentUsage.inputTokens had no jsdoc explaining that it's the full billable input (cached + non-cached). the same word "Input" means "non-cached only" in the stdout/markdown tables (derived by subtraction). document the semantic so dashboards querying WorkflowRun.inputTokens don't misinterpret it. - play.ts gitconfig isolation was hard-coded to "/dev/null" which doesn't exist on Windows. use `os.devNull` for cross-platform parity (resolves to `\\.\nul` on win32). the project is Linux-only in CI so this only helps local Windows contributors, but it's a zero-cost swap. also updated the finally-block caveat comment: usage is only pushed to toolState.usageEntries when agent.run() returns an AgentResult, not when the timeout race rejects — so timed-out runs don't persist partial usage. documented instead of trying to thread state through Promise.race. * anneal: NaN-guard cost accumulators + clarify inputTokens docs final polish from review round 4: - guard both cost accumulators (opencode step_finish.part.cost and claude result.total_cost_usd) with Number.isFinite. `typeof x === "number"` accepts NaN, and one NaN `+=` would poison the running total for the whole session. - reword prisma schema comment on WorkflowRun usage fields to call out that cacheReadTokens / cacheWriteTokens are SUB-totals within inputTokens (not additional tokens on top). prevents future dashboards from double-counting by ~2x when summing "total tokens used". |
||
|
|
57bd10d6dd |
run-issues fixes: #5, #11, #12, #15, #16/#25, #20, #21, #22, #31 (#546)
* fix(#15): precompute diff anchors in checkout_pr TOC * test(#15): update TOC snapshot for precomputed diff anchors * chore(tests): skip codex-mini-latest models.dev check + refresh latest-by-provider snapshot * fix(#22): add commitCount and commitLog to checkout_pr return * fix(#21): include PR body in checkout_pr return * fix(#5): force-fetch PR refspec to overwrite stale local branch * fix(#31): rename git tool parameter from subcommand to command * fix(#11): soft-fail post-checkout hook, bump timeout to 10min * fix(#16): strengthen diff file usage guidance Agent was bypassing diffPath and running `git diff` instead. Tighten instructions in `checkout_pr` result and remove the mixed-signal "log, diff" listing in the global Git guidance. `git log` and `git diff --stat` remain allowed for commit-range overview. * fix(#20): drop invalid inline review comments instead of failing review Previously, a single inline comment anchored outside a diff hunk would 422 the entire review submission. Pre-validate comments against the PR file patches via listFiles, drop the invalid ones, and append a note to the review body listing what was skipped. Include the dropped list in the tool response so the agent can retry targeted fixes. * fix(#12): stop MCP server on inner activity kill + filter reconnect noise Inner-activity-kill zombies were burning multi-hour runner time because mcp-proxy's SSE reconnect and provider-error retry lines kept the outer activity timer alive long after the agent subprocess was killed. - Filter [mcp-proxy] / "provider error detected" chunks so they don't count as outer-timer activity. - Add onActivityTimeout callback to spawn + thread through agent runs. - main.ts wires that callback to stop the MCP HTTP server (so reconnects finally fail instead of looping) and arms a 5min safety-net timer that force-rejects the outer timer if the agent promise is still pending. * audit: harden #12 lifecycle + cover #20/#12 with unit tests Bugs found during Ralph audit of the prior run-issues fixes: - main.ts's 5min safety-net setTimeout was never cleared on the happy path; also activityTimeout.stop() didn't null the internal rejectFn, so a late forceReject from the safety-net could still reject a long-resolved promise. Timer now cleared in finally; stop() now disarms forceReject. - mcp server disposal was non-idempotent, so the inner-kill path ran server.stop() twice once the outer `await using` block exited. Made the returned disposer idempotent. Tests: - action/mcp/review.test.ts: 14 tests for commentableLinesForFile (multi-hunk, no-count hunks, no-newline marker, empty) and validateInlineComments (file not in diff, wrong side, out-of-range line and start_line, partitioning batches, default side). - action/utils/activity.test.ts: 6 tests for isActivityNoise covering mcp-proxy lines, provider-error lines, mixed chunks, Buffer input. * audit(#22): cap commitLog at 200 + scope git-diff restriction to PR review - cap git log --oneline at 200 entries so a PR with thousands of commits cannot blow up the MCP tool response; expose commitLogTruncated so callers can warn the agent when the log was clipped - tighten instruction wording so `git diff` / `git diff --cached` remain available for inspecting an agent's own uncommitted changes, while PR review content must still come from diffPath Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#11,#22,#31): surface hook/commit warnings in instructions + polish git tool - append hookWarning + commitLogTruncated advisories to checkout_pr instructions so the agent actually sees the warning inline, not just as a field it may skip - fix stale 'subcommand' wording in git tool redirect for `pull` and in the `command` parameter description; the MCP parameter is named `command` now, and that's what the agent binds to Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(#20): reassign params.comments even when all inline comments dropped if every inline comment fails pre-validation, the earlier guard skipped reassigning params.comments, so the submission still carried the bad comments and GitHub 422'd on the whole review. always reassign to validation.valid so the downstream 'nothing left to post' skip fires and an otherwise-empty review is no-oped cleanly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#22): degrade gracefully when base ref isn't resolvable checkout_pr used to assume \`origin/<base>\` is always reachable, but it isn't guaranteed after a shallow fetch that only pulled down the PR head. Failing the whole checkout over metadata we added for ergonomics would be a regression, so wrap the rev-list / log in a try/catch and return empty commit metadata instead. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#12): anchor noise patterns to line start to avoid false positives before this, a line like "agent said: [mcp-proxy] was there" or "context: provider error detected in log" in real agent output would have been treated as noise and failed to reset the outer activity timer. both patterns now anchor at the start of the (optionally debug-timestamped) line, matching only lines mcp-proxy or our own log.info actually emit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#20): export and unit-test formatDroppedCommentsNote covers single-line `path:N`, multi-line `path:start-end`, and startLine==line fallback so changes to the dropped-comments note format surface in test diffs instead of only in GitHub UI. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#20): cap dropped-comment note to stay under GitHub body limit a pathological run (agent emits hundreds of invalid inline comments on a huge PR and they all get dropped) would push the review body past GitHub's ~65KB limit and fail the whole submission with a body-too-long 422 — the exact all-or-nothing failure #20 was meant to prevent. cap the detail list at 50 entries with a "…and N more" line so the note stays bounded. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#20): distinguish binary/no-patch files in dropped-comment reason previously a comment on a binary file (or pure rename / mode-only change) was dropped with "line X is not inside a diff hunk", which misleads the agent into retrying with different line numbers. call out the no-textual-diff case explicitly so the agent knows to move that feedback to the review body instead. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#11): replace lifecycle timeout string-match with typed sentinel spawn() now rejects with SpawnTimeoutError (code === SPAWN_TIMEOUT_CODE or SPAWN_ACTIVITY_TIMEOUT_CODE) instead of a plain Error. executeLifecycleHook now branches on that code so rewording the error message in subprocess.ts can no longer silently misroute timeouts into the "transient — retry" warning. * audit(#12): route agent hung-vs-failed via typed SpawnTimeoutError claude.ts and opentoad.ts decide between "hung" and "failed" log wording based on the subprocess error. move them off the literal "activity timeout" substring match onto the same SPAWN_ACTIVITY_TIMEOUT_CODE sentinel used by lifecycle.ts so all three call sites agree on the source of truth. * audit(#20): delete leftover pending review when submit fails Why: `createAndSubmitWithFooter` creates a PENDING review first so we can mint Fix-links with the review ID, then submits. If submitReview fails (e.g. 422 from a race where the diff moved between pre-validation and submission), the draft was left on the PR. GitHub only allows one pending review per user, so the agent's retry would then fail with "already has a pending review" — an error the agent has no tools to clean up from. Best-effort cleanup: delete the pending draft on submit failure before re-throwing the original error, so retries start from a clean slate. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#31): point agent to concrete alternative when rebase/bisect blocked Why: in disabled-shell mode, `git rebase` and `git bisect` are blocked as arbitrary-code-execution escape hatches. Previous error messages explained *why* but left the agent without a next step — especially painful right after the `pull` redirect, which suggested "merge or rebase locally." The agent would follow that advice, hit the rebase block, and loop without knowing what to try next. Now: rebase block explicitly says "use 'merge' instead"; bisect block notes that manual bisect is also unavailable through this tool; pull redirect no longer recommends rebase in shell-disabled contexts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: import security tables into security.test to prevent drift Why: the security tests re-declared AUTH_REQUIRED_REDIRECT, NOSHELL_BLOCKED_SUBCOMMANDS, and NOSHELL_BLOCKED_ARGS inline with hand-copied message strings. When the runtime messages in git.ts were tightened (recent rebase/bisect guidance updates), the test copies drifted and tests validated a stale version of the logic while passing clean. A missing or mistyped entry in git.ts could therefore slip through. Now: export the tables from git.ts and import them into the test file. If a runtime message changes, the tests exercise the new string automatically; if an entry is added or removed, tests covering that command see the change without manual sync. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: widen pending-review cleanup to cover pre-submit throws getApiUrl() (invoked in footer build) can throw if API_URL is misconfigured, which would leak a pending draft between createReview and the previous submitReview try/catch. Move the try/catch to wrap the entire post-create body so any throw routes through deletePendingReview cleanup. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: reject leading-dash refs/branch names to block flag injection git's parseopt accepts options intermixed with positional args, so a ref like "--upload-pack=evil" passed to git_fetch could be parsed as a flag rather than a refspec. Add a narrow rejectIfLeadingDash helper to git_fetch (ref), delete_branch (branchName), and push_branch (branchName). HTTPS remotes ignore --upload-pack server-side, but the hygiene matters for defense in depth (ssh remotes, future code paths). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: validate the resolved branch in push_branch too When branchName is omitted, rev-parse surfaces the current branch name, which could start with '-' if git state was tampered with. Move the leading-dash check to after the branch is resolved so both the explicit and derived paths go through validation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: cache commentable-lines snapshot at checkout to match review anchor Review comments are anchored to checkoutSha (commit_id), but validation was hitting pulls.listFiles at review time — latest HEAD, not the SHA the agent actually reviewed. If the PR was updated mid-run, valid comments could be silently dropped (or invalid ones admitted). Snapshot the commentable lines during checkout_pr so review-time validation matches the anchor exactly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#12): route activity monitor's own debug output around the write wrap startProcessOutputMonitor monkey-patches process.stdout.write to mark activity, then called log.debug(...) every 5s to report idle time — which landed right back in its own wrapper, failed isActivityNoise, and called markActivity. with ACTIONS_STEP_DEBUG=true (common on reruns) the idle counter reset every interval and the timeout could never fire, re-creating the #12 zombie-run bug for any debug-enabled run. Fix: capture the original stdout.write and use it directly for the monitor's own diagnostics so they bypass the feedback loop. Added a tight-timeout regression test that asserts the timeout still rejects in debug mode. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#12): noise-filter subprocess.ts monitor logs so outer timer survives debug activity.ts's own monitor output already bypasses the wrap (c35cd3fb), but subprocess.ts's spawn activity timer uses log.debug — which goes straight through process.stdout.write and would still mark activity on every interval when debug logging is enabled. Pattern-filter those '(spawn|process) activity (check|timer|monitor)' lines in both local ([DEBUG] ...) and GH-runner (::debug::...) formats so they don't reset the outer agent-hang timer. Kept scoped to those specific monitor messages — a blanket [DEBUG] filter would silently classify any coincidentally-debug-prefixed agent output as idle, which is a worse failure mode than the one we're fixing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#11): surface spawn ENOENT-style errors in stderr buffer spawn() resolved with exitCode=1 and an empty stderr when the command itself couldn't start (missing binary, bad permissions). lifecycle.ts then reported 'output: (empty)' to the user, who was explicitly told 'retry if the failure looks flaky' — so every run hit the same wall with no diagnostic trail. Append the '[spawn] <cmd>: <node error>' line to stderrBuffer before resolving so the real cause (ENOENT, EACCES, …) flows through to the hook-warning message. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit(#11,#12): cover executeLifecycleHook typed-timeout routing the typed SpawnTimeoutError + sentinel-code branching introduced in d7ee7fd2 / ea8dd2c4 classifies hung vs failed lifecycle hooks — critical for whether agents retry — but had no unit coverage. add tests for all four branches (no script, exit 0, non-zero exit with retry-if-flaky guidance, timeout with do-NOT-retry guidance, transient spawn failure). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: re-verify clean tree after prepush hook the pre-prepush check guarantees we enter the hook with a clean tree, but if the hook writes tracked files (formatter, type generator, build artifacts), the push still only sends the pre-hook commit — the hook's edits silently disappear from the upstream branch while the tool reports "successfully pushed". add a post-hook status check so the agent sees the dropped mutations and can commit or discard them before retrying. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: reject push_tags refspec injection via ':' in tag name without tag validation, a tag like "foo:refs/heads/main" concatenated into "refs/tags/${tag}" becomes a valid <src>:<dst> refspec — git pushes the local refs/tags/foo's commit to remote main, bypassing push_branch's default-branch guard. same shape blocks leading '-' (flag injection) and other refspec metacharacters (~ ^ ? * [ \) via an allow-list regex. only reachable in push=enabled today, so this is defense-in-depth, but hardens the tool in case push_tags is ever exposed in restricted mode. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: stop pointing agents at an internal constant they can't change the lifecycle-hook timeout warning told agents to "bump LIFECYCLE_HOOK_TIMEOUT_MS" — but that's a hard-coded constant in the action, not something the agent or repo owner can tune. the agent would plausibly loop hunting for where to change it. redirect to the actual lever they control: ask the repo owner to simplify the hook. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: drop inverted inline-comment ranges locally with precise reason validateInlineComments only checked that both line and start_line anchor inside a hunk, not that start_line <= line. an inverted range (e.g. start=44, line=42) would pass local validation and GitHub would 422 with "invalid line numbers" — opaque to the agent and unfixable without reading docs. reject locally with a reason that names the constraint. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: don't let usage-summary write error mask main's outcome writeGitHubUsageSummaryToFile is called in main's finally block. it can throw on ENOSPC / EACCES / missing parent dir. a throw here propagates past the try's successful return or the catch's error return, hiding the actual run outcome behind an I/O failure on a purely informational file. swallow the write error (debug-logged) — the summary is nice-to-have, not load-bearing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: don't mislabel agent handler errors as JSON parse failures the onStdout event loop wrapped both JSON.parse and the handler call in one try/catch that logged every caught error as 'non-JSON stdout line'. if a handler threw (e.g. todowrite state shape drift), the error was silently classified as a parse error, making diagnosis impossible. split the try blocks so JSON errors and handler errors get distinct, identifying log lines. * audit: reject leading-dash PR refs before they reach git commands PR head/base refs come from GitHub and are attacker-controlled on fork PRs (the PR author picks headRef freely). they flow straight into `git fetch origin <ref>`, `git checkout -B <ref>`, and config writes. without a leading-dash check, a ref named like '-upload-pack=evil' could be parsed as a flag instead of a refspec. validate both refs at the top of checkoutPrBranch (before any async work) and cover the two attack shapes with unit tests. * audit: cover ActivityTimeout.stop()'s forceReject disarming main.ts's safety-net-timer path depends on ActivityTimeout.stop() nulling out rejectFn so a late safety-net fire after a successful agent run is a no-op. that behavior had no direct coverage — removing the \`rejectFn = null\` in stop() would silently break the happy path (unhandled rejection / spurious failure) without failing any test. add three tests covering: forceReject rejects with the reason, stop() disarms forceReject, and forceReject after timer rejection is an idempotent no-op. * audit: stabilize activity-timeout idleSec against late stdout race * audit: reject 0ms timeout parses to avoid insta-fail from '0m' * audit: surface raw GitHub error on review 422 instead of assuming anchor cause * audit: key commentable-lines cache by PR number to prevent cross-PR drift * audit: enumerate concrete 422 causes and name checkout_pr in review error * audit: stop shipping ralph-loop runtime state in PR history .claude/ralph-loop.local.md and .claude/ralph-loop-prompt.md were accidentally staged in an earlier audit commit. the .local.md suffix is conventional for gitignored runtime state, and the prompt file is per-run harness config — neither should merge to main. ignore the pattern and untrack the existing entries (files remain on disk so the active loop keeps working). * audit: pin commentable-lines cache to checkoutSha, not just PR number a second checkout_pr(N) call advances toolState.checkoutSha at line 305 or 334, then runs fetchAndFormatPrDiff + cache population at line 549. any throw between those two points (rate limit, 5xx, network blip) left the old snapshot keyed to (pullNumber=N) while checkoutSha now points at a different sha. review_pr(N) would reuse the stale snapshot, silently validating comments against the wrong anchor — the original failure this cache was meant to prevent. track commentableLinesCheckoutSha alongside the pull number and require both to match before returning the cache. if either has moved, fall back to listFiles like any other miss. * audit: auto-clear leftover pending review from killed prior runs a workflow timeout or OOM between createReview PENDING and submitReview leaves GitHub holding a pending draft. the next run hits GitHub's one-pending-per-user-per-PR limit and 422s at pending-create, with no way to recover short of a human cleaning up manually. catch 422 at pending-create, list the PR's reviews (GitHub only exposes our own pending to us, so the filter is safe), delete the leftover, and retry once. 404/422 on the cleanup are treated as no-ops (race with another concurrent cleanup or the draft was submitted); any other cleanup error rethrows so the real cause reaches the caller. * audit: extract + unit-test stranded-pending-review cleanup the recovery branch inside createAndSubmitWithFooter had no direct test coverage. a regression in any of its guards (status check, message match, listReviews filter, 404/422 tolerance, non-retryable rethrow) would silently cause either destructive deletes of unrelated reviews or the old failure mode where a stranded pending draft blocks every retry. extract to clearStrandedPendingReview so the cases can be exercised with a mocked octokit, and add tests for each branch — including the load-bearing negative cases (non-422 passthrough, non-pending-review 422 passthrough, no-leftover-found passthrough, non-retryable cleanup error passthrough). no behavior change at the call site. * audit: document concurrent-run race in clearStrandedPendingReview two runs on the same PR using the same GitHub App installation token would both see each other's PENDING draft via listReviews (GitHub exposes PENDING only to the author, and both runs share authorship). the loser's recovery path would delete the winner's active draft, causing the winner's submitReview to 404. no reliable in-request signal distinguishes a genuinely-stranded prior-run draft from an active peer's draft — PENDING reviews have no created_at, and the user field is the same bot in both cases. the correct fix is workflow-level concurrency (a per-PR concurrency key), not a heuristic here. document the limitation so future readers don't try to bolt on a broken heuristic. * audit: report signal-killed subprocesses as failures, not exit code 0 node's close event delivers (code=null, signal=<name>) when a child is killed by signal (OOM killer, segfault, external SIGTERM). the close handler captured only exitCode and coerced null to 0 via `exitCode || 0`, so lifecycle hooks killed by signal were silently reported as successful — lifecycle.ts's `if (result.exitCode !== 0)` check skipped the warning and callers proceeded as if setup/post-checkout/prepush had completed. now capture signal, append "killed by signal <name>" to stderr, and resolve with exitCode=1 when code is null but signal is set. adds a regression test that spawns `kill -KILL \$\$` and asserts a non-zero exit plus the signal-kill marker in stderr. * audit: untrack RUN_ISSUES*.md ralph-loop working docs same pattern called out in 4f14dbf1: these files are per-run harness state and analysis scratch, not merge-to-main deliverables. the TODO literally opens with "Ralph loop instructions:", so it's unambiguously in the same category as .claude/ralph-loop-prompt.md was. files stay on disk so the active loop keeps working. * audit: block refs/... + symbolic-ref bypass of default-branch guard push_branch's restricted-mode guard compared the resolved remoteBranch against defaultBranch with exact-string equality. an agent passing branchName "refs/heads/main" flowed through: rejectIfLeadingDash passed, getPushDestination's fallback preserved the refs/heads/main string as remoteBranch, so "refs/heads/main" !== "main" and the block was skipped, yet git push happily resolved refs/heads/main to the local main commit and pushed to the remote main branch. symbolic refs (HEAD / FETCH_HEAD / ORIG_HEAD / MERGE_HEAD) are the same class of bypass — they resolve to whatever commit they point at, unconstrained by the name-based guard. add rejectSpecialRef to enforce bare branch names at the tool entry, use it in push_branch and delete_branch. checkout_pr only ever assigns pr-<number> as the local branch, so nothing legitimate relied on the refs/... form here. * audit: keep original 422 visible when listReviews fails during pending-review cleanup if listReviews threw (e.g. transient 502, rate limit) during the stranded pending-review recovery path, the listing failure replaced the original 422 "pending review" error when it propagated up through the tool's outer catch. agents then saw a generic server error with no mention of the real blocker and stopped retrying the cleanup. now the listing failure is logged at debug but does not mask the original 422. the caller's retry re-attempts cleanup, which succeeds if the listing failure was transient. * audit: block default-branch deletion even under push: enabled delete_branch required push: enabled, but within that mode the agent could delete the default branch with no local guard. GitHub branch protection usually catches this at the remote, but not every repo has protection configured — and even when it does, relying on remote config for local safety is wrong. pushing to main is reversible (revert, force-push old HEAD); deleting main is not (reflog recovery only, 30-day window). block deletion of the resolved default_branch in DeleteBranchTool regardless of push permission. push: enabled authorizes pushes, not wholesale removal of the repository's primary branch. * audit: attach no-op catch to agentPromise so a late rejection can't crash cleanup agentPromise raced against activityTimeout.promise (and the --timeout timeoutPromise), both of which had .catch(() => {}) handlers. agentPromise did not. if a timeout won the race, agentPromise became stranded and its subsequent rejection was an unhandled rejection — under node 15+'s default unhandled-rejection policy that terminates the process, which would kill main() mid-cleanup and lose the error-reporting and usage-summary work queued in the catch/finally blocks. the race still sees the rejection (the original promise is shared); this catch only prevents node from treating a post-race rejection as unobserved. * audit: close push_branch refspec-injection via ':' / '+' in branchName rejectSpecialRef only forbade leading-dash, `refs/` prefix, and symbolic refs. git push accepts `[+]src[:dst]` refspec syntax, so an agent under push:restricted could smuggle a full refspec through branchName and bypass the downstream exact-string default-branch guard: "evil:refs/heads/main" → push local 'evil' to remote main ":refs/heads/main" → delete remote main ":other" → delete arbitrary branches (outside grant) "+main" → force-push refspec prefix reject ':', '+', '^', '~', '?', '*', '[', '\\', and whitespace — git's own check-ref-format forbids all of them in branch names, so the allow-list cannot false-positive against a legitimate branch. add regression tests. * audit: stop suggesting blocked 'rebase' in push_rejected advice under shell=disabled Why: when push fails with non-fast-forward, the advice told the agent to run 'git rebase origin/...'. In shell=disabled mode the git MCP tool blocks rebase (as an arbitrary-code-execution escape hatch), so the agent's only path forward was to hit the block, read the fallback message, and try merge — one wasted round trip. Now: under shell=disabled we directly suggest 'git merge origin/...', which always works. Under other modes the advice keeps the rebase/merge choice but leads with merge so the example is copy-pastable either way. * audit: harden includeIf cleanup against shell-injection via subsection names setupGit read `includeif.*` keys via `git config --get-regexp`, split on the first space, and fed the result into `execSync(\`git config --unset "${key}"\`)`. git config subsection values preserve arbitrary characters, so a crafted `[includeIf "gitdir:$(touch${IFS}/tmp/pwn)safe"]` entry round-trips through `--get-regexp` with its `$(...)` command substitution intact, survives the split-on-space filter (IFS-bypass leaves the payload space-free), and gets evaluated when interpolated into the shell command. Confirmed reachable as an RCE sink in local repro. Switch to `--get-regexp -z` (null-terminated, no ambiguity on whitespace) and call `$("git", ["config", "--unset-all", key])` which uses spawn-array and never hands the key to a shell. Extract the logic into `removeIncludeIfEntries` and add regression tests covering the injection payload, whitespace-in-subsection keys, benign entries, and the no-op case. * audit: clear SIGKILL escalator on clean SIGTERM exit the overall-timeout path scheduled a 5s SIGKILL follow-up without capturing the timer id. if the child cooperated with SIGTERM and `close` fired promptly, the escalator stayed pending in the event loop for up to 5s — delaying any subsequent clean shutdown (e.g. the main action exiting after an agent timeout) by that long. capture sigkillEscalatorId alongside timeoutId and clear it in both close and error handlers. regression test asserts the active-timer count does not grow past the pre-spawn baseline after a timed-out child exits on SIGTERM. * audit: correct rebase-availability hints to reflect shell=restricted the MCP git tool only blocks rebase when shell=disabled (NOSHELL_BLOCKED_SUBCOMMANDS check in GitTool). under shell=restricted, git({command: "rebase"}) works fine through the tool — NOSHELL_BLOCKED_SUBCOMMANDS doesn't apply. but two agent-facing messages implied rebase is only available with shell=enabled: - AUTH_REQUIRED_REDIRECT["pull"] said "rebase is only available when shell is enabled" - push-rejected integrateStep (non-disabled branch) said "(or 'rebase' if shell is enabled)" under shell=restricted, agents reading these would wrongly think they had to pick merge — pushing them toward merge commits when rebase would have been cleaner. the push-rejected branch is already ternary-gated on shell !== "disabled", so the qualifier there was just redundant noise. * audit: block difftool/mergetool under shell=disabled git difftool -x <cmd> is the short form of --extcmd. the args blocklist only matches --extcmd / --extcmd=*, so -x slipped through and let an agent run arbitrary commands even when shell=disabled. globally blocking -x would false-positive on git cherry-pick -x, which only appends metadata, so block difftool (and mergetool, same shape via mergetool.<name>.cmd) at the subcommand level instead. agents have no legitimate need for either — diffs go through diff/show and merges are resolved by file edits. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * audit: recover stranded PENDING drafts on no-body createReview too The body path already clears a stranded PENDING draft from a prior crashed run via createAndSubmitWithFooter's own try/catch. The no-body path (approve-with-no-feedback or comments-only) called createReview directly — so a PR whose previous body-path run crashed between createReview(PENDING) and submitReview would permanently 422 any subsequent no-body review with "already has a pending review" until a body-path run happened to clear it. Factored out createReviewWithStrandedRecovery so both paths get the same recovery treatment, and added regression tests covering the no-stranded / stranded-and-retry / non-stranded-422-no-retry cases. * audit: reject timeouts past node's setTimeout ceiling a user-supplied timeout like "999h" parses fine (parseTimeString has no upper cap) but falls off the 2^31-1 ms limit setTimeout clamps to 1ms. the agent run would reject with "timed out after 999h" in a single tick. extract a resolveTimeoutMs helper that centralizes the zero/overflow/ unparseable checks (previously scattered behind inline boolean logic in main.ts) and cover the behavior with unit tests including the boundary value. * fix(#22): replace parameter property in SpawnTimeoutError node --experimental-strip-types rejects readonly/public/private param properties in constructors. tests run via node directly (no tsc), so CI was hitting ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX on every action-agents / action-agnostic job before any test code ran. declare the field and assign in the body instead. * audit: tighten git tool description and delete_branch refspec - `git` tool description previously implied `pull` had a dedicated MCP tool alongside `push_branch`/`git_fetch`. it doesn't — the redirect sends the agent back to the same git tool with `command: "merge"` (or `rebase`). update the description to teach this directly instead of letting agents discover it through the redirect error. - `delete_branch` now passes `refs/heads/${branchName}` to `git push --delete` so a same-named tag can't be silently deleted when both exist on the remote. `rejectSpecialRef` already guarantees the bare-name invariant, so the template construction stays injection-safe. Made-with: Cursor * audit: polish review.ts per anneal findings - drop `as "LEFT" | "RIGHT"` cast in `validateInlineComments` — octokit types `side?: string` at the createReview endpoint, so narrow via `c.side === "LEFT" ? "LEFT" : "RIGHT"`. no cast, no redundant annotation — TS infers the literal union from the ternary. - consolidate `clearStrandedPendingReview` from 3 params to 2 by folding `originalErr` into `params`, per AGENTS.md "max 2 parameters" rule. updates both call sites (`createReviewWithStrandedRecovery`, `createAndSubmitWithFooter`) and all 7 test paths. - upgrade `listReviews`-during-cleanup failure log from `log.debug` to `log.info` so operators not running at debug still see that recovery was attempted before the original 422 bubbles up. message now reads "surfacing original 422" to make the intent unambiguous. Made-with: Cursor * audit: signal partial commit metadata in checkout_pr previously a rev-list/log failure (e.g. shallow fetch where `origin/<base>` isn't reachable) silently returned `commitCount: 0, commitLog: ""` — indistinguishable from "this PR has no commits past base", which could mislead review reasoning about scope. add a `commitLogUnavailable: boolean` field to `CheckoutPrResult`, set when the rev-list/log calls throw. instructions footer now tells the agent to treat the values as "unknown" rather than "no commits" in that case. message phrased to cover the rare case where rev-list succeeds but git log throws (partial, not strictly zero) metadata. Made-with: Cursor * audit: fix parseDiffTocEntries to match production ' · diff-<sha>' TOC suffix the regex required $ right after the line range, but formatFilesWithLineNumbers in checkout.ts appends ` · diff-<sha256>` so agents have the GitHub "Files Changed" anchor precomputed. result: tocEntries was always empty on real PR reviews, breakdown.files was empty, and runDiffCoveragePreflight never fired its one-time "read the diff" nudge. add an optional suffix to the regex and a regression test that uses the exact production TOC shape. Made-with: Cursor * audit(#20): skip empty downgraded-APPROVE reviews before they 422 GitHub rejects `event: "COMMENT"` reviews with no body and no inline comments (HTTP 422 "Unprocessable Entity", verified empirically on repos/pullfrog/preview-546-run-issues-fixes/pulls/1). the runtime `prApproveEnabled` downgrade folds approved=true into event=COMMENT when the repo flag is off, so an agent asking to APPROVE a PR with no other feedback produces exactly that rejected shape — but the existing empty-review skip only fired for !approved cases, so the tool POSTed the doomed COMMENT, octokit returned what looked like a success-with- no-persisted-review shape, and agents reported a phantom reviewId that 404s on any subsequent GET. extract the skip decision into `reviewSkipDecision` and add a second branch for approved + !prApproveEnabled + empty. the function returns null when the review should be submitted, so a real bare APPROVE (approved + prApproveEnabled + empty) still goes through unchanged — GitHub accepts empty APPROVE reviews because the stamp itself is the content. surfaced in the PR #546 preview e2e run 24678139563 (reviewId 4141786854 reported by the agent but absent from every reviews listing). TC13 run 24680349445 re-ran the same scenario with prApproveEnabled=enabled and the review persisted correctly, isolating the cause to the downgrade + empty interaction. * audit(#31): drop misleading rebase mention from pull redirect AUTH_REQUIRED_REDIRECT["pull"] and the git tool's top-level description both said "use git_fetch then this tool with command 'merge' (or 'rebase' unless shell is disabled)". the "(or 'rebase' unless shell is disabled)" qualifier is active misinformation when the agent is already running under shell=disabled: rebase is blocked there by NOSHELL_BLOCKED_SUBCOMMANDS, so the suggestion sends the agent into a second block on the next tool call. 3b83ee97 already fixed this pattern for the push-rejected advice at line 248, but the pull redirect at line 280 and the tool description at line 351 were missed. the right copy isn't a conditional qualifier that agents have to parse against their own shell mode — it's just naming the one alternative that works everywhere (merge). agents under shell=restricted/enabled who want rebase can invoke it directly; the redirect doesn't need to advertise it. verified in preview e2e run 24679728733 (TC8 probe 6) where the agent correctly captured the verbatim redirect message under shell=disabled and explicitly flagged the "(or 'rebase' unless shell is disabled)" clause as confusing — the new test in security.test.ts asserts the message names merge and never rebase in every shell mode. * audit: drop vestigial entry/post references + add preview-546 settings util followup to d79860c6 "refactor: flatten action entrypoints" (Apr 10), which moved action.yml from built `entry`/`post` files to source `entry.ts`/`post.ts` but left three stale references lying around: - .gitignore: `action/run/entry` / `action/dispatch/entry` paths no longer exist anywhere in the build. - .github/workflows/pull-from-action.yml: agent instruction told the upstream sync agent to "Ignore `entry` files (they are built artifacts and .gitignored in this repo)". there are no built entry artifacts anymore — entry.ts is source. - .cursor/settings.json: search.exclude pattern "**/entry" excluded the old built files that no longer exist. none of these were load-bearing on their own, but the same drift had already broken preview e2e end-to-end: the pullfrog/template workflow's three-file copy step (cp .../entry, cp .../post) silently failed with cp: no such file on every preview PR since Apr 10. that template fix went to pullfrog/template@7ec7c8d and the preview-546 mirror at @17ab585, which is what unblocked this PR's full e2e validation. also adds scripts/preview-546-settings.ts, the helper used during the e2e validation to show/set/reset DB-level repo settings on the Neon preview branch (push, shell, prApproveEnabled, hook scripts). scoped to this preview repo ID so it can't accidentally mutate prod. * audit(#11): scope removeIncludeIfEntries to repoDir under inherited GIT_* the function takes `repoDir` as the target, but plain execSync / $(...) inherit GIT_DIR, GIT_WORK_TREE, and GIT_INDEX_FILE from the parent process — and `git config --local` honors GIT_DIR over cwd. when this runs as a child of another git invocation (notably the pre-push hook, but also any future caller embedded inside a git subcommand), the cleanup silently targets the outer repo instead of repoDir. latent today because the real caller is ASKPASS setup, which runs before any git-subcommand ancestor exists, but the function's contract still promised the wrong thing — and the test suite hit exactly this bug when invoked through `git push`. - envScopedToRepo() strips GIT_* before both the get-regexp and unset calls, so cwd wins. - swap the $(...) shell helper for execFileSync on the unset call. $() would merge our scoped env with a "restricted" base that's tuned for hook execution (no tokens) — overkill here and it re-introduces the shell-vs-argv distinction this function was explicitly hardened against in a9aa3b2b. execFileSync with argv is the right tool for a call where the key can contain arbitrary characters. - setup.test.ts also strips GIT_* in its own execSync harness so the suite passes identically under `pnpm vitest run`, `pnpm -r test`, and `git push`'s pre-push hook. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Colin McDonnell <colinmcd94@gmail.com> |
||
|
|
a71567af90 |
fix models-live matrix: resolve alias in PULLFROG_MODEL + pass all provider keys through docker
two bugs blocked the live matrix from reaching real APIs: 1. resolveModel returned PULLFROG_MODEL raw without passing it through the alias registry. when CI set PULLFROG_MODEL=anthropic/claude-opus (alias), the bare alias slug was forwarded to the Anthropic API as a model id and 404'd. now resolves via resolveCliModel first, with raw specifiers (anthropic/claude-opus-4-6) still passing through unchanged. 2. the testEnvAllowList in docker.ts only forwarded Anthropic/OpenAI/Google keys into the test container. XAI/DeepSeek/OpenRouter/Moonshot/OpenCode keys got stripped, so every non-big-3 alias failed with "no API key found" even when the secret existed. add all five to the allowlist. Made-with: Cursor |
||
|
|
56a5d29598 |
add diff coverage preflight for PR review submissions (#544)
* add one-time diff coverage preflight for PR reviews track diff read coverage from agent tool-use events and run a one-time pre-flight before review submission, with explicit coverage skip reasons for low-value files like lockfiles. Made-with: Cursor * add manual dispatch fallback for preview deploy workflow allow preview repo and preview sync jobs to be run via workflow_dispatch with explicit PR number and branch inputs, so preview provisioning can be retriggered when pull_request events fail to fire. Made-with: Cursor * fix manual preview dispatch PR input wiring use normalized PR number and branch env values for comment creation and script env wiring so workflow_dispatch preview runs can create and update PR-specific preview resources. Made-with: Cursor * remove obsolete snapshots invalidated by checkout instructions change * fix diff coverage read offset handling and add local sanity-check guidance normalize read offset semantics for diff coverage tracking, reuse shared range counting in review preflight, add focused diff coverage unit tests, and document the local play.ts testing workflow in AGENTS.md. Made-with: Cursor * add regenerated mcp test snapshots capture snapshot files generated by the review comment and checkout formatting tests during pre-push validation so the branch remains clean and reproducible. Made-with: Cursor * add diff coverage preflight instrumentation logs log diff coverage initialization in checkout_pr and emit preflight state/breakdown diagnostics in create_pull_request_review to debug missing coverage enforcement in preview e2e runs. Made-with: Cursor * add env override to force local cli execution in action runtime support explicit local-cli execution via PULLFROG_FORCE_LOCAL_CLI so preview workflows can run branch action code instead of the npm fallback package during e2e debugging. Made-with: Cursor * add preview e2e debugging learnings for action runtime validation capture the preview execution-path gotchas and one-time preflight verification pattern in AGENTS.md so future investigations validate the real runtime and avoid npm fallback confusion. Made-with: Cursor * reduce diff coverage log noise while preserving failure visibility downgrade verbose diff coverage lifecycle diagnostics to debug, keep a concise info-level pre-flight failure signal, and document preview runtime debugging learnings in AGENTS.md. Made-with: Cursor * WIP * tune sync.md: ff override + softer overlap verification Made-with: Cursor * chore: bump models snapshot for claude-opus-4-7 Made-with: Cursor * rip out coverage_skips waiver from diff coverage pre-flight Made-with: Cursor --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> |
||
|
|
74b313e612 |
bump claude-opus alias to 4-7
anthropic shipped claude-opus-4-7 today; opencode also republished it. point the "claude-opus" alias at the new version for both providers so existing users get the upgrade automatically. openrouter hasn't published 4.7 yet, so leave openRouterResolve at 4.6 as the BYOR fallback. also clarify the latest-model snapshot comment: new model drops usually just mean bumping the `resolve` on an existing alias, not adding a new one. Made-with: Cursor |
||
|
|
569d34b0a9 |
lower startup verbosity for git binary fingerprint log.
switch the git binary fingerprint message to debug level and keep the chevron log prefix for consistency with action logs. Made-with: Cursor |
||
|
|
a607ac29e1 |
fix restricted env filtering precedence for safe prefixes
remove broad `PULLFROG_` passthrough from restricted shell env filtering and ensure sensitive names are blocked unless explicitly allowlisted, then align the restricted test fixture with allowed-prefix coverage. Made-with: Cursor |
||
|
|
2d1f1d33db |
replace suffix-based env filtering with default-deny allowlist (#543)
* replace suffix-based env filtering with default-deny allowlist filterEnv() now only passes known-safe GitHub Actions runner/system/toolchain vars plus user-configured allowlist entries to shell subprocesses. GITHUB_TOKEN and GH_TOKEN are always blocked, even from the user allowlist. adds envAllowlist field to repo settings with dashboard textarea UI (visible only when shell isolation is enabled) and wires it through run-context API to the action runtime. Made-with: Cursor * address review: blocked-name warning, JAVA_HOME prefix, stale waitlist copy - setEnvAllowlist now strips BLOCKED_ENV_NAMES from user input and returns them so main.ts can log a warning - move JAVA_HOME to exact names, use JAVA_HOME_ as prefix for clarity - update stale suffix-based description in waitlist email script Made-with: Cursor * fix wiki/security.md snippet: JAVA_HOME -> JAVA_HOME_ to match code Made-with: Cursor * UI polish: field-sizing-content on all textareas, rename env allowlist label - add field-sizing-content to all settings textareas so they auto-expand to fit content (AgentSettings, ModesSettings, WorkflowsSettings, FlagsSettings) - rename "Environment variable passthrough" to "Environment allowlist" with clearer popover copy - drop "e.g." prefix from env allowlist placeholder - update docs/security.mdx and wiki/security.md references to match Made-with: Cursor * tweak env allowlist popover wording Made-with: Cursor * document default allowed variables in security docs with link from popover Made-with: Cursor * Update action/utils/secrets.ts Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> |
||
|
|
a120160f42 |
clean up startup run configuration logs
remove duplicate model and agent log emitters, then print model, agent, push, shell, and timeout on separate startup lines so run settings stay concise and easy to scan. Made-with: Cursor |
||
|
|
18c8d34da6 |
remove task list from review bodies; keep in progress comments only (#542)
review bodies were embedding a task-list snapshot that could capture stale in-progress state due to timing between the agent's final TodoWrite and the review submission API call. progress comments are the authoritative checklist surface — remove the review-body embedding entirely so there is a single source of truth. also adds a `completeInProgress` option to `renderCollapsible` so the progress-comment path can finalize any in-progress items at render time without mutating tracker state. Made-with: Cursor |
||
|
|
4b3c5ca905 |
rename agent key to opencode and add skill invocation coverage (#541)
* rename agent key to opencode and add skill invocation coverage. add skill-invoke tests for claude and opencode with local play-based validation signals, update CI matrices, and include the current tracked refactors in this branch for review. Made-with: Cursor * exclude agent-specific skill-invoke tests from wrong agent in CI matrix * address review follow-up and preserve workflow run UI tweak. switch changed-agents ci coverage to exercise the opentoad implementation path while keeping the opencode expectation, and include the local workflow run client interaction updates requested on this branch. Made-with: Cursor * remove opentoad agent filename from runtime. rename the opencode harness implementation file from opentoad.ts to opencode.ts and update ci coverage input accordingly so action code no longer carries the old filename. Made-with: Cursor * ensure security prompt bypass is set on every test fixture. this keeps adversarial and permissions harnesses from being blocked by the default prompt-injection refusal path during CI security tests. Made-with: Cursor --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> |
||
|
|
2799cce4bf |
homepage redesign + docs cleanup + agent prompting (#540)
* homepage redesign + docs cleanup + agent prompting improvements - rewrote hero section: new tagline, responsive font sizing with clamp(), extracted shared constants for copy management - added feature screenshots (shell isolation, github permissions, mcp tools, agent browser) and ensured consistent image sizing - reworked feature section mobile layout: caption-style descriptions, bigger h3s, image padding - made CTA buttons visible on all breakpoints (stacked on mobile, row on md+) - reorganized docs/tools.mdx into single table with category dividers, simplified tool descriptions - added markdown image syntax instruction to agent system prompt - fixed InfoPopover overflow on small screens - misc: InlineCode proportional sizing, OnboardingCard updates, shell/security doc improvements Made-with: Cursor * update pnpm-lock.yaml for agent-browser 0.25.4 Made-with: Cursor |
||
|
|
b748355cbe |
homepage copy refresh + fix skills CLI installation (#539)
* add wiki/betterstack.md documenting log querying, request-ID grouping, and MCP usage
Made-with: Cursor
* fix webhook race conditions: separate runId assignment from data updates
the workflow_run webhook handler had a race where concurrent handlers assigned
the same runId to different pending records. the loser's P2002 silently dropped
data updates (jobId, status, completedAt). fix by splitting into two steps:
assignRunId() handles the race-safe unique assignment, then data updates always
target where: { runId } so they hit the correct record regardless of who won.
also downgrade R2 ObjectLockedByBucketPolicy errors from error to warn level
since duplicate webhook deliveries writing the same key is expected under load.
Made-with: Cursor
* homepage copy refresh + fix skills CLI installation
- update hero to "Agent x GitHub" with new subtagline
- rewrite intro paragraphs: workflow, harness capabilities, billing
- add feature sections: bash isolation, headless browser, MCP tools
- update FAQ answers, footer attribution, free-for-oss copy
- update APP_DESCRIPTION for SEO
- fix skills install: use npx from tmpdir instead of local binary
(the bundled action has no node_modules; running npx from tmpdir
avoids project .npmrc with pnpm settings breaking binary resolution)
- instruct agents to use markdown image syntax in upload_file tool
- start dependency installation eagerly from main.ts
- include event title in task instructions
Made-with: Cursor
|
||
|
|
c86752cf1d |
require OIDC verification for DB secrets on run-context (#538)
* add wiki/betterstack.md documenting log querying, request-ID grouping, and MCP usage
Made-with: Cursor
* fix webhook race conditions: separate runId assignment from data updates
the workflow_run webhook handler had a race where concurrent handlers assigned
the same runId to different pending records. the loser's P2002 silently dropped
data updates (jobId, status, completedAt). fix by splitting into two steps:
assignRunId() handles the race-safe unique assignment, then data updates always
target where: { runId } so they hit the correct record regardless of who won.
also downgrade R2 ObjectLockedByBucketPolicy errors from error to warn level
since duplicate webhook deliveries writing the same key is expected under load.
Made-with: Cursor
* require OIDC verification for DB secrets on run-context endpoint
DB secrets transported via run-context were accessible to any GitHub API
token with read access, bypassing GitHub Actions' fork PR secret isolation.
Now the endpoint requires a valid GitHub Actions OIDC token
(X-GitHub-OIDC-Token header) with a matching repository claim before
returning dbSecrets. Also requires admin for account-scope CLI secret
writes (matching the dashboard), and removes dead redactSecrets code.
Made-with: Cursor
|
||
|
|
a4c7c0fc15 |
feat: workflow run artifact chips + GraphQL url resolution (#447) (#527)
* plan: issue 447 run artifact tracking and UI (supersedes stale pill notes) Made-with: Cursor * feat: workflow run artifact urls, chips, and safe PATCH validation Made-with: Cursor * chore(action): refresh latest-by-provider model snapshot Made-with: Cursor * refactor: resolve artifact urls via GraphQL nodes(ids), drop stored url columns Made-with: Cursor * docs: finalize issue 447 run-artifacts plan; remove demo backfill script Made-with: Cursor * refactor: DRY node-id constraint, replace margin with padding wrapper Made-with: Cursor * refactor: DRY audit — shared row info, derived types, unified Prisma select - extract WorkflowRunRowInfo component (description + issue link + time + pills) shared by ActiveWorkflowRunsSection and WorkflowRunHistory - derive API payload types via Omit + & instead of manual field lists; serialize with spread + override for bigint/date fields - extract workflowRunListSelect shared Prisma select base; history extends with completedAt - inline updateCommentNodeId → direct patchWorkflowRunFields calls - derive WorkflowRunArtifactSlice from canonical exported types - delete cancelling-out URL column migrations (no schema change vs main) Made-with: Cursor * refactor: artifact chips as inline CTAs with proper vertical alignment - chips now render as action links: "Open PR #N", "View summary", etc. - only render chips with resolved URLs; remove inert span fallback - inline chips in the row (right-justified) instead of a separate line - fix vertical alignment: remove ul/li wrappers that caused line-height mismatch, render chips as direct row siblings via flat flex layout - change row to items-center, remove compensating self-start/pt nudges - cancelled run X icon uses red-600 Made-with: Cursor * chore(action): refresh latest-by-provider model snapshot Made-with: Cursor --------- Co-authored-by: Colin McDonnell <colinmcd94@gmail.com> |
||
|
|
421607cf97 |
fix push-to-action: use CLI direct invocation for token acquisition
the inline `node -e` + `TOKEN=$(...)` approach broke because `core.getIDToken()` in @actions/core writes `::debug::` and `::add-mask::` to stdout, polluting the captured value. `node cli.ts gha token` uses `core.setOutput()` which writes to the $GITHUB_OUTPUT file instead of stdout. Made-with: Cursor |
||
|
|
b282e8b599 |
Improve incremental review output and fix todo tracker race (#529)
* improve incremental review output and fix todo tracker race - reviewed changes section: summarize at logical-change level with past-tense verbs, not per-file enumerations - add TodoTracker.completeAll() to mark all non-cancelled items as completed before snapshotting the collapsible in review/progress posts Made-with: Cursor * completeAll -> completeInProgress: only mark in-progress items as completed Pending items that were genuinely skipped stay as-is in the collapsible, so the task list honestly reflects what the agent actually did. Made-with: Cursor |
||
|
|
1c730300b6 | Clarify push, prepush, and progress errors in agent prompts (#521) | ||
|
|
2ea447a780 |
refactor: replace narrow parameter types with context objects (#519)
* refactor: replace narrow parameter types with context objects across action/ pass broader context objects (ToolContext, PromptContext, PostCleanupContext) to utility functions instead of cherry-picking fields into single-use interfaces. deletes 8 narrow types, simplifies call sites, and makes buildCommentFooter synchronous by reading ctx.runId/ctx.jobId directly instead of re-deriving from env vars and making an extra API call. Made-with: Cursor * fix: replace non-null assertion with local guard in validatePushDestination addresses review feedback — the function now validates pushUrl itself instead of relying on the caller's check, eliminating the ! assertion. Made-with: Cursor * revert: remove GH_TOKEN injection from restricted shell the original change exposed the git token in restricted-mode shell so `gh` CLI would work. this is a security regression for public repos: MCP tools are deliberately constrained (no merge, no release, no arbitrary API calls), but `gh api` with the token gives full GitHub API access to any prompt-injected agent. Made-with: Cursor |
||
|
|
b9b6503315 |
reorder prompt sections: task-first with dynamic TOC (#513)
* reorder prompt sections: task-first with dynamic TOC put the actual task at the top of the prompt for primacy, add a dynamic table of contents, and push system/runtime metadata to the end. new section order: TOC → YOUR TASK → PROCEDURE → EVENT CONTEXT → SYSTEM → LEARNINGS → RUNTIME Made-with: Cursor * enforce clean working tree: continue session if agent leaves uncommitted changes after each agent run, check `git status --porcelain`. if dirty, resume the same session with instructions to commit on a new branch, push, and open a PR. retries up to 3 times before giving up. - claude code: capture session_id from result event, use --resume <id> - opencode: use --continue to resume the last session - remove --no-session-persistence from claude (needed for --resume) - update Task mode to clarify branch/push/PR is the default finalize step Made-with: Cursor * log full prompt in collapsible group for debugging Made-with: Cursor * fix: format tool refs in buildCommitPrompt via formatMcpToolRef * enforce clean git status: general instructions, stop hook, and Task mode Made-with: Cursor * fix: rename stale titleBody references after body leak fix Made-with: Cursor --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> |
||
|
|
6b93e6b368 |
review/incremental-review: always submit review, never call report_progress (#516)
* WIP * WIP * review/incremental-review: always submit review, never call report_progress The progress comment is auto-deleted by the stranded-comment cleanup in main.ts when the agent skips report_progress. This makes reviews the sole PR artifact for both modes, reducing noise. - soften report_progress tool description to allow mode opt-out - Review mode: always submit exactly one review (approve or request changes) - IncrementalReview mode: submit review for substantive outcomes, silently exit for non-substantive changes (formatting-only pushes produce zero artifacts) Made-with: Cursor * incremental-review: clarify approval condition for substantive no-issues case Made-with: Cursor * report_progress: s/completed/current task list Made-with: Cursor * system instructions: align report_progress guidance with mode opt-out Made-with: Cursor |
||
|
|
37f984f4f8 |
fix autofix body leak, harden prompt against injection, trigger-aware comments (#512)
* fix autofix body leak, harden prompt against injection, trigger-aware comments never forward event bodies to the agent prompt — they are user-generated content and a prompt injection vector. the agent fetches bodies on demand via MCP tools (checkout_pr, get_issue, etc.). - always set event.body to null in dispatch(), add promptFromBody: false to autofix, strip body from nested pull_request object - replace buildEventTitleBody with buildEventTitle rendering inline references like PR #497 ("Title") instead of raw markdown headings - add LEAPING_REASON_MAP for trigger-aware progress comments (e.g. "CI failure detected. Leaping into action...") - thread type through buildLeapingIntoActionComment, createLeapingComment, and updateCommentToLeaping Made-with: Cursor * rename translateWorkflowRunType.ts to workflowRunTypes.ts Made-with: Cursor |
||
|
|
b1f9878877 |
extract resolveModel() to run before agent selection
model resolution was duplicated inside each agent (opentoad, claude) and PULLFROG_MODEL override was not considered when choosing the agent. now resolveModel() runs first in main.ts, its result feeds into resolveAgent() for agent selection, and the resolved model is passed to the agent via ctx.resolvedModel. agents only handle their own fallback (opentoad: auto-select via opencode models, claude: strip provider prefix). also removes the hardcoded anthropic/claude-sonnet test runner default since ANTHROPIC_API_KEY is no longer in CI. Made-with: Cursor |
||
|
|
fcb835d129 |
0.0.186
rebrand "Repo intelligence" to "Learnings" with brain icon Made-with: Cursor |
||
|
|
f1400ffb7c |
remove cost logging from agent runs; extract secrets into own tab
- stop capturing/displaying total_cost_usd from Claude CLI (theoretical cost is misleading for subscription users) - remove Cost column from action logs table and GitHub Job Summary - extract SecretsCard into its own sidebar tab with KeyRound icon - remove children prop from AgentSettingsSection Made-with: Cursor |
||
|
|
ba1966f17c |
feat: encrypted account-level secrets (#501)
* feat: add encrypted account-level secrets with UI for adding API keys Adds AccountSecret model with AES-256-GCM encryption, API routes for CRUD, "Add secret" button in model costs section, and injects decrypted secrets into action env (YAML secrets take precedence). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: repo secrets, sidebar icons, lazy learnings history - add repo-level secrets with inheritance from org secrets - add icons to console sidebar sections - fix learnings history modal: lazy fetch with hover prefetch, strip content from list response, load content per-expansion Made-with: Cursor * add input validation bounds for secrets and fix client-side name filter Made-with: Cursor * refactor: migrate all client-side data fetching to TanStack Query Replace manual useState/useEffect/fetch patterns and the custom usePolling hook with useQuery, useInfiniteQuery, and useMutation across the entire frontend for consistent caching, background refetching, and reactive invalidation. - ActiveWorkflowRunsSection: useQuery + refetchInterval - WorkflowRunHistory: useInfiniteQuery + polling query - LearningsSection: useQuery per revision (lazy) - FlagsSettings: self-contained useQuery + useMutation - SecretsCard: useMutation for delete - AddWorkflowButton, VerifyWorkflowButton: useMutation - EmailSignupForm, email-waitlist: useMutation - providers.tsx: enable refetchOnWindowFocus - Delete usePolling.ts (no remaining consumers) Made-with: Cursor * address PR review: squash migrations, rename accountSecrets → dbSecrets Squash the two separate secrets migrations into a single migration. Rename the wire format field from accountSecrets to dbSecrets since it now carries merged account + repo secrets. Made-with: Cursor * fix: update proxyKeys.ts imports after cache.ts -> yes package migration Made-with: Cursor --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
0055aef618 |
feat: add Claude Code agent for Anthropic model users (#502)
* feat: add Claude Code agent for Anthropic model users Re-adds Claude Code support (removed in #478) so users with Anthropic API keys or Claude Code OAuth tokens can use their Claude subscriptions directly. When an Anthropic model is selected and Claude Code credentials are available, the system auto-selects the Claude agent instead of OpenCode. The harness mirrors opentoad's security model: native Bash blocked via --disallowedTools, MCP ShellTool for restricted shell, ASKPASS for git auth. Includes NDJSON streaming, provider error detection, cache/cost tracking, browser skill, and todo progress tracking. Key changes: - action/agents/claude.ts: full Claude Code harness - action/utils/agent.ts: auto-select Claude for anthropic/* models - action/utils/providerErrors.ts: extracted shared provider error detection - action/utils/skills.ts: extracted shared skill installation (agent-aware) - action/models.ts: add CLAUDE_CODE_OAUTH_TOKEN to anthropic envVars - action/utils/docker.ts: add CLAUDE_CODE_OAUTH_TOKEN to test env allowlist - CI: add claude to test matrix, pass CLAUDE_CODE_OAUTH_TOKEN secret Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove unused toolId variable, fix apiKeys test env cleanup The apiKeys test cleanup stripped *_API_KEY vars but missed CLAUDE_CODE_OAUTH_TOKEN which doesn't match that pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: strip provider prefix from PULLFROG_MODEL in Claude agent the env override path was returning the raw value (e.g. "anthropic/claude-sonnet-4-5") without stripping the provider prefix, causing the Claude CLI to receive an invalid model ID. Made-with: Cursor * fix: remove dead cliPath field, add CLAUDE_CODE_OAUTH_TOKEN to workflows remove unused cliPath from Claude agent RunParams, and pass CLAUDE_CODE_OAUTH_TOKEN through all pullfrog.yml workflow templates so users with Claude Pro/Team subscriptions can use their membership. Made-with: Cursor * fix: block Bash subagent in Claude Code disallowedTools Made-with: Cursor * chore: update model snapshot (opencode/openrouter latest → qwen3.6-plus-free) Made-with: Cursor --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
9f566d20e4 |
fix proxy key usage tracking and add OSS spend reporting (#500)
* fix proxy key usage tracking and add OSS spend reporting replace ProxyKey.disabled with finalizedAt to fix a race where keys were disabled before their usage was synced, causing all HWM values to be zero. retireKey now fetches final usage from OpenRouter and records it atomically with optimistic concurrency. syncAccountUsage skips keys that fail to fetch rather than recording false zeros. other fixes: - wrap OpenRouter API calls in retry logic (exponential backoff) - reconcileStaleWorkflowRuns now retires proxy keys for completed runs - subprocess activity timeout only tracks stdout (stderr retry loops no longer prevent hung agent detection) - add oss-spend script (single bulk fetch from OpenRouter) and backfill-proxy-key-usage script Made-with: Cursor * address review: move isActiveKey check inside transaction, remove redundant guard Made-with: Cursor |
||
|
|
bf68e0d915 |
fix: add blank line before footer divider to fix rendering after details
GitHub markdown needs a blank line between </details> and subsequent HTML elements. Without it, the footer renders inside the collapsed section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
a7b8dcbced |
Rework incremental diffing (#499)
* Improve our deepening logic * Use consistent SHA for PR-related operations in CheckoutPrTool * compute `deepenDepth` at more appropriate time * fix stale comment * add comments for `alreadyOnBranch` * ensure before sha is available * small cleanup * computeIncrementalDiff * move the util * improve algorithm * improve algorithm further * get rid of temp result array * add comment * compute incremental diff and updte instructions * add comment * update stale comment * get rid of redundant rev-parse call * improve comment * strenghten the instructions * make diff paths unique |
||
|
|
cb8e33360c |
feat: add prepush lifecycle hook (#498)
* feat: add prepush lifecycle hook Add `prepushScript` configuration — an optional shell script that runs automatically before pushing code to the remote repository. Reuses the existing `executeLifecycleHook` infrastructure (bash execution, 2-min timeout, error propagation on non-zero exit). When unconfigured the hook is a no-op. Made-with: Cursor * fix: add prepushScript to run-context API response, fix UI separator Include prepushScript in the settings returned by the run-context endpoint so the hook actually fires in production. Also fix the separator pattern in AgentSettings to match the existing convention (spacer + hr + spacer instead of margin). Made-with: Cursor |
||
|
|
c0f6f9ef2a |
Browser skill (#485)
* Add `BrowserTool` * add some logging * go with npm install -g * remove dep changes since the switch to npm install -g * tweak * tweak * tweak * tweak * tweak timeout * tweak * remove logs * skill investigation doc * wip * wip * tweak * lock agent-browser version * tweak * logs * logs * more logs * more debug stuff * try this * try this * try this * fix PATH * try this * tweak * tweak * tweak * update wiki entries * update wiki once again * lint fix --------- Co-authored-by: Colin McDonnell <colinmcd94@gmail.com> |
||
|
|
6b18b6730b |
live todo tracking, collapsible task list in final progress, hide set_output outside standalone (#492)
* fix false "without reporting progress" error + live todo tracking clean up orphaned progress comments when review is skipped or only set_output is used, preventing the false positive in handleAgentResult. parse todowrite events from OpenCode's NDJSON stream and render a live markdown checklist in the PR progress comment (2s debounce). agent's explicit report_progress always takes priority. Made-with: Cursor * fix contradictory review/progress prompting align Review and IncrementalReview mode prompts with their guidance — mode prompts said "always submit" while guidance said "skip if clean." remove the empty-approval submission that was silently dropped by the tool. make progress comment lifecycle explicit: created on first call, updated in place, removed after review submission. Made-with: Cursor * centralize todo tracking into shared TodoTracker module extract inline todo tracking logic (~95 lines) from opentoad.ts into action/utils/todoTracking.ts. the tracker is created once in main.ts and passed to agents via AgentRunContext.todoTracker, making it agent-agnostic and reusable for future agent implementations. Made-with: Cursor * fix todoTracker optional type to match file convention add | undefined to todoTracker in AgentRunContext, matching every other optional property in the same interface. Made-with: Cursor * instruct agents to always maintain a task list for live progress system prompt now tells agents to create an internal task list at the start of every run. the tracker renders it to the progress comment automatically. report_progress is reserved for final results only — no more intermediate "Checking..." messages that cancel the tracker and leave stale text on the comment. Made-with: Cursor * require report_progress summary at end of every run agents must always call report_progress with a final summary — the completed task list should never be the end state of the progress comment. updated all review mode prompts to call report_progress after submitting (or not submitting) a review. Made-with: Cursor * keep progress comment after review with final summary stop deleting the progress comment after review submission — the agent now always calls report_progress with a summary at the end, and that summary should persist as a record of what was done. Made-with: Cursor * harden stranded progress comment cleanup - main.ts: detect when tracker was last writer (agent never called report_progress) and delete the stranded checklist instead of leaving it as the final comment state - postCleanup.ts: expand stuck-comment detection to also catch stranded todo checklists (regex match for checklist patterns) when the process is killed before normal cleanup runs - modes.ts + selectMode.ts: add report_progress step to Summarize and SummaryUpdate modes (only modes that were missing it) Made-with: Cursor * fix stale comments, typo, and build mode redundancy - comment.ts: update deleteProgressComment docstring and inline comment to reflect current usage (stranded-comment cleanup, not post-review) - modes.ts: merge duplicate report_progress steps (8 + 10) into single step 9, fix "optimizatfixons" typo - wiki/post-cleanup.md: document checklist detection regex Made-with: Cursor * collapsible completed todos in final progress, hide set_output outside standalone mode - add renderCollapsible() to TodoTracker, append completed task list as <details> section when agent calls report_progress - cancel tracker after agent's final report_progress so it doesn't overwrite with raw checklist - conditionally register SetOutputTool only in standalone mode or when output_schema is provided - remove unconditional set_output instruction from orchestrator task section - update Summarize/SummaryUpdate mode guidance to not reference set_output Made-with: Cursor * show completion count in collapsible task list summary Made-with: Cursor * only count completed (not cancelled) in collapsible task list summary Made-with: Cursor * reinforce concise summary prompting across system prompt, modes, and tool description Made-with: Cursor * address review feedback: wasUpdated bypass, tracker false-positive, race condition - remove wasUpdated=true from cleanup paths so handleAgentResult correctly detects genuinely silent runs - add hadProgressComment to ToolState as immutable snapshot for the safety check - use todoTracker.hasPublished instead of enabled for stranded-comment cleanup - serialize onUpdate calls via inflightPromise chain with post-cancel guard - add settled() to wait for in-flight updates before writing final summary Made-with: Cursor * address round-2 review: hasPublished after success, finalSummaryWritten flag - set hasPublished only after onUpdate resolves (not before) so failed writes are not counted as published - add finalSummaryWritten flag to ToolState, set after successful non-plan reportProgress; decouple cleanup detection from todoTracker.enabled so it survives API failures where cancel() ran but the write didn't succeed Made-with: Cursor |
||
|
|
64f2238316 |
Repo Intelligence: agent-managed per-repo learnings with revision history (#487)
* add repo learnings feature with edit history introduces a new "Learnings" section in the repo console where agents can persist operational knowledge (setup steps, test commands, conventions) at the end of runs via an MCP tool. users can also edit learnings manually. - add `learnings` field to Repo model and `LearningsRevision` audit table - add `update_learnings` MCP tool for agents to persist repo knowledge - integrate learnings into prompt assembly as REPO LEARNINGS section - add learnings step to mode guidance (Build, AddressReviews, Plan, Fix, Task) - add PATCH /api/repo/[owner]/[repo]/learnings endpoint (JWT auth) - add GET /api/repo/[owner]/[repo]/learnings/history endpoint (Clerk auth) - add LearningsSection component with textarea, save-on-blur, and history modal - record revision history with actor tracking (agent vs user) and pruning (50 max) - gate UI behind owner === "pullfrog" for internal dogfooding Made-with: Cursor * fix prisma enum import path for LearningsActor Made-with: Cursor * simplify learnings schema: remove LearningsActor enum, store model name directly the actor/actorName split was unnecessary — learnings are only written by agents so the revision table just needs a model column. removes all user editing concepts from schema, API, and frontend. Made-with: Cursor * fix migration: add separate migration instead of rewriting existing one restores original learnings_revisions migration and adds a new migration that drops actor/actorName columns, backfills model from actorName, and drops the LearningsActor enum. Made-with: Cursor * polish learnings feature: rename to Repo Intelligence, fix atomicity, fix review skip - rename user-facing "learnings" to "Repo Intelligence" (UI, prompt section, wiki, sidebar) - simplify description to "Automatically discovered by the agent across runs." - wrap repo.update + revision create in $transaction for atomicity - refactor recordLearningsRevision to pruneLearningsRevisions (prune-only) - fix empty review skip: don't block APPROVE reviews with no body - fix broken docs anchor: #free-options → #free-models - update agent guidance to require flat bullet list format with pruning - add accessibility: aria-expanded, sr-only loading, output element - add chevron rotation, stale data clear on modal close, max-h scroll - trim + length-limit model field, remove type cast, restore pre-existing comment - update wiki prompt examples with actual bullet-formatted content - update model test snapshot Made-with: Cursor * fix stale free model name in docs, rename utility file to match export - docs/keys.mdx: MiMo V2 Flash → MiMo V2 Pro (matches model code change) - rename recordLearningsRevision.ts → pruneLearningsRevisions.ts Made-with: Cursor * Add skill, .neon * polish learnings UI and remove verbose log - learnings code block: read-only appearance with muted text, copy button, rounded corners - history modal: full-width rows with cursor-pointer, chevron moved to right, no preview text - drop noisy update_learnings log line Made-with: Cursor * inject learningsStep into all modes, drop seed script, soften revision styling Made-with: Cursor * Drop seed --------- Co-authored-by: Colin McDonnell <colinmcd94@gmail.com> |
||
|
|
e6d34ee01b |
add OpenRouter proxy for managed model routing and OSS program (#488)
* add OpenRouter proxy for managed model routing and OSS program proxy layer that mints ephemeral OpenRouter keys for users without BYOK API keys. two paths: pro plan users get their selected model proxied via OpenRouter; OSS program repos (hard-coded allowlist in config.ts) get free Claude Opus. BYOK env vars (PULLFROG_MODEL/OPENCODE_MODEL) always take precedence. frontend: OSS repos see a static "Opus (Free)" badge with the model dropdown disabled and no API key requirement. all models now carry openRouterResolve metadata for proxy target resolution. Made-with: Cursor * implement OSS program: proxy infrastructure for free model credits server-side OSS allowlist determines eligible repos. action mints ephemeral OpenRouter keys via OIDC-authenticated /api/proxy-token endpoint (idempotent on runId, $10 per-key safety limit). keys are disabled on workflow completion when no running refs remain. HWM-based usage sync tracks cumulative spend per account. schema: ProxyKey model, Account.usageUsd/activeKeyId, WorkflowRun.proxyKeyId action: OIDC credential stashing, resolveProxyModel uses server oss flag frontend: isOss flows from server page to components (no client allowlist) Made-with: Cursor * address PR review: repo cross-check, key retirement lifecycle, dead code removal - proxy-token: verify runId belongs to OIDC-authenticated repo via repo relation - add retireKey() shared primitive: disable in OpenRouter first, then mark disabled in DB - rotateKey: retire old active key after swap to prevent orphans - webhook: replace inline cleanupProxyKey with retireKey calls - syncAccountUsage: skip disabled keys - remove vestigial AccountPlan/plan field from action types - add disabled field to ProxyKey schema + migration Made-with: Cursor * replace deprecated opencode/mimo-v2-flash-free with mimo-v2-pro-free Made-with: Cursor * fix migration ordering: rename disabled migration to sort after table creation Made-with: Cursor * squash proxy key migrations into single migration Made-with: Cursor * add preview repo to OSS allowlist for testing Made-with: Cursor * populate OSS allowlist from oss-program-invitees.json Made-with: Cursor * format oss-program-invitees.json Made-with: Cursor * add installed public repos to OSS allowlist split ossRepos into three provenance-tracked lists: - internalRepos (pullfrog, colinhacks, RobinTail) - installedPublicRepos (external public non-fork repos with active installs) - invitees (from oss-program-invitees.json) also adds scripts/list-oss-candidates.ts to regenerate the installed list Made-with: Cursor * fix: resolve tokens before clearing OIDC env vars resolveTokens → acquireNewToken → isOIDCAvailable() checks ACTIONS_ID_TOKEN_REQUEST_URL/TOKEN env vars. The new OIDC stashing code was deleting them in restricted shell mode before resolveTokens ran, causing it to fall through to the GitHub App path which requires GITHUB_APP_ID/GITHUB_PRIVATE_KEY. Made-with: Cursor * derive proxy-token auth from OIDC claims, add ensureWorkflowRun upsert - proxy-token no longer requires body.runId; uses claims.run_id + claims.repository - shared ensureWorkflowRun upsert called from both webhook and proxy-token - workflow_run_requested handler now eagerly creates WorkflowRun records - eliminates race condition between webhook and action proxy-token call Made-with: Cursor * hardcode PULLFROG_ACCOUNT_ID, document preview debugging lessons GitHub node IDs are constant — no reason for this to be an env var. Removes the trailing-newline bug that caused P2025 errors. Adds wiki docs on workflow testing, Vercel env gotchas, and Neon preview branch discovery. Made-with: Cursor * fix: parse OpenRouter create-key response correctly the API returns `key` at the top level, not inside `data` Made-with: Cursor * onboarding cards, unlock OSS model selection, simplify console - add OnboardingCard component with two states: workflow install and model+test (dispatches "Tell me a joke" for test run) - replace PromptBox overlay gates with dedicated onboarding cards; PromptBox is now just the form, always enabled - use hasWorkflowRuns DB check to decide onboarding vs promptbox - unlock ModelSelector for OSS repos (was locked to Opus badge); resolve proxyModel from repo's selected model alias in run-context - rename "API key" row to "Model costs" with pure client-side states: OSS covered, auto-resolve, free model, BYOK with env var names - add "(Recommended)" badge to model aliases with recommended: true - remove OSS_MODEL_DISPLAY_NAME constant and secrets-fetching logic Made-with: Cursor * update stale xai model snapshot Made-with: Cursor * rename Permissions to Security, hide git push toggle, add shell isolation toggle with disabled state for public repos, remove opentoad agent name from logs Made-with: Cursor * chevron hover states, sidebar hooks/security entries Made-with: Cursor * address PR review feedback: rename recommended→preferred, fix dispatch orphan, update wiki docs - rename `recommended` to `preferred` in model alias registry to distinguish from the UI "Recommended" badge (which is hardcoded for opus + codex only) - cancel precreated WorkflowRun when workflow lookup fails in dispatch-workflow - replace run_sql/vercel env pull in wiki docs with neonctl + Prisma pattern - extend scripts/neon-branch.ts to output DATABASE_URL via neonctl - add Recommended badge to GPT Codex alongside Claude Opus in ModelSelector Made-with: Cursor |
||
|
|
3ff11f97eb |
replace deprecated opencode/mimo-v2-flash-free with mimo-v2-pro-free, update model snapshot
Made-with: Cursor |
||
|
|
3a1ffde545 |
update model snapshot (opencode latest → gpt-5.4-nano)
Made-with: Cursor |
||
|
|
c6a3ee0e9a |
show model name in footer, drop pullfrog.com link (#484)
* show model name in footer, drop pullfrog.com link add model slug to buildPullfrogFooter so every Pullfrog comment displays the active model (e.g. "Using `Big Pickle` (free)" or "Using `Claude Opus`"). remove the pullfrog.com link from all footers. Made-with: Cursor * reject <br/> tags in comment bodies, add prompt guidance add runtime validation in addFooter that throws if <br/> is followed by a non-blank line (breaks GitHub heading rendering). the agent sees the error and retries with clean markdown. also update Summarize mode prompt to explicitly forbid <br/> tags. Made-with: Cursor * fix <br/> guidance: move to event instructions, clarify blank line rule the formatting rule belongs in DEFAULT_PR_SUMMARY_INSTRUCTIONS (event instructions), not the Summarize mode prompt. clarify that <br/> must always be followed by a blank line before headings. Made-with: Cursor * generalize block-level HTML rule in summary instructions add a prominent top-level rule about requiring blank lines between ALL block-level HTML elements and markdown syntax, not just <br/>. Made-with: Cursor * move model to toolState instead of threading through params model is set once at startup and read everywhere — it belongs on toolState, not threaded as a separate param through 8 call sites. postCleanup runs without toolState so it just omits the model label. Made-with: Cursor * update models.dev snapshot (openai latest changed) Made-with: Cursor * add comment to models snapshot test explaining its purpose Made-with: Cursor |
||
|
|
30d68e53a7 |
fix: skip API key validation for free opencode models
free models (big-pickle, gpt-5-nano, etc.) define envVars: [] and isFree: true but validateAgentApiKey always required at least one provider key. now the validation is model-aware: free models bypass the check, keyed models validate their specific vars, and auto-select still requires at least one known key. closes #483 Made-with: Cursor |
||
|
|
cbbcb64859 |
restructure docs: split triggers into usage pages, add model resolution docs
- split triggers.mdx into direct-prompting, pr-reviews, issue-enrichment, coding-tasks - rename manual-setup.mdx to headless-action.mdx (CI integration) - reorganize sidebar into Getting started / Usage / Reference groups - add redirects for /triggers and /manual-setup - add PULLFROG_MODEL env var support across action, workflows, and docs - rewrite models.mdx with aliases, free models, resolution chain, routers - update all cross-references in app, components, and docs Made-with: Cursor |
||
|
|
250fe7eaa1 |
fix test token scoping: override GITHUB_TOKEN via OIDC in ensureGitHubToken
the runner's GITHUB_TOKEN (scoped to pullfrog/app) was leaking into test subprocesses targeting pullfrog/test-repo, causing 400s from the Pullfrog API on run-context fetches. instead of deleting GITHUB_TOKEN from the subprocess env, ensureGitHubToken now always mints a fresh OIDC token scoped to GITHUB_REPOSITORY when OIDC is available — replacing any inherited token with a correctly-scoped one. also adds an informative throw in acquireTokenViaGitHubApp when GITHUB_APP_ID/GITHUB_PRIVATE_KEY are missing. Made-with: Cursor |
||
|
|
6d25adfd1a |
Agent & model refactor (#478)
* agent & model refactor with ASKPASS git auth, UI restructure, clerk v7 Made-with: Cursor * fix stale agent/effort refs, add tests for askpass + model resolution - reviewCleanup.ts: payload.agent -> payload.model, remove effort - selectMode.ts PlanEdit: remove delegation/subagent/effort references - pullfrog.yml.ts: update env vars (drop GOOGLE_API_KEY/CURSOR_API_KEY, add GOOGLE_GENERATIVE_AI_API_KEY/XAI_API_KEY/MOONSHOT_API_KEY/OPENCODE_API_KEY) - FlagsSettings/RepoInstructionsSection: remove stale effort/timeout copy - new: gitAuthServer.test.ts (10 tests — lifecycle, token delivery, tamper detection, script gen) - new: agent.test.ts (4 tests — default opentoad, AGENT_OVERRIDE, invalid override) - new: models.test.ts (19 tests — parseModel, resolution, registry invariants) - update models.dev snapshot Made-with: Cursor * fix changed-agents.sh to filter legacy agent files from CI matrix legacy agent files (claude.ts, codex.ts, etc.) are @ts-nocheck and not exported from index.ts. changed-agents.sh now reads index.ts imports to build the active agent set and treats changes to inactive files as non-agent changes (opentoad canary only). Made-with: Cursor * remove MCP file tools, old agent harnesses, and obsolete security tests ASKPASS-based git auth makes the old MCP file tool security layer unnecessary: - token never in subprocess env, so symlink/gitattributes/hook attacks can't exfiltrate it - agents now use native file tools (OpenCode builtin read/edit) deleted: - action/mcp/file.ts (file_read, file_write, file_edit, file_delete, list_directory) - action/mcp/index.ts (dead re-export) - agent harnesses: claude.ts, codex.ts, cursor.ts, gemini.ts, opencode.ts - opencode-runner.ts (inlined into opentoad.ts) - security tests that validated MCP file tool restrictions - commented-out three-step review flow (~300 lines) - sanitizeSchema/wrapSchema dead code from mcp/shared.ts - OPENCODE_MODEL_MINI/MAX env vars (effort-level model overrides removed) updated test prompts to use generic file ops instead of MCP tool names. restored pkg-json-scripts + requirements-txt-attack (test --ignore-scripts defense). Made-with: Cursor * bump actions/checkout v4 → v6 (node 24) node 20 actions deprecated june 2, 2026. Made-with: Cursor * temporarily disable fail-fast on agnostic tests to debug checkout@v6 Made-with: Cursor * re-enable fail-fast on agnostic tests Made-with: Cursor * fix test token mismatch: mint OIDC tokens scoped to target repo CI tests override GITHUB_REPOSITORY to pullfrog/test-repo but inherit the runner's GITHUB_TOKEN (scoped to pullfrog/app), causing 401s on every run-context fetch. Clear GITHUB_TOKEN in the test subprocess so ensureGitHubToken() mints a properly scoped token via OIDC. Also centralizes the default GITHUB_REPOSITORY in runAgentStreaming instead of repeating it in every test file, and fixes preview-cleanup to remove workers from all queues (not just name-matching ones). Made-with: Cursor * fix ensureGitHubToken to try OIDC when app credentials are absent ensureGitHubToken only attempted token minting when GITHUB_APP_ID and GITHUB_PRIVATE_KEY were set. In CI, OIDC is available but app creds aren't exposed — so the guard prevented minting entirely. Made-with: Cursor * dead code cleanup: remove remnants of deleted agents, file tools, effort system remove unused @anthropic-ai/claude-agent-sdk and @openai/codex-sdk deps, orphaned file-tool security tests, dead GEMINI_MODEL passthrough, stale opencode-runner wiki refs, deleted test file references, and MCP file tool docs. rename docs/effort → docs/models. fix vitest setup: move dotenv to globalSetup (runs once before forks instead of per-file, 19s → 200ms). Made-with: Cursor * address review feedback: remove dead code, update stale references - remove AGENT_OVERRIDE (only opentoad exists) - remove shellToolName plumbing (always restricted shell) - bump action version to 0.0.179 - remove CURSOR_API_KEY from all workflows/configs - remove OPENCODE_MODEL_MINI/MAX from workflows/docs - delete wiki/effort.md, rewrite docs/effort.mdx as "Models" - rewrite wiki/modes.md: orchestrator/subagent → single agent - simplify flag system: drop builtin flag extraction (debug, effort, timeout, agent), keep custom flag replacement only - reserve all legacy flag names to prevent custom flag conflicts Made-with: Cursor * regenerate lockfile after removing claude-agent-sdk and codex-sdk Made-with: Cursor * fix import ordering, add lockfile check to pre-push hook Made-with: Cursor * remove dead debug payload field, stale packageExtensions Made-with: Cursor * merge proc-sandbox and token-exfil into a single test proc-sandbox and token-exfil were duplicative — both tested that SANDBOX_TEST_TOKEN couldn't be exfiltrated. consolidated into token-exfil with shell:restricted (which actually exercises filterEnv) and the /proc attack vector hints from proc-sandbox. Made-with: Cursor * fix wiki adversarial.md to match actual tokenExfil validator Made-with: Cursor |
||
|
|
5bcfae990a |
restructure dashboard UI, add mode instructions, post-review follow-up dispatch (#453)
* add mode instructions and restructure dashboard sidebar
- add modeInstructions JSONB field to Repo model for per-mode user instructions
- thread modeInstructions through settings API, run-context API, RepoSettings, ToolContext, and selectMode runtime
- merge user-defined mode instructions with hardcoded orchestrator guidance, with IncrementalReview inheriting from Review
- reduce visible built-in modes from 7 to 4 (Build, Review, Plan, Fix) with editable Instructions textareas
- add TRIGGERS group header to sidebar above Mentions, Pull requests, Issues
- add wiki/modes.md documenting triggers and modes conceptual model
Made-with: Cursor
* fix leaping comment deletion and address review feedback
- wrap post-createReview operations in try/finally so deleteProgressComment
runs even when updateReview or reportReviewNodeId throws
- add parseModeInstructions runtime guard to filter non-string values
from the JSONB field before passing to buildOrchestratorGuidance
- add useEffect sync for localInstructions when props change
- guard onBlur to skip save when instructions haven't changed
- update wiki/modes.md to reflect V2 is implemented (no longer "proposed")
Made-with: Cursor
* harden review cleanup, fix type cast, stabilize mode instructions state
- wrap deleteProgressComment in try/catch inside finally to prevent masking original errors
- replace `as Record<string,string>` cast with runtime parseModeInstructions + useMemo
- fix wiki dual-prompt table to reflect mode.prompt fallback status
Made-with: Cursor
* fix wiki tense and heading ambiguity from PR review
Made-with: Cursor
* fix review "edited" badge by using pending review + submit flow
create review as PENDING first (no event/body), build the footer with
the now-known review ID, then submitReview with the full body. single
atomic publish — no updateReview edit needed.
Made-with: Cursor
* add post-agent follow-up re-review dispatch
After the agent exits, check if PR HEAD moved past the reviewed commit
and dispatch a follow-up re-review. This closes the gap where push
webhooks are suppressed during in-flight reviews.
Made-with: Cursor
* add silent flag to follow-up re-review dispatch
Made-with: Cursor
* restructure dashboard for consistency and clarity
- consolidate tools into single grouped card (was 4 separate cards)
- merge coding + autofix CI into one section
- remove redundant trigger section descriptions
- add bidirectional crosslinks between modes and triggers
- inline instruction links (review/plan/build) into descriptions
- add save status indicators to all sections
- restructure flags with grouped built-in/custom cards
- flatten sidebar (remove dividers and group headers)
- tighten all descriptions
Made-with: Cursor
* update PR screenshots for new dashboard layout
Made-with: Cursor
* extend review context inline instead of dispatching new workflow
when commits are pushed during a review, the agent now handles them
inline: create_pull_request_review detects HEAD movement, returns
instructions to pull and review the incremental diff, and the agent
submits a second review covering only the new changes. this avoids
the cost of spinning up a full new workflow run.
also fixes a bug where reviewedSha was set to the submission HEAD
(current) rather than the checkout HEAD (what was actually reviewed),
which caused commits pushed between checkout and submission to be
silently missed by postReviewCleanup.
the workflow dispatch is kept as a safety net for agent timeout/error.
Made-with: Cursor
* polish dashboard UI: fix debug markers, crosslinks, title consistency, descriptions
- remove all red debug borders/labels and CM component
- remove all inline style={{}} debug outlines from crosslinks
- fix ambiguous crosslinks: Build→"Coding ↓", Plan→"Enrich issues ↓"
- add missing "Edit build instructions ↑" backlink on Auto-address reviews
- normalize card title weight to text-sm font-semibold across all cards
- rename "Default" subcard to "Setup" with broader description
- fix Mentions description to imperative tone
- broaden Flags section description to cover built-in and custom
- remove useless fragments in ModesSection and ToolsSettings
- restructure Agent section: remove ConsoleSection wrappers, add sidebar indent support
Made-with: Cursor
* extract PR quick links as standalone card, consistent with issues
- PR quick links is now its own card under Reviews (was a sub-toggle inside Review PRs disabled state)
- Review PRs OFF sets prCreated="none" instead of auto-falling back to "links"
- Review PRs card hides sub-toggles when disabled (re-review/approve don't apply)
- Both PRs and Issues now have identical Quick links card structure
Made-with: Cursor
* update reviews screenshot with standalone quick links card
Made-with: Cursor
* polish dashboard UI: revert quick links to inline toggles, fix fonts and spacing
- revert standalone PR/issue Quick Links cards back to inline toggles inside
Review PRs and Enrich Issues cards (fixes prCreated state coupling bug)
- restore original font-medium card titles across all trigger/settings cards
- fix sidebar: add CONSOLE heading, remove nested indentation, remove truncation
- right-justify Enrich Issues mode dropdown, group description with label
- move instructions links inline with behavior descriptions
- replace text save indicators with icon spinner/checkmark
- standardize section title spacing, move footer below danger zone
Made-with: Cursor
* fix formatting for biome lint
Made-with: Cursor
* address PR review feedback: cleanup guard, shared util, wiki update
- clear ctx.toolState.review after read to prevent double-execution of postReviewCleanup
- forward authorPermission in safety-net re-review dispatch
- extract parseModeInstructions to utils/schemas/modeInstructions.ts
- update wiki/modes.md: remove stale v1/v2 language, fix dashboard layout
- add typecheck to pre-push hook
Made-with: Cursor
* add action typecheck to pre-push, fix exactOptionalPropertyTypes errors
Made-with: Cursor
* fix duplicate actuallyReviewedSha from rebase
Made-with: Cursor
* remove PR screenshots
Made-with: Cursor
* add label/textarea association for mode instruction accessibility
Made-with: Cursor
---------
Co-authored-by: Colin McDonnell <colinmcd94@gmail.com>
|
||
|
|
8bac460177 |
fix: add concurrency protection to action sync workflows (#451)
* fix: add concurrency protection to action sync workflows * style: fix formatting in action/modes.ts --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> |
||
|
|
5684cbef77 |
Implement support for output schemas (#411)
* tweak examples * tweak prompt * Implement support for output schemas * fix: add example for structured output with zod schema * tweak * remove redundant cast * fix input name * strip $schema * hack around vendor requirement * clarify required result output --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> |