From 3280a5439c0d9de33ecb98cab0d15d0da1f256b6 Mon Sep 17 00:00:00 2001 From: David Ahmann <46606159+davidahmann@users.noreply.github.com> Date: Thu, 24 Sep 2026 13:52:01 -0400 Subject: [PATCH] fix: parse timestamped GitHub Codex review summaries Signed-off-by: David Ahmann <46606159+davidahmann@users.noreply.github.com> --- README.md | 2 +- docs/releases/v0.10.2.md | 11 +++++++ package-lock.json | 4 +-- package.json | 2 +- src/runtime/github.ts | 25 ++++++++++++-- src/version.ts | 2 +- test/runtime-github.test.ts | 65 +++++++++++++++++++++++++++++++++++++ 7 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 docs/releases/v0.10.2.md diff --git a/README.md b/README.md index f014ce8..8f017b2 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ GitHub workflow. Pin a released version in the repository that will use Mill: ```sh -npm i -D -E --ignore-scripts @davidahmann/mill@0.10.1 +npm i -D -E --ignore-scripts @davidahmann/mill@0.10.2 npx --no-install millctl --version ``` diff --git a/docs/releases/v0.10.2.md b/docs/releases/v0.10.2.md new file mode 100644 index 0000000..d981656 --- /dev/null +++ b/docs/releases/v0.10.2.md @@ -0,0 +1,11 @@ +# Mill 0.10.2 + +Mill now recognizes the GitHub Codex review summary's timestamped `Completed` +and `Running` statuses. Version 0.10.1 treated a completed summary with the +provider's `` element as invalid, so a reviewed draft PR could +not pass the hosted-review merge gate. + +The parser accepts the earlier bare statuses and matching UTC timestamps. It +still rejects mismatched timestamps, extra status text, ambiguous commit +prefixes, stale summaries, and blocking feedback. Regression checks use the +observed GitHub summary shape. This source record does not claim publication. diff --git a/package-lock.json b/package-lock.json index a0e6e36..9ce6688 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@davidahmann/mill", - "version": "0.10.1", + "version": "0.10.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@davidahmann/mill", - "version": "0.10.1", + "version": "0.10.2", "bundleDependencies": [ "typescript" ], diff --git a/package.json b/package.json index fd695b4..d5ee54a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@davidahmann/mill", - "version": "0.10.1", + "version": "0.10.2", "description": "Local-first software factory for new and existing codebases. Turns approved product intent into tested, reviewed PRs with repo-native evidence and explicit human approval for delivery and merge.", "license": "Apache-2.0", "author": "David Ahmann", diff --git a/src/runtime/github.ts b/src/runtime/github.ts index 17f9337..e9b85d0 100644 --- a/src/runtime/github.ts +++ b/src/runtime/github.ts @@ -298,6 +298,27 @@ function priority(body: string): GitHubFeedback["priority"] { ); } +function codexSummaryStatus( + status: string, + label: "✅ **Completed**" | "🔄 **Running**", +): boolean { + if (status === label) return true; + const prefix = label === "✅ **Completed**" ? `${label} ` : `${label} since `; + if (!status.startsWith(prefix)) return false; + const suffix = status.slice(prefix.length); + const match = + /^]+)">([^<>]+)<\/relative-time>$/u.exec( + suffix, + ); + const timestamp = match?.[1]; + return ( + timestamp !== undefined && + timestamp === match?.[2] && + timestamp.endsWith("Z") && + Number.isFinite(Date.parse(timestamp)) + ); +} + function codexSummaryReview( value: unknown, headSha: string, @@ -321,8 +342,8 @@ function codexSummaryReview( const commit = cells?.[2] ?? ""; const commitMatch = /^`([a-f0-9]{7,40})`$/iu.exec(commit); const commitPrefix = (commitMatch?.[1] ?? "").toLowerCase(); - const completed = status === "✅ **Completed**"; - const running = status === "🔄 **Running**"; + const completed = codexSummaryStatus(status, "✅ **Completed**"); + const running = codexSummaryStatus(status, "🔄 **Running**"); const state = cells !== null && commitMatch !== null && completed !== running && completed ? "CODEX_COMPLETED" diff --git a/src/version.ts b/src/version.ts index 554d75f..b37ab7c 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,3 +1,3 @@ export const MILL_PACKAGE = "@davidahmann/mill"; -export const MILL_VERSION = "0.10.1"; +export const MILL_VERSION = "0.10.2"; export const RESULT_SCHEMA_VERSION = "1"; diff --git a/test/runtime-github.test.ts b/test/runtime-github.test.ts index bf31ee6..917e2c6 100644 --- a/test/runtime-github.test.ts +++ b/test/runtime-github.test.ts @@ -197,6 +197,71 @@ else process.exit(2); { priority: "P2", commitId: sha, path: "src/index.ts" }, ], }); + const timestamp = "2026-09-24T17:45:04.727157Z"; + const relativeTime = `${timestamp}`; + await writeFile( + path.join(tools.path, "mode.json"), + JSON.stringify({ + issueBody: completedSummary.replace( + "✅ **Completed**", + `✅ **Completed** ${relativeTime}`, + ), + }), + ); + await expect( + adapter.observe({ + config, + pullRequestNumber: 41, + deadlineMs: Date.now() + 10_000, + }), + ).resolves.toMatchObject({ + reviews: [ + { state: "COMMENTED", commitId: sha }, + { state: "CODEX_COMPLETED", commitId: sha }, + ], + }); + await writeFile( + path.join(tools.path, "mode.json"), + JSON.stringify({ + issueBody: completedSummary.replace( + "✅ **Completed**", + `🔄 **Running** since ${relativeTime}`, + ), + }), + ); + await expect( + adapter.observe({ + config, + pullRequestNumber: 41, + deadlineMs: Date.now() + 10_000, + }), + ).resolves.toMatchObject({ + reviews: [ + { state: "COMMENTED", commitId: sha }, + { state: "CODEX_RUNNING", commitId: sha }, + ], + }); + await writeFile( + path.join(tools.path, "mode.json"), + JSON.stringify({ + issueBody: completedSummary.replace( + "✅ **Completed**", + `✅ **Completed** ${relativeTime.replace(timestamp, "2026-09-24T17:45:05Z")}`, + ), + }), + ); + await expect( + adapter.observe({ + config, + pullRequestNumber: 41, + deadlineMs: Date.now() + 10_000, + }), + ).resolves.toMatchObject({ + reviews: [ + { state: "COMMENTED", commitId: sha }, + { state: "CODEX_INVALID", commitId: sha }, + ], + }); await writeFile( path.join(tools.path, "mode.json"), JSON.stringify({ review: { state: "APPROVED", body: "LGTM" } }),