// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. pr-monitor module for scripts/src/hwlab-legacy-runtime.ts.

// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:8387-9507 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 type { CiPipelineMetrics, CiServiceMetric, CommandJsonResult, LegacyRuntimeRecordRolloutOptions, OpenPullRequest, DefaultRuntimeLanePrCommentInput } from "./types";
import { cliJson, commandData, commandJson, isCommandSuccess, nested, printEvent, record, shortSha, sleep, stringOrNull, textHash } from "./remote";
import { getDefaultRuntimeLaneHead } from "./source";
import { ARGO_NAMESPACE, BEIJING_OFFSET_MS, CI_NAMESPACE, DEV_APP, DEV_NAMESPACE, LEGACY_RUNTIME_BRIEF_INDEX_ISSUE, LEGACY_RUNTIME_PROVIDER, LEGACY_RUNTIME_WORKSPACE, HWLAB_REPO, DEFAULT_RUNTIME_LANE_SOURCE_BRANCH } from "./types";

export function listOpenLegacyRuntimePullRequests(): CommandJsonResult {
  return cliJson(["gh", "pr", "list", "--repo", HWLAB_REPO, "--state", "open", "--limit", "30", "--json", "number,title,state,url,head,base,draft,headRefName,baseRefName"], 60_000);
}

export function preflightPullRequest(number: number): CommandJsonResult {
  return cliJson(["gh", "pr", "preflight", String(number), "--repo", HWLAB_REPO], 80_000);
}

export function mergePullRequest(number: number, dryRun: boolean): CommandJsonResult {
  return cliJson(["gh", "pr", "merge", String(number), "--repo", HWLAB_REPO, "--merge", "--delete-branch", ...(dryRun ? ["--dry-run"] : [])], 100_000);
}

