diff --git a/src/client.ts b/src/client.ts index a05c4dd..0e249a2 100644 --- a/src/client.ts +++ b/src/client.ts @@ -291,8 +291,12 @@ export class PlatformClient { /** * Sends a report_progress event to update the PR title and/or description. * This triggers the platform to update the pull request associated with the job. + * + * `commitSha` is the SHA of the commit pushed alongside this update, when known. + * Omitted keys are left out of the payload entirely, so servers that do not + * understand a field are unaffected. */ - async sendReportProgress(options: { prTitle?: string; prDescription?: string }): Promise { + async sendReportProgress(options: { prTitle?: string; prDescription?: string; commitSha?: string }): Promise { const url = new URL(`jobs/${this._jobId}/progress`, this.baseUrl); const contentObj: Record = {}; if (options.prTitle !== undefined) { @@ -301,6 +305,9 @@ export class PlatformClient { if (options.prDescription !== undefined) { contentObj.pr_description = options.prDescription; } + if (options.commitSha !== undefined) { + contentObj.commit_sha = options.commitSha; + } const payload: ProgressPayload = { namespace: this.namespace, kind: "report_progress", diff --git a/src/git.ts b/src/git.ts index 0c3f709..345882e 100644 --- a/src/git.ts +++ b/src/git.ts @@ -101,6 +101,12 @@ export interface CommitAndPushResult { hadChanges: boolean; /** Human-readable message describing the outcome */ message: string; + /** + * SHA of the commit at HEAD after the push succeeded. + * Undefined if the SHA could not be resolved; a successful push is never + * reported as a failure just because this lookup did not work. + */ + commitSha?: string; } // ============================================================================= @@ -301,12 +307,27 @@ export function commitAndPush(repoLocation: string, commitMessage: string): Comm pushWithRebaseFallback(repoLocation); + // Resolved only after the push succeeded, so the SHA always refers to a commit + // that is actually on the remote. Deliberately isolated in its own try/catch: + // a rev-parse failure must never turn an already-successful push into a + // reported error, which callers could retry into a duplicate commit. + let commitSha: string | undefined; + try { + commitSha = git(["rev-parse", "HEAD"], repoLocation).trim(); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + // Note: stderr, not stdout — commitAndPush runs inside the stdio MCP + // server (src/mcp-server.ts) where stdout is reserved for the MCP protocol. + console.error(`[Engine SDK] Push succeeded but resolving the commit SHA failed - ${msg}`); + } + return { success: true, hadChanges, message: hadChanges ? `Committed and pushed: ${commitMessage}` : "No changes to commit. Pushed existing commits.", + commitSha, }; } diff --git a/src/mcp-server.ts b/src/mcp-server.ts index d2967c9..c8c087d 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -116,6 +116,9 @@ async function executeReportProgress( const { commitMessage, prDescription } = input; const results: string[] = []; let hasError = false; + // Stays undefined unless a push actually happened and the SHA was resolved, + // so the local-only and error paths never report a SHA that is not on the remote. + let pushedCommitSha: string | undefined; log("executeReportProgress", { commitMessage, prDescriptionLength: prDescription.length }); @@ -125,6 +128,7 @@ async function executeReportProgress( const gitResult = commitAndPush(config.workingDir, commitMessage); log("git commit and push", { hadChanges: gitResult.hadChanges, message: gitResult.message }); results.push(gitResult.message); + pushedCommitSha = gitResult.commitSha; } else { // Local-only mode: just stage and commit without pushing const { execFileSync } = await import("child_process"); @@ -160,6 +164,7 @@ async function executeReportProgress( try { const sendResult = await config.platformClient.sendReportProgress({ prDescription, + commitSha: pushedCommitSha, }); if (sendResult.success) { log("sent report_progress to platform");