pullfrog 0.1.42 → 0.1.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -103022,7 +103022,7 @@ var import_semver = __toESM(require_semver2(), 1);
103022
103022
  // package.json
103023
103023
  var package_default = {
103024
103024
  name: "pullfrog",
103025
- version: "0.1.42",
103025
+ version: "0.1.43",
103026
103026
  type: "module",
103027
103027
  bin: {
103028
103028
  pullfrog: "dist/cli.mjs",
@@ -103704,8 +103704,78 @@ HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
103704
103704
 
103705
103705
  Report findings clearly with file:line references and quoted evidence where possible. Flag uncertainty explicitly \u2014 if you cannot verify a claim, say so rather than guess.`;
103706
103706
 
103707
+ // utils/promptProfile.ts
103708
+ function promptProfile() {
103709
+ return process.env.PULLFROG_PROMPT_PROFILE === "full" ? "full" : "lean";
103710
+ }
103711
+ function byProfile(lean, full) {
103712
+ return promptProfile() === "lean" ? lean : full;
103713
+ }
103714
+
103707
103715
  // modes.ts
103708
- var PR_SUMMARY_FORMAT = `### Default format
103716
+ var PR_SUMMARY_FORMAT_LEAN = `### Default format
103717
+
103718
+ The body has at most three parts, in this order:
103719
+
103720
+ 1. **Reviewed changes preamble** \u2014 a bolded \`**Reviewed changes**\` lead-in with one sentence on what was reviewed in this run (for \`IncrementalReview\`: what changed since the prior pullfrog review), then a bullet list of the substantive changes \u2014 short bolded title, one sentence each. A reviewer should understand the full reviewed scope from this list alone. Close the preamble with the metadata comment below.
103721
+ 2. **Cross-cutting issue sections** (zero or more) \u2014 one \`### {emoji} {what's wrong, not what to do}\` heading per concern.
103722
+ 3. **\`### \u2139\uFE0F Nitpicks\`** at the very bottom, if any \u2014 a flat bullet list, no technical-details block.
103723
+
103724
+ **Inline vs. body.** Concerns that anchor to a specific line go inline (the \`comments\` parameter), even when their implications are broad. Body \`### \` sections are reserved for concerns that have **no line to anchor to** \u2014 *absence* (something the diff should have done but didn't), *sequencing* (rollout / deletion / migration order), *design decisions only the human can make*, or *scope questions the diff raises but doesn't address*. With no non-anchorable concerns, the body is just the preamble + metadata.
103725
+
103726
+ **Severity emoji** on every \`### \` heading, and nowhere else: \u{1F6A8} critical (blocks merge \u2014 data loss, security, broken core flow) \xB7 \u26A0\uFE0F important (must address before merging) \xB7 \u2139\uFE0F informational (mergeable as-is).
103727
+
103728
+ **Blank line between every block-level element.** GitHub's markdown parser requires one before and after HTML tags (\`<details>\`, \`<summary>\`, \`<sub>\`, \`<br/>\`) \u2014 without it GitHub treats what follows as a continuation of the HTML block and renders your markdown as literal text. This is a parser quirk, not a style preference, and it permanently breaks the posted review.
103729
+
103730
+ ## Metadata comment
103731
+
103732
+ Fill every field from the \`checkout_pr\` response \u2014 never count files or commits by hand. For \`IncrementalReview\`, fill \`Prior pullfrog review\` from \`list_pull_request_reviews\`.
103733
+
103734
+ \`\`\`
103735
+ <!--
103736
+ Pullfrog review metadata. These findings were written against {head_sha_short};
103737
+ if commits have landed on {head_ref} since, treat every specific bug, file, or
103738
+ line callout as POTENTIALLY STALE and re-diff before acting on it.
103739
+
103740
+ - Mode: Review (initial) or IncrementalReview (delta against prior pullfrog review)
103741
+ - Files reviewed: {file_count}
103742
+ - Commits reviewed: {commit_count}
103743
+ - Base: {base_ref} ({base_sha_short})
103744
+ - Head: {head_ref} ({head_sha_short})
103745
+ - Reviewed commits:
103746
+ - {sha_short} \u2014 {commit_subject}
103747
+ - Prior pullfrog review: none or {prior_sha_short} ({prior_review_html_url})
103748
+ - Submitted at: {iso_timestamp}
103749
+ -->
103750
+ \`\`\`
103751
+
103752
+ ## Technical details
103753
+
103754
+ Every body \`### \` section carries one; an inline comment carries one when its fix is non-trivial or spans files. The visible part above it states the PROBLEM in 2-3 sentences \u2014 what's broken and what the blast radius is. Asks, fixes, and open questions live inside the block, which a downstream fix-agent pulls down as its brief, so \`file:line\` refs and identifier density belong here.
103755
+
103756
+ \`\`\`
103757
+ <details><summary>Technical details</summary>
103758
+
103759
+ \\\`\\\`\\\`\\\`markdown
103760
+ # {title}
103761
+
103762
+ ## Affected sites
103763
+ - {file path:line} \u2014 {what's wrong there}
103764
+
103765
+ ## Required outcome
103766
+ - {what the fix needs to achieve, not how to achieve it}
103767
+
103768
+ ## Suggested approach (optional)
103769
+ ## Open questions for the human (optional)
103770
+ \\\`\\\`\\\`\\\`
103771
+
103772
+ </details>
103773
+ \`\`\`
103774
+
103775
+ The 4-backtick fence lets the block hold its own 3-backtick fences and stay one-click copyable. Skip the optional sections when they'd add nothing.
103776
+
103777
+ Backtick-wrap identifiers and file names. Don't repeat diff content, don't include raw \`+123 / -45\` stats, no changelog, no horizontal rules, and no \`### Key changes\` / \`### Issues found\` / \`<b>TL;DR</b>\` heading \u2014 each \`### \` heading IS the issue.`;
103778
+ var PR_SUMMARY_FORMAT_FULL = `### Default format
103709
103779
 
103710
103780
  The body has at most three parts in this exact order:
103711
103781
 
@@ -103846,6 +103916,9 @@ Inline comments use the same severity framing as body \`### \` sections, scaled
103846
103916
  - **Don't repeat diff content**, don't include raw \`+123 / -45\` stats, don't include a changelog section, don't use horizontal rules (\`---\`).
103847
103917
  - **Pull file/commit counts from \`checkout_pr\` metadata** \u2014 never count manually.
103848
103918
  - **Legacy headings REMOVED.** Do not use \`### Key changes\`, \`### Issues found\`, \`<b>TL;DR</b>\`, or \`<sub><b>Summary</b>\`. The new structure subsumes them.`;
103919
+ function prSummaryFormat() {
103920
+ return byProfile(PR_SUMMARY_FORMAT_LEAN, PR_SUMMARY_FORMAT_FULL);
103921
+ }
103849
103922
  function computeModes(agentId, signedCommits = false) {
103850
103923
  const t2 = (toolName) => formatMcpToolRef(agentId, toolName);
103851
103924
  const commitStep = signedCommits ? `commit via \`${t2("commit_changes")}\` \u2014 it lands a GitHub-signed commit directly on the remote branch (no push step)` : `commit locally via shell (\`git add . && git commit -m "..."\`)`;
@@ -103869,7 +103942,17 @@ function computeModes(agentId, signedCommits = false) {
103869
103942
  - plan your approach before writing code: identify which files need to change, key design decisions, and edge cases. for non-trivial changes, consider whether there's a more elegant approach.
103870
103943
  - run relevant tests/lints before committing
103871
103944
 
103872
- 5. **self-review**: judgment call \u2014 does YOUR diff warrant a fresh-eyes pass?
103945
+ 5. **self-review**: ${byProfile(
103946
+ `unless the diff has no behavioral surface at all \u2014 docs, comments, whitespace, import reordering, lockfile or generated-code regeneration, a mechanical rename, a trusted dep patch bump \u2014 dispatch the \`${REVIEWER_AGENT_NAME}\` subagent to review it with fresh eyes against YOUR TASK. Line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default earns a pass. When in doubt, run it \u2014 a false-positive dispatch costs cents, a missed bug costs much more.
103947
+
103948
+ Before dispatching, make \`origin/<base>\` available: \`git fetch --no-tags --deepen=1000 origin <base>:refs/remotes/origin/<base>\`. The explicit destination refspec is required \u2014 a shallow single-branch checkout otherwise only updates \`FETCH_HEAD\` and never creates the tracking ref. The reviewer is read-only by contract, so fetching is your job.
103949
+
103950
+ In the dispatch prompt: say this is a PRE-COMMIT self-review whose work is uncommitted in the working tree, give the branch and base, name \`git diff --merge-base origin/<base>\` as the canonical diff command, paste YOUR TASK, and summarize any build-phase failures. If that diff comes back empty, there is nothing to review \u2014 stop.
103951
+
103952
+ Give it the diff and the task, nothing else. Do not summarize what you implemented, curate a reading list of files, or pre-shape the output with a severity schema \u2014 each biases the reviewer toward validating your solution instead of questioning it. Where the diff rests on third-party API, SDK, framework, or DB-engine semantics, tell it to verify load-bearing claims by web search and quote sources.
103953
+
103954
+ Treat what comes back as hypotheses, not directives: verify each against the code before applying, and reject findings that would add ceremony without correctness \u2014 defensive checks for cases that cannot happen, single-use abstractions, comments restating code, tautological tests. After applying what you accept, re-read your own diff and revert anything that turned out to be bloat. Then ${commitStep}.`,
103955
+ `judgment call \u2014 does YOUR diff warrant a fresh-eyes pass?
103873
103956
 
103874
103957
  Skip self-review (commit directly) when the diff is **genuinely trivial**:
103875
103958
  - doc typos, comment-only edits, whitespace/format-only, import reordering
@@ -103920,7 +104003,8 @@ function computeModes(agentId, signedCommits = false) {
103920
104003
  - Do NOT defect-hunt the diff yourself in parallel with the subagent. Your role is dispatch + evaluation; doing the review yourself reintroduces the implementation bias the subagent is meant to mitigate.
103921
104004
  - For diffs that rely on third-party API contracts, SDK semantics, framework directives, or DB engine specifics, instruct the subagent to verify load-bearing claims via web search and quote source URLs rather than trust training data \u2014 this is the single most common review-quality failure mode.
103922
104005
 
103923
- Be **discerning** about what comes back. The reviewer is an AI subagent and is fallible \u2014 treat every finding as a hypothesis, not a directive, and **verify each one yourself** against the diff and the code before deciding whether to apply. You are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. Do not over-engineer, do not be over-defensive, **do not write AI slop**. Reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for cases that cannot happen, extra logging, new abstractions used once, comments restating code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. Reject those. For each surviving finding, ask: would applying it leave the code more sound, correct, AND elegant? Two-out-of-three means look harder for a fix that gets all three before settling. After applying the fixes you accept, re-read your diff and be discerning about what *you just changed*: if any fix turned out to be bloat in context, revert it. Then verify only intended changes are present, no debug artifacts or commented-out code remain, no unrelated files were modified. Then ${commitStep}.
104006
+ Be **discerning** about what comes back. The reviewer is an AI subagent and is fallible \u2014 treat every finding as a hypothesis, not a directive, and **verify each one yourself** against the diff and the code before deciding whether to apply. You are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. Do not over-engineer, do not be over-defensive, **do not write AI slop**. Reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for cases that cannot happen, extra logging, new abstractions used once, comments restating code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. Reject those. For each surviving finding, ask: would applying it leave the code more sound, correct, AND elegant? Two-out-of-three means look harder for a fix that gets all three before settling. After applying the fixes you accept, re-read your diff and be discerning about what *you just changed*: if any fix turned out to be bloat in context, revert it. Then verify only intended changes are present, no debug artifacts or commented-out code remain, no unrelated files were modified. Then ${commitStep}.`
104007
+ )}
103924
104008
 
103925
104009
  6. **finalize**:
103926
104010
  - ${finalizeStep} (see *SYSTEM* Git rules if this fails \u2014 prepush errors are usually the repo's tests/lint, not infra timeouts)
@@ -103973,7 +104057,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
103973
104057
  //
103974
104058
  // Severity categorization is split across two surfaces: the opening
103975
104059
  // callout (CAUTION/IMPORTANT/ℹ️/✅) sets the review's overall tier, and
103976
- // per-bullet emoji prefixes (🚨/⚠️/ℹ️ in PR_SUMMARY_FORMAT) tag
104060
+ // per-bullet emoji prefixes (🚨/⚠️/ℹ️ in prSummaryFormat()) tag
103977
104061
  // individual points inside summary sections — scoping severity to the
103978
104062
  // specific bullet rather than the whole section keeps a section that
103979
104063
  // mixes a 🚨 and an ℹ️ from being mislabeled by either of them.
@@ -103986,7 +104070,30 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
103986
104070
 
103987
104071
  2. **checkout**: call \`${t2("checkout_pr")}\` \u2014 this returns PR metadata, a \`diffPath\`, and a supplemental \`impactPath\` when change-impact extraction is enabled. read the complete raw diff end-to-end, beginning with the TOC and using its file line ranges as your coverage checklist. only after that, use \`impactPath\` as an explicitly incomplete list of reference leads; it never replaces raw-diff reading or establishes coverage.
103988
104072
 
103989
- 3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
104073
+ ${byProfile(
104074
+ `3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
104075
+
104076
+ skip the deeper pass and submit a \`No new issues found.\` review per step 7 only when the diff has **no behavioral surface at all** \u2014 doc typos, whitespace/formatting, lockfile or generated-code regeneration, a mechanical rename whose only effect is import-path updates. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
104077
+
104078
+ 4. **specialist decision**: after reading the complete diff, name the questions you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most reviews need zero or one; some need several.
104079
+
104080
+ **There is NO one-specialist cap or fixed maximum.** cover every orthogonal question that remains; do not collapse several real questions into one broad prompt just to reduce the count. there is no file-count, line-count, or budget threshold either \u2014 diff size is not a proxy for review uncertainty.
104081
+
104082
+ frame each question through the lens that primes the right failure modes. for high-stakes subsystems, lead with the **domain** ("the billing lens", "the auth lens", "the schema-migration lens") rather than the generic equivalent ("correctness on billing code") \u2014 the domain framing makes the subagent recall double-charges, refund races, currency rounding, and dispute flows that a generic lens misses.
104083
+
104084
+ you remain the synthesizer: reading the complete raw diff, investigating surrounding code, validating every returned finding, and writing the review are yours. specialist reads supplement that work; they never satisfy your own coverage obligation.
104085
+
104086
+ 5. **dispatch specialists (only if step 4 found unresolved questions)**: for 2+ questions, emit every Task tool_use block **IN A SINGLE ASSISTANT TURN** before reading any result, so the investigations run in parallel rather than serially. your own \`read\` / \`grep\` / \`webfetch\` calls can ride in that same turn at zero extra wall time.
104087
+
104088
+ if a specialist errors out, times out, or returns nothing usable, retry it once. if it still fails, resolve the question yourself; if it remains disposition-changing and unresolved, surface the limitation and do not approve. each dispatch carries:
104089
+ - **the absolute \`diffPath\` (and \`incrementalDiffPath\` if available) from step 2's \`${t2("checkout_pr")}\` return, named verbatim in the dispatch prompt** (e.g. \`diffPath: /tmp/pullfrog-XXXX/pr-NNN-SHA.diff\`). the reviewer's baked-in system prompt selects its FIRST action on this token \u2014 paraphrasing ("review the diff", "look at this PR") sends it down a \`git diff origin/<base>\` fallback that fails on shallow GHA checkouts. it \`read\`s those files for scope and must NOT re-derive the diff itself; reading and codebase exploration are still its job.
104090
+ - **exactly one falsifiable question with explicit scope boundaries** \u2014 ask for evidence that supports or refutes it, never a broad "review for X, Y, and Z" prompt.
104091
+ - **a Task \`description\` set to a short hypothesis label** (e.g. \`"webhook-replay"\`, \`"billing-rounding"\`) \u2014 the harness reads this field to label the subagent's log lines so parallel runs can be told apart. without it, every subagent shows up as \`subagent#N\`.
104092
+ - if the question touches third-party API, SDK, or framework contracts, instruct the subagent to verify load-bearing claims via web search and quote source URLs rather than trust training data. action runs are non-interactive \u2014 nobody is in the loop to catch "I'm pretty sure Stripe does X."
104093
+ - ask for findings with file paths and NEW line numbers from the diff so you can validate and anchor them.
104094
+
104095
+ delegation discipline: do NOT summarize the PR for them (a lossy summary biases toward a validation frame; the raw diff is the source), do NOT hand them a curated reading list, do NOT pre-shape their output with a finding schema, and do NOT mention the other specialists \u2014 independence is the point, and overlapping findings are a strong signal.`,
104096
+ `3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
103990
104097
 
103991
104098
  if the PR is **genuinely trivial**, skip specialists entirely and submit a \`No new issues found.\` review per step 7.
103992
104099
 
@@ -104057,7 +104164,8 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
104057
104164
  - do NOT summarize the PR for them (a lossy summary biases toward a validation frame; the raw diff is the source)
104058
104165
  - do NOT hand them a curated reading list (let them discover scope)
104059
104166
  - do NOT pre-shape their output with a finding schema
104060
- - do NOT mention the other lenses (independence is the point \u2014 overlapping findings are a strong signal)
104167
+ - do NOT mention the other lenses (independence is the point \u2014 overlapping findings are a strong signal)`
104168
+ )}
104061
104169
 
104062
104170
  6. **aggregate & draft**: when specialist results land, merge findings; de-dup overlaps (two specialists catching the same issue = higher-confidence signal); trace each finding yourself before accepting it. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the PR (heuristic: if the finding's root cause lives in lines this PR added or modified, it's in scope; otherwise drop unless the PR plausibly introduced or amplified the regression), and anything not actionable. also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or worse, degrades elegance to nominally improve correctness) makes the codebase worse, not better.
104063
104171
 
@@ -104091,7 +104199,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
104091
104199
  - **no actionable issues**:
104092
104200
  \`approved: true\`. Body opens with \`> \u2705 No new issues found.\\n\\n\` followed by the PR summary.
104093
104201
 
104094
- ${PR_SUMMARY_FORMAT}`
104202
+ ${prSummaryFormat()}`
104095
104203
  },
104096
104204
  // IncrementalReview shares Review's minimum hypothesis-covering specialist
104097
104205
  // routing and body format, scoped to the incremental delta against the
@@ -104125,7 +104233,30 @@ ${PR_SUMMARY_FORMAT}`
104125
104233
 
104126
104234
  The remaining open threads feed step 8's dedup filter \u2014 anything already flagged and unchanged by the new commits should not be re-raised. The rolling PR summary snapshot is the durable record of retire activity; you don't need to surface it in the review body.
104127
104235
 
104128
- 5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
104236
+ ${byProfile(
104237
+ `5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
104238
+
104239
+ skip the deeper pass and jump to step 10's non-substantive path (do NOT submit a review) only when the incremental changes have **no behavioral surface at all** \u2014 formatting, comment tweaks, import reordering, lockfile regen, a mechanical rename of import paths. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
104240
+
104241
+ 6. **specialist decision**: after covering the incremental and full diffs, name the questions about the new changes that you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most incremental reviews need zero or one, especially thread-reply re-reviews; some need several.
104242
+
104243
+ **There is NO one-specialist cap or fixed maximum.** cover every orthogonal question that remains; do not collapse several real questions into one broad prompt just to reduce the count. there is no file-count, line-count, or budget threshold either \u2014 diff size is not a proxy for review uncertainty.
104244
+
104245
+ frame each question through the lens that primes the right failure modes. for high-stakes subsystems, lead with the **domain** ("the billing lens", "the auth lens", "the schema-migration lens") rather than the generic equivalent \u2014 the domain framing makes the subagent recall failure modes a generic lens misses.
104246
+
104247
+ you remain the synthesizer: reading the complete raw full diff plus the incremental diff, investigating surrounding code, validating every returned finding, and writing the review are yours. specialist reads supplement that work; they never satisfy your own coverage obligation.
104248
+
104249
+ 7. **dispatch specialists (only if step 6 found unresolved questions)**: for 2+ questions, emit every Task tool_use block **IN A SINGLE ASSISTANT TURN** before reading any result, so the investigations run in parallel rather than serially. your own \`read\` / \`grep\` / \`webfetch\` calls can ride in that same turn.
104250
+
104251
+ if a specialist errors out, times out, or returns nothing usable, retry it once. if it still fails, resolve the question yourself; if it remains disposition-changing and unresolved, surface the limitation and do not approve. each dispatch carries:
104252
+ - **the absolute diff path(s) from step 2's \`${t2("checkout_pr")}\` return, named verbatim in the dispatch prompt.** when \`incrementalDiffPath\` is present, name BOTH (\`incrementalDiffPath: /tmp/.../pr-NNN-SHA-incremental.diff\` then \`diffPath: /tmp/.../pr-NNN-SHA.diff\`) \u2014 the reviewer's baked-in prompt reads incremental first and uses full for context; when only \`diffPath\` exists, name it alone. it \`read\`s those files and must NOT re-derive the diff itself; paraphrasing ("review the new commits") sends it down a \`git diff\` fallback that fails on shallow GHA checkouts. do NOT tell them to skip pre-existing issues \u2014 that suppresses regressions the new commits amplified; the "issues must be NEW" filter lives at aggregation time (step 8), not in the subagent prompt.
104253
+ - **exactly one falsifiable question with explicit scope boundaries** \u2014 ask for evidence that supports or refutes it, never a broad "review for X, Y, and Z" prompt.
104254
+ - **a Task \`description\` set to a short hypothesis label** \u2014 the harness reads this field to label log lines so parallel runs can be told apart.
104255
+ - if the question touches third-party API, SDK, or framework contracts, instruct the subagent to verify load-bearing claims via web search and quote source URLs.
104256
+ - ask for findings with file paths and NEW line numbers from the full PR diff so you can validate and anchor them.
104257
+
104258
+ delegation discipline: do NOT summarize the changes for them (a lossy summary biases toward a validation frame; the raw diff is the source), do NOT hand them a curated reading list, do NOT pre-shape their output with a finding schema, and do NOT mention the other specialists \u2014 independence is the point.`,
104259
+ `5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
104129
104260
 
104130
104261
  if the incremental changes are **genuinely trivial**, skip specialists entirely and jump to step 10's non-substantive path (do NOT submit a review).
104131
104262
 
@@ -104166,7 +104297,8 @@ ${PR_SUMMARY_FORMAT}`
104166
104297
  - do NOT summarize the changes for them (a lossy summary biases toward a validation frame; the raw diff is the source)
104167
104298
  - do NOT hand them a curated reading list (let them discover scope)
104168
104299
  - do NOT pre-shape their output with a finding schema
104169
- - do NOT mention the other lenses (independence is the point)
104300
+ - do NOT mention the other lenses (independence is the point)`
104301
+ )}
104170
104302
 
104171
104303
  8. **aggregate, draft, self-critique**: merge findings (yours + output from every specialist you dispatched); de-dup overlaps; trace each finding yourself. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the new commits, anything not actionable, and anything that re-states prior review feedback (heuristic: if the finding's root cause lives in lines the *new commits* added or modified, it's in scope; otherwise drop). also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or degrades elegance to nominally improve correctness) makes the codebase worse, not better. To compute "lines the new commits added or modified": if \`incrementalDiffPath\` from step 2 is present, use it directly. Otherwise, take the prior Pullfrog review's \`commit_id\` (returned alongside each entry from \`${t2("list_pull_request_reviews")}\` in step 4) and run \`git diff <prior-review-sha>..HEAD\` to isolate the lines added since that review.
104172
104304
 
@@ -104189,7 +104321,7 @@ ${PR_SUMMARY_FORMAT}`
104189
104321
  - ELSE IF INFORMATIONAL OBSERVATIONS (mergeable as-is, but worth surfacing \u2014 e.g. prior feedback addressed cleanly with one minor stale doc reference, or a noteworthy positive observation): call \`${t2("create_pull_request_review")}\` with \`approved: true\`, NO inline comments, and the review body. body opens with \`> \u2705 No new issues found.\\n\\n\` (or similar friendly green opener), followed by the PR summary using the default format below. If a point is concrete enough to anchor to a line, downgrade the whole review to "minor suggestions only" (\`approved: false\`) instead \u2014 the \u2705 signals "no action needed", which contradicts an actionable anchor.
104190
104322
  - ELSE IF NO NEW ISSUES, SUBSTANTIVE CHANGES (new functionality, behavior changes, or fixes to prior review feedback): call \`${t2("create_pull_request_review")}\` to create a PR review. If all previous reviews have been properly addressed and no new issues were discovered, set \`approved: true\`. body opens with \`> \u2705 No new issues found.\\n\\n\`, followed by the PR summary using the default format below.
104191
104323
 
104192
- ${PR_SUMMARY_FORMAT}`
104324
+ ${prSummaryFormat()}`
104193
104325
  },
104194
104326
  {
104195
104327
  name: "Plan",
@@ -160848,7 +160980,10 @@ var CreatePullRequestReview = type({
160848
160980
  // running. required-ness is the only pressure that empirically held.
160849
160981
  // see wiki/review-approval.md.
160850
160982
  body: type.string.describe(
160851
- `1-2 sentence high-level summary with urgency level, critical callouts, and feedback about code outside the diff. Specific feedback on diff lines goes in 'comments' array. ALWAYS pass this parameter \u2014 pass an empty string "" when approving with no commentary, never omit it.`
160983
+ byProfile(
160984
+ `The review summary, plus any concern that has no diff line to anchor to. Your mode guidance defines its structure. Feedback that does anchor to a line goes in the 'comments' array. ALWAYS pass this parameter \u2014 pass an empty string "" when approving with no commentary, never omit it.`,
160985
+ `1-2 sentence high-level summary with urgency level, critical callouts, and feedback about code outside the diff. Specific feedback on diff lines goes in 'comments' array. ALWAYS pass this parameter \u2014 pass an empty string "" when approving with no commentary, never omit it.`
160986
+ )
160852
160987
  ),
160853
160988
  approved: type.boolean.describe(
160854
160989
  "Set to true to submit as an approval. Use for `> \u2705 No new issues found.` reviews where the PR is mergeable as-is and nothing in the body warrants code changes \u2014 approving also suppresses the Fix-button footer affordance so users don't dispatch a fix run on non-actionable feedback. Reserve approved: false for `> \u2139\uFE0F ...` (minor suggestions inline), `> [!IMPORTANT]` (recommended changes), and `> [!CAUTION]` (critical) reviews. Defaults to false (comment-only review). Mutually exclusive with request_changes. Approval is REJECTED while any unresolved Pullfrog review thread remains open on the PR (not just the latest commit's diff): resolve the threads the current code addresses (reply + resolve_review_thread) first, or submit a non-approving review if a real issue remains."
@@ -160884,7 +161019,10 @@ function CreatePullRequestReviewTool(ctx) {
160884
161019
  return tool({
160885
161020
  name: "create_pull_request_review",
160886
161021
  mutates: true,
160887
- description: `Submit a review for an existing pull request. Example: \`create_pull_request_review({ pull_number: 1234, body: "LGTM", approved: true, comments: [{ path: "src/api.ts", line: 42, body: "nit: rename" }] })\`. Each call creates a permanent, visible review on the PR \u2014 NEVER submit test or diagnostic reviews. Set \`approved: true\` to approve, \`request_changes: true\` to submit a blocking review, or neither for a plain comment review (the three are mutually exclusive). Reviews with no body AND no comments are silently skipped (nothing to post). IMPORTANT: 95%+ of feedback should be in 'comments' array with file paths and line numbers. Only use 'body' for a 1-2 sentence summary with urgency and critical callouts. Use 'suggestion' to propose replacement code - MUST preserve exact indentation of original code. The first submission may error once with a one-time diff-coverage nudge listing unread TOC regions \u2014 retry with the same arguments and the pre-flight will not block again. Example replacing lines 42-44 (3 lines) with 5 lines: { path: 'src/api.ts', start_line: 42, line: 44, suggestion: ' const result = await fetch(url);\\n if (!result.ok) {\\n log.error(result.status);\\n throw new Error("request failed");\\n }' } CONSTRAINT: Inline comments can ONLY target files and lines that appear in the PR diff. Comments anchored outside a diff hunk are dropped automatically (with a note appended to the review body) \u2014 the rest of the review still posts.`,
161022
+ description: 'Submit a review for an existing pull request. Example: `create_pull_request_review({ pull_number: 1234, body: "LGTM", approved: true, comments: [{ path: "src/api.ts", line: 42, body: "nit: rename" }] })`. Each call creates a permanent, visible review on the PR \u2014 NEVER submit test or diagnostic reviews. Set `approved: true` to approve, `request_changes: true` to submit a blocking review, or neither for a plain comment review (the three are mutually exclusive). Reviews with no body AND no comments are silently skipped (nothing to post). ' + byProfile(
161023
+ "Feedback that anchors to a specific line goes in the 'comments' array with file paths and line numbers. The 'body' carries the review summary plus any concern with no line to anchor to; your mode guidance defines its structure. ",
161024
+ "IMPORTANT: 95%+ of feedback should be in 'comments' array with file paths and line numbers. Only use 'body' for a 1-2 sentence summary with urgency and critical callouts. "
161025
+ ) + `Use 'suggestion' to propose replacement code - MUST preserve exact indentation of original code. The first submission may error once with a one-time diff-coverage nudge listing unread TOC regions \u2014 retry with the same arguments and the pre-flight will not block again. Example replacing lines 42-44 (3 lines) with 5 lines: { path: 'src/api.ts', start_line: 42, line: 44, suggestion: ' const result = await fetch(url);\\n if (!result.ok) {\\n log.error(result.status);\\n throw new Error("request failed");\\n }' } CONSTRAINT: Inline comments can ONLY target files and lines that appear in the PR diff. Comments anchored outside a diff hunk are dropped automatically (with a note appended to the review body) \u2014 the rest of the review still posts.`,
160888
161026
  parameters: CreatePullRequestReview,
160889
161027
  execute: execute(
160890
161028
  async ({ pull_number, body, approved, request_changes, commit_id, comments = [] }) => {
@@ -165817,7 +165955,10 @@ You execute tasks directly using your native tools and the ${pullfrogMcpName} MC
165817
165955
 
165818
165956
  Call \`${t2("select_mode")}\` with the appropriate mode name. This returns **your workflow** \u2014 a step-by-step playbook you must follow.
165819
165957
 
165820
- **Follow the returned guidance as your primary instruction set.** Do not improvise \u2014 the guidance defines the exact steps.
165958
+ ${byProfile(
165959
+ `**Work through the returned steps in order.** It is the house playbook for this kind of task and it encodes what usually matters. Where the task in front of you genuinely calls for something better, do that instead and say why in your final summary.`,
165960
+ `**Follow the returned guidance as your primary instruction set.** Do not improvise \u2014 the guidance defines the exact steps.`
165961
+ )}
165821
165962
 
165822
165963
  Available modes:
165823
165964
  ${ctx.modes.map((m) => `- "${m.name}": ${m.description}`).join("\n")}
@@ -165855,13 +165996,18 @@ You are a diligent, detail-oriented, no-nonsense software engineering agent. You
165855
165996
 
165856
165997
  ## Persona
165857
165998
 
165858
- - Careful, to-the-point, and kind. You only say things you know to be true.
165999
+ ${byProfile(
166000
+ `- Careful, to-the-point, and kind. You only say things you know to be true.
166001
+ - Write code that reads like the surrounding code: match its comment density, naming, and idiom.
166002
+ - Do not break up sentences with hyphens. Use emdashes. Use backticks liberally for inline code (e.g. \`z.string()\`) even in headers.`,
166003
+ `- Careful, to-the-point, and kind. You only say things you know to be true.
165859
166004
  - Do not break up sentences with hyphens. Use emdashes.
165860
166005
  - Strong bias toward minimalism: no dead code, no premature abstractions, no speculative features, and no comments that merely restate what the code does.
165861
166006
  - Code is focused, elegant, and production-ready.
165862
166007
  - Do not add unnecessary comments, tests, or documentation unless explicitly prompted to do so.
165863
166008
  - Adapt your writing style to match existing patterns in the codebase (commit messages, PR descriptions, code comments) while never being unprofessional.
165864
- - Use backticks liberally for inline code (e.g. \`z.string()\`) even in headers.
166009
+ - Use backticks liberally for inline code (e.g. \`z.string()\`) even in headers.`
166010
+ )}
165865
166011
 
165866
166012
  ## Environment
165867
166013
 
@@ -165881,7 +166027,10 @@ MCP servers provide tools you can call. Inspect your available MCP servers at st
165881
166027
 
165882
166028
  ### Git
165883
166029
 
165884
- Use \`${t2("git")}\` for local git commands (status, log, add, commit, checkout, branch, merge, etc.). When reviewing a PR, do NOT re-derive the PR diff via \`git diff\` \u2014 the diffPath returned by \`${t2("checkout_pr")}\` is authoritative. If you ever do need to diff a branch against its base via \`${t2("git")}\`, use \`git diff --merge-base <base>\` (single call, includes uncommitted edits) or three-dot \`git diff <base>...HEAD\` (committed-only). Do NOT use bare \`<base>\` or two-dot \`<base>..HEAD\` \u2014 those are symmetric and include the *inverse* of every commit landed on \`<base>\` since your branch forked (the tool will reject those forms when the divergence is detected). Do NOT try \`$(git merge-base \u2026)\` subshells \u2014 the git tool runs git directly with no shell interpolation. \`git log\` and \`git diff --stat\` are fine for commit-range overview; \`git diff\` / \`git diff --cached\` are fine for inspecting your *own* uncommitted changes. For operations requiring remote authentication, use the dedicated MCP tools:
166030
+ ${byProfile(
166031
+ `Use \`${t2("git")}\` for local git commands (status, log, add, commit, checkout, branch, merge, etc.). When reviewing a PR, the diffPath returned by \`${t2("checkout_pr")}\` is authoritative \u2014 read it rather than re-deriving the diff. To diff a branch against its base yourself, use \`git diff --merge-base <base>\`; the tool rejects the symmetric forms and tells you what to use instead. Note the git tool runs git directly, so \`$(\u2026)\` subshells do not interpolate. For operations requiring remote authentication, use the dedicated MCP tools:`,
166032
+ `Use \`${t2("git")}\` for local git commands (status, log, add, commit, checkout, branch, merge, etc.). When reviewing a PR, do NOT re-derive the PR diff via \`git diff\` \u2014 the diffPath returned by \`${t2("checkout_pr")}\` is authoritative. If you ever do need to diff a branch against its base via \`${t2("git")}\`, use \`git diff --merge-base <base>\` (single call, includes uncommitted edits) or three-dot \`git diff <base>...HEAD\` (committed-only). Do NOT use bare \`<base>\` or two-dot \`<base>..HEAD\` \u2014 those are symmetric and include the *inverse* of every commit landed on \`<base>\` since your branch forked (the tool will reject those forms when the divergence is detected). Do NOT try \`$(git merge-base \u2026)\` subshells \u2014 the git tool runs git directly with no shell interpolation. \`git log\` and \`git diff --stat\` are fine for commit-range overview; \`git diff\` / \`git diff --cached\` are fine for inspecting your *own* uncommitted changes. For operations requiring remote authentication, use the dedicated MCP tools:`
166033
+ )}
165885
166034
  - \`${t2("push_branch")}\` - push current or specified branch
165886
166035
  - \`${t2("git_fetch")}\` - fetch refs from remote
165887
166036
  - \`${t2("checkout_pr")}\` - checkout a PR branch (fetches and configures push for forks)
@@ -165920,7 +166069,11 @@ ${getStandaloneModeInstructions(ctx.payload.event.trigger, t2, ctx.outputSchema)
165920
166069
 
165921
166070
  ## Workflow
165922
166071
 
165923
- ### Efficiency
166072
+ ${byProfile(
166073
+ `### Efficiency
166074
+
166075
+ Trust tool results \u2014 re-verify only after an actual error, or right before \`${t2("push_branch")}\`, which rejects a dirty tree (tests you ran earlier often leave untracked output). Issue independent tool calls together in one turn rather than one per turn; sequence anything that depends on prior output. Commands run synchronously, so never \`sleep\` to wait for one.`,
166076
+ `### Efficiency
165924
166077
 
165925
166078
  Trust the tools \u2014 do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error. Exception: right before \`${t2("push_branch")}\`, ensure the working tree is clean \u2014 that tool rejects dirty trees, and tests you ran earlier often leave untracked output.
165926
166079
 
@@ -165939,7 +166092,8 @@ Emit multiple \`tool_use\` blocks in the same assistant message for independent
165939
166092
 
165940
166093
  ### Command execution
165941
166094
 
165942
- Never use \`sleep\` to wait for commands to complete. Commands run synchronously \u2014 when the shell tool returns, the command has finished.
166095
+ Never use \`sleep\` to wait for commands to complete. Commands run synchronously \u2014 when the shell tool returns, the command has finished.`
166096
+ )}
165943
166097
 
165944
166098
  ### Commenting style
165945
166099
 
@@ -165949,7 +166103,17 @@ Never \`@\`-mention a GitHub username unless that exact handle appears in the us
165949
166103
 
165950
166104
  When embedding images (e.g. uploaded screenshots) in comments or PR bodies, always use markdown image syntax: \`![description](url)\`. Never paste a naked URL \u2014 it will not render as an image.
165951
166105
 
165952
- ### Progress reporting
166106
+ ${byProfile(
166107
+ `### Progress reporting
166108
+
166109
+ **Your raw assistant messages are never delivered** \u2014 they exist only in the run logs. Anything the user is meant to see (an answer to a question, a mention reply, a result) MUST go through \`report_progress\` or another ${pullfrogMcpName} write tool.
166110
+
166111
+ Keep an internal task list from your mode's steps; the system renders it to the progress comment on its own, so don't call \`report_progress\` for intermediate status. Call it once at the end with a short outcome-focused summary \u2014 what was accomplished and links to artifacts, not a replay of the steps. If something failed, include the tool's exact error text. Use \`create_issue_comment\` only when a standalone comment is the explicit deliverable; when it is, that replaces the final \`report_progress\` call rather than adding to it.
166112
+
166113
+ ### If you get stuck
166114
+
166115
+ Don't silently fail or produce incomplete work. Report what blocked you and what would unblock it, specifically enough to act on. If the same approach has failed repeatedly, step back and say what you tried and what alternatives exist rather than repeating it.`,
166116
+ `### Progress reporting
165953
166117
 
165954
166118
  **Task list**: at the start of every run, create an internal task list based on the steps in your current mode. Update it as you complete each step. The system automatically renders this list to the progress comment \u2014 you do not need to call \`report_progress\` for this.
165955
166119
 
@@ -165965,7 +166129,8 @@ If you cannot complete a task due to missing information, ambiguity, or an unrec
165965
166129
  1. Do not silently fail or produce incomplete work
165966
166130
  2. Post a comment via ${pullfrogMcpName} explaining what blocked you and what information or action would unblock you
165967
166131
  3. Make your blocker comment specific and actionable (e.g., "I need the database schema to proceed" not "I'm stuck")
165968
- 4. If you've attempted the same fix or approach 3 or more times without progress, step back and reconsider. Report what you tried, why it failed, and what alternative approaches exist \u2014 rather than repeating failed attempts.
166132
+ 4. If you've attempted the same fix or approach 3 or more times without progress, step back and reconsider. Report what you tried, why it failed, and what alternative approaches exist \u2014 rather than repeating failed attempts.`
166133
+ )}
165969
166134
 