export function getLegacyRuntimeHead(): string | null {
  const result = cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:${LEGACY_RUNTIME_WORKSPACE}`, "sh", "--", "git fetch origin G14 --prune >/dev/null 2>&1; git rev-parse origin/G14"], 120_000);
  if (!isCommandSuccess(result)) return null;
  const output = String(nested(result.parsed, ["data", "stdout"]) ?? result.stdout).trim();
  const match = /[0-9a-f]{40}/iu.exec(output);
  return match?.[0] ?? null;
}

export function refreshArgoDev(): void {
  cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, "kubectl", "annotate", "application", "-n", ARGO_NAMESPACE, DEV_APP, "argocd.argoproj.io/refresh=hard", "--overwrite"], 60_000);
}

export function getPipelineStatus(sourceCommit: string): CommandJsonResult {
  return cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, "kubectl", "get", "pipelinerun", "-n", CI_NAMESPACE, `hwlab-g14-ci-poll-${shortSha(sourceCommit)}`, "-o", "jsonpath={.status.conditions[0].status}{\"\\n\"}{.status.conditions[0].reason}{\"\\n\"}{.status.conditions[0].message}{\"\\n\"}"], 60_000);
}

export function getArgoStatus(): CommandJsonResult {
  return cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, "kubectl", "get", "application", "-n", ARGO_NAMESPACE, DEV_APP, "-o", "jsonpath={.status.sync.revision}{\"\\n\"}{.status.sync.status}{\"\\n\"}{.status.health.status}{\"\\n\"}{.status.operationState.phase}{\"\\n\"}{.status.operationState.syncResult.revision}{\"\\n\"}"], 60_000);
}

export function getDevWorkloads(): CommandJsonResult {
  return cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, "kubectl", "get", "deploy,statefulset,pod", "-n", DEV_NAMESPACE, "-o", "wide"], 60_000);
}

export function getLiveHealth(): CommandJsonResult {
  return commandJson(["curl", "-fsS", "--max-time", "20", "http://74.48.78.17:17667/health/live"], 30_000);
}

export function statusText(result: CommandJsonResult): string {
  return String(nested(result.parsed, ["data", "stdout"]) ?? result.stdout).trim();
}

export function workloadReadiness(workloadsText: string, commandOk: boolean): Record<string, unknown> {
  const blockers: string[] = [];
  const ignoredPods: string[] = [];
  const readyWorkloads: string[] = [];
  const allowedZeroDeployments = new Set(["deployment.apps/hwlab-gateway", "deployment.apps/hwlab-tunnel-client"]);
  for (const line of workloadsText.split(/\r?\n/u)) {
    const trimmed = line.trim();
    if (trimmed.length === 0 || trimmed.startsWith("NAME ")) continue;
    const fields = trimmed.split(/\s+/u);
    const name = fields[0] ?? "";
    const ready = fields[1] ?? "";
    if (name.startsWith("deployment.apps/") || name.startsWith("statefulset.apps/")) {
      if (ready === "0/0" && allowedZeroDeployments.has(name)) {
        readyWorkloads.push(`${name}:${ready}:scaled-zero`);
        continue;
      }
      const match = /^(\d+)\/(\d+)$/u.exec(ready);
      if (match === null) {
        blockers.push(`${name}:unparseable-ready:${ready}`);
        continue;
      }
      const readyCount = Number(match[1]);
      const desiredCount = Number(match[2]);
      if (desiredCount > 0 && readyCount < desiredCount) blockers.push(`${name}:not-ready:${ready}`);
      else readyWorkloads.push(`${name}:${ready}`);
      continue;
    }
    if (name.startsWith("pod/")) {
      const status = fields[2] ?? "";
      if (status === "Completed" || status === "Succeeded") ignoredPods.push(`${name}:${status}`);
      if (/^(CrashLoopBackOff|ImagePullBackOff|ErrImagePull|CreateContainerConfigError|CreateContainerError|RunContainerError)$/u.test(status)) {
        blockers.push(`${name}:pod-status:${status}`);
      }
    }
  }
  return {
    ready: commandOk && blockers.length === 0,
    blockers,
    readyWorkloads: readyWorkloads.slice(0, 20),
    ignoredPods: ignoredPods.slice(0, 20),
  };
}

export function beijingParts(date = new Date()): { date: string; time: string; iso: string } {
  const shifted = new Date(date.getTime() + BEIJING_OFFSET_MS).toISOString();
  return { date: shifted.slice(0, 10), time: shifted.slice(11, 19), iso: shifted };
}

export function durationSeconds(start?: string | null, end?: string | null): number | null {
  if (start === undefined || start === null || end === undefined || end === null) return null;
  const startMs = Date.parse(start);
  const endMs = Date.parse(end);
  if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs) return null;
  return Math.round((endMs - startMs) / 1000);
}

export function formatDuration(seconds: number | null): string {
  if (seconds === null) return "n/a";
  const minutes = Math.floor(seconds / 60);
  const rest = seconds % 60;
  if (minutes === 0) return `${rest}s`;
  return `${minutes}m${String(rest).padStart(2, "0")}s`;
}

export function pipelineTaskRunResultsJsonPath(): string {
  return [
    "jsonpath=",
    "{range .items[*]}",
    "{.metadata.name}{\"\\t\"}",
    "{.metadata.labels.tekton\\.dev/pipelineTask}{\"\\t\"}",
    "{.status.startTime}{\"\\t\"}",
    "{.status.completionTime}{\"\\t\"}",
    "{range .status.results[*]}{.name}{\"=\"}{.value}{\";\"}{end}",
    "{\"\\n\"}",
    "{end}",
  ].join("");
}

export function getPipelineTaskRunResults(pipelineRun: string): CommandJsonResult {
  return cliJson([
    "ssh",
    `${LEGACY_RUNTIME_PROVIDER}:k3s`,
    "kubectl",
    "get",
    "taskrun",
    "-n",
    CI_NAMESPACE,
    "-l",
    `tekton.dev/pipelineRun=${pipelineRun}`,
    "-o",
    pipelineTaskRunResultsJsonPath(),
  ], 80_000);
}

export function parseResultMap(raw: string): Record<string, string> {
  const values: Record<string, string> = {};
  for (const item of raw.split(";")) {
    if (item.length === 0) continue;
    const separator = item.indexOf("=");
    if (separator <= 0) continue;
    values[item.slice(0, separator)] = item.slice(separator + 1);
  }
  return values;
}

export function serviceMetricSort(left: CiServiceMetric, right: CiServiceMetric): number {
  return left.serviceId.localeCompare(right.serviceId);
}

export function parsePipelineTaskRunMetrics(pipelineRun: string, text: string): CiPipelineMetrics {
  const services: CiServiceMetric[] = [];
  for (const line of text.split(/\r?\n/u)) {
    const trimmed = line.trimEnd();
    if (trimmed.length === 0) continue;
    const [taskRunName = "", pipelineTask = "", startTime = "", completionTime = "", resultsRaw = ""] = trimmed.split("\t");
    if (!pipelineTask.startsWith("build-")) continue;
    const results = parseResultMap(resultsRaw);
    const serviceId = results["service-id"] ?? pipelineTask.replace(/^build-/u, "");
    if (serviceId.length === 0) continue;
    services.push({
      taskRun: taskRunName,
      serviceId,
      status: results.status ?? "unknown",
      durationSeconds: durationSeconds(startTime, completionTime),
      buildBackend: results["build-backend"] ?? null,
      reusedFrom: results["reused-from"] ?? null,
      imageTag: results["image-tag"] ?? null,
      sourceCommitId: results["source-commit-id"] ?? null,
    });
  }
  services.sort(serviceMetricSort);
  const reusedServices = services.filter((service) => service.status === "reused").map((service) => service.serviceId);
  const rebuildServices = services.filter((service) => service.status !== "reused").map((service) => service.serviceId);
  return {
    ok: services.length > 0,
    pipelineRun,
    serviceTotal: services.length,
    reusedCount: reusedServices.length,
    rebuildCount: rebuildServices.length,
    reusedServices,
    rebuildServices,
    services,
    ...(services.length === 0 ? { degradedReason: "no-build-task-results", rawText: text.slice(0, 2000) } : {}),
  };
}

export function ciMetricsFromValue(value: unknown): CiPipelineMetrics | null {
  const data = record(value);
  if (data.ok !== true || typeof data.pipelineRun !== "string") return null;
  const services = Array.isArray(data.services) ? data.services.map((item) => {
    const service = record(item);
    return {
      taskRun: String(service.taskRun ?? ""),
      serviceId: String(service.serviceId ?? ""),
      status: String(service.status ?? "unknown"),
      durationSeconds: typeof service.durationSeconds === "number" ? service.durationSeconds : null,
      buildBackend: typeof service.buildBackend === "string" ? service.buildBackend : null,
      reusedFrom: typeof service.reusedFrom === "string" ? service.reusedFrom : null,
      imageTag: typeof service.imageTag === "string" ? service.imageTag : null,
      sourceCommitId: typeof service.sourceCommitId === "string" ? service.sourceCommitId : null,
    };
  }).filter((service) => service.serviceId.length > 0).sort(serviceMetricSort) : [];
  const reusedServices = Array.isArray(data.reusedServices) ? data.reusedServices.map(String) : services.filter((service) => service.status === "reused").map((service) => service.serviceId);
  const rebuildServices = Array.isArray(data.rebuildServices) ? data.rebuildServices.map(String) : services.filter((service) => service.status !== "reused").map((service) => service.serviceId);
  return {
    ok: true,
    pipelineRun: data.pipelineRun,
    serviceTotal: typeof data.serviceTotal === "number" ? data.serviceTotal : services.length,
    reusedCount: typeof data.reusedCount === "number" ? data.reusedCount : reusedServices.length,
    rebuildCount: typeof data.rebuildCount === "number" ? data.rebuildCount : rebuildServices.length,
    reusedServices,
    rebuildServices,
    services,
  };
}

export function collectPipelineMetrics(pipelineRun: string): CiPipelineMetrics {
  const result = getPipelineTaskRunResults(pipelineRun);
  if (!isCommandSuccess(result)) {
    return {
      ok: false,
      pipelineRun,
      serviceTotal: 0,
      reusedCount: 0,
      rebuildCount: 0,
      reusedServices: [],
      rebuildServices: [],
      services: [],
      degradedReason: "taskrun-query-failed",
      rawText: `${result.stderr}\n${result.stdout}`.trim().slice(0, 2000),
    };
  }
  return parsePipelineTaskRunMetrics(pipelineRun, statusText(result));
}

export function ciMetricLines(metrics: CiPipelineMetrics | null): string[] {
  if (metrics === null) {
    return [
      "- CI/CD 关键指标:",
      "  - lazy build reused: n/a",
      "  - reused services: n/a",
      "  - rebuild services: n/a",
      "  - service durations: n/a",
    ];
  }
  if (metrics.ok !== true) {
    return [
      "- CI/CD 关键指标:",
      `  - lazy build reused: unavailable (${metrics.degradedReason ?? "unknown"})`,
      "  - reused services: n/a",
      "  - rebuild services: n/a",
      "  - service durations: n/a",
    ];
  }
  const lines = [
    "- CI/CD 关键指标:",
    `  - lazy build reused: ${metrics.reusedCount}/${metrics.serviceTotal}`,
    `  - reused services: ${metrics.reusedServices.length > 0 ? metrics.reusedServices.join(", ") : "none"}`,
    `  - rebuild services: ${metrics.rebuildServices.length > 0 ? metrics.rebuildServices.join(", ") : "none"}`,
    "  - service durations:",
  ];
  for (const service of metrics.services) {
    const backend = service.buildBackend === null ? "" : `, backend=${service.buildBackend}`;
    lines.push(`    - ${service.serviceId}: ${service.status}, ${formatDuration(service.durationSeconds)}${backend}`);
  }
  return lines;
}

export function stripMarkdownInline(value: string): string {
  return value
    .replace(/\[([^\]]+)\]\([^)]+\)/gu, "$1")
    .replace(/\*\*([^*]+)\*\*/gu, "$1")
    .replace(/\*([^*]+)\*/gu, "$1")
    .replace(/__([^_]+)__/gu, "$1")
    .replace(/_([^_]+)_/gu, "$1")
    .replace(/\s+/gu, " ")
    .trim();
}

export function markdownSection(body: string, headings: string[]): string {
  const normalizedHeadings = new Set(headings.map((heading) => heading.toLowerCase()));
  const lines = body.split(/\r?\n/u);
  let capturing = false;
  const captured: string[] = [];
  for (const line of lines) {
    const heading = /^(#{1,6})\s+(.+?)\s*$/u.exec(line);
    if (heading !== null) {
      const title = stripMarkdownInline(heading[2] ?? "").replace(/[：:]\s*$/u, "").toLowerCase();
      if (capturing) break;
      capturing = normalizedHeadings.has(title);
      continue;
    }
    if (capturing) captured.push(line);
  }
  return captured.join("\n").trim();
}

export function firstParagraph(section: string): string | null {
  for (const paragraph of section.split(/\n\s*\n/u)) {
    const trimmed = stripMarkdownInline(paragraph.replace(/^[-*]\s+/gmu, ""));
    if (trimmed.length > 0) return trimmed;
  }
  return null;
}

export function topLevelBullets(section: string, limit = 8): string[] {
  const bullets: string[] = [];
  for (const line of section.split(/\r?\n/u)) {
    const match = /^[-*]\s+(.+)$/u.exec(line);
    if (match === null) continue;
    const text = stripMarkdownInline(match[1] ?? "");
    if (text.length > 0) bullets.push(text);
    if (bullets.length >= limit) break;
  }
  return bullets;
}

export function conventionalTitleSummary(title: string): string {
  const match = /^(feat|fix|docs|test|refactor|perf|ci|build|chore)(?:\([^)]+\))?:\s*(.+)$/iu.exec(title.trim());
  if (match === null) return stripMarkdownInline(title);
  const kind = String(match[1] ?? "").toLowerCase();
  const subject = stripMarkdownInline(match[2] ?? title);
  const labels: Record<string, string> = {
    feat: "功能",
    fix: "修复",
    docs: "文档",
    test: "测试",
    refactor: "重构",
    perf: "性能",
    ci: "CI/CD",
    build: "构建",
    chore: "维护",
  };
  return `${labels[kind] ?? "变更"}：${subject}`;
}

export function fileHeuristicBullets(fileSummary: Record<string, unknown>): string[] {
  const keyFiles = Array.isArray(fileSummary.keyFiles) ? fileSummary.keyFiles.map(String) : [];
  const bullets: string[] = [];
  if (keyFiles.some((file) => file.includes("web/hwlab-cloud-web/"))) {
    bullets.push("前端：更新 HWLAB Cloud Web / Workbench 交互行为。");
  }
  if (keyFiles.some((file) => /\.(test|spec)\./u.test(file) || file.includes("/test"))) {
    bullets.push("验证：补充或更新回归测试，覆盖本次变更路径。");
  }
  if (keyFiles.some((file) => file.includes("deploy/") || file.includes("k8s") || file.includes("gitops"))) {
    bullets.push("部署：更新 G14 Kubernetes/GitOps 发布声明或发布计划。");
  }
  if (keyFiles.some((file) => file.includes("internal/cloud/") || file.includes("code-agent"))) {
    bullets.push("运行时：调整 Cloud API / Code Agent 运行链路。");
  }
  return bullets;
}

export function semanticChangelogBullets(title: string, body: string, fileSummary: Record<string, unknown>): string[] {
  const bullets: string[] = [];
  const background = firstParagraph(markdownSection(body, ["背景", "问题", "problem", "background", "context"]));
  if (background !== null && /^(fix|perf)(?:\([^)]+\))?:/iu.test(title)) {
    bullets.push(`修复目标：${background}`);
  }
  const changeSection = markdownSection(body, ["上线 changelog", "changelog", "摘要", "概述", "总结", "修改", "变更", "改动", "changes", "what changed", "summary"]);
  bullets.push(...topLevelBullets(changeSection));
  if (bullets.length === 0) bullets.push(conventionalTitleSummary(title));
  for (const bullet of fileHeuristicBullets(fileSummary)) {
    if (bullets.length >= 8) break;
    if (!bullets.includes(bullet)) bullets.push(bullet);
  }
  return bullets.slice(0, 8);
}

export function readIssue(issueNumber: number): CommandJsonResult {
  return cliJson(["gh", "issue", "read", String(issueNumber), "--repo", HWLAB_REPO, "--json", "title,body,state,updatedAt", "--raw"], 80_000);
}

export function issueFromRead(result: CommandJsonResult): Record<string, unknown> {
  return record(commandData(result).issue);
}

export function parseBriefIssueFromIndex(indexBody: string, date: string): { number: number; url: string } | null {
  const escapedDate = date.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
  const linePattern = new RegExp(`^\\|\\s*${escapedDate}\\s*\\|([^\\n]+)$`, "mu");
  const line = linePattern.exec(indexBody)?.[0];
  if (line === undefined) return null;
  const match = /\[#(\d+)\]\((https:\/\/github\.com\/pikasTech\/HWLAB\/issues\/\d+)\)/u.exec(line);
  if (match === null) return null;
  return { number: Number(match[1]), url: match[2] };
}

export function dailyBriefTitle(date: string): string {
  return `${date} 指挥简报（北京时间）`;
}

export function writeStateFile(name: string, content: string): string {
  const dir = rootPath(".state", "hwlab-g14");
  mkdirSync(dir, { recursive: true });
  const path = join(dir, name);
  writeFileSync(path, content, "utf8");
  return path;
}

export function listIssues(): CommandJsonResult {
  return cliJson(["gh", "issue", "list", "--repo", HWLAB_REPO, "--state", "open", "--limit", "100", "--json", "number,title,state,url"], 80_000);
}

export function findBriefIssueInList(date: string): { number: number; url: string } | null {
  const listed = listIssues();
  if (!isCommandSuccess(listed)) return null;
  const issues = commandData(listed).issues;
  if (!Array.isArray(issues)) return null;
  const title = dailyBriefTitle(date);
  const match = issues.map((item) => record(item)).find((issue) => issue.title === title);
  if (match === undefined) return null;
  return { number: Number(match.number), url: String(match.url) };
}

export function newDailyBriefBody(date: string): string {
  return [
    `# ${dailyBriefTitle(date)}`,
    "",
    "## 当日更新",
    "",
    "## 常驻观察与长期建议",
    "",
    "- Legacy G14 DEV rollout 已退役；当前 HWLAB runtime 证据必须来自 active runtime lane。",
    "- GitHub issue/PR 写操作必须走 UniDesk `gh` 子命令；G14 k3s 操作必须走 UniDesk `trans G14:k3s`。",
  ].join("\n");
}

