Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
11 changes: 11 additions & 0 deletions docs/releases/v0.10.2.md
Original file line number Diff line number Diff line change
@@ -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 `<relative-time>` 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.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
25 changes: 23 additions & 2 deletions src/runtime/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 datetime="([^"<>]+)">([^<>]+)<\/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,
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -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";
65 changes: 65 additions & 0 deletions test/runtime-github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `<relative-time datetime="${timestamp}">${timestamp}</relative-time>`;
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" } }),
Expand Down