diff --git a/entry b/entry index bb4e81a..d478dca 100755 --- a/entry +++ b/entry @@ -146767,6 +146767,40 @@ function AddLabelsTool(ctx) { }); } +// mcp/learnings.ts +var UpdateLearningsParams = type({ + learnings: type.string.describe( + "the FULL merged learnings as a flat bullet list. each line starts with `- `. one discrete, actionable fact per bullet. combine existing bullets from the prompt with your new discoveries. deduplicate \u2014 if an existing bullet covers the same fact, update it in place rather than adding a new one. drop bullets that are clearly wrong or no longer relevant to the current codebase. keep the list focused and concise." + ) +}); +function UpdateLearningsTool(ctx) { + return tool({ + name: "update_learnings", + description: "persist operational learnings about this repository (setup steps, test commands, key conventions, patterns). ONLY call this when you have high confidence the information is correct and broadly useful for future runs \u2014 not for one-off findings or uncertain observations. format: flat bullet list (`- ` per line, one fact per bullet). pass the FULL merged list \u2014 combine existing learnings from the prompt with new discoveries. deduplicate, and drop bullets that are clearly wrong or no longer relevant to the current codebase.", + parameters: UpdateLearningsParams, + execute: execute(async (params) => { + const response = await apiFetch({ + path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/learnings`, + method: "PATCH", + headers: { + authorization: `Bearer ${ctx.apiToken}`, + "content-type": "application/json" + }, + body: JSON.stringify({ + learnings: params.learnings, + model: ctx.toolState.model + }), + signal: AbortSignal.timeout(1e4) + }); + if (!response.ok) { + const error49 = await response.text(); + throw new Error(`failed to update learnings: ${error49}`); + } + return { success: true }; + }) + }); +} + // mcp/output.ts var import_ajv3 = __toESM(require_ajv(), 1); var SetOutputParams = type({ @@ -147014,7 +147048,7 @@ function CreatePullRequestReviewTool(ctx) { execute: execute(async ({ pull_number, body, approved, commit_id, comments = [] }) => { if (body) body = fixDoubleEscapedString(body); ctx.toolState.issueNumber = pull_number; - if (!body && comments.length === 0) { + if (!approved && !body && comments.length === 0) { log.info( "review has no body and no inline comments \u2014 skipping submission (no issues found)" ); @@ -147673,6 +147707,9 @@ var SelectModeParams = type({ function resolveMode(modes2, modeName) { return modes2.find((m) => m.name.toLowerCase() === modeName.toLowerCase()) ?? null; } +function learningsStep(n) { + return `${n}. **learnings** (only if high confidence): if you discovered something about repo setup, test commands, conventions, or patterns that you are confident is correct and would reliably help future runs, call \`${ghPullfrogMcpName}/update_learnings\` to persist it. skip this step if you are unsure or the finding is speculative/one-off. format as a flat bullet list (\`- \` per line, one fact per bullet). merge with existing learnings from the prompt \u2014 pass the FULL merged list. deduplicate, and drop bullets that are clearly wrong or no longer relevant to the current codebase.`; +} var modeGuidance = { Build: `### Checklist @@ -147694,6 +147731,8 @@ var modeGuidance = { - create a PR via \`${ghPullfrogMcpName}/create_pull_request\` - call \`${ghPullfrogMcpName}/report_progress\` with the final summary including PR link +${learningsStep(5)} + ### Notes For simple, well-defined tasks, skip the plan phase and go straight to build.`, @@ -147738,7 +147777,9 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`, - 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`, + - call \`${ghPullfrogMcpName}/report_progress\` with a brief summary + +${learningsStep(6)}`, Review: `### Checklist 1. Checkout the PR via \`${ghPullfrogMcpName}/checkout_pr\` \u2014 this returns PR metadata and a \`diffPath\`. Read the diff to identify the major areas of change. @@ -147788,7 +147829,9 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`, 2. Produce a structured, actionable plan with clear milestones. -3. Call \`${ghPullfrogMcpName}/report_progress\` with the plan.`, +3. Call \`${ghPullfrogMcpName}/report_progress\` with the plan. + +${learningsStep(4)}`, PlanEdit: `### Checklist (editing existing plan) An existing plan comment was found for this issue. Update that comment with the revised plan \u2014 do not create a new plan comment. @@ -147817,7 +147860,9 @@ An existing plan comment was found for this issue. Update that comment with the 5. Finalize: - push changes via \`${ghPullfrogMcpName}/push_branch\` - - call \`${ghPullfrogMcpName}/report_progress\` with the diagnosis and fix summary`, + - call \`${ghPullfrogMcpName}/report_progress\` with the diagnosis and fix summary + +${learningsStep(6)}`, Task: `### Checklist 1. Analyze the task. For simple operations (labeling, commenting, answering questions, running a single command), handle directly. @@ -147831,7 +147876,9 @@ An existing plan comment was found for this issue. Update that comment with the 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`, + - if the task involved labeling, commenting, or other GitHub operations, perform those directly + +${learningsStep(4)}`, Summarize: `### Checklist 1. Checkout the PR via \`${ghPullfrogMcpName}/checkout_pr\` \u2014 this returns PR metadata and a \`diffPath\`. @@ -148368,7 +148415,8 @@ function buildOrchestratorTools(ctx, outputSchema) { PushTagsTool(ctx), DeleteBranchTool(ctx), CreatePullRequestTool(ctx), - UpdatePullRequestBodyTool(ctx) + UpdatePullRequestBodyTool(ctx), + UpdateLearningsTool(ctx) ]; } async function tryStartMcpServer(ctx, tools, port) { @@ -149849,10 +149897,15 @@ function buildCommonInputs(ctx) { }; } function assembleFullPrompt(ctx) { + const learningsSection = ctx.learnings ? `************* REPO INTELLIGENCE ************* + +${ctx.learnings}` : ""; const rawFull = `************* RUNTIME CONTEXT ************* ${ctx.runtime} +${learningsSection} + ${ctx.system} ${ctx.contextSections}`; @@ -149897,7 +149950,8 @@ If the task clearly requires no work, call \`${ghPullfrogMcpName}/report_progres const full = assembleFullPrompt({ runtime: inputs.runtime, system, - contextSections + contextSections, + learnings: ctx.learnings }); return { full, @@ -150275,7 +150329,8 @@ var defaultSettings = { push: "restricted", shell: "restricted", prApproveEnabled: false, - modeInstructions: {} + modeInstructions: {}, + learnings: null }; var defaultRunContext = { settings: defaultSettings, @@ -150635,7 +150690,8 @@ async function main() { payload, repo: runContext.repo, modes: modes2, - outputSchema + outputSchema, + learnings: runContext.repoSettings.learnings }); const logParts = [ instructions.eventInstructions ? `EVENT-LEVEL INSTRUCTIONS: diff --git a/main.ts b/main.ts index 03646f3..65d2bd8 100644 --- a/main.ts +++ b/main.ts @@ -289,6 +289,7 @@ export async function main(): Promise { repo: runContext.repo, modes, outputSchema, + learnings: runContext.repoSettings.learnings, }); // log instructions as soon as they are fully resolved const logParts = [ diff --git a/mcp/learnings.ts b/mcp/learnings.ts new file mode 100644 index 0000000..d509df4 --- /dev/null +++ b/mcp/learnings.ts @@ -0,0 +1,41 @@ +import { type } from "arktype"; +import { apiFetch } from "../utils/apiFetch.ts"; +import type { ToolContext } from "./server.ts"; +import { execute, tool } from "./shared.ts"; + +const UpdateLearningsParams = type({ + learnings: type.string.describe( + "the FULL merged learnings as a flat bullet list. each line starts with `- `. one discrete, actionable fact per bullet. combine existing bullets from the prompt with your new discoveries. deduplicate — if an existing bullet covers the same fact, update it in place rather than adding a new one. drop bullets that are clearly wrong or no longer relevant to the current codebase. keep the list focused and concise." + ), +}); + +export function UpdateLearningsTool(ctx: ToolContext) { + return tool({ + name: "update_learnings", + description: + "persist operational learnings about this repository (setup steps, test commands, key conventions, patterns). ONLY call this when you have high confidence the information is correct and broadly useful for future runs — not for one-off findings or uncertain observations. format: flat bullet list (`- ` per line, one fact per bullet). pass the FULL merged list — combine existing learnings from the prompt with new discoveries. deduplicate, and drop bullets that are clearly wrong or no longer relevant to the current codebase.", + parameters: UpdateLearningsParams, + execute: execute(async (params) => { + const response = await apiFetch({ + path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/learnings`, + method: "PATCH", + headers: { + authorization: `Bearer ${ctx.apiToken}`, + "content-type": "application/json", + }, + body: JSON.stringify({ + learnings: params.learnings, + model: ctx.toolState.model, + }), + signal: AbortSignal.timeout(10_000), + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(`failed to update learnings: ${error}`); + } + + return { success: true }; + }), + }); +} diff --git a/mcp/review.ts b/mcp/review.ts index 39509c3..0c6fba6 100644 --- a/mcp/review.ts +++ b/mcp/review.ts @@ -84,8 +84,9 @@ export function CreatePullRequestReviewTool(ctx: ToolContext) { // set issue context (PRs are issues) ctx.toolState.issueNumber = pull_number; - // skip empty reviews (no body, no inline comments) — nothing to post - if (!body && comments.length === 0) { + // skip empty COMMENT reviews (no body, no inline comments) — nothing to post. + // APPROVE reviews are never skipped: the approval stamp itself is the content. + if (!approved && !body && comments.length === 0) { log.info( "review has no body and no inline comments — skipping submission (no issues found)" ); diff --git a/mcp/selectMode.ts b/mcp/selectMode.ts index 7c0eda2..c30738d 100644 --- a/mcp/selectMode.ts +++ b/mcp/selectMode.ts @@ -19,6 +19,10 @@ function resolveMode(modes: Mode[], modeName: string): Mode | null { return modes.find((m) => m.name.toLowerCase() === modeName.toLowerCase()) ?? null; } +function learningsStep(n: number): string { + return `${n}. **learnings** (only if high confidence): if you discovered something about repo setup, test commands, conventions, or patterns that you are confident is correct and would reliably help future runs, call \`${ghPullfrogMcpName}/update_learnings\` to persist it. skip this step if you are unsure or the finding is speculative/one-off. format as a flat bullet list (\`- \` per line, one fact per bullet). merge with existing learnings from the prompt — pass the FULL merged list. deduplicate, and drop bullets that are clearly wrong or no longer relevant to the current codebase.`; +} + const modeGuidance: Record = { Build: `### Checklist @@ -40,6 +44,8 @@ const modeGuidance: Record = { - create a PR via \`${ghPullfrogMcpName}/create_pull_request\` - call \`${ghPullfrogMcpName}/report_progress\` with the final summary including PR link +${learningsStep(5)} + ### Notes For simple, well-defined tasks, skip the plan phase and go straight to build.`, @@ -86,7 +92,9 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`, - 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`, + - call \`${ghPullfrogMcpName}/report_progress\` with a brief summary + +${learningsStep(6)}`, Review: `### Checklist @@ -139,7 +147,9 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`, 2. Produce a structured, actionable plan with clear milestones. -3. Call \`${ghPullfrogMcpName}/report_progress\` with the plan.`, +3. Call \`${ghPullfrogMcpName}/report_progress\` with the plan. + +${learningsStep(4)}`, PlanEdit: `### Checklist (editing existing plan) @@ -170,7 +180,9 @@ An existing plan comment was found for this issue. Update that comment with the 5. Finalize: - push changes via \`${ghPullfrogMcpName}/push_branch\` - - call \`${ghPullfrogMcpName}/report_progress\` with the diagnosis and fix summary`, + - call \`${ghPullfrogMcpName}/report_progress\` with the diagnosis and fix summary + +${learningsStep(6)}`, Task: `### Checklist @@ -185,7 +197,9 @@ An existing plan comment was found for this issue. Update that comment with the 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`, + - if the task involved labeling, commenting, or other GitHub operations, perform those directly + +${learningsStep(4)}`, Summarize: `### Checklist diff --git a/mcp/server.ts b/mcp/server.ts index 56cfd14..c4c833c 100644 --- a/mcp/server.ts +++ b/mcp/server.ts @@ -29,6 +29,7 @@ import { GetIssueCommentsTool } from "./issueComments.ts"; import { GetIssueEventsTool } from "./issueEvents.ts"; import { IssueInfoTool } from "./issueInfo.ts"; import { AddLabelsTool } from "./labels.ts"; +import { UpdateLearningsTool } from "./learnings.ts"; import { SetOutputTool } from "./output.ts"; import { CreatePullRequestTool, UpdatePullRequestBodyTool } from "./pr.ts"; import { PullRequestInfoTool } from "./prInfo.ts"; @@ -213,6 +214,7 @@ function buildOrchestratorTools(ctx: ToolContext, outputSchema?: JsonSchema): To DeleteBranchTool(ctx), CreatePullRequestTool(ctx), UpdatePullRequestBodyTool(ctx), + UpdateLearningsTool(ctx), ]; } diff --git a/utils/instructions.ts b/utils/instructions.ts index 63e3a88..5489216 100644 --- a/utils/instructions.ts +++ b/utils/instructions.ts @@ -11,6 +11,7 @@ interface InstructionsContext { repo: RunContextData["repo"]; modes: Mode[]; outputSchema?: Record | undefined; + learnings: string | null; } function buildRuntimeContext(ctx: InstructionsContext): string { @@ -328,13 +329,20 @@ interface AssembleFullPromptInput { runtime: string; system: string; contextSections: string; + learnings: string | null; } function assembleFullPrompt(ctx: AssembleFullPromptInput): string { + const learningsSection = ctx.learnings + ? `************* REPO INTELLIGENCE *************\n\n${ctx.learnings}` + : ""; + const rawFull = `************* RUNTIME CONTEXT ************* ${ctx.runtime} +${learningsSection} + ${ctx.system} ${ctx.contextSections}`; @@ -385,6 +393,7 @@ If the task clearly requires no work, call \`${ghPullfrogMcpName}/report_progres runtime: inputs.runtime, system, contextSections, + learnings: ctx.learnings, }); return { diff --git a/utils/runContext.ts b/utils/runContext.ts index 7664b37..40419aa 100644 --- a/utils/runContext.ts +++ b/utils/runContext.ts @@ -18,6 +18,7 @@ export interface RepoSettings { shell: ShellPermission; prApproveEnabled: boolean; modeInstructions: Record; + learnings: string | null; } export interface RunContext { @@ -36,6 +37,7 @@ const defaultSettings: RepoSettings = { shell: "restricted", prApproveEnabled: false, modeInstructions: {}, + learnings: null, }; const defaultRunContext: RunContext = {