export function insertBriefIndexRow(indexBody: string, date: string, brief: { number: number; url: string }): string {
  if (parseBriefIssueFromIndex(indexBody, date) !== null) return indexBody;
  const row = `| ${date} | [#${brief.number}](${brief.url}) | legacy G14 DEV rollout 自动简报入口已退役；当前证据写入 active runtime lane closeout。 | 使用 active runtime lane 的受控 CI/CD 入口和 issue 评论记录。 |`;
  const marker = "### 指挥简报索引";
  const markerIndex = indexBody.indexOf(marker);
  if (markerIndex === -1) return `${indexBody.trimEnd()}\n\n${marker}\n\n| 日期（北京时间） | 指挥简报 issue | 当日推进 | 下一步计划 |\n| --- | --- | --- | --- |\n${row}\n`;
  const afterMarker = indexBody.slice(markerIndex);
  const separator = "| --- | --- | --- | --- |";
  const separatorInBody = afterMarker.indexOf(separator);
  if (separatorInBody === -1) return `${indexBody.slice(0, markerIndex + marker.length)}\n\n| 日期（北京时间） | 指挥简报 issue | 当日推进 | 下一步计划 |\n| --- | --- | --- | --- |\n${row}\n${indexBody.slice(markerIndex + marker.length)}`;
  const insertAt = markerIndex + separatorInBody + separator.length;
  return `${indexBody.slice(0, insertAt)}\n${row}${indexBody.slice(insertAt)}`;
}

export function ensureDailyBriefIssue(date: string, dryRun: boolean): Record<string, unknown> {
  const indexRead = readIssue(LEGACY_RUNTIME_BRIEF_INDEX_ISSUE);
  if (!isCommandSuccess(indexRead)) return { ok: false, phase: "read-index", indexRead };
  const indexIssue = issueFromRead(indexRead);
  const indexBody = String(indexIssue.body ?? "");
  let brief = parseBriefIssueFromIndex(indexBody, date) ?? findBriefIssueInList(date);
  const actions: Record<string, unknown>[] = [];
  if (brief === null) {
    const bodyPath = writeStateFile(`daily-brief-${date}.md`, `${newDailyBriefBody(date)}\n`);
    if (dryRun) {
      brief = { number: 0, url: `https://github.com/pikasTech/HWLAB/issues/new?title=${encodeURIComponent(dailyBriefTitle(date))}` };
      actions.push({ action: "would-create-daily-brief", title: dailyBriefTitle(date), bodyPath });
    } else {
      const created = cliJson(["gh", "issue", "create", "--repo", HWLAB_REPO, "--title", dailyBriefTitle(date), "--body-file", bodyPath], 80_000);
      if (!isCommandSuccess(created)) return { ok: false, phase: "create-daily-brief", created };
      const createdIssue = record(commandData(created).issue);
      brief = { number: Number(createdIssue.number), url: String(createdIssue.url) };
      actions.push({ action: "created-daily-brief", issue: brief, bodyPath });
    }
  }
  if (brief.number > 0 && parseBriefIssueFromIndex(indexBody, date) === null) {
    const nextBody = insertBriefIndexRow(indexBody, date, brief);
    const bodyPath = writeStateFile(`issue-${LEGACY_RUNTIME_BRIEF_INDEX_ISSUE}-brief-index-${date}.md`, `${nextBody.trimEnd()}\n`);
    if (dryRun) {
      actions.push({ action: "would-update-brief-index", issue: LEGACY_RUNTIME_BRIEF_INDEX_ISSUE, bodyPath });
    } else {
      const bodySha = String(indexIssue.bodySha ?? "");
      const update = cliJson(["gh", "issue", "update", String(LEGACY_RUNTIME_BRIEF_INDEX_ISSUE), "--repo", HWLAB_REPO, "--mode", "replace", "--body-file", bodyPath, ...(bodySha.length > 0 ? ["--expect-body-sha", bodySha] : [])], 100_000);
      if (!isCommandSuccess(update)) return { ok: false, phase: "update-brief-index", update, bodyPath };
      actions.push({ action: "updated-brief-index", issue: LEGACY_RUNTIME_BRIEF_INDEX_ISSUE, bodyPath });
    }
  }
  return { ok: true, date, brief, actions };
}

