From be380a53caad16a61186b2571c54d5a8e40eed30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Sun, 19 Apr 2026 03:33:52 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20promoter=20role=20=E2=80=94=20code=5Fre?= =?UTF-8?q?v=20from=20engine=20git=20commit=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/workflows/meta.ts | 14 +++-- src/workflows/roles/meta-promoter.ts | 80 ++++++++++++++++++++++++++++ src/workflows/roles/meta-tester.ts | 56 +------------------ 3 files changed, 92 insertions(+), 58 deletions(-) create mode 100644 src/workflows/roles/meta-promoter.ts diff --git a/src/workflows/meta.ts b/src/workflows/meta.ts index 8bb567d..283e374 100644 --- a/src/workflows/meta.ts +++ b/src/workflows/meta.ts @@ -29,6 +29,7 @@ import { } from '@uncaged/pulse'; import { createMetaGateRole, type GateMeta } from './roles/meta-gate.js'; +import { type MetaPromoterMeta } from './roles/meta-promoter.js'; // ── Meta Types ───────────────────────────────────────────────── @@ -49,9 +50,6 @@ export interface MetaTesterMeta { [key: string]: unknown; pass: boolean; reason: string; - /** Only present when pass=true */ - commitHash?: string; - pushed?: boolean; } export type MetaWorkflowRoles = { @@ -59,6 +57,7 @@ export type MetaWorkflowRoles = { coder: Role; checker: Role; tester: Role; + promoter: Role; }; // ── Moderator ────────────────────────────────────────────────── @@ -82,8 +81,10 @@ function metaModerator( } case 'tester': { const meta = input.meta as MetaTesterMeta | null; - return meta?.pass ? END : 'coder'; + return meta?.pass ? 'promoter' : 'coder'; } + case 'promoter': + return END; default: return END; } @@ -103,12 +104,17 @@ function createDefaultMetaRoles(engineDir: string): MetaWorkflowRoles { content: 'e2e stub', meta: { pass: true, reason: 'e2e stub' }, }); + const stubPromoter: Role = async () => ({ + content: 'promote stub', + meta: { commitHash: 'stub', pushed: false, codeRev: 'stub' }, + }); return { gate: createMetaGateRole({ engineDir }), coder: stubCoder, checker: stubChecker, tester: stubTester, + promoter: stubPromoter, }; } diff --git a/src/workflows/roles/meta-promoter.ts b/src/workflows/roles/meta-promoter.ts new file mode 100644 index 0000000..27688fa --- /dev/null +++ b/src/workflows/roles/meta-promoter.ts @@ -0,0 +1,80 @@ +/** + * Meta Promoter role — commit, push, and emit promote event. + * code_rev = engine repo git commit hash. + * + * 小橘 🍊 (NEKO Team) + */ + +import { execSync } from 'node:child_process'; +import type { Role, RoleResult, WorkflowMessage } from '@uncaged/pulse'; + +export interface MetaPromoterMeta { + [key: string]: unknown; + commitHash: string; + pushed: boolean; + codeRev: string; +} + +export function createMetaPromoterRole(opts: { + repoDir: string; + remote?: string; + branch?: string; +}): Role { + const branch = opts.branch ?? 'main'; + + return async (chain: WorkflowMessage[]): Promise> => { + const cwd = opts.repoDir; + const exec = (cmd: string) => + execSync(cmd, { cwd, encoding: 'utf-8', timeout: 30_000 }).trim(); + + // Get commit message from __start__ + const startMsg = chain.find((m) => m.role === '__start__'); + const firstLine = (startMsg?.content ?? '').split('\n')[0].slice(0, 60); + const commitMsg = firstLine || 'meta workflow auto-commit'; + + // Stage all changes + exec('git add -A'); + + let commitHash: string; + + try { + exec('git diff --cached --quiet'); + // No changes to commit + commitHash = exec('git rev-parse --short HEAD'); + } catch { + // Has staged changes — commit + exec(`git commit -m "${commitMsg}" --author="小橘 "`); + commitHash = exec('git rev-parse --short HEAD'); + } + + // Full hash for code_rev + const fullHash = exec('git rev-parse HEAD'); + + // Push + let pushed = false; + const remote = opts.remote ?? (() => { + try { + const remotes = exec('git remote').split('\n').filter(Boolean); + return remotes[0] || null; + } catch { return null; } + })(); + + if (remote) { + try { + exec(`git push ${remote} ${branch} --no-verify`); + pushed = true; + } catch { + pushed = false; + } + } + + return { + content: `promote: ${commitHash} (pushed: ${pushed})`, + meta: { + commitHash, + pushed, + codeRev: fullHash, + }, + }; + }; +} diff --git a/src/workflows/roles/meta-tester.ts b/src/workflows/roles/meta-tester.ts index d44125f..206fa34 100644 --- a/src/workflows/roles/meta-tester.ts +++ b/src/workflows/roles/meta-tester.ts @@ -8,7 +8,6 @@ * 小橘 🍊 (NEKO Team) */ -import { execSync } from 'node:child_process'; import { existsSync, mkdtempSync, readdirSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; @@ -186,60 +185,9 @@ export function createMetaTesterRole(opts: { }; } - // Step 4: All pass — commit + push - let commitHash: string | undefined; - let pushed: boolean | undefined; - - const exec = (cmd: string) => - execSync(cmd, { cwd, encoding: 'utf-8', timeout: 30_000 }).trim(); - - try { - const startMsg = chain.find((m) => m.role === '__start__'); - const firstLine = (startMsg?.content ?? '').split('\n')[0].slice(0, 60); - const commitMsg = firstLine || 'meta workflow auto-commit'; - - exec('git add -A'); - - // Check if there's anything to commit - try { - exec('git diff --cached --quiet'); - // No changes — still pass, just no commit needed - commitHash = exec('git rev-parse --short HEAD'); - pushed = false; - } catch { - // There are staged changes - exec( - `git commit -m "${commitMsg}" --author="小橘 "`, - ); - commitHash = exec('git rev-parse --short HEAD'); - - // Auto-detect remote - const remote = opts.remote ?? (() => { - try { - const remotes = exec('git remote').split('\n').filter(Boolean); - return remotes[0] || null; - } catch { return null; } - })(); - - if (remote) { - try { - exec(`git push ${remote} ${branch} --no-verify`); - pushed = true; - } catch { - pushed = false; - } - } else { - pushed = false; - } - } - } catch (err: any) { - commitHash = undefined; - pushed = false; - } - return { - content: `e2e 验证通过\n\n${summary}\n\nCommit: ${commitHash ?? 'none'}\nPushed: ${pushed ? 'yes' : 'no'}`, - meta: { pass: true, reason: 'e2e verification passed', commitHash, pushed }, + content: `e2e 验证通过\n\n${summary}`, + meta: { pass: true, reason: 'e2e verification passed' }, }; }; }