// SPEC: PJ2026-010606 GitHub入口 draft-2026-06-25-gh-split // Moved mechanically from scripts/src/gh.ts:1669-1889. import { bodySafetySignals, preview, previewLines } from "./auth-and-safety"; import { isMarkdownTableSeparator } from "./board-parser"; import { CODE_QUEUE_BOARD_TARGET_ISSUE, COMMANDER_BRIEF_UPDATE_HEADING_PATTERNS } from "./types"; import type { CommanderBriefDiff, CommanderBriefSection } from "./types"; export function dryRunBody(repo: string, title: string | undefined, body: string): Record { return { repo, ...(title === undefined ? {} : { title }), bodyPreview: preview(body), bodyPreviewLines: previewLines(body), ...bodySafetySignals(body), }; } export function normalizeNewlines(text: string): string { return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); } export function isTimelineHeading(line: string): boolean { return /^##\s+更新\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\s+北京时间\s*$/u.test(line.trimEnd()); } export function isCommanderBriefUpdateHeading(line: string): boolean { const trimmed = line.trimEnd(); return COMMANDER_BRIEF_UPDATE_HEADING_PATTERNS.some((pattern) => pattern.test(trimmed)); } export function commanderBriefUpdateHeadings(body: string): string[] { return normalizeNewlines(body) .split("\n") .map((line) => line.trimEnd()) .filter((line) => isCommanderBriefUpdateHeading(line)); } export function codeQueueBoardHwlabProductRoutingFindings(body: string | undefined): Array> { if (body === undefined) return []; const findings: Array> = []; const directIssuePatterns = [ { kind: "hwlab-repo-issue-url", pattern: /(?:https?:\/\/github\.com\/)?pikasTech\/HWLAB\/issues\/\d+/iu }, { kind: "hwlab-repo-issue-short-ref", pattern: /\bpikasTech\/HWLAB#\d+\b/iu }, { kind: "hwlab-short-issue-ref", pattern: /\bHWLAB#\d+\b/iu }, ]; const hwlabContextPattern = /\bHWLAB\b|pikasTech\/HWLAB|hwlab-/iu; const strongProductPattern = /hwlab-cloud-web|Cloud Workbench|DEV-LIVE|res_boxsimu|hwlab-patch-panel|patch panel|M3\s*(?:虚拟硬件|可信闭环)/iu; const genericProductPattern = /用户反馈|user feedback/iu; const governanceContextPattern = /\b(?:commander|Code Queue|CLI|infra|governance|guard|guardrail|routing|misfile|board)\b|治理|调度|基础设施|守卫|边界|分流|看板/iu; const lines = normalizeNewlines(body).split("\n"); for (let index = 0; index < lines.length; index += 1) { const line = lines[index]; const isTableRow = line.trim().startsWith("|") && !isMarkdownTableSeparator(line); const strongProductSignal = strongProductPattern.test(line); const genericProductSignal = genericProductPattern.test(line); const governanceContext = governanceContextPattern.test(line); for (const definition of directIssuePatterns) { const match = definition.pattern.exec(line); if (match !== null && !governanceContext && (isTableRow || strongProductSignal || (genericProductSignal && hwlabContextPattern.test(line)))) { findings.push({ kind: definition.kind, lineNumber: index + 1, match: match[0], snippet: preview(line.trim()), }); } } if (isTableRow && !governanceContext && (strongProductSignal || (genericProductSignal && hwlabContextPattern.test(line)))) { const match = strongProductSignal ? strongProductPattern.exec(line) : genericProductPattern.exec(line); findings.push({ kind: "hwlab-product-signal", lineNumber: index + 1, match: match?.[0] ?? "HWLAB product signal", snippet: preview(line.trim()), }); } if (findings.length >= 12) break; } return findings; } export function codeQueueBoardHwlabProductRoutingHint(boardIssueNumber: number, body?: string): Record | null { if (boardIssueNumber !== CODE_QUEUE_BOARD_TARGET_ISSUE) return null; const findings = codeQueueBoardHwlabProductRoutingFindings(body); return { warning: "#20 is only for UniDesk commander/Code Queue/CLI/infra governance; HWLAB user/product issues belong in pikasTech/HWLAB.", route: "Create or update the corresponding issue in pikasTech/HWLAB; keep #20 rows limited to UniDesk governance or infrastructure support work.", forbiddenPatterns: [ "pikasTech/HWLAB#", "github.com/pikasTech/HWLAB/issues/", "HWLAB#", "HWLAB product/live validation rows", ], allowedScope: ["commander governance", "Code Queue supervision", "UniDesk CLI guardrails", "UniDesk infrastructure"], detected: findings.length > 0, findings, }; } export function codeQueueBoardHintHasHwlabProductRouting(hint: Record | null): boolean { if (hint === null) return false; const routing = hint.hwlabProductRouting; return typeof routing === "object" && routing !== null && (routing as { detected?: unknown }).detected === true; } export function codeQueueBoardHintHasCommanderBriefUpdates(hint: Record | null): boolean { if (hint === null) return false; const commanderBrief = hint.commanderBrief; return typeof commanderBrief === "object" && commanderBrief !== null && (commanderBrief as { detected?: unknown }).detected === true; } export function codeQueueBoardCommanderBriefHint(boardIssueNumber: number, body?: string): Record | null { if (boardIssueNumber !== CODE_QUEUE_BOARD_TARGET_ISSUE) return null; const headings = body === undefined ? [] : commanderBriefUpdateHeadings(body); const hwlabProductRouting = codeQueueBoardHwlabProductRoutingHint(boardIssueNumber, body); const commanderBriefDetected = headings.length > 0; const hwlabProductDetected = codeQueueBoardHintHasHwlabProductRouting({ hwlabProductRouting }); return { warning: "#20 is the long-term UniDesk commander/Code Queue/CLI/infra governance board only; do not write daily commander brief updates or HWLAB product/user issue tracking into #20.", route: commanderBriefDetected ? "Move daily progress notes to the daily rolling commander brief issue referenced in #20's 指挥简报索引, using --body-profile commander-brief." : hwlabProductDetected ? "Move HWLAB product/user issue tracking to pikasTech/HWLAB; keep #20 limited to UniDesk commander/Code Queue/CLI/infra governance." : "Use #20 only for UniDesk commander/Code Queue/CLI/infra governance rows.", forbiddenHeadings: ["## 更新 YYYY-MM-DD HH:mm 北京时间", "## YYYY-MM-DD HH:mm 北京时间指挥更新", "### YYYY-MM-DD HH:mm CST:..."], suggestedCommand: "bun scripts/cli.ts gh issue update --mode replace --body-stdin --body-profile commander-brief --expect-body-sha <<'EOF'\n\nEOF", detected: commanderBriefDetected || hwlabProductDetected, detectedHeadings: headings, commanderBrief: { detected: commanderBriefDetected, detectedHeadings: headings, }, hwlabProductRouting, }; } export function extractTimelineSections(markdown: string): CommanderBriefSection[] { const normalized = normalizeNewlines(markdown); const lines = normalized.split("\n"); const sections: CommanderBriefSection[] = []; let currentStart = -1; for (let index = 0; index < lines.length; index += 1) { if (!isTimelineHeading(lines[index])) continue; if (currentStart !== -1) { sections.push({ heading: lines[currentStart].trimEnd(), text: lines.slice(currentStart, index).join("\n").trimEnd(), startLine: currentStart + 1, }); } currentStart = index; } if (currentStart !== -1) { sections.push({ heading: lines[currentStart].trimEnd(), text: lines.slice(currentStart).join("\n").trimEnd(), startLine: currentStart + 1, }); } return sections.filter((section) => section.text.length > 0); } export function commanderBriefDiff(oldBodyRaw: string, newBodyRaw: string): CommanderBriefDiff { const oldBody = normalizeNewlines(oldBodyRaw); const newBody = normalizeNewlines(newBodyRaw); if (oldBody === newBody) { return { ok: false, mode: "identical", message: "", chars: 0, sections: [], sectionCount: 0, skippedReason: "issue body is unchanged", }; } if (newBody.startsWith(oldBody)) { const suffix = newBody.slice(oldBody.length); const sections = extractTimelineSections(suffix).map((section) => section.text); const message = sections.join("\n\n").trim(); if (message.length === 0) { return { ok: false, mode: "append-only", message: "", chars: 0, sections: [], sectionCount: 0, skippedReason: "append-only suffix contains no new commander brief update section", }; } return { ok: true, mode: "append-only", message, chars: message.length, sections, sectionCount: sections.length }; } const oldSections = new Set(extractTimelineSections(oldBody).map((section) => section.text)); const allNewSections = extractTimelineSections(newBody); const newSections = allNewSections.map((section) => section.text).filter((section) => !oldSections.has(section)); if (newSections.length === 0) { return { ok: false, mode: "heading-diff", message: "", chars: 0, sections: [], sectionCount: 0, skippedReason: "no new commander brief update section found; non-timeline edits are not notified", }; } const duplicateNewHeadings = allNewSections.some((section, index) => allNewSections.findIndex((candidate) => candidate.heading === section.heading) !== index); const oldHeadings = new Set(extractTimelineSections(oldBody).map((section) => section.heading)); const ambiguousChangedHeadings = allNewSections.some((section) => oldHeadings.has(section.heading) && !oldSections.has(section.text)); if (duplicateNewHeadings || ambiguousChangedHeadings) { return { ok: false, mode: "unreliable", message: "", chars: 0, sections: [], sectionCount: 0, skippedReason: duplicateNewHeadings ? "duplicate update headings make commander brief diff unreliable" : "an existing update section changed; cannot reliably isolate only new timeline content", }; } const message = newSections.join("\n\n").trim(); return { ok: true, mode: "heading-diff", message, chars: message.length, sections: newSections, sectionCount: newSections.length }; }