export function readPullRequest(number: number): CommandJsonResult {
  return cliJson(["gh", "pr", "read", String(number), "--repo", HWLAB_REPO, "--json", "title,body,url,stateDetail,merged,mergedAt,mergeCommit,headRefName,baseRefName"], 80_000);
}

export function readPullRequestFiles(number: number): CommandJsonResult {
  return cliJson(["gh", "pr", "files", String(number), "--repo", HWLAB_REPO, "--limit", "30"], 80_000);
}

export function summarizePrFiles(filesResult: CommandJsonResult): Record<string, unknown> {
  if (!isCommandSuccess(filesResult)) return { ok: false, filesResult };
  const data = commandData(filesResult);
  const files = Array.isArray(data.files) ? data.files.map((item) => record(item)) : [];
  return {
    ok: true,
    summary: data.summary ?? null,
    keyFiles: files.slice(0, 8).map((file) => `${file.status ?? "changed"} ${file.filename ?? ""}`),
  };
}

export function mergeCommitFromPr(prData: Record<string, unknown>): string | null {
  const mergeCommit = record(record(prData.json).mergeCommit);
  const fromJson = mergeCommit.oid ?? mergeCommit.sha;
  if (typeof fromJson === "string") return fromJson;
  const fromSummary = record(record(prData.pullRequest).mergeCommit).oid ?? record(record(prData.pullRequest).mergeCommit).sha;
  return typeof fromSummary === "string" ? fromSummary : null;
}

export function sourceCommitFromMergeResult(merge: CommandJsonResult, prNumber: number): string | null {
  const fromMerge = mergeCommitFromPr(commandData(merge));
  if (fromMerge !== null) return fromMerge;
  const prRead = readPullRequest(prNumber);
  if (!isCommandSuccess(prRead)) return null;
  return mergeCommitFromPr(commandData(prRead));
}

export async function waitForDefaultRuntimeLaneHead(expectedCommit: string | null, timeoutSeconds: number): Promise<Record<string, unknown>> {
  const started = Date.now();
  let head: string | null = null;
  while (Date.now() - started < timeoutSeconds * 1000) {
    head = getDefaultRuntimeLaneHead();
    if (expectedCommit === null) {
      if (head !== null) return { ok: true, sourceCommit: head, expectedCommit, matched: true, waitedSeconds: Math.round((Date.now() - started) / 1000) };
    } else if (head === expectedCommit) {
      return { ok: true, sourceCommit: head, expectedCommit, matched: true, waitedSeconds: Math.round((Date.now() - started) / 1000) };
    }
    printEvent("v02.source.wait", { expectedCommit, head, waitedSeconds: Math.round((Date.now() - started) / 1000) });
    await sleep(5_000);
  }
  return { ok: false, sourceCommit: head, expectedCommit, matched: false, waitedSeconds: Math.round((Date.now() - started) / 1000), degradedReason: "v02-source-head-not-aligned-after-merge" };
}

export function currentGitopsRevision(): string | null {
  const argo = getArgoStatus();
  if (!isCommandSuccess(argo)) return null;
  return statusText(argo).split(/\r?\n/u)[0] ?? null;
}

export function rolloutRecordBody(input: {
  pr: OpenPullRequest;
  prData: Record<string, unknown>;
  fileSummary: Record<string, unknown>;
  sourceCommit: string;
  pipelineRun: string;
  gitopsRevision: string | null;
  mergedAt: string | null;
  pipelineSucceededAt: string | null;
  finishedAt: string | null;
  rollout: Record<string, unknown>;
  ciMetrics?: CiPipelineMetrics | null;
}): string {
  const now = beijingParts();
  const title = String(record(input.prData.json).title ?? input.pr.title ?? `PR #${input.pr.number}`);
  const prBody = String(record(input.prData.json).body ?? record(input.prData.pullRequest).body ?? "");
  const url = String(record(input.prData.json).url ?? input.pr.url ?? `https://github.com/pikasTech/HWLAB/pull/${input.pr.number}`);
  const summary = record(input.fileSummary.summary);
  const keyFiles = Array.isArray(input.fileSummary.keyFiles) ? input.fileSummary.keyFiles.map(String) : [];
  const semanticBullets = semanticChangelogBullets(title, prBody, input.fileSummary);
  const mergeToPipeline = durationSeconds(input.mergedAt, input.pipelineSucceededAt);
  const pipelineToDev = durationSeconds(input.pipelineSucceededAt, input.finishedAt);
  const mergeToDev = durationSeconds(input.mergedAt, input.finishedAt);
  const metrics = input.ciMetrics ?? ciMetricsFromValue(input.rollout.ciMetrics);
  return [
    "",
    "",
    `## 更新 ${now.date} ${now.time.slice(0, 5)} 北京时间`,
    "",
    `### Retired legacy G14 DEV rollout record：PR #${input.pr.number}`,
    "",
    `- PR: [#${input.pr.number} ${title}](${url})`,
    `- 合并 commit: \`${input.sourceCommit}\``,
    `- GitOps revision: \`${input.gitopsRevision ?? "unknown"}\``,
    `- PipelineRun: \`${input.pipelineRun}\``,
    "- CI/CD 耗时:",
    `  - merge -> pipeline succeeded: ${formatDuration(mergeToPipeline)}`,
    `  - pipeline succeeded -> DEV Healthy: ${formatDuration(pipelineToDev)}`,
    `  - merge -> DEV Healthy: ${formatDuration(mergeToDev)}`,
    ...ciMetricLines(metrics),
    "- 上线 changelog（语义化）:",
    ...semanticBullets.map((bullet) => `  - ${bullet}`),
    "- 自动 diff 摘要:",
    `  - changed files: ${String(summary.files ?? "n/a")}; +${String(summary.additions ?? "n/a")} / -${String(summary.deletions ?? "n/a")}; commits: ${String(summary.commits ?? "n/a")}`,
    ...keyFiles.map((file) => `  - ${file}`),
    "- DEV 验证:",
    `  - Tekton: ${String(input.rollout.pipelineText ?? "Succeeded").split(/\r?\n/u).join(" / ")}`,
    `  - Argo: ${String(input.rollout.argoText ?? "Synced / Healthy").split(/\r?\n/u).slice(0, 5).join(" / ")}`,
    `  - health/live: ${input.rollout.healthOk === false ? "not-ok" : "ok"}`,
    "  - workload readiness: Deployment/StatefulSet ready；历史 Completed smoke/debug pod 不作为 DEV rollout blocker。",
  ].join("\n");
}

