11import type {
2- FlowRun , PRCheckpoints , Checkpoint , TaskState ,
2+ PRCheckpoints , Checkpoint , TaskState ,
33} from "./types.js"
4- import { canMerge } from "./gate.js"
5- import { gh } from "../util/gh.js"
4+
5+ // ─── 可替换的 gh executor(用于测试) ───
6+
7+ type GhFn = ( args : string ) => Promise < { stdout : string ; stderr : string } >
8+
9+ let mergeGhExecutor : GhFn | null = null
10+
11+ export function setMergeGhExecutor ( fn : GhFn ) {
12+ mergeGhExecutor = fn
13+ }
14+
15+ async function gh ( args : string ) : Promise < { stdout : string ; stderr : string } > {
16+ if ( mergeGhExecutor ) {
17+ return mergeGhExecutor ( args )
18+ }
19+ const mod = await import ( "../util/gh.js" )
20+ return mod . gh ( args )
21+ }
22+
23+ // ─── 类型 ───
624
725export interface BranchProtectionStatus {
826 exists : boolean
@@ -17,6 +35,8 @@ export interface MergeGateResult {
1735 checkpointResults : Record < string , "pending" | "pass" | "fail" | "skipped" >
1836}
1937
38+ // ─── Branch Protection ───
39+
2040export async function checkBranchProtection ( owner : string , repo : string ) : Promise < BranchProtectionStatus > {
2141 try {
2242 const { stdout } = await gh ( `api repos/${ owner } /${ repo } /branches/main/protection` )
@@ -46,6 +66,8 @@ export async function checkBranchProtection(owner: string, repo: string): Promis
4666 }
4767}
4868
69+ // ─── Checkpoint 验证 ───
70+
4971export function validateCheckpoint ( cp : Checkpoint ) : "pending" | "pass" | "fail" {
5072 if ( cp . status === "pass" ) return "pass"
5173 if ( cp . status === "fail" ) return "fail"
@@ -88,12 +110,22 @@ export function validatePRCheckpoints(checkpoints: PRCheckpoints): MergeGateResu
88110 return { allowed : true , checkpointResults : results }
89111}
90112
91- export function canAutoMergeTask ( flowRun : FlowRun , task : TaskState , protection : BranchProtectionStatus ) : MergeGateResult {
92- const flowGate = canMerge ( flowRun )
93- if ( ! flowGate . allowed ) {
94- return { allowed : false , reason : flowGate . reason , checkpointResults : { } }
95- }
96-
113+ // ─── Task-local Merge Gate ───
114+
115+ /**
116+ * Task-local merge gate(不依赖全局 canMerge)。
117+ *
118+ * 检查条件(按顺序):
119+ * 1. Task 状态为 "reviewing"
120+ * 2. 存在 PR Checkpoints
121+ * 3. 所有 checkpoint 状态为 pass
122+ * 4. TDD compliance 为 pass / waived / null(无 TDD 要求)
123+ * 5. Branch Protection 已启用
124+ *
125+ * 不要求全局 review Stage 为 pass。
126+ */
127+ export function canMergeTaskPR ( task : TaskState , hasBranchProtection : boolean ) : MergeGateResult {
128+ // 1. Task 状态检查
97129 if ( task . status === "merged" ) {
98130 return { allowed : false , reason : "Task already merged" , checkpointResults : { } }
99131 }
@@ -102,14 +134,32 @@ export function canAutoMergeTask(flowRun: FlowRun, task: TaskState, protection:
102134 return { allowed : false , reason : `Task status is ${ task . status } , expected reviewing` , checkpointResults : { } }
103135 }
104136
137+ // 2. PR Checkpoints 存在性
105138 if ( ! task . prCheckpoints ) {
106139 return { allowed : false , reason : "No PR checkpoints recorded" , checkpointResults : { } }
107140 }
108141
142+ // 3. 标准 checkpoint 验证
109143 const prResult = validatePRCheckpoints ( task . prCheckpoints )
110144 if ( ! prResult . allowed ) return prResult
111145
112- if ( ! protection . exists ) {
146+ // 4. TDD compliance 验证
147+ const tdd = task . prCheckpoints . tddCompliance
148+ if ( tdd !== null && tdd . status === "fail" ) {
149+ prResult . checkpointResults [ "tddCompliance" ] = "fail"
150+ return {
151+ allowed : false ,
152+ reason : `TDD compliance failed: ${ tdd . summary } ` ,
153+ checkpointResults : prResult . checkpointResults ,
154+ }
155+ }
156+ // tdd === null → 无 TDD 要求,视为通过
157+ // tdd.status === "pass" / "waived" → 通过
158+ prResult . checkpointResults [ "tddCompliance" ] =
159+ tdd === null ? "skipped" : ( tdd . status as "pass" | "fail" | "pending" )
160+
161+ // 5. Branch Protection
162+ if ( ! hasBranchProtection ) {
113163 prResult . checkpointResults . branchProtection = "fail"
114164 return {
115165 allowed : false ,
@@ -122,6 +172,11 @@ export function canAutoMergeTask(flowRun: FlowRun, task: TaskState, protection:
122172 return prResult
123173}
124174
175+ // ─── PR 合并操作 ───
176+
177+ /**
178+ * 标准化合并 PR(无安全检查,向后兼容)。
179+ */
125180export async function mergePR ( prNumber : number ) : Promise < { success : boolean ; error ?: string } > {
126181 try {
127182 await gh ( `pr merge ${ prNumber } --squash --delete-branch` )
@@ -131,6 +186,30 @@ export async function mergePR(prNumber: number): Promise<{ success: boolean; err
131186 }
132187}
133188
189+ /**
190+ * Task-local PR 合并(带 --match-head-commit 安全检查)。
191+ *
192+ * 步骤:
193+ * 1. 验证 verifiedSha 非空
194+ * 2. 使用 gh pr merge --squash --delete-branch --match-head-commit <verifiedSha>
195+ * 3. 失败时返回错误
196+ */
197+ export async function mergeTaskPR ( prNumber : number , verifiedSha : string ) : Promise < { success : boolean ; error ?: string } > {
198+ if ( ! verifiedSha ) {
199+ return { success : false , error : "verifiedSha is required for merge --match-head-commit" }
200+ }
201+
202+ try {
203+ // --match-head-commit 确保只有 verified 的 commit 才会被合并
204+ await gh ( `pr merge ${ prNumber } --squash --delete-branch --match-head-commit ${ verifiedSha } ` )
205+ return { success : true }
206+ } catch ( err ) {
207+ return { success : false , error : String ( err ) }
208+ }
209+ }
210+
211+ // ─── Revert ───
212+
134213export async function createRevertPR ( prNumber : number , reason : string ) : Promise < { prNumber ?: number ; error ?: string } > {
135214 try {
136215 const { stdout } = await gh ( `pr view ${ prNumber } --json headRefName,headRepository,body,title` )
0 commit comments