karajan-code 4.61.0 → 4.61.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/review/clean-merge.js +23 -0
- package/src/review/publish-pr.js +3 -0
- package/src/review/verify-pr.js +11 -4
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KJC-BUG-0391: "Update branch" puts a merge of the base on top of a PR. A
|
|
3
|
+
* clean merge brings no work of the PR: its tree is exactly what git merges
|
|
4
|
+
* from its parents, so it needs no cross-AI verdict. A merge whose tree differs
|
|
5
|
+
* (conflicts resolved by hand, or anything slipped in) is work and still needs
|
|
6
|
+
* one. Exit codes only, never git's messages, which follow the user's language.
|
|
7
|
+
*/
|
|
8
|
+
import { runCommand } from "../utils/process.js";
|
|
9
|
+
|
|
10
|
+
/** True when `sha` is a merge whose tree is the clean merge of its two parents. */
|
|
11
|
+
export async function isCleanMerge(projectDir, sha, run = runCommand) {
|
|
12
|
+
const git = (...args) => run("git", ["-C", projectDir, ...args]);
|
|
13
|
+
const parents = await git("rev-list", "--parents", "-n", "1", sha);
|
|
14
|
+
if (parents.exitCode !== 0) throw new Error(`git rev-list ${sha.slice(0, 7)} failed: ${(parents.stderr || "").trim()}`);
|
|
15
|
+
const ids = parents.stdout.trim().split(/\s+/).slice(1);
|
|
16
|
+
if (ids.length !== 2) return false;
|
|
17
|
+
// Exit 1 means conflicts: the merge commit then holds a resolution, which is work.
|
|
18
|
+
const merged = await git("merge-tree", "--write-tree", ...ids);
|
|
19
|
+
if (merged.exitCode !== 0) return false;
|
|
20
|
+
const tree = await git("rev-parse", `${sha}^{tree}`);
|
|
21
|
+
if (tree.exitCode !== 0) throw new Error(`git rev-parse ${sha.slice(0, 7)} failed: ${(tree.stderr || "").trim()}`);
|
|
22
|
+
return merged.stdout.trim().split("\n")[0] === tree.stdout.trim();
|
|
23
|
+
}
|
package/src/review/publish-pr.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { runCommand } from "../utils/process.js";
|
|
8
8
|
import { diffHash, loadVerdict } from "./verdict-store.js";
|
|
9
|
+
import { isCleanMerge } from "./clean-merge.js";
|
|
9
10
|
|
|
10
11
|
export const MARKER = (head) => `<!-- kj-review head=${head} -->`;
|
|
11
12
|
|
|
@@ -30,6 +31,8 @@ export async function publishPrReview({ projectDir, pr, run = runCommand }) {
|
|
|
30
31
|
const rows = [];
|
|
31
32
|
const uncovered = [];
|
|
32
33
|
for (const c of info.commits ?? []) {
|
|
34
|
+
// KJC-BUG-0391: a clean merge of the base brings no work of the PR and holds no verdict.
|
|
35
|
+
if (await isCleanMerge(projectDir, c.oid, run)) continue;
|
|
33
36
|
const v = await verdictOf(projectDir, c.oid, run);
|
|
34
37
|
if (v?.verdict === "approved") rows.push({ ...c, v });
|
|
35
38
|
else uncovered.push(`${c.oid.slice(0, 7)} (${c.messageHeadline})`);
|
package/src/review/verify-pr.js
CHANGED
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
import { runCommand } from "../utils/process.js";
|
|
9
9
|
import { diffHash } from "./verdict-store.js";
|
|
10
10
|
import { MARKER } from "./publish-pr.js";
|
|
11
|
+
import { isCleanMerge } from "./clean-merge.js";
|
|
11
12
|
|
|
12
13
|
const TRUSTED = new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
|
|
14
|
+
// The marker of a kj review for any head: what MARKER writes up to the head itself.
|
|
15
|
+
const MARKER_PREFIX = MARKER("\0").split("\0")[0];
|
|
13
16
|
// One line per commit, as publish-pr writes it: - `<sha7>` headline: approved by X (diff <hash12>)...
|
|
14
17
|
const LISTED = /^- `([0-9a-f]{7})` .*?\(diff ([0-9a-f]{12})\)/gm;
|
|
15
18
|
|
|
@@ -45,13 +48,17 @@ export async function verifyPrReview({ projectDir, pr, run = runCommand }) {
|
|
|
45
48
|
const res = await run("gh", ["api", "--paginate", `repos/{owner}/{repo}/pulls/${pr}/reviews`, "--jq", ".[] | {body, author_association}"], { cwd: projectDir });
|
|
46
49
|
if (res.exitCode !== 0) throw new Error(`gh api reviews failed: ${(res.stderr || res.stdout || "").trim()}`);
|
|
47
50
|
const reviews = (res.stdout || "").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
// KJC-BUG-0391: any kj review a member published on the PR counts, not only the one for
|
|
52
|
+
// today's head: the diff hash of each commit is what proves it, and a commit pushed after
|
|
53
|
+
// the review is simply not listed. "Update branch" no longer leaves the check red.
|
|
54
|
+
const kjReviews = reviews.filter((r) => TRUSTED.has(r.author_association) && String(r.body ?? "").includes(MARKER_PREFIX));
|
|
55
|
+
if (kjReviews.length === 0) {
|
|
56
|
+
return { ok: false, commits: commits.length, problems: [`no kj review published by a member: run kj review --publish-pr ${pr}`] };
|
|
51
57
|
}
|
|
52
|
-
const listed = new Map([...
|
|
58
|
+
const listed = new Map(kjReviews.flatMap((r) => [...r.body.matchAll(LISTED)].map((m) => [m[1], m[2]])));
|
|
53
59
|
const problems = [];
|
|
54
60
|
for (const c of commits) {
|
|
61
|
+
if (await isCleanMerge(projectDir, c.oid, run)) continue;
|
|
55
62
|
const hash = listed.get(c.oid.slice(0, 7));
|
|
56
63
|
if (!hash || !(await hashesOf(projectDir, c.oid, run)).has(hash)) problems.push(`${c.oid.slice(0, 7)} (${c.messageHeadline}): no approved verdict for the diff it brings`);
|
|
57
64
|
}
|