export function appendRolloutBrief(options: LegacyRuntimeRecordRolloutOptions, rollout: Record<string, unknown> = {}): Record<string, unknown> {
  const now = beijingParts();
  const ensured = ensureDailyBriefIssue(now.date, options.dryRun);
  if (record(ensured).ok !== true) return ensured;
  const brief = record(record(ensured).brief);
  const prRead = readPullRequest(options.prNumber);
  if (!isCommandSuccess(prRead)) return { ok: false, phase: "read-pr", prRead };
  const prData = commandData(prRead);
  const sourceCommit = options.sourceCommit ?? mergeCommitFromPr(prData);
  if (sourceCommit === null || sourceCommit === undefined) return { ok: false, phase: "source-commit", message: "source commit unavailable", prData };
  const pipelineRun = options.pipelineRun ?? `hwlab-g14-ci-poll-${shortSha(sourceCommit)}`;
  const files = summarizePrFiles(readPullRequestFiles(options.prNumber));
  const rolloutGitopsRevision = typeof rollout.gitopsRevision === "string" && rollout.gitopsRevision.length > 0 ? rollout.gitopsRevision : null;
  const gitopsRevision = options.gitopsRevision ?? rolloutGitopsRevision ?? currentGitopsRevision();
  const prJson = record(prData.json);
  const prSummary = record(prData.pullRequest);
  const prMergedAt = typeof prJson.mergedAt === "string" && prJson.mergedAt.length > 0
    ? prJson.mergedAt
    : typeof prSummary.mergedAt === "string" && prSummary.mergedAt.length > 0
    ? prSummary.mergedAt
    : null;
  const rolloutPipelineSucceededAt = typeof rollout.pipelineSucceededAt === "string" && rollout.pipelineSucceededAt.length > 0 ? rollout.pipelineSucceededAt : null;
  const rolloutFinishedAt = typeof rollout.finishedAt === "string" && rollout.finishedAt.length > 0 ? rollout.finishedAt : null;
  const mergedAt = options.mergedAt ?? prMergedAt;
  const pipelineSucceededAt = options.pipelineSucceededAt ?? rolloutPipelineSucceededAt;
  const finishedAt = options.finishedAt ?? rolloutFinishedAt;
  const ciMetrics = ciMetricsFromValue(rollout.ciMetrics) ?? collectPipelineMetrics(pipelineRun);
  const pr: OpenPullRequest = {
    number: options.prNumber,
    title: String(record(prData.json).title ?? ""),
    url: String(record(prData.json).url ?? ""),
    baseRefName: String(record(prData.json).baseRefName ?? ""),
    headRefName: String(record(prData.json).headRefName ?? ""),
  };
  const body = rolloutRecordBody({ pr, prData, fileSummary: files, sourceCommit, pipelineRun, gitopsRevision, mergedAt, pipelineSucceededAt, finishedAt, rollout, ciMetrics });
  if (Number(brief.number) <= 0 || options.dryRun) {
    return { ok: true, dryRun: true, date: now.date, brief, wouldAppend: { bodyPreview: body.slice(0, 1000), bodyChars: body.length }, ensured };
  }
  const currentBrief = readIssue(Number(brief.number));
  if (!isCommandSuccess(currentBrief)) return { ok: false, phase: "read-daily-brief", currentBrief, brief };
  const currentBody = String(issueFromRead(currentBrief).body ?? "");
  if (currentBody.includes(`PR #${options.prNumber}`) && currentBody.includes(sourceCommit)) {
    return { ok: true, skipped: true, reason: "rollout-brief-already-recorded", brief, sourceCommit, ensured };
  }
  const bodyPath = writeStateFile(`rollout-pr-${options.prNumber}-${shortSha(sourceCommit)}.md`, `${body}\n`);
  const update = cliJson(["gh", "issue", "update", String(brief.number), "--repo", HWLAB_REPO, "--mode", "append", "--body-file", bodyPath, "--body-profile", "commander-brief"], 100_000);
  if (!isCommandSuccess(update)) return { ok: false, phase: "append-rollout-brief", update, bodyPath, brief, ensured };
  return { ok: true, date: now.date, brief, sourceCommit, pipelineRun, gitopsRevision, ciMetrics, bodyPath, update: commandData(update), ensured };
}

export function defaultRuntimeLanePrCommentStatePath(): string {
  return rootPath(".state", "hwlab-g14", "v02-pr-comment-signatures.json");
}

export function readDefaultRuntimeLanePrCommentState(): Record<string, string> {
  const path = defaultRuntimeLanePrCommentStatePath();
  if (!existsSync(path)) return {};
  try {
    const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
    const state: Record<string, string> = {};
    for (const [key, value] of Object.entries(record(parsed))) {
      if (typeof value === "string") state[key] = value;
    }
    return state;
  } catch {
    return {};
  }
}

