// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. entry module for scripts/src/hwlab-g14.ts. // Moved mechanically from scripts/src/hwlab-g14.ts:10619-12000 for #903. import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { createHash, randomBytes } from "node:crypto"; import { repoRoot, rootPath, type Config } from "../config"; import { runCommand } from "../command"; import { cancelJob, listJobs, readJob, startJob } from "../jobs"; import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes"; import { runRuntimeLaneControlPlane, runV02ControlPlane } from "./control-plane"; import { runG14GitMirror } from "./git-mirror"; import { hwlabG14Help, monitorStatus } from "./help"; import { runG14ToolsImage, runG14UpstreamImage, startControlPlaneTriggerJob, startGitMirrorJob } from "./images"; import { runMonitorWorker } from "./monitor"; import { runG14Observability } from "./observability"; import { hwlabG14MonitorStateFileName, hwlabG14MonitorStateRole, parseControlPlaneOptions, parseGitMirrorOptions, parseLegacyRetirementOptions, parseObservabilityOptions, parseOptions, parseSecretOptions, parseToolsImageOptions, parseUpstreamImageOptions } from "./options"; import { monitorBaseBranch } from "./remote"; import { legacyG14RecordRolloutRetired, legacyG14RetirementBlocksMonitor, runLegacyG14Retirement } from "./retirement"; import { runG14Secret } from "./secrets"; import { G14_SOURCE_BRANCH, HWLAB_REPO } from "./types"; export async function runHwlabG14Command(_config: Config, args: string[]): Promise> { if (args.length === 0 || args.includes("--help") || args.includes("-h")) return { ok: true, ...hwlabG14Help() }; const [action] = args; if (action === "record-rollout") { return legacyG14RecordRolloutRetired(); } if (action === "control-plane") { const options = parseControlPlaneOptions(args.slice(1)); if (options.action === "trigger-current" && options.confirm && !options.dryRun && !options.wait) { return startControlPlaneTriggerJob(options); } if (isHwlabRuntimeLane(options.lane) && options.lane !== "v02") { return runRuntimeLaneControlPlane(hwlabRuntimeLaneSpec(options.lane), options); } return runV02ControlPlane(options); } if (action === "retirement") { const options = parseLegacyRetirementOptions(args.slice(1)); return runLegacyG14Retirement(options); } if (action === "secret") { const options = parseSecretOptions(args.slice(1)); return runG14Secret(options); } if (action === "tools-image") { const options = parseToolsImageOptions(args.slice(1)); return runG14ToolsImage(options); } if (action === "upstream-image") { const options = parseUpstreamImageOptions(args.slice(1)); return runG14UpstreamImage(options); } if (action === "git-mirror") { const options = parseGitMirrorOptions(args.slice(1)); if ((options.action === "sync" || options.action === "flush") && options.confirm && !options.dryRun && !options.wait) { return startGitMirrorJob(options); } return runG14GitMirror(options); } if (action === "observability") { const options = parseObservabilityOptions(args.slice(1)); return runG14Observability(options); } if (action !== "monitor-prs") { return { ok: false, command: `hwlab g14 ${action ?? ""}`.trim(), degradedReason: "unsupported-command", message: "supported commands: hwlab g14 monitor-prs, hwlab g14 retirement, hwlab g14 control-plane, hwlab g14 secret, hwlab g14 git-mirror, hwlab g14 observability, hwlab g14 tools-image, hwlab g14 upstream-image" }; } const options = parseOptions(args.slice(1)); if (options.worker) return runMonitorWorker(options); if (args.includes("--status")) return monitorStatus(options); const retirement = options.lane === "g14" ? legacyG14RetirementBlocksMonitor() : null; if (retirement !== null) { return { ok: false, command: "hwlab g14 monitor-prs", lane: options.lane, baseBranch: monitorBaseBranch(options.lane), degradedReason: "legacy-g14-dev-prod-retired", message: "Legacy G14 DEV/PROD has a retirement marker; start a runtime lane monitor with --lane v02/v03 or inspect retirement status.", retirement, next: { retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status", v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02", v03Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v03", }, }; } const command = ["bun", "scripts/cli.ts", "hwlab", "g14", "monitor-prs", "--worker", "--lane", options.lane, "--interval-seconds", String(options.intervalSeconds), "--timeout-seconds", String(options.timeoutSeconds), ...(options.once ? ["--once"] : []), ...(options.dryRun ? ["--dry-run"] : []), ...(options.maxCycles > 0 ? ["--max-cycles", String(options.maxCycles)] : [])]; const jobName = options.lane === "g14" ? "hwlab_g14_pr_monitor" : `hwlab_g14_${options.lane}_pr_monitor`; const jobNote = options.lane === "g14" ? `Monitor ${HWLAB_REPO} PRs targeting ${G14_SOURCE_BRANCH} and roll merged changes to G14 DEV` : `Monitor ${HWLAB_REPO} PRs targeting ${monitorBaseBranch(options.lane)}, merge ready PRs, trigger ${hwlabRuntimeLaneSpec(options.lane).version} CD, comment PR progress, and file failure issues`; const job = startJob(jobName, command, jobNote); const statusCommand = `bun scripts/cli.ts job status ${job.id} --tail-bytes 30000`; const stateDir = rootPath(".state", "hwlab-g14"); mkdirSync(stateDir, { recursive: true }); const stateFileName = hwlabG14MonitorStateFileName(options); const stateFileRole = hwlabG14MonitorStateRole(options); const latestPath = join(stateDir, stateFileName); const previousLatest = existsSync(latestPath) ? readFileSync(latestPath, "utf8") : null; writeFileSync(latestPath, `${JSON.stringify({ jobId: job.id, createdAt: job.createdAt, statusCommand, role: stateFileRole, lane: options.lane, baseBranch: monitorBaseBranch(options.lane) }, null, 2)}\n`, "utf8"); return { ok: true, command: "hwlab g14 monitor-prs", lane: options.lane, baseBranch: monitorBaseBranch(options.lane), mode: "async-job", job, statusCommand, latestPath, stateFileName, stateFileRole, previousLatest, next: { status: statusCommand, tail: `tail -f ${job.stdoutFile}`, }, }; }