165970
166135
  ### Agent context files
165971
166136
 
@@ -167693,6 +167858,7 @@ function logRunStartup(ctx) {
167693
167858
  log.info(`\xBB push: ${ctx.payload.push}`);
167694
167859
  log.info(`\xBB shell: ${ctx.payload.shell}`);
167695
167860
  log.info(`\xBB timeout: ${resolveTimeoutForLog(ctx.payload.timeout)}`);
167861
+ log.info(`\xBB prompt: ${promptProfile()}`);
167696
167862
  }
167697
167863
 
167698
167864
  // utils/todoTracking.ts
@@ -169330,7 +169496,7 @@ async function runCli4(input) {
169330
169496
  }
169331
169497
 
169332
169498
  // cli.ts
169333
- var VERSION10 = "0.1.42";
169499
+ var VERSION10 = "0.1.43";
169334
169500
  var bin = basename2(process.argv[1] || "");
169335
169501
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
169336
169502
  var rawArgs = process.argv.slice(2);
package/dist/index.js CHANGED
@@ -101093,7 +101093,7 @@ var import_semver = __toESM(require_semver2(), 1);
101093
101093
  // package.json
101094
101094
  var package_default = {
101095
101095
  name: "pullfrog",
101096
- version: "0.1.42",
101096
+ version: "0.1.43",
101097
101097
  type: "module",
101098
101098
  bin: {
101099
101099
  pullfrog: "dist/cli.mjs",
@@ -101775,8 +101775,78 @@ HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
101775
101775
 
101776
101776
  Report findings clearly with file:line references and quoted evidence where possible. Flag uncertainty explicitly \u2014 if you cannot verify a claim, say so rather than guess.`;
101777
101777
 
101778
+ // utils/promptProfile.ts
101779
+ function promptProfile() {
101780
+ return process.env.PULLFROG_PROMPT_PROFILE === "full" ? "full" : "lean";
101781
+ }
101782
+ function byProfile(lean, full) {
101783
+ return promptProfile() === "lean" ? lean : full;
101784
+ }
101785
+
101778
101786
  // modes.ts
101779
- var PR_SUMMARY_FORMAT = `### Default format
101787
+ var PR_SUMMARY_FORMAT_LEAN = `### Default format
101788
+
101789
+ The body has at most three parts, in this order:
101790
+
101791
+ 1. **Reviewed changes preamble** \u2014 a bolded \`**Reviewed changes**\` lead-in with one sentence on what was reviewed in this run (for \`IncrementalReview\`: what changed since the prior pullfrog review), then a bullet list of the substantive changes \u2014 short bolded title, one sentence each. A reviewer should understand the full reviewed scope from this list alone. Close the preamble with the metadata comment below.
101792
+ 2. **Cross-cutting issue sections** (zero or more) \u2014 one \`### {emoji} {what's wrong, not what to do}\` heading per concern.
101793
+ 3. **\`### \u2139\uFE0F Nitpicks\`** at the very bottom, if any \u2014 a flat bullet list, no technical-details block.
101794
+
101795
+ **Inline vs. body.** Concerns that anchor to a specific line go inline (the \`comments\` parameter), even when their implications are broad. Body \`### \` sections are reserved for concerns that have **no line to anchor to** \u2014 *absence* (something the diff should have done but didn't), *sequencing* (rollout / deletion / migration order), *design decisions only the human can make*, or *scope questions the diff raises but doesn't address*. With no non-anchorable concerns, the body is just the preamble + metadata.
101796
+
101797
+ **Severity emoji** on every \`### \` heading, and nowhere else: \u{1F6A8} critical (blocks merge \u2014 data loss, security, broken core flow) \xB7 \u26A0\uFE0F important (must address before merging) \xB7 \u2139\uFE0F informational (mergeable as-is).
101798
+
101799
+ **Blank line between every block-level element.** GitHub's markdown parser requires one before and after HTML tags (\`<details>\`, \`<summary>\`, \`<sub>\`, \`<br/>\`) \u2014 without it GitHub treats what follows as a continuation of the HTML block and renders your markdown as literal text. This is a parser quirk, not a style preference, and it permanently breaks the posted review.
101800
+
101801
+ ## Metadata comment
101802
+
101803
+ Fill every field from the \`checkout_pr\` response \u2014 never count files or commits by hand. For \`IncrementalReview\`, fill \`Prior pullfrog review\` from \`list_pull_request_reviews\`.
101804
+
101805
+ \`\`\`
101806
+ <!--
101807
+ Pullfrog review metadata. These findings were written against {head_sha_short};
101808
+ if commits have landed on {head_ref} since, treat every specific bug, file, or
101809
+ line callout as POTENTIALLY STALE and re-diff before acting on it.
101810
+
101811
+ - Mode: Review (initial) or IncrementalReview (delta against prior pullfrog review)
101812
+ - Files reviewed: {file_count}
101813
+ - Commits reviewed: {commit_count}
101814
+ - Base: {base_ref} ({base_sha_short})
101815
+ - Head: {head_ref} ({head_sha_short})
101816
+ - Reviewed commits:
101817
+ - {sha_short} \u2014 {commit_subject}
101818
+ - Prior pullfrog review: none or {prior_sha_short} ({prior_review_html_url})
101819
+ - Submitted at: {iso_timestamp}
101820
+ -->
101821
+ \`\`\`
101822
+
101823
+ ## Technical details
101824
+
101825
+ Every body \`### \` section carries one; an inline comment carries one when its fix is non-trivial or spans files. The visible part above it states the PROBLEM in 2-3 sentences \u2014 what's broken and what the blast radius is. Asks, fixes, and open questions live inside the block, which a downstream fix-agent pulls down as its brief, so \`file:line\` refs and identifier density belong here.
101826
+
101827
+ \`\`\`
101828
+ <details><summary>Technical details</summary>
101829
+
101830
+ \\\`\\\`\\\`\\\`markdown
101831
+ # {title}
101832
+
101833
+ ## Affected sites
101834
+ - {file path:line} \u2014 {what's wrong there}
101835
+
101836
+ ## Required outcome
101837
+ - {what the fix needs to achieve, not how to achieve it}
101838
+
101839
+ ## Suggested approach (optional)
101840
+ ## Open questions for the human (optional)
101841
+ \\\`\\\`\\\`\\\`
101842
+
101843
+ </details>
101844
+ \`\`\`
101845
+
101846
+ The 4-backtick fence lets the block hold its own 3-backtick fences and stay one-click copyable. Skip the optional sections when they'd add nothing.
101847
+
101848
+ Backtick-wrap identifiers and file names. Don't repeat diff content, don't include raw \`+123 / -45\` stats, no changelog, no horizontal rules, and no \`### Key changes\` / \`### Issues found\` / \`<b>TL;DR</b>\` heading \u2014 each \`### \` heading IS the issue.`;
101849
+ var PR_SUMMARY_FORMAT_FULL = `### Default format
101780
101850
 
101781
101851
  The body has at most three parts in this exact order:
101782
101852
 
@@ -101917,6 +101987,9 @@ Inline comments use the same severity framing as body \`### \` sections, scaled
101917
101987
  - **Don't repeat diff content**, don't include raw \`+123 / -45\` stats, don't include a changelog section, don't use horizontal rules (\`---\`).
101918
101988
  - **Pull file/commit counts from \`checkout_pr\` metadata** \u2014 never count manually.
101919
101989
  - **Legacy headings REMOVED.** Do not use \`### Key changes\`, \`### Issues found\`, \`<b>TL;DR</b>\`, or \`<sub><b>Summary</b>\`. The new structure subsumes them.`;
101990
+ function prSummaryFormat() {
101991
+ return byProfile(PR_SUMMARY_FORMAT_LEAN, PR_SUMMARY_FORMAT_FULL);
101992
+ }
101920
101993
  function computeModes(agentId, signedCommits = false) {
101921
101994
  const t = (toolName) => formatMcpToolRef(agentId, toolName);
101922
101995
  const commitStep = signedCommits ? `commit via \`${t("commit_changes")}\` \u2014 it lands a GitHub-signed commit directly on the remote branch (no push step)` : `commit locally via shell (\`git add . && git commit -m "..."\`)`;
@@ -101940,7 +102013,17 @@ function computeModes(agentId, signedCommits = false) {
101940
102013
  - plan your approach before writing code: identify which files need to change, key design decisions, and edge cases. for non-trivial changes, consider whether there's a more elegant approach.
101941
102014
  - run relevant tests/lints before committing
101942
102015
 
101943
- 5. **self-review**: judgment call \u2014 does YOUR diff warrant a fresh-eyes pass?
102016
+ 5. **self-review**: ${byProfile(
102017
+ `unless the diff has no behavioral surface at all \u2014 docs, comments, whitespace, import reordering, lockfile or generated-code regeneration, a mechanical rename, a trusted dep patch bump \u2014 dispatch the \`${REVIEWER_AGENT_NAME}\` subagent to review it with fresh eyes against YOUR TASK. Line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default earns a pass. When in doubt, run it \u2014 a false-positive dispatch costs cents, a missed bug costs much more.
102018
+
102019
+ Before dispatching, make \`origin/<base>\` available: \`git fetch --no-tags --deepen=1000 origin <base>:refs/remotes/origin/<base>\`. The explicit destination refspec is required \u2014 a shallow single-branch checkout otherwise only updates \`FETCH_HEAD\` and never creates the tracking ref. The reviewer is read-only by contract, so fetching is your job.
102020
+
102021
+ In the dispatch prompt: say this is a PRE-COMMIT self-review whose work is uncommitted in the working tree, give the branch and base, name \`git diff --merge-base origin/<base>\` as the canonical diff command, paste YOUR TASK, and summarize any build-phase failures. If that diff comes back empty, there is nothing to review \u2014 stop.
102022
+
102023
+ Give it the diff and the task, nothing else. Do not summarize what you implemented, curate a reading list of files, or pre-shape the output with a severity schema \u2014 each biases the reviewer toward validating your solution instead of questioning it. Where the diff rests on third-party API, SDK, framework, or DB-engine semantics, tell it to verify load-bearing claims by web search and quote sources.
102024
+
102025
+ Treat what comes back as hypotheses, not directives: verify each against the code before applying, and reject findings that would add ceremony without correctness \u2014 defensive checks for cases that cannot happen, single-use abstractions, comments restating code, tautological tests. After applying what you accept, re-read your own diff and revert anything that turned out to be bloat. Then ${commitStep}.`,
102026
+ `judgment call \u2014 does YOUR diff warrant a fresh-eyes pass?
101944
102027
 
101945
102028
  Skip self-review (commit directly) when the diff is **genuinely trivial**:
101946
102029
  - doc typos, comment-only edits, whitespace/format-only, import reordering
@@ -101991,7 +102074,8 @@ function computeModes(agentId, signedCommits = false) {
101991
102074
  - Do NOT defect-hunt the diff yourself in parallel with the subagent. Your role is dispatch + evaluation; doing the review yourself reintroduces the implementation bias the subagent is meant to mitigate.
101992
102075
  - For diffs that rely on third-party API contracts, SDK semantics, framework directives, or DB engine specifics, instruct the subagent to verify load-bearing claims via web search and quote source URLs rather than trust training data \u2014 this is the single most common review-quality failure mode.
101993
102076
 
101994
- Be **discerning** about what comes back. The reviewer is an AI subagent and is fallible \u2014 treat every finding as a hypothesis, not a directive, and **verify each one yourself** against the diff and the code before deciding whether to apply. You are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. Do not over-engineer, do not be over-defensive, **do not write AI slop**. Reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for cases that cannot happen, extra logging, new abstractions used once, comments restating code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. Reject those. For each surviving finding, ask: would applying it leave the code more sound, correct, AND elegant? Two-out-of-three means look harder for a fix that gets all three before settling. After applying the fixes you accept, re-read your diff and be discerning about what *you just changed*: if any fix turned out to be bloat in context, revert it. Then verify only intended changes are present, no debug artifacts or commented-out code remain, no unrelated files were modified. Then ${commitStep}.
102077
+ Be **discerning** about what comes back. The reviewer is an AI subagent and is fallible \u2014 treat every finding as a hypothesis, not a directive, and **verify each one yourself** against the diff and the code before deciding whether to apply. You are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. Do not over-engineer, do not be over-defensive, **do not write AI slop**. Reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for cases that cannot happen, extra logging, new abstractions used once, comments restating code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. Reject those. For each surviving finding, ask: would applying it leave the code more sound, correct, AND elegant? Two-out-of-three means look harder for a fix that gets all three before settling. After applying the fixes you accept, re-read your diff and be discerning about what *you just changed*: if any fix turned out to be bloat in context, revert it. Then verify only intended changes are present, no debug artifacts or commented-out code remain, no unrelated files were modified. Then ${commitStep}.`
102078
+ )}
101995
102079
 
101996
102080
  6. **finalize**:
101997
102081
  - ${finalizeStep} (see *SYSTEM* Git rules if this fails \u2014 prepush errors are usually the repo's tests/lint, not infra timeouts)
@@ -102044,7 +102128,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
102044
102128
  //
102045
102129
  // Severity categorization is split across two surfaces: the opening
102046
102130
  // callout (CAUTION/IMPORTANT/ℹ️/✅) sets the review's overall tier, and
102047
- // per-bullet emoji prefixes (🚨/⚠️/ℹ️ in PR_SUMMARY_FORMAT) tag
102131
+ // per-bullet emoji prefixes (🚨/⚠️/ℹ️ in prSummaryFormat()) tag
102048
102132
  // individual points inside summary sections — scoping severity to the
102049
102133
  // specific bullet rather than the whole section keeps a section that
102050
102134
  // mixes a 🚨 and an ℹ️ from being mislabeled by either of them.
@@ -102057,7 +102141,30 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
102057
102141
 
102058
102142
  2. **checkout**: call \`${t("checkout_pr")}\` \u2014 this returns PR metadata, a \`diffPath\`, and a supplemental \`impactPath\` when change-impact extraction is enabled. read the complete raw diff end-to-end, beginning with the TOC and using its file line ranges as your coverage checklist. only after that, use \`impactPath\` as an explicitly incomplete list of reference leads; it never replaces raw-diff reading or establishes coverage.
102059
102143
 
102060
- 3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
102144
+ ${byProfile(
102145
+ `3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
102146
+
102147
+ skip the deeper pass and submit a \`No new issues found.\` review per step 7 only when the diff has **no behavioral surface at all** \u2014 doc typos, whitespace/formatting, lockfile or generated-code regeneration, a mechanical rename whose only effect is import-path updates. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
102148
+
102149
+ 4. **specialist decision**: after reading the complete diff, name the questions you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most reviews need zero or one; some need several.
102150
+
102151
+ **There is NO one-specialist cap or fixed maximum.** cover every orthogonal question that remains; do not collapse several real questions into one broad prompt just to reduce the count. there is no file-count, line-count, or budget threshold either \u2014 diff size is not a proxy for review uncertainty.
102152
+
102153
+ frame each question through the lens that primes the right failure modes. for high-stakes subsystems, lead with the **domain** ("the billing lens", "the auth lens", "the schema-migration lens") rather than the generic equivalent ("correctness on billing code") \u2014 the domain framing makes the subagent recall double-charges, refund races, currency rounding, and dispute flows that a generic lens misses.
102154
+
102155
+ you remain the synthesizer: reading the complete raw diff, investigating surrounding code, validating every returned finding, and writing the review are yours. specialist reads supplement that work; they never satisfy your own coverage obligation.
102156
+
102157
+ 5. **dispatch specialists (only if step 4 found unresolved questions)**: for 2+ questions, emit every Task tool_use block **IN A SINGLE ASSISTANT TURN** before reading any result, so the investigations run in parallel rather than serially. your own \`read\` / \`grep\` / \`webfetch\` calls can ride in that same turn at zero extra wall time.
102158
+
102159
+ if a specialist errors out, times out, or returns nothing usable, retry it once. if it still fails, resolve the question yourself; if it remains disposition-changing and unresolved, surface the limitation and do not approve. each dispatch carries:
102160
+ - **the absolute \`diffPath\` (and \`incrementalDiffPath\` if available) from step 2's \`${t("checkout_pr")}\` return, named verbatim in the dispatch prompt** (e.g. \`diffPath: /tmp/pullfrog-XXXX/pr-NNN-SHA.diff\`). the reviewer's baked-in system prompt selects its FIRST action on this token \u2014 paraphrasing ("review the diff", "look at this PR") sends it down a \`git diff origin/<base>\` fallback that fails on shallow GHA checkouts. it \`read\`s those files for scope and must NOT re-derive the diff itself; reading and codebase exploration are still its job.
102161
+ - **exactly one falsifiable question with explicit scope boundaries** \u2014 ask for evidence that supports or refutes it, never a broad "review for X, Y, and Z" prompt.
102162
+ - **a Task \`description\` set to a short hypothesis label** (e.g. \`"webhook-replay"\`, \`"billing-rounding"\`) \u2014 the harness reads this field to label the subagent's log lines so parallel runs can be told apart. without it, every subagent shows up as \`subagent#N\`.
102163
+ - if the question touches third-party API, SDK, or framework contracts, instruct the subagent to verify load-bearing claims via web search and quote source URLs rather than trust training data. action runs are non-interactive \u2014 nobody is in the loop to catch "I'm pretty sure Stripe does X."
102164
+ - ask for findings with file paths and NEW line numbers from the diff so you can validate and anchor them.
102165
+
102166
+ delegation discipline: do NOT summarize the PR for them (a lossy summary biases toward a validation frame; the raw diff is the source), do NOT hand them a curated reading list, do NOT pre-shape their output with a finding schema, and do NOT mention the other specialists \u2014 independence is the point, and overlapping findings are a strong signal.`,
102167
+ `3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
102061
102168
 
102062
102169
  if the PR is **genuinely trivial**, skip specialists entirely and submit a \`No new issues found.\` review per step 7.
102063
102170
 
@@ -102128,7 +102235,8 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
102128
102235
  - do NOT summarize the PR for them (a lossy summary biases toward a validation frame; the raw diff is the source)
102129
102236
  - do NOT hand them a curated reading list (let them discover scope)
102130
102237
  - do NOT pre-shape their output with a finding schema
102131
- - do NOT mention the other lenses (independence is the point \u2014 overlapping findings are a strong signal)
102238
+ - do NOT mention the other lenses (independence is the point \u2014 overlapping findings are a strong signal)`
102239
+ )}
102132
102240
 
102133
102241
  6. **aggregate & draft**: when specialist results land, merge findings; de-dup overlaps (two specialists catching the same issue = higher-confidence signal); trace each finding yourself before accepting it. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the PR (heuristic: if the finding's root cause lives in lines this PR added or modified, it's in scope; otherwise drop unless the PR plausibly introduced or amplified the regression), and anything not actionable. also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or worse, degrades elegance to nominally improve correctness) makes the codebase worse, not better.
102134
102242
 
@@ -102162,7 +102270,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
102162
102270
  - **no actionable issues**:
102163
102271
  \`approved: true\`. Body opens with \`> \u2705 No new issues found.\\n\\n\` followed by the PR summary.
102164
102272
 
102165
- ${PR_SUMMARY_FORMAT}`
102273
+ ${prSummaryFormat()}`
102166
102274
  },
102167
102275
  // IncrementalReview shares Review's minimum hypothesis-covering specialist
102168
102276
  // routing and body format, scoped to the incremental delta against the
@@ -102196,7 +102304,30 @@ ${PR_SUMMARY_FORMAT}`
102196
102304
 
102197
102305
  The remaining open threads feed step 8's dedup filter \u2014 anything already flagged and unchanged by the new commits should not be re-raised. The rolling PR summary snapshot is the durable record of retire activity; you don't need to surface it in the review body.
102198
102306
 
102199
- 5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
102307
+ ${byProfile(
102308
+ `5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
102309
+
102310
+ skip the deeper pass and jump to step 10's non-substantive path (do NOT submit a review) only when the incremental changes have **no behavioral surface at all** \u2014 formatting, comment tweaks, import reordering, lockfile regen, a mechanical rename of import paths. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
102311
+
102312
+ 6. **specialist decision**: after covering the incremental and full diffs, name the questions about the new changes that you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most incremental reviews need zero or one, especially thread-reply re-reviews; some need several.
102313
+
102314
+ **There is NO one-specialist cap or fixed maximum.** cover every orthogonal question that remains; do not collapse several real questions into one broad prompt just to reduce the count. there is no file-count, line-count, or budget threshold either \u2014 diff size is not a proxy for review uncertainty.
102315
+
102316
+ frame each question through the lens that primes the right failure modes. for high-stakes subsystems, lead with the **domain** ("the billing lens", "the auth lens", "the schema-migration lens") rather than the generic equivalent \u2014 the domain framing makes the subagent recall failure modes a generic lens misses.
102317
+
102318
+ you remain the synthesizer: reading the complete raw full diff plus the incremental diff, investigating surrounding code, validating every returned finding, and writing the review are yours. specialist reads supplement that work; they never satisfy your own coverage obligation.
102319
+
102320
+ 7. **dispatch specialists (only if step 6 found unresolved questions)**: for 2+ questions, emit every Task tool_use block **IN A SINGLE ASSISTANT TURN** before reading any result, so the investigations run in parallel rather than serially. your own \`read\` / \`grep\` / \`webfetch\` calls can ride in that same turn.
102321
+
102322
+ if a specialist errors out, times out, or returns nothing usable, retry it once. if it still fails, resolve the question yourself; if it remains disposition-changing and unresolved, surface the limitation and do not approve. each dispatch carries:
102323
+ - **the absolute diff path(s) from step 2's \`${t("checkout_pr")}\` return, named verbatim in the dispatch prompt.** when \`incrementalDiffPath\` is present, name BOTH (\`incrementalDiffPath: /tmp/.../pr-NNN-SHA-incremental.diff\` then \`diffPath: /tmp/.../pr-NNN-SHA.diff\`) \u2014 the reviewer's baked-in prompt reads incremental first and uses full for context; when only \`diffPath\` exists, name it alone. it \`read\`s those files and must NOT re-derive the diff itself; paraphrasing ("review the new commits") sends it down a \`git diff\` fallback that fails on shallow GHA checkouts. do NOT tell them to skip pre-existing issues \u2014 that suppresses regressions the new commits amplified; the "issues must be NEW" filter lives at aggregation time (step 8), not in the subagent prompt.
102324
+ - **exactly one falsifiable question with explicit scope boundaries** \u2014 ask for evidence that supports or refutes it, never a broad "review for X, Y, and Z" prompt.
102325
+ - **a Task \`description\` set to a short hypothesis label** \u2014 the harness reads this field to label log lines so parallel runs can be told apart.
102326
+ - if the question touches third-party API, SDK, or framework contracts, instruct the subagent to verify load-bearing claims via web search and quote source URLs.
102327
+ - ask for findings with file paths and NEW line numbers from the full PR diff so you can validate and anchor them.
102328
+
102329
+ delegation discipline: do NOT summarize the changes for them (a lossy summary biases toward a validation frame; the raw diff is the source), do NOT hand them a curated reading list, do NOT pre-shape their output with a finding schema, and do NOT mention the other specialists \u2014 independence is the point.`,
102330
+ `5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
102200
102331
 
102201
102332
  if the incremental changes are **genuinely trivial**, skip specialists entirely and jump to step 10's non-substantive path (do NOT submit a review).
102202
102333
 
@@ -102237,7 +102368,8 @@ ${PR_SUMMARY_FORMAT}`
102237
102368
  - do NOT summarize the changes for them (a lossy summary biases toward a validation frame; the raw diff is the source)
102238
102369
  - do NOT hand them a curated reading list (let them discover scope)
102239
102370
  - do NOT pre-shape their output with a finding schema
102240
- - do NOT mention the other lenses (independence is the point)
102371
+ - do NOT mention the other lenses (independence is the point)`
102372
+ )}
102241
102373
 
102242
102374
  8. **aggregate, draft, self-critique**: merge findings (yours + output from every specialist you dispatched); de-dup overlaps; trace each finding yourself. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the new commits, anything not actionable, and anything that re-states prior review feedback (heuristic: if the finding's root cause lives in lines the *new commits* added or modified, it's in scope; otherwise drop). also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or degrades elegance to nominally improve correctness) makes the codebase worse, not better. To compute "lines the new commits added or modified": if \`incrementalDiffPath\` from step 2 is present, use it directly. Otherwise, take the prior Pullfrog review's \`commit_id\` (returned alongside each entry from \`${t("list_pull_request_reviews")}\` in step 4) and run \`git diff <prior-review-sha>..HEAD\` to isolate the lines added since that review.
102243
102375
 
@@ -102260,7 +102392,7 @@ ${PR_SUMMARY_FORMAT}`
102260
102392
  - ELSE IF INFORMATIONAL OBSERVATIONS (mergeable as-is, but worth surfacing \u2014 e.g. prior feedback addressed cleanly with one minor stale doc reference, or a noteworthy positive observation): call \`${t("create_pull_request_review")}\` with \`approved: true\`, NO inline comments, and the review body. body opens with \`> \u2705 No new issues found.\\n\\n\` (or similar friendly green opener), followed by the PR summary using the default format below. If a point is concrete enough to anchor to a line, downgrade the whole review to "minor suggestions only" (\`approved: false\`) instead \u2014 the \u2705 signals "no action needed", which contradicts an actionable anchor.
102261
102393
  - ELSE IF NO NEW ISSUES, SUBSTANTIVE CHANGES (new functionality, behavior changes, or fixes to prior review feedback): call \`${t("create_pull_request_review")}\` to create a PR review. If all previous reviews have been properly addressed and no new issues were discovered, set \`approved: true\`. body opens with \`> \u2705 No new issues found.\\n\\n\`, followed by the PR summary using the default format below.
102262
102394
 
102263
- ${PR_SUMMARY_FORMAT}`
102395
+ ${prSummaryFormat()}`
102264
102396
  },
102265
102397
  {
102266
102398
  name: "Plan",
@@ -158961,7 +159093,10 @@ var CreatePullRequestReview = type({
158961
159093
  // running. required-ness is the only pressure that empirically held.
158962
159094
  // see wiki/review-approval.md.
158963
159095
  body: type.string.describe(
158964
- `1-2 sentence high-level summary with urgency level, critical callouts, and feedback about code outside the diff. Specific feedback on diff lines goes in 'comments' array. ALWAYS pass this parameter \u2014 pass an empty string "" when approving with no commentary, never omit it.`
159096
+ byProfile(
159097
+ `The review summary, plus any concern that has no diff line to anchor to. Your mode guidance defines its structure. Feedback that does anchor to a line goes in the 'comments' array. ALWAYS pass this parameter \u2014 pass an empty string "" when approving with no commentary, never omit it.`,
159098
+ `1-2 sentence high-level summary with urgency level, critical callouts, and feedback about code outside the diff. Specific feedback on diff lines goes in 'comments' array. ALWAYS pass this parameter \u2014 pass an empty string "" when approving with no commentary, never omit it.`
159099
+ )
158965
159100
  ),
158966
159101
  approved: type.boolean.describe(
158967
159102
  "Set to true to submit as an approval. Use for `> \u2705 No new issues found.` reviews where the PR is mergeable as-is and nothing in the body warrants code changes \u2014 approving also suppresses the Fix-button footer affordance so users don't dispatch a fix run on non-actionable feedback. Reserve approved: false for `> \u2139\uFE0F ...` (minor suggestions inline), `> [!IMPORTANT]` (recommended changes), and `> [!CAUTION]` (critical) reviews. Defaults to false (comment-only review). Mutually exclusive with request_changes. Approval is REJECTED while any unresolved Pullfrog review thread remains open on the PR (not just the latest commit's diff): resolve the threads the current code addresses (reply + resolve_review_thread) first, or submit a non-approving review if a real issue remains."
@@ -158997,7 +159132,10 @@ function CreatePullRequestReviewTool(ctx) {
158997
159132
  return tool({
158998
159133
  name: "create_pull_request_review",
158999
159134
  mutates: true,
159000
- description: `Submit a review for an existing pull request. Example: \`create_pull_request_review({ pull_number: 1234, body: "LGTM", approved: true, comments: [{ path: "src/api.ts", line: 42, body: "nit: rename" }] })\`. Each call creates a permanent, visible review on the PR \u2014 NEVER submit test or diagnostic reviews. Set \`approved: true\` to approve, \`request_changes: true\` to submit a blocking review, or neither for a plain comment review (the three are mutually exclusive). Reviews with no body AND no comments are silently skipped (nothing to post). IMPORTANT: 95%+ of feedback should be in 'comments' array with file paths and line numbers. Only use 'body' for a 1-2 sentence summary with urgency and critical callouts. Use 'suggestion' to propose replacement code - MUST preserve exact indentation of original code. The first submission may error once with a one-time diff-coverage nudge listing unread TOC regions \u2014 retry with the same arguments and the pre-flight will not block again. Example replacing lines 42-44 (3 lines) with 5 lines: { path: 'src/api.ts', start_line: 42, line: 44, suggestion: ' const result = await fetch(url);\\n if (!result.ok) {\\n log.error(result.status);\\n throw new Error("request failed");\\n }' } CONSTRAINT: Inline comments can ONLY target files and lines that appear in the PR diff. Comments anchored outside a diff hunk are dropped automatically (with a note appended to the review body) \u2014 the rest of the review still posts.`,
159135
+ description: 'Submit a review for an existing pull request. Example: `create_pull_request_review({ pull_number: 1234, body: "LGTM", approved: true, comments: [{ path: "src/api.ts", line: 42, body: "nit: rename" }] })`. Each call creates a permanent, visible review on the PR \u2014 NEVER submit test or diagnostic reviews. Set `approved: true` to approve, `request_changes: true` to submit a blocking review, or neither for a plain comment review (the three are mutually exclusive). Reviews with no body AND no comments are silently skipped (nothing to post). ' + byProfile(
159136
+ "Feedback that anchors to a specific line goes in the 'comments' array with file paths and line numbers. The 'body' carries the review summary plus any concern with no line to anchor to; your mode guidance defines its structure. ",
159137
+ "IMPORTANT: 95%+ of feedback should be in 'comments' array with file paths and line numbers. Only use 'body' for a 1-2 sentence summary with urgency and critical callouts. "
159138
+ ) + `Use 'suggestion' to propose replacement code - MUST preserve exact indentation of original code. The first submission may error once with a one-time diff-coverage nudge listing unread TOC regions \u2014 retry with the same arguments and the pre-flight will not block again. Example replacing lines 42-44 (3 lines) with 5 lines: { path: 'src/api.ts', start_line: 42, line: 44, suggestion: ' const result = await fetch(url);\\n if (!result.ok) {\\n log.error(result.status);\\n throw new Error("request failed");\\n }' } CONSTRAINT: Inline comments can ONLY target files and lines that appear in the PR diff. Comments anchored outside a diff hunk are dropped automatically (with a note appended to the review body) \u2014 the rest of the review still posts.`,
159001
159139
  parameters: CreatePullRequestReview,
159002
159140
  execute: execute(
159003
159141
  async ({ pull_number, body, approved, request_changes, commit_id, comments = [] }) => {
@@ -163930,7 +164068,10 @@ You execute tasks directly using your native tools and the ${pullfrogMcpName} MC
163930
164068
 
163931
164069
  Call \`${t("select_mode")}\` with the appropriate mode name. This returns **your workflow** \u2014 a step-by-step playbook you must follow.
163932
164070
 
163933
- **Follow the returned guidance as your primary instruction set.** Do not improvise \u2014 the guidance defines the exact steps.
164071
+ ${byProfile(
164072
+ `**Work through the returned steps in order.** It is the house playbook for this kind of task and it encodes what usually matters. Where the task in front of you genuinely calls for something better, do that instead and say why in your final summary.`,
164073
+ `**Follow the returned guidance as your primary instruction set.** Do not improvise \u2014 the guidance defines the exact steps.`
164074
+ )}
163934
164075
 
163935
164076
  Available modes:
163936
164077
  ${ctx.modes.map((m) => `- "${m.name}": ${m.description}`).join("\n")}
@@ -163968,13 +164109,18 @@ You are a diligent, detail-oriented, no-nonsense software engineering agent. You
163968
164109
 
163969
164110
  ## Persona
163970
164111
 
163971
- - Careful, to-the-point, and kind. You only say things you know to be true.
164112
+ ${byProfile(
164113
+ `- Careful, to-the-point, and kind. You only say things you know to be true.
164114
+ - Write code that reads like the surrounding code: match its comment density, naming, and idiom.
164115
+ - Do not break up sentences with hyphens. Use emdashes. Use backticks liberally for inline code (e.g. \`z.string()\`) even in headers.`,
164116
+ `- Careful, to-the-point, and kind. You only say things you know to be true.
163972
164117
  - Do not break up sentences with hyphens. Use emdashes.
163973
164118
  - Strong bias toward minimalism: no dead code, no premature abstractions, no speculative features, and no comments that merely restate what the code does.
163974
164119
  - Code is focused, elegant, and production-ready.
163975
164120
  - Do not add unnecessary comments, tests, or documentation unless explicitly prompted to do so.
163976
164121
  - Adapt your writing style to match existing patterns in the codebase (commit messages, PR descriptions, code comments) while never being unprofessional.
163977
- - Use backticks liberally for inline code (e.g. \`z.string()\`) even in headers.
164122
+ - Use backticks liberally for inline code (e.g. \`z.string()\`) even in headers.`
164123
+ )}
163978
164124
 
163979
164125
  ## Environment
163980
164126
 
@@ -163994,7 +164140,10 @@ MCP servers provide tools you can call. Inspect your available MCP servers at st
163994
164140
 
163995
164141
  ### Git
163996
164142
 
163997
- Use \`${t("git")}\` for local git commands (status, log, add, commit, checkout, branch, merge, etc.). When reviewing a PR, do NOT re-derive the PR diff via \`git diff\` \u2014 the diffPath returned by \`${t("checkout_pr")}\` is authoritative. If you ever do need to diff a branch against its base via \`${t("git")}\`, use \`git diff --merge-base <base>\` (single call, includes uncommitted edits) or three-dot \`git diff <base>...HEAD\` (committed-only). Do NOT use bare \`<base>\` or two-dot \`<base>..HEAD\` \u2014 those are symmetric and include the *inverse* of every commit landed on \`<base>\` since your branch forked (the tool will reject those forms when the divergence is detected). Do NOT try \`$(git merge-base \u2026)\` subshells \u2014 the git tool runs git directly with no shell interpolation. \`git log\` and \`git diff --stat\` are fine for commit-range overview; \`git diff\` / \`git diff --cached\` are fine for inspecting your *own* uncommitted changes. For operations requiring remote authentication, use the dedicated MCP tools:
164143
+ ${byProfile(
164144
+ `Use \`${t("git")}\` for local git commands (status, log, add, commit, checkout, branch, merge, etc.). When reviewing a PR, the diffPath returned by \`${t("checkout_pr")}\` is authoritative \u2014 read it rather than re-deriving the diff. To diff a branch against its base yourself, use \`git diff --merge-base <base>\`; the tool rejects the symmetric forms and tells you what to use instead. Note the git tool runs git directly, so \`$(\u2026)\` subshells do not interpolate. For operations requiring remote authentication, use the dedicated MCP tools:`,
164145
+ `Use \`${t("git")}\` for local git commands (status, log, add, commit, checkout, branch, merge, etc.). When reviewing a PR, do NOT re-derive the PR diff via \`git diff\` \u2014 the diffPath returned by \`${t("checkout_pr")}\` is authoritative. If you ever do need to diff a branch against its base via \`${t("git")}\`, use \`git diff --merge-base <base>\` (single call, includes uncommitted edits) or three-dot \`git diff <base>...HEAD\` (committed-only). Do NOT use bare \`<base>\` or two-dot \`<base>..HEAD\` \u2014 those are symmetric and include the *inverse* of every commit landed on \`<base>\` since your branch forked (the tool will reject those forms when the divergence is detected). Do NOT try \`$(git merge-base \u2026)\` subshells \u2014 the git tool runs git directly with no shell interpolation. \`git log\` and \`git diff --stat\` are fine for commit-range overview; \`git diff\` / \`git diff --cached\` are fine for inspecting your *own* uncommitted changes. For operations requiring remote authentication, use the dedicated MCP tools:`
164146
+ )}
163998
164147
  - \`${t("push_branch")}\` - push current or specified branch
163999
164148
  - \`${t("git_fetch")}\` - fetch refs from remote
164000
164149
  - \`${t("checkout_pr")}\` - checkout a PR branch (fetches and configures push for forks)
@@ -164033,7 +164182,11 @@ ${getStandaloneModeInstructions(ctx.payload.event.trigger, t, ctx.outputSchema)}
164033
164182
 
164034
164183
  ## Workflow
164035
164184
 
164036
- ### Efficiency
164185
+ ${byProfile(
164186
+ `### Efficiency
164187
+
164188
+ Trust tool results \u2014 re-verify only after an actual error, or right before \`${t("push_branch")}\`, which rejects a dirty tree (tests you ran earlier often leave untracked output). Issue independent tool calls together in one turn rather than one per turn; sequence anything that depends on prior output. Commands run synchronously, so never \`sleep\` to wait for one.`,
164189
+ `### Efficiency
164037
164190
 
164038
164191
  Trust the tools \u2014 do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error. Exception: right before \`${t("push_branch")}\`, ensure the working tree is clean \u2014 that tool rejects dirty trees, and tests you ran earlier often leave untracked output.
164039
164192
 
@@ -164052,7 +164205,8 @@ Emit multiple \`tool_use\` blocks in the same assistant message for independent
164052
164205
 
164053
164206
  ### Command execution
164054
164207
 
164055
- Never use \`sleep\` to wait for commands to complete. Commands run synchronously \u2014 when the shell tool returns, the command has finished.
164208
+ Never use \`sleep\` to wait for commands to complete. Commands run synchronously \u2014 when the shell tool returns, the command has finished.`
164209
+ )}
164056
164210
 
164057
164211
  ### Commenting style
164058
164212
 
@@ -164062,7 +164216,17 @@ Never \`@\`-mention a GitHub username unless that exact handle appears in the us
164062
164216
 
164063
164217
  When embedding images (e.g. uploaded screenshots) in comments or PR bodies, always use markdown image syntax: \`![description](url)\`. Never paste a naked URL \u2014 it will not render as an image.
164064
164218
 
164065
- ### Progress reporting
164219
+ ${byProfile(
164220
+ `### Progress reporting
164221
+
164222
+ **Your raw assistant messages are never delivered** \u2014 they exist only in the run logs. Anything the user is meant to see (an answer to a question, a mention reply, a result) MUST go through \`report_progress\` or another ${pullfrogMcpName} write tool.
164223
+
164224
+ Keep an internal task list from your mode's steps; the system renders it to the progress comment on its own, so don't call \`report_progress\` for intermediate status. Call it once at the end with a short outcome-focused summary \u2014 what was accomplished and links to artifacts, not a replay of the steps. If something failed, include the tool's exact error text. Use \`create_issue_comment\` only when a standalone comment is the explicit deliverable; when it is, that replaces the final \`report_progress\` call rather than adding to it.
164225
+
164226
+ ### If you get stuck
164227
+
164228
+ Don't silently fail or produce incomplete work. Report what blocked you and what would unblock it, specifically enough to act on. If the same approach has failed repeatedly, step back and say what you tried and what alternatives exist rather than repeating it.`,
164229
+ `### Progress reporting
164066
164230
 
164067
164231
  **Task list**: at the start of every run, create an internal task list based on the steps in your current mode. Update it as you complete each step. The system automatically renders this list to the progress comment \u2014 you do not need to call \`report_progress\` for this.
164068
164232
 
@@ -164078,7 +164242,8 @@ If you cannot complete a task due to missing information, ambiguity, or an unrec
164078
164242
  1. Do not silently fail or produce incomplete work
164079
164243
  2. Post a comment via ${pullfrogMcpName} explaining what blocked you and what information or action would unblock you
164080
164244
  3. Make your blocker comment specific and actionable (e.g., "I need the database schema to proceed" not "I'm stuck")
164081
- 4. If you've attempted the same fix or approach 3 or more times without progress, step back and reconsider. Report what you tried, why it failed, and what alternative approaches exist \u2014 rather than repeating failed attempts.
164245
+ 4. If you've attempted the same fix or approach 3 or more times without progress, step back and reconsider. Report what you tried, why it failed, and what alternative approaches exist \u2014 rather than repeating failed attempts.`
164246
+ )}
164082
164247
 