export function writeDefaultRuntimeLanePrCommentState(state: Record<string, string>): string {
  const path = defaultRuntimeLanePrCommentStatePath();
  mkdirSync(dirname(path), { recursive: true });
  writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`, "utf8");
  return path;
}

export function preflightSummary(preflight: CommandJsonResult): Record<string, unknown> {
  const data = commandData(preflight);
  const mergeability = record(data.mergeability);
  return {
    ok: isCommandSuccess(preflight),
    conclusion: stringOrNull(mergeability.conclusion) ?? (isCommandSuccess(preflight) ? "unknown" : "preflight-command-failed"),
    readyForCommanderMerge: mergeability.readyForCommanderMerge === true,
    blockers: Array.isArray(mergeability.blockers) ? mergeability.blockers.map(String).slice(0, 20) : [],
    pending: Array.isArray(mergeability.pending) ? mergeability.pending.map(String).slice(0, 20) : [],
    mergeable: mergeability.mergeable ?? nested(data, ["pullRequest", "mergeable"]) ?? null,
    mergeStateStatus: mergeability.mergeStateStatus ?? nested(data, ["pullRequest", "mergeStateStatus"]) ?? null,
    headRefName: nested(data, ["pullRequest", "headRefName"]) ?? null,
    baseRefName: nested(data, ["pullRequest", "baseRefName"]) ?? null,
    statusChecks: record(data.statusChecks).summary ?? data.statusChecks ?? null,
  };
}

export function defaultRuntimeLanePrConflictState(summary: Record<string, unknown>): string {
  const mergeable = String(summary.mergeable ?? "").toUpperCase();
  const mergeStateStatus = String(summary.mergeStateStatus ?? "").toUpperCase();
  const blockers = Array.isArray(summary.blockers) ? summary.blockers.map(String).join("\n") : "";
  if (mergeable.includes("CONFLICT") || mergeStateStatus.includes("DIRTY") || /conflict/i.test(blockers)) return "conflict";
  return "clear-or-unknown";
}

export function summarizeDefaultRuntimeLaneCdStatus(status: Record<string, unknown>): Record<string, unknown> {
  const pipelineRun = record(status.pipelineRun);
  const targetValidation = record(status.targetValidation);
  const gitMirror = record(status.gitMirror);
  const gitMirrorSummary = record(gitMirror.summary);
  const planArtifacts = record(status.planArtifacts);
  const planSummary = record(planArtifacts.summary);
  const argo = record(status.argo);
  const webAssets = record(status.webAssets);
  return {
    ok: status.ok === true,
    sourceCommit: status.sourceCommit ?? nested(status, ["statusTarget", "sourceCommit"]) ?? null,
    pipelineRun: pipelineRun.pipelineRun ?? pipelineRun.name ?? nested(status, ["statusTarget", "pipelineRun"]) ?? null,
    pipelineStatus: pipelineRun.status ?? null,
    pipelineReason: pipelineRun.reason ?? null,
    targetValidationState: targetValidation.state ?? null,
    targetValidationOk: targetValidation.ok ?? null,
    targetValidationFailures: Array.isArray(targetValidation.failures) ? targetValidation.failures.slice(0, 10) : [],
    targetValidationWarnings: Array.isArray(targetValidation.warnings) ? targetValidation.warnings.slice(0, 10) : [],
    argoSync: nested(argo, ["fields", "syncStatus"]) ?? argo.syncStatus ?? nested(argo, ["summary", "syncStatus"]) ?? null,
    argoHealth: nested(argo, ["fields", "health"]) ?? argo.healthStatus ?? nested(argo, ["summary", "healthStatus"]) ?? null,
    webAssetsOk: webAssets.ok ?? null,
    pendingFlush: gitMirrorSummary.pendingFlush ?? null,
    githubInSync: gitMirrorSummary.githubInSync ?? null,
    buildServices: planSummary.buildServices ?? planArtifacts.buildServices ?? null,
    reusedServices: planSummary.reusedServices ?? planArtifacts.reusedServices ?? null,
    rolloutServices: planSummary.rolloutServices ?? planArtifacts.rolloutServices ?? null,
  };
}

export function defaultRuntimeLaneCdPassed(status: Record<string, unknown>): boolean {
  const state = String(nested(status, ["targetValidation", "state"]) ?? "");
  return state === "passed" || state === "superseded";
}

export function defaultRuntimeLaneControlPlaneCloseout(status: Record<string, unknown>): Record<string, unknown> {
  const closeout = record(status.closeout);
  const sourceCommit = stringOrNull(status.sourceCommit);
  const pipelineRun = stringOrNull(nested(status, ["statusTarget", "pipelineRun"])) ?? stringOrNull(nested(status, ["pipelineRun", "pipelineRun"]));
  const command = sourceCommit !== null
    ? `hwlab g14 control-plane closeout --lane v02 --source-commit ${sourceCommit}`
    : pipelineRun !== null
      ? `hwlab g14 control-plane closeout --lane v02 --pipeline-run ${pipelineRun}`
      : "hwlab g14 control-plane closeout --lane v02";
  return {
    ok: closeout.closeable === true,
    command,
    lane: "v02",
    closeout,
    target: status.statusTarget ?? null,
    sourceCommit: status.sourceCommit ?? null,
    pipelineRun: status.pipelineRun ?? null,
    targetValidation: status.targetValidation ?? null,
    latestHead: nested(status, ["commitAlignment", "originHead"]) ?? nested(status, ["sourceHeads", "originHead"]) ?? null,
    gitMirror: record(record(status.gitMirror).summary),
    argo: record(status.argo).fields ?? null,
    activePipelineRuns: status.activePipelineRuns ?? [],
    recommendedNext: closeout.recommendedNext ?? null,
    issueCommentMarkdown: closeout.issueCommentMarkdown ?? null,
  };
}

export function defaultRuntimeLanePipelineSucceeded(status: Record<string, unknown>): boolean {
  return String(nested(status, ["pipelineRun", "status"]) ?? "") === "True";
}

export function defaultRuntimeLaneCdFailed(status: Record<string, unknown>): boolean {
  const pipelineStatus = String(nested(status, ["pipelineRun", "status"]) ?? "");
  return pipelineStatus === "False";
}

export function activeDefaultRuntimeLanePipelineRuns(status: Record<string, unknown>): Record<string, unknown>[] {
  const items = Array.isArray(status.activePipelineRuns)
    ? status.activePipelineRuns
    : Array.isArray(record(status.activePipelineRuns).items)
      ? record(status.activePipelineRuns).items
      : [];
  return items.map((item) => record(item)).filter((item) => String(item.status ?? "") === "Unknown");
}

export function defaultRuntimeLaneStatusHistoryPolicy(status: Record<string, unknown>): Record<string, unknown> {
  const history = record(status.history);
  return {
    included: history.included === true,
    recentPipelineRunsVisible: Object.hasOwn(status, "recentPipelineRuns"),
    activePipelineRunsVisible: Object.hasOwn(status, "activePipelineRuns"),
    note: history.note ?? null,
    command: history.command ?? null,
  };
}

export function defaultRuntimeLanePrCommentSignature(input: DefaultRuntimeLanePrCommentInput): string {
  const summary = input.preflight ?? {};
  const cd = input.cd ?? {};
  const flush = input.flush ?? {};
  return textHash(JSON.stringify({
    pr: input.pr.number,
    state: input.state,
    phase: input.phase,
    conclusion: summary.conclusion ?? null,
    readyForCommanderMerge: summary.readyForCommanderMerge ?? null,
    conflict: defaultRuntimeLanePrConflictState(summary),
    blockers: summary.blockers ?? [],
    pending: summary.pending ?? [],
    sourceCommit: input.sourceCommit ?? null,
    pipelineRun: input.pipelineRun ?? null,
    pipelineStatus: cd.pipelineStatus ?? null,
    pipelineReason: cd.pipelineReason ?? null,
    targetValidationState: cd.targetValidationState ?? null,
    pendingFlush: cd.pendingFlush ?? null,
    flushOk: flush.ok ?? null,
    dryRun: input.dryRun === true,
  }));
}

export function listValue(value: unknown): string {
  if (!Array.isArray(value) || value.length === 0) return "none";
  return value.map(String).slice(0, 8).join("; ");
}

export function scalarValue(value: unknown): string {
  if (value === null || value === undefined || value === "") return "n/a";
  if (typeof value === "object") return JSON.stringify(value).slice(0, 240);
  return String(value);
}

export function defaultRuntimeLanePrAutomationCommentBody(input: DefaultRuntimeLanePrCommentInput): string {
  const preflight = input.preflight ?? {};
  const cd = input.cd ?? {};
  const flush = input.flush ?? {};
  const title = input.pr.title ?? `PR #${input.pr.number}`;
  const url = input.pr.url ?? `https://github.com/${HWLAB_REPO}/pull/${input.pr.number}`;
  const elapsed = formatDuration(input.elapsedSeconds);
  const dryRunPrefix = input.dryRun === true ? "[dry-run] " : "";
  return [
    `### ${dryRunPrefix}v0.2 自动 CI/CD 状态`,
    "",
    `${input.message ?? "UniDesk monitor 已记录本轮 PR 自动化状态。"}`,
    "",
    "- PR:",
    `  - [#${input.pr.number} ${title}](${url})`,
    `  - base: \`${input.pr.baseRefName ?? DEFAULT_RUNTIME_LANE_SOURCE_BRANCH}\`; head: \`${input.pr.headRefName ?? "unknown"}\``,
    "- 自动化状态:",
    `  - state: \`${input.state}\`; phase: \`${input.phase}\``,
    `  - startedAt: \`${input.startedAt}\`; observedAt: \`${input.observedAt}\`; elapsed: ${elapsed}`,
    "- CI / mergeability:",
    `  - conclusion: \`${scalarValue(preflight.conclusion)}\`; readyForCommanderMerge: \`${String(preflight.readyForCommanderMerge ?? "n/a")}\`; conflict: \`${defaultRuntimeLanePrConflictState(preflight)}\``,
    `  - blockers: ${listValue(preflight.blockers)}`,
    `  - pending: ${listValue(preflight.pending)}`,
    "- CD / runtime:",
    `  - sourceCommit: \`${input.sourceCommit ?? "n/a"}\``,
    `  - PipelineRun: \`${input.pipelineRun ?? scalarValue(cd.pipelineRun)}\``,
    `  - pipeline: \`${scalarValue(cd.pipelineStatus)} / ${scalarValue(cd.pipelineReason)}\`; targetValidation: \`${scalarValue(cd.targetValidationState)}\``,
    `  - Argo: \`${scalarValue(cd.argoSync)} / ${scalarValue(cd.argoHealth)}\`; webAssets.ok: \`${String(cd.webAssetsOk ?? "n/a")}\``,
    `  - Git mirror: pendingFlush=\`${String(cd.pendingFlush ?? "n/a")}\`, githubInSync=\`${String(cd.githubInSync ?? "n/a")}\`, flush.ok=\`${String(flush.ok ?? "n/a")}\``,
    `  - rolloutServices: ${listValue(cd.rolloutServices)}; buildServices: ${listValue(cd.buildServices)}; reusedServices: ${listValue(cd.reusedServices)}`,
    "",
    "后续动作由 `bun scripts/cli.ts hwlab g14 monitor-prs --lane v02` 继续驱动；同一状态签名不会重复刷评论。",
  ].join("\n");
}

export function commentDefaultRuntimeLanePullRequest(input: DefaultRuntimeLanePrCommentInput): Record<string, unknown> {
  const body = defaultRuntimeLanePrAutomationCommentBody(input);
  const signature = defaultRuntimeLanePrCommentSignature(input);
  const cacheKey = `pr-${input.pr.number}`;
  if (input.dryRun === true) {
    return { ok: true, dryRun: true, skipped: true, signature, wouldComment: { bodyPreview: body.slice(0, 1200), bodyChars: body.length } };
  }
  const state = readDefaultRuntimeLanePrCommentState();
  if (state[cacheKey] === signature) {
    return { ok: true, skipped: true, reason: "same-pr-comment-signature", signature, stateFile: defaultRuntimeLanePrCommentStatePath() };
  }
  const bodyPath = writeStateFile(`v02-pr-${input.pr.number}-${input.state}-${signature}.md`, `${body}\n`);
  const create = cliJson(["gh", "pr", "comment", "create", String(input.pr.number), "--repo", HWLAB_REPO, "--body-file", bodyPath], 100_000);
  if (!isCommandSuccess(create)) return { ok: false, phase: "pr-comment", signature, bodyPath, create };
  state[cacheKey] = signature;
  const stateFile = writeDefaultRuntimeLanePrCommentState(state);
  return { ok: true, signature, bodyPath, stateFile, create: commandData(create) };
}

