6d25adfd1a
* agent & model refactor with ASKPASS git auth, UI restructure, clerk v7 Made-with: Cursor * fix stale agent/effort refs, add tests for askpass + model resolution - reviewCleanup.ts: payload.agent -> payload.model, remove effort - selectMode.ts PlanEdit: remove delegation/subagent/effort references - pullfrog.yml.ts: update env vars (drop GOOGLE_API_KEY/CURSOR_API_KEY, add GOOGLE_GENERATIVE_AI_API_KEY/XAI_API_KEY/MOONSHOT_API_KEY/OPENCODE_API_KEY) - FlagsSettings/RepoInstructionsSection: remove stale effort/timeout copy - new: gitAuthServer.test.ts (10 tests — lifecycle, token delivery, tamper detection, script gen) - new: agent.test.ts (4 tests — default opentoad, AGENT_OVERRIDE, invalid override) - new: models.test.ts (19 tests — parseModel, resolution, registry invariants) - update models.dev snapshot Made-with: Cursor * fix changed-agents.sh to filter legacy agent files from CI matrix legacy agent files (claude.ts, codex.ts, etc.) are @ts-nocheck and not exported from index.ts. changed-agents.sh now reads index.ts imports to build the active agent set and treats changes to inactive files as non-agent changes (opentoad canary only). Made-with: Cursor * remove MCP file tools, old agent harnesses, and obsolete security tests ASKPASS-based git auth makes the old MCP file tool security layer unnecessary: - token never in subprocess env, so symlink/gitattributes/hook attacks can't exfiltrate it - agents now use native file tools (OpenCode builtin read/edit) deleted: - action/mcp/file.ts (file_read, file_write, file_edit, file_delete, list_directory) - action/mcp/index.ts (dead re-export) - agent harnesses: claude.ts, codex.ts, cursor.ts, gemini.ts, opencode.ts - opencode-runner.ts (inlined into opentoad.ts) - security tests that validated MCP file tool restrictions - commented-out three-step review flow (~300 lines) - sanitizeSchema/wrapSchema dead code from mcp/shared.ts - OPENCODE_MODEL_MINI/MAX env vars (effort-level model overrides removed) updated test prompts to use generic file ops instead of MCP tool names. restored pkg-json-scripts + requirements-txt-attack (test --ignore-scripts defense). Made-with: Cursor * bump actions/checkout v4 → v6 (node 24) node 20 actions deprecated june 2, 2026. Made-with: Cursor * temporarily disable fail-fast on agnostic tests to debug checkout@v6 Made-with: Cursor * re-enable fail-fast on agnostic tests Made-with: Cursor * fix test token mismatch: mint OIDC tokens scoped to target repo CI tests override GITHUB_REPOSITORY to pullfrog/test-repo but inherit the runner's GITHUB_TOKEN (scoped to pullfrog/app), causing 401s on every run-context fetch. Clear GITHUB_TOKEN in the test subprocess so ensureGitHubToken() mints a properly scoped token via OIDC. Also centralizes the default GITHUB_REPOSITORY in runAgentStreaming instead of repeating it in every test file, and fixes preview-cleanup to remove workers from all queues (not just name-matching ones). Made-with: Cursor * fix ensureGitHubToken to try OIDC when app credentials are absent ensureGitHubToken only attempted token minting when GITHUB_APP_ID and GITHUB_PRIVATE_KEY were set. In CI, OIDC is available but app creds aren't exposed — so the guard prevented minting entirely. Made-with: Cursor * dead code cleanup: remove remnants of deleted agents, file tools, effort system remove unused @anthropic-ai/claude-agent-sdk and @openai/codex-sdk deps, orphaned file-tool security tests, dead GEMINI_MODEL passthrough, stale opencode-runner wiki refs, deleted test file references, and MCP file tool docs. rename docs/effort → docs/models. fix vitest setup: move dotenv to globalSetup (runs once before forks instead of per-file, 19s → 200ms). Made-with: Cursor * address review feedback: remove dead code, update stale references - remove AGENT_OVERRIDE (only opentoad exists) - remove shellToolName plumbing (always restricted shell) - bump action version to 0.0.179 - remove CURSOR_API_KEY from all workflows/configs - remove OPENCODE_MODEL_MINI/MAX from workflows/docs - delete wiki/effort.md, rewrite docs/effort.mdx as "Models" - rewrite wiki/modes.md: orchestrator/subagent → single agent - simplify flag system: drop builtin flag extraction (debug, effort, timeout, agent), keep custom flag replacement only - reserve all legacy flag names to prevent custom flag conflicts Made-with: Cursor * regenerate lockfile after removing claude-agent-sdk and codex-sdk Made-with: Cursor * fix import ordering, add lockfile check to pre-push hook Made-with: Cursor * remove dead debug payload field, stale packageExtensions Made-with: Cursor * merge proc-sandbox and token-exfil into a single test proc-sandbox and token-exfil were duplicative — both tested that SANDBOX_TEST_TOKEN couldn't be exfiltrated. consolidated into token-exfil with shell:restricted (which actually exercises filterEnv) and the /proc attack vector hints from proc-sandbox. Made-with: Cursor * fix wiki adversarial.md to match actual tokenExfil validator Made-with: Cursor
294 lines
15 KiB
TypeScript
294 lines
15 KiB
TypeScript
import { type } from "arktype";
|
|
import { ghPullfrogMcpName } from "../external.ts";
|
|
import type { Mode } from "../modes.ts";
|
|
import { apiFetch } from "../utils/apiFetch.ts";
|
|
import type { ToolContext } from "./server.ts";
|
|
import { execute, tool } from "./shared.ts";
|
|
|
|
export const SelectModeParams = type({
|
|
mode: type.string.describe(
|
|
"the name of the mode to select (e.g., 'Build', 'Plan', 'Review', 'IncrementalReview', 'Fix', 'AddressReviews', 'Task', 'ResolveConflicts')"
|
|
),
|
|
"issue_number?": type("number").describe(
|
|
"optional issue number; when provided with Plan mode, used to look up an existing plan comment for this issue (edit vs create)"
|
|
),
|
|
});
|
|
|
|
function resolveMode(modes: Mode[], modeName: string): Mode | null {
|
|
return modes.find((m) => m.name.toLowerCase() === modeName.toLowerCase()) ?? null;
|
|
}
|
|
|
|
const modeGuidance: Record<string, string> = {
|
|
Build: `### Checklist
|
|
|
|
1. **plan** (optional, for complex tasks): analyze requirements, read AGENTS.md and relevant code, produce a step-by-step implementation plan.
|
|
|
|
2. **setup**: checkout or create the branch:
|
|
- **PR event, modifying the existing PR**: call \`${ghPullfrogMcpName}/checkout_pr\`
|
|
- **new branch**: use \`${ghPullfrogMcpName}/git\` to create a branch (\`git checkout -b pullfrog/branch-name\`)
|
|
|
|
3. **build**: implement changes using your native file and shell tools:
|
|
- follow the plan (if you ran a plan phase)
|
|
- 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.
|
|
- run relevant tests/lints before committing
|
|
- review your own diff before committing — verify only intended changes are present, no debug artifacts or commented-out code remain, and no unrelated files were modified. the change should be clean enough that a senior engineer would approve it without hesitation.
|
|
- commit locally via shell (\`git add . && git commit -m "..."\`)
|
|
|
|
4. **finalize**:
|
|
- push the branch via \`${ghPullfrogMcpName}/push_branch\`
|
|
- create a PR via \`${ghPullfrogMcpName}/create_pull_request\`
|
|
- call \`${ghPullfrogMcpName}/report_progress\` with the final summary including PR link
|
|
|
|
### Notes
|
|
|
|
For simple, well-defined tasks, skip the plan phase and go straight to build.`,
|
|
|
|
ResolveConflicts: `### Checklist
|
|
|
|
1. **Setup**:
|
|
- Call \`${ghPullfrogMcpName}/checkout_pr\` to get the PR branch.
|
|
- Call \`${ghPullfrogMcpName}/get_pull_request\` to identify the base branch (e.g., 'main').
|
|
- Call \`${ghPullfrogMcpName}/git_fetch\` to fetch the base branch.
|
|
|
|
2. **Merge Attempt**:
|
|
- Run \`git merge origin/<base_branch>\` via shell.
|
|
- If it succeeds automatically, push via \`${ghPullfrogMcpName}/push_branch\` and report success.
|
|
- If it fails (conflicts), resolve them manually.
|
|
|
|
3. **Resolve Conflicts**:
|
|
- Run \`git status\` or parse the merge output to find the list of conflicting files.
|
|
- For each conflicting file: read it, find the conflict markers (\`<<<<<<<\`, \`=======\`, \`>>>>>>>\`), understand the code context, and rewrite the file with the correct resolution. Remove all markers.
|
|
- Verify the file syntax is correct after resolution.
|
|
|
|
4. **Finalize**:
|
|
- Run a final verification (build/test) to ensure the resolution works.
|
|
- \`git add . && git commit -m "resolve merge conflicts"\`
|
|
- Push via \`${ghPullfrogMcpName}/push_branch\`
|
|
- Call \`${ghPullfrogMcpName}/report_progress\` with a summary of what was resolved`,
|
|
|
|
AddressReviews: `### Checklist
|
|
|
|
1. Checkout the PR branch via \`${ghPullfrogMcpName}/checkout_pr\`.
|
|
|
|
2. Fetch review comments via \`${ghPullfrogMcpName}/get_review_comments\`.
|
|
|
|
3. For each comment:
|
|
- understand the feedback
|
|
- make the code change using your native tools
|
|
- record what was done
|
|
|
|
4. Quality check:
|
|
- test changes, then review the diff before committing — verify only intended changes are present, no debug artifacts remain, and the changes are clean enough that a senior engineer would approve without hesitation
|
|
- commit locally via shell (\`git add . && git commit -m "..."\`)
|
|
|
|
5. Finalize:
|
|
- push changes via \`${ghPullfrogMcpName}/push_branch\`
|
|
- reply to each comment using \`${ghPullfrogMcpName}/reply_to_review_comment\`
|
|
- resolve addressed threads via \`${ghPullfrogMcpName}/resolve_review_thread\`
|
|
- call \`${ghPullfrogMcpName}/report_progress\` with a brief summary`,
|
|
|
|
Review: `### Checklist
|
|
|
|
1. Checkout the PR via \`${ghPullfrogMcpName}/checkout_pr\` — this returns PR metadata and a \`diffPath\`. Read the diff to identify the major areas of change.
|
|
|
|
2. For each area of change:
|
|
- read the diff and trace data flow, check boundaries, and verify assumptions
|
|
- plan your investigation: identify the highest-risk areas (tricky state transitions, boundary crossings, assumption chains) and prioritize depth over breadth
|
|
- use \`${ghPullfrogMcpName}/get_pull_request\` and other read-only GitHub tools for additional context
|
|
- if the PR removes features, deletes exports, renames concepts, or changes architectural patterns, run a dedicated impact analysis: list what changed, then use grep across code, tests, docs (\`docs/\`, \`wiki/\`), comments, configs, and UI to find stale references
|
|
- report impact-analysis findings in the summary body, ordered by severity (runtime breakage > incorrect docs > stale comments)
|
|
- draft inline comments with NEW line numbers from the diff — every comment must be actionable (2-3 sentences max)
|
|
- use GitHub permalink format for code references
|
|
|
|
3. Self-critique: review all drafted comments and drop any that are praise, style preferences, speculative/unverified claims, about pre-existing code unrelated to the PR, or not actionable.
|
|
|
|
4. Submit a **single** review:
|
|
- call \`${ghPullfrogMcpName}/create_pull_request_review\` with all comments and a unified summary body
|
|
- call \`${ghPullfrogMcpName}/report_progress\` with the summary
|
|
- if no actionable issues found, skip the review — just call \`report_progress\` noting the PR was reviewed`,
|
|
|
|
IncrementalReview: `### Checklist
|
|
|
|
1. Checkout the PR via \`${ghPullfrogMcpName}/checkout_pr\` — this returns PR metadata and a \`diffPath\`. Read the diff to identify the major areas of change.
|
|
|
|
2. Generate the incremental diff using the \`before_sha\` from EVENT DATA: \`git diff <before_sha>...HEAD\`. This isolates only the new commits. If the command fails (e.g., force-push rewrote history), fall back to reviewing the full PR diff.
|
|
|
|
3. Fetch previous reviews via \`${ghPullfrogMcpName}/list_pull_request_reviews\`. For the most recent Pullfrog review, call \`${ghPullfrogMcpName}/get_review_comments\` with the review ID to retrieve specific prior line-level feedback.
|
|
|
|
4. For each area of the new changes:
|
|
- review the incremental diff while using the full diff for context
|
|
- check whether prior review feedback was addressed by the new commits
|
|
- trace data flow, check boundaries, verify assumptions, consider lifecycle, spot performance issues
|
|
- if the new commits remove, rename, or deprecate anything, run impact analysis with grep across code/tests/docs/comments/configs to find stale references and include those findings in the summary body
|
|
- never repeat prior feedback. if the author did not address an earlier comment, assume it was intentionally declined; only comment on genuinely new issues introduced by the new commits
|
|
- draft inline comments with NEW line numbers from the full PR diff — every comment must be actionable (2-3 sentences max)
|
|
|
|
5. Self-critique: drop any comments that are praise, style preferences, speculative, about pre-existing code, or not actionable.
|
|
|
|
6. Submit a **single** review:
|
|
- if actionable issues found: call \`${ghPullfrogMcpName}/create_pull_request_review\` with \`approved: false\`, all comments, and an **empty body** (do NOT include a summary — inline comments speak for themselves and a top-level comment clutters the PR conversation on every re-review)
|
|
- if no actionable issues found: submit with \`approved: true\` and an **empty body** (no inline comments, no summary)
|
|
- do NOT call \`${ghPullfrogMcpName}/report_progress\` — incremental reviews should be silent`,
|
|
|
|
Plan: `### Checklist
|
|
|
|
1. Analyze the task and gather context:
|
|
- read AGENTS.md and relevant codebase files
|
|
- understand the architecture and constraints
|
|
|
|
2. Produce a structured, actionable plan with clear milestones.
|
|
|
|
3. Call \`${ghPullfrogMcpName}/report_progress\` with the plan.`,
|
|
|
|
PlanEdit: `### Checklist (editing existing plan)
|
|
|
|
An existing plan comment was found for this issue. Update that comment with the revised plan — do not create a new plan comment.
|
|
|
|
1. Use \`previousPlanBody\` from this response as the plan to revise; do not call \`get_issue\` or \`get_issue_comments\`.
|
|
2. Revise the plan based on the user's request:
|
|
- incorporate the current plan (\`previousPlanBody\`) and the user's revision request
|
|
- gather relevant codebase context (file paths, architecture notes from AGENTS.md)
|
|
- produce a structured plan with clear milestones
|
|
3. Call \`${ghPullfrogMcpName}/report_progress\` with the full revised plan text and \`{ target_plan_comment: true }\` so it updates the existing plan comment (not the progress comment).
|
|
4. Then post a short note to the progress comment (e.g. "Plan has been updated in the comment above.") via \`${ghPullfrogMcpName}/report_progress\` so it is not left as "Leaping...".`,
|
|
|
|
Fix: `### Checklist
|
|
|
|
1. Checkout the PR branch via \`${ghPullfrogMcpName}/checkout_pr\`.
|
|
|
|
2. Fetch check suite logs via \`${ghPullfrogMcpName}/get_check_suite_logs\`.
|
|
|
|
3. **CRITICAL**: verify the failure was INTRODUCED BY THIS PR before fixing. If unrelated, abort and report.
|
|
|
|
4. Diagnose and fix:
|
|
- read the workflow file, reproduce locally with the EXACT same commands CI runs
|
|
- fix the issue using your native file and shell tools
|
|
- verify the fix by re-running the exact CI command
|
|
- review the diff before committing — verify only the fix is present, no debug artifacts, no unrelated changes. the fix should be clean enough that a senior engineer would approve without hesitation.
|
|
- commit locally via shell (\`git add . && git commit -m "..."\`)
|
|
|
|
5. Finalize:
|
|
- push changes via \`${ghPullfrogMcpName}/push_branch\`
|
|
- call \`${ghPullfrogMcpName}/report_progress\` with the diagnosis and fix summary`,
|
|
|
|
Task: `### Checklist
|
|
|
|
1. Analyze the task. For simple operations (labeling, commenting, answering questions, running a single command), handle directly.
|
|
|
|
2. For substantial work — code changes across multiple files, multi-step investigations:
|
|
- plan your approach before starting
|
|
- use native file and shell tools for local operations
|
|
- use ${ghPullfrogMcpName} MCP tools for GitHub/git operations
|
|
- if code changes are needed: review your own diff before committing — verify only intended changes are present, no debug artifacts remain, and the changes are clean enough that a senior engineer would approve without hesitation
|
|
|
|
3. Finalize:
|
|
- call \`${ghPullfrogMcpName}/report_progress\` with results
|
|
- if the task involved code changes, push via \`${ghPullfrogMcpName}/push_branch\` and create a PR via \`${ghPullfrogMcpName}/create_pull_request\`
|
|
- if the task involved labeling, commenting, or other GitHub operations, perform those directly`,
|
|
};
|
|
|
|
type OrchestratorGuidance = {
|
|
modeName: string;
|
|
description: string;
|
|
orchestratorGuidance: string;
|
|
};
|
|
|
|
const modeInstructionParent: Record<string, string> = {
|
|
IncrementalReview: "Review",
|
|
Fix: "Build",
|
|
};
|
|
|
|
type BuildGuidanceOpts = {
|
|
modeInstructions?: Record<string, string>;
|
|
overrideGuidance?: string;
|
|
};
|
|
|
|
function buildOrchestratorGuidance(mode: Mode, opts: BuildGuidanceOpts = {}): OrchestratorGuidance {
|
|
const hardcoded = opts.overrideGuidance ?? modeGuidance[mode.name] ?? mode.prompt ?? "";
|
|
const lookupKey = modeInstructionParent[mode.name] ?? mode.name;
|
|
const userInstructions = opts.modeInstructions?.[lookupKey] ?? "";
|
|
const guidance = [hardcoded, userInstructions].filter(Boolean).join("\n\n");
|
|
return {
|
|
modeName: mode.name,
|
|
description: mode.description,
|
|
orchestratorGuidance: guidance,
|
|
};
|
|
}
|
|
|
|
// matches the API response for /repo/[owner]/[repo]/issue/[issueNumber]/plan-comment
|
|
export type PlanCommentResponsePayload = { error: string } | { commentId: number; body: string };
|
|
|
|
async function fetchExistingPlanComment(
|
|
ctx: ToolContext,
|
|
issueNumber: number
|
|
): Promise<Extract<PlanCommentResponsePayload, { commentId: number }> | null> {
|
|
if (!ctx.apiToken) return null;
|
|
try {
|
|
const response = await apiFetch({
|
|
path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/issue/${issueNumber}/plan-comment`,
|
|
method: "GET",
|
|
headers: { authorization: `Bearer ${ctx.apiToken}` },
|
|
signal: AbortSignal.timeout(10_000),
|
|
});
|
|
const data = (await response.json()) as PlanCommentResponsePayload;
|
|
return response.ok && "commentId" in data ? data : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function SelectModeTool(ctx: ToolContext) {
|
|
return tool({
|
|
name: "select_mode",
|
|
description:
|
|
"Select a mode and receive step-by-step guidance on how to handle the task. Call this to understand the best workflow for the current mode.",
|
|
parameters: SelectModeParams,
|
|
execute: execute(async (params) => {
|
|
if (ctx.toolState.selectedMode) {
|
|
return {
|
|
error: `mode already selected: "${ctx.toolState.selectedMode}". mode selection is final and cannot be changed. complete your current workflow within this mode.`,
|
|
};
|
|
}
|
|
|
|
const modeName = params.mode;
|
|
|
|
const selectedMode = resolveMode(ctx.modes, modeName);
|
|
|
|
if (!selectedMode) {
|
|
const availableModes = ctx.modes.map((m) => m.name).join(", ");
|
|
return {
|
|
error: `mode "${modeName}" not found. available modes: ${availableModes}`,
|
|
availableModes: ctx.modes.map((m) => ({
|
|
name: m.name,
|
|
description: m.description,
|
|
})),
|
|
};
|
|
}
|
|
|
|
ctx.toolState.selectedMode = selectedMode.name;
|
|
|
|
const guidanceOpts: BuildGuidanceOpts = { modeInstructions: ctx.modeInstructions };
|
|
|
|
if (selectedMode.name === "Plan") {
|
|
const issueNumber = params.issue_number ?? ctx.payload.event.issue_number;
|
|
if (issueNumber !== undefined) {
|
|
const existing = await fetchExistingPlanComment(ctx, issueNumber);
|
|
if (existing !== null) {
|
|
ctx.toolState.existingPlanCommentId = existing.commentId;
|
|
ctx.toolState.previousPlanBody = existing.body;
|
|
return {
|
|
...buildOrchestratorGuidance(selectedMode, {
|
|
...guidanceOpts,
|
|
overrideGuidance: modeGuidance.PlanEdit,
|
|
}),
|
|
previousPlanBody: existing.body,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
return buildOrchestratorGuidance(selectedMode, guidanceOpts);
|
|
}),
|
|
});
|
|
}
|