164083
164248
  ### Agent context files
164084
164249
 
@@ -165806,6 +165971,7 @@ function logRunStartup(ctx) {
165806
165971
  log.info(`\xBB push: ${ctx.payload.push}`);
165807
165972
  log.info(`\xBB shell: ${ctx.payload.shell}`);
165808
165973
  log.info(`\xBB timeout: ${resolveTimeoutForLog(ctx.payload.timeout)}`);
165974
+ log.info(`\xBB prompt: ${promptProfile()}`);
165809
165975
  }
165810
165976
 
165811
165977
  // utils/todoTracking.ts
package/dist/internal.js CHANGED
@@ -632,8 +632,78 @@ function formatMcpToolRef(agentId, toolName) {
632
632
  // agents/reviewer.ts
633
633
  var REVIEWER_AGENT_NAME = "reviewfrog";
634
634
 
635
+ // utils/promptProfile.ts
636
+ function promptProfile() {
637
+ return process.env.PULLFROG_PROMPT_PROFILE === "full" ? "full" : "lean";
638
+ }
639
+ function byProfile(lean, full) {
640
+ return promptProfile() === "lean" ? lean : full;
641
+ }
642
+
635
643
  // modes.ts
636
- var PR_SUMMARY_FORMAT = `### Default format
644
+ var PR_SUMMARY_FORMAT_LEAN = `### Default format
645
+
646
+ The body has at most three parts, in this order:
647
+
648
+ 1. **Reviewed changes preamble** \u2014 a bolded \`**Reviewed changes**\` lead-in with one sentence on what was reviewed in this run (for \`IncrementalReview\`: what changed since the prior pullfrog review), then a bullet list of the substantive changes \u2014 short bolded title, one sentence each. A reviewer should understand the full reviewed scope from this list alone. Close the preamble with the metadata comment below.
649
+ 2. **Cross-cutting issue sections** (zero or more) \u2014 one \`### {emoji} {what's wrong, not what to do}\` heading per concern.
650
+ 3. **\`### \u2139\uFE0F Nitpicks\`** at the very bottom, if any \u2014 a flat bullet list, no technical-details block.
651
+
652
+ **Inline vs. body.** Concerns that anchor to a specific line go inline (the \`comments\` parameter), even when their implications are broad. Body \`### \` sections are reserved for concerns that have **no line to anchor to** \u2014 *absence* (something the diff should have done but didn't), *sequencing* (rollout / deletion / migration order), *design decisions only the human can make*, or *scope questions the diff raises but doesn't address*. With no non-anchorable concerns, the body is just the preamble + metadata.
653
+
654
+ **Severity emoji** on every \`### \` heading, and nowhere else: \u{1F6A8} critical (blocks merge \u2014 data loss, security, broken core flow) \xB7 \u26A0\uFE0F important (must address before merging) \xB7 \u2139\uFE0F informational (mergeable as-is).
655
+
656
+ **Blank line between every block-level element.** GitHub's markdown parser requires one before and after HTML tags (\`<details>\`, \`<summary>\`, \`<sub>\`, \`<br/>\`) \u2014 without it GitHub treats what follows as a continuation of the HTML block and renders your markdown as literal text. This is a parser quirk, not a style preference, and it permanently breaks the posted review.
657
+
658
+ ## Metadata comment
659
+
660
+ Fill every field from the \`checkout_pr\` response \u2014 never count files or commits by hand. For \`IncrementalReview\`, fill \`Prior pullfrog review\` from \`list_pull_request_reviews\`.
661
+
662
+ \`\`\`
663
+ <!--
664
+ Pullfrog review metadata. These findings were written against {head_sha_short};
665
+ if commits have landed on {head_ref} since, treat every specific bug, file, or
666
+ line callout as POTENTIALLY STALE and re-diff before acting on it.
667
+
668
+ - Mode: Review (initial) or IncrementalReview (delta against prior pullfrog review)
669
+ - Files reviewed: {file_count}
670
+ - Commits reviewed: {commit_count}
671
+ - Base: {base_ref} ({base_sha_short})
672
+ - Head: {head_ref} ({head_sha_short})
673
+ - Reviewed commits:
674
+ - {sha_short} \u2014 {commit_subject}
675
+ - Prior pullfrog review: none or {prior_sha_short} ({prior_review_html_url})
676
+ - Submitted at: {iso_timestamp}
677
+ -->
678
+ \`\`\`
679
+
680
+ ## Technical details
681
+
682
+ Every body \`### \` section carries one; an inline comment carries one when its fix is non-trivial or spans files. The visible part above it states the PROBLEM in 2-3 sentences \u2014 what's broken and what the blast radius is. Asks, fixes, and open questions live inside the block, which a downstream fix-agent pulls down as its brief, so \`file:line\` refs and identifier density belong here.
683
+
684
+ \`\`\`
685
+ <details><summary>Technical details</summary>
686
+
687
+ \\\`\\\`\\\`\\\`markdown
688
+ # {title}
689
+
690
+ ## Affected sites
691
+ - {file path:line} \u2014 {what's wrong there}
692
+
693
+ ## Required outcome
694
+ - {what the fix needs to achieve, not how to achieve it}
695
+
696
+ ## Suggested approach (optional)
697
+ ## Open questions for the human (optional)
698
+ \\\`\\\`\\\`\\\`
699
+
700
+ </details>
701
+ \`\`\`
702
+
703
+ The 4-backtick fence lets the block hold its own 3-backtick fences and stay one-click copyable. Skip the optional sections when they'd add nothing.
704
+
705
+ Backtick-wrap identifiers and file names. Don't repeat diff content, don't include raw \`+123 / -45\` stats, no changelog, no horizontal rules, and no \`### Key changes\` / \`### Issues found\` / \`<b>TL;DR</b>\` heading \u2014 each \`### \` heading IS the issue.`;
706
+ var PR_SUMMARY_FORMAT_FULL = `### Default format
637
707
 
638
708
  The body has at most three parts in this exact order:
639
709
 
@@ -774,6 +844,9 @@ Inline comments use the same severity framing as body \`### \` sections, scaled
774
844
  - **Don't repeat diff content**, don't include raw \`+123 / -45\` stats, don't include a changelog section, don't use horizontal rules (\`---\`).
775
845
  - **Pull file/commit counts from \`checkout_pr\` metadata** \u2014 never count manually.
776
846
  - **Legacy headings REMOVED.** Do not use \`### Key changes\`, \`### Issues found\`, \`<b>TL;DR</b>\`, or \`<sub><b>Summary</b>\`. The new structure subsumes them.`;
847
+ function prSummaryFormat() {
848
+ return byProfile(PR_SUMMARY_FORMAT_LEAN, PR_SUMMARY_FORMAT_FULL);
849
+ }
777
850
  function computeModes(agentId, signedCommits = false) {
778
851
  const t = (toolName) => formatMcpToolRef(agentId, toolName);
779
852
  const commitStep = signedCommits ? `commit via \`${t("commit_changes")}\` \u2014 it lands a GitHub-signed commit directly on the remote branch (no push step)` : `commit locally via shell (\`git add . && git commit -m "..."\`)`;
@@ -797,7 +870,17 @@ function computeModes(agentId, signedCommits = false) {
797
870
  - plan your approach before writing code: identify which files need to change, key design decisions, and edge cases. for non-trivial changes, consider whether there's a more elegant approach.
798
871
  - run relevant tests/lints before committing
799
872
 
800
- 5. **self-review**: judgment call \u2014 does YOUR diff warrant a fresh-eyes pass?
873
+ 5. **self-review**: ${byProfile(
874
+ `unless the diff has no behavioral surface at all \u2014 docs, comments, whitespace, import reordering, lockfile or generated-code regeneration, a mechanical rename, a trusted dep patch bump \u2014 dispatch the \`${REVIEWER_AGENT_NAME}\` subagent to review it with fresh eyes against YOUR TASK. Line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default earns a pass. When in doubt, run it \u2014 a false-positive dispatch costs cents, a missed bug costs much more.
875
+
876
+ Before dispatching, make \`origin/<base>\` available: \`git fetch --no-tags --deepen=1000 origin <base>:refs/remotes/origin/<base>\`. The explicit destination refspec is required \u2014 a shallow single-branch checkout otherwise only updates \`FETCH_HEAD\` and never creates the tracking ref. The reviewer is read-only by contract, so fetching is your job.
877
+
878
+ In the dispatch prompt: say this is a PRE-COMMIT self-review whose work is uncommitted in the working tree, give the branch and base, name \`git diff --merge-base origin/<base>\` as the canonical diff command, paste YOUR TASK, and summarize any build-phase failures. If that diff comes back empty, there is nothing to review \u2014 stop.
879
+
880
+ Give it the diff and the task, nothing else. Do not summarize what you implemented, curate a reading list of files, or pre-shape the output with a severity schema \u2014 each biases the reviewer toward validating your solution instead of questioning it. Where the diff rests on third-party API, SDK, framework, or DB-engine semantics, tell it to verify load-bearing claims by web search and quote sources.
881
+
882
+ Treat what comes back as hypotheses, not directives: verify each against the code before applying, and reject findings that would add ceremony without correctness \u2014 defensive checks for cases that cannot happen, single-use abstractions, comments restating code, tautological tests. After applying what you accept, re-read your own diff and revert anything that turned out to be bloat. Then ${commitStep}.`,
883
+ `judgment call \u2014 does YOUR diff warrant a fresh-eyes pass?
801
884
 
802
885
  Skip self-review (commit directly) when the diff is **genuinely trivial**:
803
886
  - doc typos, comment-only edits, whitespace/format-only, import reordering
@@ -848,7 +931,8 @@ function computeModes(agentId, signedCommits = false) {
848
931
  - Do NOT defect-hunt the diff yourself in parallel with the subagent. Your role is dispatch + evaluation; doing the review yourself reintroduces the implementation bias the subagent is meant to mitigate.
849
932
  - For diffs that rely on third-party API contracts, SDK semantics, framework directives, or DB engine specifics, instruct the subagent to verify load-bearing claims via web search and quote source URLs rather than trust training data \u2014 this is the single most common review-quality failure mode.
850
933
 
851
- Be **discerning** about what comes back. The reviewer is an AI subagent and is fallible \u2014 treat every finding as a hypothesis, not a directive, and **verify each one yourself** against the diff and the code before deciding whether to apply. You are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. Do not over-engineer, do not be over-defensive, **do not write AI slop**. Reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for cases that cannot happen, extra logging, new abstractions used once, comments restating code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. Reject those. For each surviving finding, ask: would applying it leave the code more sound, correct, AND elegant? Two-out-of-three means look harder for a fix that gets all three before settling. After applying the fixes you accept, re-read your diff and be discerning about what *you just changed*: if any fix turned out to be bloat in context, revert it. Then verify only intended changes are present, no debug artifacts or commented-out code remain, no unrelated files were modified. Then ${commitStep}.
934
+ Be **discerning** about what comes back. The reviewer is an AI subagent and is fallible \u2014 treat every finding as a hypothesis, not a directive, and **verify each one yourself** against the diff and the code before deciding whether to apply. You are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. Do not over-engineer, do not be over-defensive, **do not write AI slop**. Reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for cases that cannot happen, extra logging, new abstractions used once, comments restating code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. Reject those. For each surviving finding, ask: would applying it leave the code more sound, correct, AND elegant? Two-out-of-three means look harder for a fix that gets all three before settling. After applying the fixes you accept, re-read your diff and be discerning about what *you just changed*: if any fix turned out to be bloat in context, revert it. Then verify only intended changes are present, no debug artifacts or commented-out code remain, no unrelated files were modified. Then ${commitStep}.`
935
+ )}
852
936
 
853
937
  6. **finalize**:
854
938
  - ${finalizeStep} (see *SYSTEM* Git rules if this fails \u2014 prepush errors are usually the repo's tests/lint, not infra timeouts)
@@ -901,7 +985,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
901
985
  //
902
986
  // Severity categorization is split across two surfaces: the opening
903
987
  // callout (CAUTION/IMPORTANT/ℹ️/✅) sets the review's overall tier, and
904
- // per-bullet emoji prefixes (🚨/⚠️/ℹ️ in PR_SUMMARY_FORMAT) tag
988
+ // per-bullet emoji prefixes (🚨/⚠️/ℹ️ in prSummaryFormat()) tag
905
989
  // individual points inside summary sections — scoping severity to the
906
990
  // specific bullet rather than the whole section keeps a section that
907
991
  // mixes a 🚨 and an ℹ️ from being mislabeled by either of them.
@@ -914,7 +998,30 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
914
998
 
915
999
  2. **checkout**: call \`${t("checkout_pr")}\` \u2014 this returns PR metadata, a \`diffPath\`, and a supplemental \`impactPath\` when change-impact extraction is enabled. read the complete raw diff end-to-end, beginning with the TOC and using its file line ranges as your coverage checklist. only after that, use \`impactPath\` as an explicitly incomplete list of reference leads; it never replaces raw-diff reading or establishes coverage.
916
1000
 
917
- 3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
1001
+ ${byProfile(
1002
+ `3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
1003
+
1004
+ skip the deeper pass and submit a \`No new issues found.\` review per step 7 only when the diff has **no behavioral surface at all** \u2014 doc typos, whitespace/formatting, lockfile or generated-code regeneration, a mechanical rename whose only effect is import-path updates. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
1005
+
1006
+ 4. **specialist decision**: after reading the complete diff, name the questions you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most reviews need zero or one; some need several.
1007
+
1008
+ **There is NO one-specialist cap or fixed maximum.** cover every orthogonal question that remains; do not collapse several real questions into one broad prompt just to reduce the count. there is no file-count, line-count, or budget threshold either \u2014 diff size is not a proxy for review uncertainty.
1009
+
1010
+ frame each question through the lens that primes the right failure modes. for high-stakes subsystems, lead with the **domain** ("the billing lens", "the auth lens", "the schema-migration lens") rather than the generic equivalent ("correctness on billing code") \u2014 the domain framing makes the subagent recall double-charges, refund races, currency rounding, and dispute flows that a generic lens misses.
1011
+
1012
+ you remain the synthesizer: reading the complete raw diff, investigating surrounding code, validating every returned finding, and writing the review are yours. specialist reads supplement that work; they never satisfy your own coverage obligation.
1013
+
1014
+ 5. **dispatch specialists (only if step 4 found unresolved questions)**: for 2+ questions, emit every Task tool_use block **IN A SINGLE ASSISTANT TURN** before reading any result, so the investigations run in parallel rather than serially. your own \`read\` / \`grep\` / \`webfetch\` calls can ride in that same turn at zero extra wall time.
1015
+
1016
+ if a specialist errors out, times out, or returns nothing usable, retry it once. if it still fails, resolve the question yourself; if it remains disposition-changing and unresolved, surface the limitation and do not approve. each dispatch carries:
1017
+ - **the absolute \`diffPath\` (and \`incrementalDiffPath\` if available) from step 2's \`${t("checkout_pr")}\` return, named verbatim in the dispatch prompt** (e.g. \`diffPath: /tmp/pullfrog-XXXX/pr-NNN-SHA.diff\`). the reviewer's baked-in system prompt selects its FIRST action on this token \u2014 paraphrasing ("review the diff", "look at this PR") sends it down a \`git diff origin/<base>\` fallback that fails on shallow GHA checkouts. it \`read\`s those files for scope and must NOT re-derive the diff itself; reading and codebase exploration are still its job.
1018
+ - **exactly one falsifiable question with explicit scope boundaries** \u2014 ask for evidence that supports or refutes it, never a broad "review for X, Y, and Z" prompt.
1019
+ - **a Task \`description\` set to a short hypothesis label** (e.g. \`"webhook-replay"\`, \`"billing-rounding"\`) \u2014 the harness reads this field to label the subagent's log lines so parallel runs can be told apart. without it, every subagent shows up as \`subagent#N\`.
1020
+ - if the question touches third-party API, SDK, or framework contracts, instruct the subagent to verify load-bearing claims via web search and quote source URLs rather than trust training data. action runs are non-interactive \u2014 nobody is in the loop to catch "I'm pretty sure Stripe does X."
1021
+ - ask for findings with file paths and NEW line numbers from the diff so you can validate and anchor them.
1022
+
1023
+ delegation discipline: do NOT summarize the PR for them (a lossy summary biases toward a validation frame; the raw diff is the source), do NOT hand them a curated reading list, do NOT pre-shape their output with a finding schema, and do NOT mention the other specialists \u2014 independence is the point, and overlapping findings are a strong signal.`,
1024
+ `3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
918
1025
 
919
1026
  if the PR is **genuinely trivial**, skip specialists entirely and submit a \`No new issues found.\` review per step 7.
920
1027
 
@@ -985,7 +1092,8 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
985
1092
  - do NOT summarize the PR for them (a lossy summary biases toward a validation frame; the raw diff is the source)
986
1093
  - do NOT hand them a curated reading list (let them discover scope)
987
1094
  - do NOT pre-shape their output with a finding schema
988
- - do NOT mention the other lenses (independence is the point \u2014 overlapping findings are a strong signal)
1095
+ - do NOT mention the other lenses (independence is the point \u2014 overlapping findings are a strong signal)`
1096
+ )}
989
1097
 
990
1098
  6. **aggregate & draft**: when specialist results land, merge findings; de-dup overlaps (two specialists catching the same issue = higher-confidence signal); trace each finding yourself before accepting it. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the PR (heuristic: if the finding's root cause lives in lines this PR added or modified, it's in scope; otherwise drop unless the PR plausibly introduced or amplified the regression), and anything not actionable. also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or worse, degrades elegance to nominally improve correctness) makes the codebase worse, not better.
991
1099
 
@@ -1019,7 +1127,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
1019
1127
  - **no actionable issues**:
1020
1128
  \`approved: true\`. Body opens with \`> \u2705 No new issues found.\\n\\n\` followed by the PR summary.
1021
1129
 
1022
- ${PR_SUMMARY_FORMAT}`
1130
+ ${prSummaryFormat()}`
1023
1131
  },
1024
1132
  // IncrementalReview shares Review's minimum hypothesis-covering specialist
1025
1133
  // routing and body format, scoped to the incremental delta against the
@@ -1053,7 +1161,30 @@ ${PR_SUMMARY_FORMAT}`
1053
1161
 
1054
1162
  The remaining open threads feed step 8's dedup filter \u2014 anything already flagged and unchanged by the new commits should not be re-raised. The rolling PR summary snapshot is the durable record of retire activity; you don't need to surface it in the review body.
1055
1163
 
1056
- 5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
1164
+ ${byProfile(
1165
+ `5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
1166
+
1167
+ skip the deeper pass and jump to step 10's non-substantive path (do NOT submit a review) only when the incremental changes have **no behavioral surface at all** \u2014 formatting, comment tweaks, import reordering, lockfile regen, a mechanical rename of import paths. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
1168
+
1169
+ 6. **specialist decision**: after covering the incremental and full diffs, name the questions about the new changes that you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most incremental reviews need zero or one, especially thread-reply re-reviews; some need several.
1170
+
1171
+ **There is NO one-specialist cap or fixed maximum.** cover every orthogonal question that remains; do not collapse several real questions into one broad prompt just to reduce the count. there is no file-count, line-count, or budget threshold either \u2014 diff size is not a proxy for review uncertainty.
1172
+
1173
+ frame each question through the lens that primes the right failure modes. for high-stakes subsystems, lead with the **domain** ("the billing lens", "the auth lens", "the schema-migration lens") rather than the generic equivalent \u2014 the domain framing makes the subagent recall failure modes a generic lens misses.
1174
+
1175
+ you remain the synthesizer: reading the complete raw full diff plus the incremental diff, investigating surrounding code, validating every returned finding, and writing the review are yours. specialist reads supplement that work; they never satisfy your own coverage obligation.
1176
+
1177
+ 7. **dispatch specialists (only if step 6 found unresolved questions)**: for 2+ questions, emit every Task tool_use block **IN A SINGLE ASSISTANT TURN** before reading any result, so the investigations run in parallel rather than serially. your own \`read\` / \`grep\` / \`webfetch\` calls can ride in that same turn.
1178
+
1179
+ if a specialist errors out, times out, or returns nothing usable, retry it once. if it still fails, resolve the question yourself; if it remains disposition-changing and unresolved, surface the limitation and do not approve. each dispatch carries:
1180
+ - **the absolute diff path(s) from step 2's \`${t("checkout_pr")}\` return, named verbatim in the dispatch prompt.** when \`incrementalDiffPath\` is present, name BOTH (\`incrementalDiffPath: /tmp/.../pr-NNN-SHA-incremental.diff\` then \`diffPath: /tmp/.../pr-NNN-SHA.diff\`) \u2014 the reviewer's baked-in prompt reads incremental first and uses full for context; when only \`diffPath\` exists, name it alone. it \`read\`s those files and must NOT re-derive the diff itself; paraphrasing ("review the new commits") sends it down a \`git diff\` fallback that fails on shallow GHA checkouts. do NOT tell them to skip pre-existing issues \u2014 that suppresses regressions the new commits amplified; the "issues must be NEW" filter lives at aggregation time (step 8), not in the subagent prompt.
1181
+ - **exactly one falsifiable question with explicit scope boundaries** \u2014 ask for evidence that supports or refutes it, never a broad "review for X, Y, and Z" prompt.
1182
+ - **a Task \`description\` set to a short hypothesis label** \u2014 the harness reads this field to label log lines so parallel runs can be told apart.
1183
+ - if the question touches third-party API, SDK, or framework contracts, instruct the subagent to verify load-bearing claims via web search and quote source URLs.
1184
+ - ask for findings with file paths and NEW line numbers from the full PR diff so you can validate and anchor them.
1185
+
1186
+ delegation discipline: do NOT summarize the changes for them (a lossy summary biases toward a validation frame; the raw diff is the source), do NOT hand them a curated reading list, do NOT pre-shape their output with a finding schema, and do NOT mention the other specialists \u2014 independence is the point.`,
1187
+ `5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
1057
1188
 
1058
1189
  if the incremental changes are **genuinely trivial**, skip specialists entirely and jump to step 10's non-substantive path (do NOT submit a review).
1059
1190
 
@@ -1094,7 +1225,8 @@ ${PR_SUMMARY_FORMAT}`
1094
1225
  - do NOT summarize the changes for them (a lossy summary biases toward a validation frame; the raw diff is the source)
1095
1226
  - do NOT hand them a curated reading list (let them discover scope)
1096
1227
  - do NOT pre-shape their output with a finding schema
1097
- - do NOT mention the other lenses (independence is the point)
1228
+ - do NOT mention the other lenses (independence is the point)`
1229
+ )}
1098
1230
 
1099
1231
  8. **aggregate, draft, self-critique**: merge findings (yours + output from every specialist you dispatched); de-dup overlaps; trace each finding yourself. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the new commits, anything not actionable, and anything that re-states prior review feedback (heuristic: if the finding's root cause lives in lines the *new commits* added or modified, it's in scope; otherwise drop). also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or degrades elegance to nominally improve correctness) makes the codebase worse, not better. To compute "lines the new commits added or modified": if \`incrementalDiffPath\` from step 2 is present, use it directly. Otherwise, take the prior Pullfrog review's \`commit_id\` (returned alongside each entry from \`${t("list_pull_request_reviews")}\` in step 4) and run \`git diff <prior-review-sha>..HEAD\` to isolate the lines added since that review.
1100
1232
 
@@ -1117,7 +1249,7 @@ ${PR_SUMMARY_FORMAT}`
1117
1249
  - ELSE IF INFORMATIONAL OBSERVATIONS (mergeable as-is, but worth surfacing \u2014 e.g. prior feedback addressed cleanly with one minor stale doc reference, or a noteworthy positive observation): call \`${t("create_pull_request_review")}\` with \`approved: true\`, NO inline comments, and the review body. body opens with \`> \u2705 No new issues found.\\n\\n\` (or similar friendly green opener), followed by the PR summary using the default format below. If a point is concrete enough to anchor to a line, downgrade the whole review to "minor suggestions only" (\`approved: false\`) instead \u2014 the \u2705 signals "no action needed", which contradicts an actionable anchor.
1118
1250
  - ELSE IF NO NEW ISSUES, SUBSTANTIVE CHANGES (new functionality, behavior changes, or fixes to prior review feedback): call \`${t("create_pull_request_review")}\` to create a PR review. If all previous reviews have been properly addressed and no new issues were discovered, set \`approved: true\`. body opens with \`> \u2705 No new issues found.\\n\\n\`, followed by the PR summary using the default format below.
1119
1251
 
1120
- ${PR_SUMMARY_FORMAT}`
1252
+ ${prSummaryFormat()}`
1121
1253
  },
1122
1254
  {
1123
1255
  name: "Plan",
package/dist/modes.d.ts CHANGED
@@ -4,7 +4,11 @@ export interface Mode {
4
4
  description: string;
5
5
  prompt?: string | undefined;
6
6
  }
7
- export declare const PR_SUMMARY_FORMAT = "### Default format\n\nThe body has at most three parts in this exact order:\n\n1. **Reviewed changes preamble** \u2014 one bolded inline lead-in describing what was reviewed in this run, a bullet list of the substantive changes, and an HTML comment carrying review metadata for downstream agents.\n2. **Cross-cutting issue sections** (zero or more) \u2014 one `### ` heading per concern, with a human-readable problem write-up and a collapsed `<details>Technical details</details>` block underneath.\n3. **`### \u2139\uFE0F Nitpicks`** at the very bottom (only if there are nits worth surfacing in the body) \u2014 a flat bullet list, no technical-details block.\n\nInline-vs-body split: concerns that anchor to a specific line go inline (use the `comments` parameter). Body `### ` sections are reserved for concerns that **have no line to anchor to** \u2014 typically because the concern is about *absence* (something the diff should have done but didn't), *sequencing* (rollout / deletion / migration order), *design decisions only the human can make*, or *scope questions the diff implicitly raises but doesn't address*. A concern that anchors to a line but has broad implications still goes inline (use the technical-details block there to capture the implications \u2014 see Inline technical details below). If you found no non-anchorable concerns, the body has zero `### ` issue sections \u2014 just the preamble + metadata.\n\n## 1. Reviewed changes preamble\n\nOpen with a single bolded inline lead-in followed immediately by the bullet list (no `### Key changes` heading, no `<b>TL;DR</b>`):\n\n```\n**Reviewed changes** \u2014 one sentence on what was reviewed in this run. For Review (initial), this is what the PR does and why. For IncrementalReview, this is what changed since the prior pullfrog review. Focus on intent, not mechanics.\n\n- **Short human-readable title** \u2014 1 sentence per substantive change. Write a short prose phrase; when you name a file, type, or function, put that name in backticks (e.g. **Add \\`TodoTracker\\` for live checklists**). A reviewer should understand the full reviewed scope from this list alone \u2014 this IS the dispassionate \"what was reviewed and what changed\" overview, so cover the substantive changes, not just the loudest ones.\n\n<!--\nPullfrog review metadata \u2014 for any agent (or human-with-agent) reading this\nreview. Incorporate the fields below into your understanding of the context\nthis review was made in. The findings below were written against\n{head_sha_short}; if new commits have landed on {head_ref} since this review\nwas submitted, treat any specific bug, file, or line callout as POTENTIALLY\nSTALE \u2014 re-diff against {head_sha_short} (or trigger a fresh review) and\nfactor commits past {head_sha_short} into your understanding of the current\nstate before acting on findings.\n\n- Mode: Review (initial) or IncrementalReview (delta against prior pullfrog review)\n- Files reviewed: {file_count}\n- Commits reviewed: {commit_count}\n- Base: {base_ref} ({base_sha_short})\n- Head: {head_ref} ({head_sha_short})\n- Reviewed commits:\n - {sha_short} \u2014 {commit_subject}\n - ...\n- Prior pullfrog review: none or {prior_sha_short} ({prior_review_html_url})\n- Submitted at: {iso_timestamp}\n-->\n```\n\nPull every metadata field from the `checkout_pr` tool's response \u2014 file count, commit count, base/head ref + SHA, the commit list. For `IncrementalReview` runs, populate `Prior pullfrog review` with the prior review's commit_id (short SHA) and `html_url` from `list_pull_request_reviews`.\n\n## 2. Cross-cutting issue sections (zero or more)\n\nFor each cross-cutting concern, one `### ` section. Use this exact shape:\n\n```\n### {emoji} {short, descriptive title \u2014 what's wrong, not what to do}\n\n{Human-readable problem write-up. Describes the PROBLEM only \u2014 what's broken, what the symptom is, what the blast radius is. NO asks, NO suggested fixes, NO \"the right thing to do is...\". Asks and fixes live in the technical-details block below; the visible part is for the human to *understand* the problem, not to implement it.}\n\n<details><summary>Technical details</summary>\n\n\\`\\`\\`\\`markdown\n# {title repeated}\n\n## Affected sites\n- {file path:line} \u2014 {what's wrong there}\n- ...\n\n## Required outcome\n- {what the fix needs to achieve, not how to achieve it}\n- ...\n\n## Suggested approach (optional)\n{When the fix shape is non-obvious, sketch one or more reasonable directions. Skip when the outcome alone makes the fix obvious.}\n\n## Open questions for the human (optional)\n- {Any decision an implementing agent shouldn't make unilaterally \u2014 pricing thresholds, breaking-change policy, naming, scope of follow-up.}\n\\`\\`\\`\\`\n\n</details>\n```\n\nConcrete example of the visible part of a non-anchored section (technical-details block unchanged from the template above):\n\n```\n### \u2139\uFE0F Legacy `opencode.ts` has no documented deletion plan\n\nThe v2 harness lands alongside the v1 file and imports one helper from it. Worth a follow-up issue or a TODO so the next maintainer doesn't have to re-derive the cleanup plan.\n```\n\nThe example's value is its *shape*: a finding about absence (no deletion plan), not a line-anchored bug. Body sections live or die on whether the concern genuinely doesn't fit on a line.\n\n**Heading severity emoji** \u2014 every `### ` heading carries one:\n\n- \uD83D\uDEA8 critical \u2014 blocks merge (data loss, security, broken core flow)\n- \u26A0\uFE0F important \u2014 must address before merging (regression, missing validation, incorrect behavior)\n- \u2139\uFE0F informational \u2014 surfaced for awareness; mergeable as-is\n\n**Visible problem write-up rules:**\n\n- **No asks, no suggested fixes** in the visible part. The visible portion describes the problem; the technical-details block describes the fix shape and any open questions. The exception: a fix so self-evident that NOT stating it would be weird (e.g. \"the typo is missing an 'r'\") \u2014 in that case, fold it into the problem statement and skip the suggested-approach block in technical details too.\n- **Never two successive plain paragraphs.** Every transition between block-level elements must alternate prose with structure: paragraph \u2192 bullet list \u2192 paragraph; paragraph \u2192 code fence \u2192 bullet list; paragraph \u2192 table \u2192 paragraph. Two consecutive paragraphs in a row create a wall of text that's impossible to digest. If you catch yourself writing one, find a way to split it: pull a list out of it, drop a 2-3 line code fence between them, or merge them into a single tighter paragraph.\n- **Per-paragraph budget:** ~3 sentences max. Past that, you're explaining where you should be structuring.\n- **Identifier discipline still applies** in the visible part. Lead with behavior in plain English; name an identifier only when it's the subject of the concern or a public surface a reader would recognize. The technical-details block is where dense identifier references belong.\n\n**Technical-details block rules:**\n\n- Wrapped in a 4-backtick markdown fence (`\\`\\`\\`\\`markdown ... \\`\\`\\`\\``) so it's visually distinct, one-click copyable, and can contain its own 3-backtick code fences without escape gymnastics. The contents are agent-readable \u2014 a fix-agent will pull the body down and use this block as the brief.\n- File paths and `file:line` refs are encouraged (and necessary) \u2014 the next agent uses these to navigate. Identifier density is fine here.\n- Slightly more verbose than the absolute minimum is OK when it materially helps the next agent: a small code snippet showing the symptom, a short table of mismatched key/column pairs, a one-paragraph \"why CI doesn't catch it\" note. Skip massive regression-test scaffolding or full route rewrites \u2014 the implementing agent writes those.\n- Use the four standard sections (`Affected sites`, `Required outcome`, optional `Suggested approach`, optional `Open questions for the human`). Skip the optional sections when they wouldn't add anything.\n\n## Inline technical details\n\nInline comments are short (~2-3 sentences) by default. When an inline finding has broader implications worth recording for a fix-agent \u2014 e.g. a localized bug whose proper fix requires touching several files, or where the right fix depends on a design decision the human needs to make \u2014 append a collapsed `<details><summary>Technical details</summary>` block to the inline comment's body. Same shape as the body-section technical-details block (4-backtick fenced markdown, `## Affected sites` / `## Required outcome` / optional `## Suggested approach` / optional `## Open questions for the human`).\n\nGitHub renders the same markdown parser in inline comments as in the review body, so the collapsed-details affordance works the same way. The visible part of the inline comment stays scannable; the depth is one click away for any agent that needs it.\n\n## 3. `### \u2139\uFE0F Nitpicks` (optional, last section)\n\nOnly when there are nits that for some reason can't be inlined. Filepaths in nit text are fine \u2014 these are simple enough that a human or agent reads once and acts. No technical-details block.\n\n```\n### \u2139\uFE0F Nitpicks\n\n- {nit, with file path inline if useful, \u2264 ~200 chars}\n- ...\n```\n\n## Inline comment shape\n\nInline comments use the same severity framing as body `### ` sections, scaled down for line-anchored use:\n\n- **Lead with a 1-2 sentence problem statement.** The reader is looking at the line in question, so don't restate what the line says \u2014 describe what's wrong with it. Optionally prefix the visible line with a severity emoji (\uD83D\uDEA8 / \u26A0\uFE0F / \u2139\uFE0F) when severity isn't obvious from context.\n- **Optional `<details><summary>Technical details</summary>...</details>` collapsible** for findings whose technical context (longer file:line references, related-code snippets, suggested approach, regression-risk notes) would overwhelm the human-readable lead-in. Same agent-readable purpose, same 4-backtick fence shape, and same 4-section structure as the body's technical-details block \u2014 see *Inline technical details* above. Encouraged whenever the depth helps a downstream fix-agent; don't force one when the inline lead-in already says everything.\n- **Visible portion \u2264 2-3 sentences.** If you find yourself writing more, that's the cue to split the depth into the `Technical details` collapsible.\n\n## Body-wide rules\n\n- **Inline-vs-body discipline (repeated for emphasis):** anything that anchors to a specific line goes inline (with a `<details>Technical details</details>` block when the implications are broad). The body is for non-anchorable concerns only \u2014 absence, sequencing, design decisions, scope questions, architectural risk.\n- **No `### Issues found` heading** above the issue sections \u2014 each `### ` heading IS the issue.\n- **Severity emoji on every `### ` heading** (\uD83D\uDEA8 / \u26A0\uFE0F / \u2139\uFE0F). No emoji on the preamble lead-in or anywhere else.\n- **GitHub block-level rendering**: GitHub's markdown parser requires a blank line between ALL block-level elements (HTML tags like `<br/>`, `<sub>`, `<details>`, `<b>` and markdown syntax like headings, lists, blockquotes, code fences, paragraphs). Without a blank line, GitHub treats following content as a continuation of the HTML block and renders markdown syntax as literal text. ALWAYS separate block-level elements with a blank line.\n- **Backtick-wrap** every variable, identifier, or file name when you mention one (in either visible or technical-details portions).\n- **Don't repeat diff content**, don't include raw `+123 / -45` stats, don't include a changelog section, don't use horizontal rules (`---`).\n- **Pull file/commit counts from `checkout_pr` metadata** \u2014 never count manually.\n- **Legacy headings REMOVED.** Do not use `### Key changes`, `### Issues found`, `<b>TL;DR</b>`, or `<sub><b>Summary</b>`. The new structure subsumes them.";
7
+ /**
8
+ * Must stay a function, not a const: the profile is chosen from `process.env`
9
+ * inside `main()`, long after this module is imported.
10
+ */
11
+ export declare function prSummaryFormat(): string;
8
12
  export declare function computeModes(agentId: AgentId, signedCommits?: boolean): Mode[];
9
13
  export declare const modes: Mode[];
10
14
  /**
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Which instruction profile a run assembles.
3
+ *
4
+ * `full` reproduces the prompt as of 2026-07-28 byte-for-byte; `lean` is the cut that trusts the
5
+ * model's own judgement instead of enumerating every case. Read from env (not run context) so a
6
+ * single deployed build can serve both arms of an A/B.
7
+ *
8
+ * Anything other than `full` resolves to `lean`, so a typo'd value lands in the treatment arm
9
+ * silently — `logRunStartup` prints the RESOLVED arm for exactly that reason.
10
+ *
11
+ * Prefer driving the arm from the workflow `env:` block over `unsafe_overrides`: the override path
12
+ * runs `core.setSecret()` on every applied value (`overrides.ts`), and the runner then
13
+ * substring-masks that value everywhere downstream, so `full` turns `successfully` into
14
+ * `success***y` and corrupts the very logs an A/B reads.
15
+ */
16
+ export type PromptProfile = "lean" | "full";
17
+ export declare function promptProfile(): PromptProfile;
18
+ /** Pick between the two profiles' text. Keeps the branch legible at each cut site. */
19
+ export declare function byProfile(lean: string, full: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.42",
3
+ "version": "0.1.43",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "pullfrog": "dist/cli.mjs",