export function runtimeLanePrCommentStatePath(spec: HwlabRuntimeLaneSpec): string {
  return rootPath(".state", "hwlab-g14", `${spec.lane}-pr-comment-signatures.json`);
}

export function readRuntimeLanePrCommentState(spec: HwlabRuntimeLaneSpec): Record<string, string> {
  const path = runtimeLanePrCommentStatePath(spec);
  if (!existsSync(path)) return {};
  try {
    const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
    const state: Record<string, string> = {};
    for (const [key, value] of Object.entries(record(parsed))) {
      if (typeof value === "string") state[key] = value;
    }
    return state;
  } catch {
    return {};
  }
}

export function writeRuntimeLanePrCommentState(spec: HwlabRuntimeLaneSpec, state: Record<string, string>): string {
  const path = runtimeLanePrCommentStatePath(spec);
  mkdirSync(dirname(path), { recursive: true });
  writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`, "utf8");
  return path;
}

export function summarizeRuntimeLaneCdStatus(spec: HwlabRuntimeLaneSpec, status: Record<string, unknown>): Record<string, unknown> {
  const pipelineRun = record(status.pipelineRun);
  const argo = record(status.argo);
  const runtimeWorkloads = record(status.runtimeWorkloads);
  const publicProbes = record(status.publicProbes);
  const gitMirror = record(status.gitMirror);
  const gitMirrorSummary = record(gitMirror.summary);
  return {
    ok: status.ok === true,
    sourceCommit: status.sourceCommit ?? nested(status, ["statusTarget", "sourceCommit"]) ?? null,
    pipelineRun: pipelineRun.pipelineRun ?? pipelineRun.name ?? nested(status, ["statusTarget", "pipelineRun"]) ?? null,
    pipelineStatus: pipelineRun.status ?? null,
    pipelineReason: pipelineRun.reason ?? null,
    argoSync: nested(argo, ["fields", "syncStatus"]) ?? null,
    argoHealth: nested(argo, ["fields", "health"]) ?? null,
    runtimeNamespace: spec.runtimeNamespace,
    runtimeWorkloadsOk: runtimeWorkloads.ok ?? null,
    runtimeWorkloadCount: runtimeWorkloads.count ?? null,
    publicProbesOk: publicProbes.ok ?? null,
    publicApiUrl: publicProbes.apiUrl ?? `${spec.publicApiUrl.replace(/\/+$/u, "")}/health/live`,
    publicWebUrl: publicProbes.webUrl ?? spec.publicWebUrl,
    publicApiStatusOk: publicProbes.apiStatusOk ?? null,
    publicWebBytes: publicProbes.webBytes ?? null,
    pendingFlush: gitMirrorSummary.pendingFlush ?? null,
    githubInSync: spec.lane === "v03" ? gitMirrorSummary.v03GitopsInSync ?? null : gitMirrorSummary.githubInSync ?? null,
  };
}

export function runtimeLaneCdPassed(status: Record<string, unknown>): boolean {
  return status.ok === true
    && String(nested(status, ["pipelineRun", "status"]) ?? "") === "True"
    && String(nested(status, ["argo", "fields", "syncStatus"]) ?? "") === "Synced"
    && String(nested(status, ["argo", "fields", "health"]) ?? "") === "Healthy"
    && nested(status, ["runtimeWorkloads", "ok"]) === true
    && nested(status, ["publicProbes", "ok"]) === true;
}

export function runtimeLaneCdFailed(status: Record<string, unknown>): boolean {
  return String(nested(status, ["pipelineRun", "status"]) ?? "") === "False";
}

export function runtimeLanePrCommentSignature(spec: HwlabRuntimeLaneSpec, input: DefaultRuntimeLanePrCommentInput): string {
  const summary = input.preflight ?? {};
  const cd = input.cd ?? {};
  const flush = input.flush ?? {};
  return textHash(JSON.stringify({
    lane: spec.lane,
    pr: input.pr.number,
    state: input.state,
    phase: input.phase,
    conclusion: summary.conclusion ?? null,
    readyForCommanderMerge: summary.readyForCommanderMerge ?? null,
    conflict: defaultRuntimeLanePrConflictState(summary),
    blockers: summary.blockers ?? [],
    pending: summary.pending ?? [],
    sourceCommit: input.sourceCommit ?? null,
    pipelineRun: input.pipelineRun ?? null,
    pipelineStatus: cd.pipelineStatus ?? null,
    pipelineReason: cd.pipelineReason ?? null,
    argoSync: cd.argoSync ?? null,
    argoHealth: cd.argoHealth ?? null,
    publicProbesOk: cd.publicProbesOk ?? null,
    flushOk: flush.ok ?? null,
    dryRun: input.dryRun === true,
  }));
}

export function runtimeLanePrAutomationCommentBody(spec: HwlabRuntimeLaneSpec, input: DefaultRuntimeLanePrCommentInput): string {
  const preflight = input.preflight ?? {};
  const cd = input.cd ?? {};
  const flush = input.flush ?? {};
  const title = input.pr.title ?? `PR #${input.pr.number}`;
  const url = input.pr.url ?? `https://github.com/${HWLAB_REPO}/pull/${input.pr.number}`;
  const elapsed = formatDuration(input.elapsedSeconds);
  const dryRunPrefix = input.dryRun === true ? "[dry-run] " : "";
  return [
    `### ${dryRunPrefix}${spec.version} 自动 CI/CD 状态`,
    "",
    `${input.message ?? "UniDesk monitor 已记录本轮 PR 自动化状态。"}`,
    "",
    "- PR:",
    `  - [#${input.pr.number} ${title}](${url})`,
    `  - base: \`${input.pr.baseRefName ?? spec.sourceBranch}\`; head: \`${input.pr.headRefName ?? "unknown"}\``,
    "- 自动化状态:",
    `  - lane: \`${spec.lane}\`; state: \`${input.state}\`; phase: \`${input.phase}\``,
    `  - startedAt: \`${input.startedAt}\`; observedAt: \`${input.observedAt}\`; elapsed: ${elapsed}`,
    "- CI / mergeability:",
    `  - conclusion: \`${scalarValue(preflight.conclusion)}\`; readyForCommanderMerge: \`${String(preflight.readyForCommanderMerge ?? "n/a")}\`; conflict: \`${defaultRuntimeLanePrConflictState(preflight)}\``,
    `  - blockers: ${listValue(preflight.blockers)}`,
    `  - pending: ${listValue(preflight.pending)}`,
    "- CD / runtime:",
    `  - sourceCommit: \`${input.sourceCommit ?? "n/a"}\``,
    `  - PipelineRun: \`${input.pipelineRun ?? scalarValue(cd.pipelineRun)}\``,
    `  - pipeline: \`${scalarValue(cd.pipelineStatus)} / ${scalarValue(cd.pipelineReason)}\``,
    `  - Argo: \`${scalarValue(cd.argoSync)} / ${scalarValue(cd.argoHealth)}\`; runtimeWorkloads.ok: \`${String(cd.runtimeWorkloadsOk ?? "n/a")}\``,
    `  - public probes: ok=\`${String(cd.publicProbesOk ?? "n/a")}\`, api=\`${scalarValue(cd.publicApiUrl)}\`, webBytes=\`${scalarValue(cd.publicWebBytes)}\``,
    `  - Git mirror: pendingFlush=\`${String(cd.pendingFlush ?? "n/a")}\`, githubInSync=\`${String(cd.githubInSync ?? "n/a")}\`, flush.ok=\`${String(flush.ok ?? "n/a")}\``,
    "",
    `后续动作由 \`bun scripts/cli.ts hwlab g14 monitor-prs --lane ${spec.lane}\` 继续驱动；同一状态签名不会重复刷评论。`,
  ].join("\n");
}

export function commentRuntimeLanePullRequest(spec: HwlabRuntimeLaneSpec, input: DefaultRuntimeLanePrCommentInput): Record<string, unknown> {
  const body = runtimeLanePrAutomationCommentBody(spec, input);
  const signature = runtimeLanePrCommentSignature(spec, input);
  const cacheKey = `pr-${input.pr.number}`;
  if (input.dryRun === true) {
    return { ok: true, dryRun: true, skipped: true, signature, wouldComment: { bodyPreview: body.slice(0, 1200), bodyChars: body.length } };
  }
  const state = readRuntimeLanePrCommentState(spec);
  if (state[cacheKey] === signature) {
    return { ok: true, skipped: true, reason: "same-pr-comment-signature", signature, stateFile: runtimeLanePrCommentStatePath(spec) };
  }
  const bodyPath = writeStateFile(`${spec.lane}-pr-${input.pr.number}-${input.state}-${signature}.md`, `${body}\n`);
  const create = cliJson(["gh", "pr", "comment", "create", String(input.pr.number), "--repo", HWLAB_REPO, "--body-file", bodyPath], 100_000);
  if (!isCommandSuccess(create)) return { ok: false, phase: "pr-comment", signature, bodyPath, create };
  state[cacheKey] = signature;
  const stateFile = writeRuntimeLanePrCommentState(spec, state);
  return { ok: true, signature, bodyPath, stateFile, create: commandData(create) };
}

export function runtimeLaneAutomationIssueTitle(spec: HwlabRuntimeLaneSpec, pr: OpenPullRequest, state: string, phase: string): string {
  return `[${spec.version} 自动 CI/CD] PR #${pr.number} ${state}: ${phase}`;
}

export function runtimeLaneAutomationIssueBody(input: {
  spec: HwlabRuntimeLaneSpec;
  pr: OpenPullRequest;
  state: string;
  phase: string;
  startedAt: string;
  observedAt: string;
  preflight?: Record<string, unknown>;
  sourceCommit?: string | null;
  pipelineRun?: string | null;
  cd?: Record<string, unknown>;
  details?: Record<string, unknown>;
  message?: string;
}): string {
  const preflight = input.preflight ?? {};
  const cd = input.cd ?? {};
  const details = input.details ?? {};
  const title = input.pr.title ?? `PR #${input.pr.number}`;
  const url = input.pr.url ?? `https://github.com/${HWLAB_REPO}/pull/${input.pr.number}`;
  const dedupeKey = `${input.spec.lane}/pr-${input.pr.number}/${input.state}/${input.phase}/${input.sourceCommit ?? "no-source"}/${input.pipelineRun ?? "no-run"}`;
  return [
    `## ${input.spec.version} 自动 CI/CD 阻塞`,
    "",
    input.message ?? "UniDesk v0.3 PR monitor 发现自动 CI/CD 阻塞，已按 #1072 的失败沉淀规则创建或更新 issue。",
    "",
    "- 来源:",
    `  - PR: [#${input.pr.number} ${title}](${url})`,
    `  - base: \`${input.pr.baseRefName ?? input.spec.sourceBranch}\`; head: \`${input.pr.headRefName ?? "unknown"}\``,
    `  - lane: \`${input.spec.lane}\`; runtime namespace: \`${input.spec.runtimeNamespace}\``,
    `  - tracking issue: #1072`,
    "- 阶段:",
    `  - state: \`${input.state}\`; phase: \`${input.phase}\``,
    `  - startedAt: \`${input.startedAt}\`; observedAt: \`${input.observedAt}\``,
    `  - dedupeKey: \`${dedupeKey}\``,
    "- CI / mergeability:",
    `  - conclusion: \`${scalarValue(preflight.conclusion)}\`; readyForCommanderMerge: \`${String(preflight.readyForCommanderMerge ?? "n/a")}\`; conflict: \`${defaultRuntimeLanePrConflictState(preflight)}\``,
    `  - blockers: ${listValue(preflight.blockers)}`,
    `  - pending: ${listValue(preflight.pending)}`,
    "- CD / runtime:",
    `  - sourceCommit: \`${input.sourceCommit ?? "n/a"}\``,
    `  - PipelineRun: \`${input.pipelineRun ?? scalarValue(cd.pipelineRun)}\``,
    `  - pipeline: \`${scalarValue(cd.pipelineStatus)} / ${scalarValue(cd.pipelineReason)}\``,
    `  - Argo: \`${scalarValue(cd.argoSync)} / ${scalarValue(cd.argoHealth)}\`; publicProbes.ok: \`${String(cd.publicProbesOk ?? "n/a")}\``,
    "- 下一步:",
    `  - PR preflight: \`bun scripts/cli.ts gh pr preflight ${input.pr.number} --repo ${HWLAB_REPO}\``,
    `  - monitor once: \`bun scripts/cli.ts hwlab g14 monitor-prs --lane ${input.spec.lane} --once\``,
    input.sourceCommit !== null && input.sourceCommit !== undefined
      ? `  - lane status: \`bun scripts/cli.ts hwlab g14 control-plane status --lane ${input.spec.lane} --source-commit ${input.sourceCommit}\``
      : `  - lane status: \`bun scripts/cli.ts hwlab g14 control-plane status --lane ${input.spec.lane}\``,
    "",
    "## 细节摘要",
    "",
    "```json",
    JSON.stringify({ preflight, cd, details }, null, 2).slice(0, 5000),
    "```",
  ].join("\n");
}

export function reportRuntimeLaneAutomationIssue(input: {
  spec: HwlabRuntimeLaneSpec;
  pr: OpenPullRequest;
  state: string;
  phase: string;
  startedAt: string;
  observedAt: string;
  preflight?: Record<string, unknown>;
  sourceCommit?: string | null;
  pipelineRun?: string | null;
  cd?: Record<string, unknown>;
  details?: Record<string, unknown>;
  message?: string;
  dryRun?: boolean;
}): Record<string, unknown> {
  const title = runtimeLaneAutomationIssueTitle(input.spec, input.pr, input.state, input.phase);
  const body = runtimeLaneAutomationIssueBody(input);
  const signature = textHash(body);
  if (input.dryRun === true) {
    return { ok: true, dryRun: true, title, signature, wouldIssue: { bodyPreview: body.slice(0, 1200), bodyChars: body.length } };
  }
  const listed = cliJson(["gh", "issue", "list", "--repo", HWLAB_REPO, "--state", "open", "--limit", "10", "--search", title, "--json", "number,title,state,url"], 80_000);
  if (!isCommandSuccess(listed)) return { ok: false, phase: "issue-search", title, listed };
  const issues = Array.isArray(commandData(listed).issues) ? commandData(listed).issues.map((item) => record(item)) : [];
  const existing = issues.find((issue) => issue.title === title);
  const bodyPath = writeStateFile(`${input.spec.lane}-automation-issue-pr-${input.pr.number}-${input.state}-${input.phase}-${signature}.md`, `${body}\n`);
  if (existing !== undefined) {
    const comment = cliJson(["gh", "issue", "comment", "create", String(existing.number), "--repo", HWLAB_REPO, "--body-file", bodyPath], 100_000);
    if (!isCommandSuccess(comment)) return { ok: false, phase: "issue-comment", title, issue: existing, bodyPath, comment };
    return { ok: true, action: "commented-existing-issue", title, issue: existing, bodyPath, comment: commandData(comment) };
  }
  const created = cliJson(["gh", "issue", "create", "--repo", HWLAB_REPO, "--title", title, "--body-file", bodyPath], 100_000);
  if (!isCommandSuccess(created)) return { ok: false, phase: "issue-create", title, bodyPath, created };
  return { ok: true, action: "created-issue", title, bodyPath, issue: commandData(created).issue, created: commandData(created) };
}
