ci: gate merges on all platforms and structure issue intake

Pull requests: `lint (fmt, clippy)` and `pr-size` join the required checks
together with all five `Tests (<os>)` jobs and both `Release workspace` jobs,
so a PR can no longer merge with a red platform. Draft PRs run the three fast
platforms on every push; marking ready for review adds Intel Mac and Windows
ARM64. `pr-size` labels size/S-XL and fails above 1500 changed source lines
unless a maintainer adds `size-exempt`.

Issues: the bug form now asks for OS version, shell, program in the pane,
connection type, non-default settings and mandatory logs/screenshots, with
log paths per platform. `issue-labels` turns the dropdowns into os:/area:
labels, flags regressions, and parks reports without usable steps, evidence
or a version as needs-info with a checklist comment; an edit that fills the
gaps clears the label. config.yml pointed at the old nebula repository.
This commit is contained in:
kuddev committed 2026-09-23 12:18:02 +08:00
1 parent 0110c7c139
commit b0caec9dcb
7 files changed
+510 -84

No files matched your search

+140 -39
View File
@@ -1,114 +1,215 @@
name: Bug Report
description: Report a reproducible problem in Nebula
name: Bug Report / 问题反馈
description: Report a reproducible problem in Pebrel / 反馈一个可复现的问题
title: "[Bug] "
labels: ["bug", "triage"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report a bug. Please search existing issues first and include enough detail for someone else to reproduce the problem.
Thanks for taking the time to report a bug. A report we can reproduce gets fixed in the next release; one we cannot reproduce is asked for details and waits.
感谢反馈。**能复现的问题会进下个版本;说不清的问题只能先挂起等补充。** 下拉选项会自动转成标签。
- type: dropdown
id: platform
attributes:
label: Operating system
label: Operating system / 操作系统
options:
- Windows
- Windows (WSL)
- macOS
- Linux
- Other
- Other / 其他
validations:
required: true
- type: input
id: os_version
attributes:
label: OS version and display / 系统版本与显示
description: Windows 11 23H2 / macOS 26.0 / Ubuntu 24.04; DPI scaling and monitor count if display-related. / 系统版本;渲染问题请附缩放比例与显示器数量。
placeholder: "Windows 11 23H2, 150% scaling, 2 monitors"
validations:
required: true
- type: input
id: version
attributes:
label: Nebula version
description: Find it in Settings > Home, or on the latest release page.
placeholder: "e.g. 1.0.0"
label: Pebrel version / 版本号
description: Settings > Home, or the release page. / 设置 > 主页,或发布页。
placeholder: "e.g. 1.9.0"
validations:
required: true
- type: dropdown
id: install_source
attributes:
label: How did you install Nebula?
label: How did you install Pebrel? / 安装方式
options:
- GitHub Release (.msi / .exe / portable archive)
- Built from source (cargo build / cargo run)
- Other
- GitHub Release (.exe / .zip / .dmg / .AppImage / .deb)
- Built from source / 源码构建 (cargo build)
- Other / 其他
validations:
required: true
- type: dropdown
id: shell
attributes:
label: Shell in the affected pane / 出问题的面板用的 Shell
multiple: true
options:
- PowerShell 7 (pwsh)
- Windows PowerShell 5.1
- cmd
- WSL (bash / zsh inside WSL)
- Git Bash / MSYS2
- zsh
- bash
- fish
- nushell
- Not sure / 不确定
validations:
required: true
- type: dropdown
id: program
attributes:
label: Program running in the pane / 面板里正在跑的程序
description: What was running when it happened? / 出问题时面板里跑着什么?
multiple: true
options:
- Nothing, plain shell prompt / 什么都没跑
- Claude Code
- Codex CLI
- Pi
- Gemini CLI / other AI agent — 其他 AI agent
- Terminal multiplexer (screen etc.) — 终端复用器
- vim / nvim / other full-screen TUI — 全屏 TUI
- ssh client / remote shell — 远程 shell
- A long-running command (build, ping, adb…) — 长时间运行的命令
- Other / 其他
validations:
required: true
- type: dropdown
id: connection
attributes:
label: Connection type / 连接方式
options:
- Local terminal / 本地终端
- SSH via Pebrel host list / Pebrel 内 SSH 主机
- SSH via command line (ssh user@host) / 手动 ssh 命令
- WSL
- Other / 其他
validations:
required: true
- type: dropdown
id: area
attributes:
label: Affected area
label: Affected area / 影响范围
description: Pick every area that applies. / 可多选。
multiple: true
options:
- Local terminal / shell
- SSH connection / authentication
- SFTP / file tree
- Tabs / splits / sessions
- AI assistant / providers
- Settings / backup / sync
- UI / layout / rendering
- Startup / crash
- Other
- Terminal rendering / scrolling / selection / garbled text — 终端渲染、滚动、选区、乱码
- Shell startup / PATH / start directory / context menu — Shell 启动、环境变量、起始目录、右键菜单
- SSH / proxy / jump host — SSH、代理、跳板
- File tree / drag-and-drop upload — 文件树、拖拽上传
- Tabs / splits / session restore / window size — 标签页、分屏、会话恢复、窗口尺寸
- AI agent (Claude Code / Codex / Pi / OMP) — AI Agent 集成与状态
- Markdown / LaTeX / image reader — 阅读页与内联渲染
- Settings / theme / layout / blur — 设置页、主题、布局、模糊背景
- Keyboard / IME / mouse / shortcuts / clipboard — 键盘、输入法、鼠标、快捷键、剪贴板
- Git / SVN
- Startup crash / hang / cannot exit — 启动崩溃、卡死、无法退出
- Performance / lag — 性能、卡顿
- Installer / update / packaging — 安装包、更新、打包
- Other / 其他
validations:
required: true
- type: dropdown
id: regression
attributes:
label: Did it work in a previous version? / 旧版本是否正常?
options:
- Not sure / 不确定
- Yes, it worked before / 旧版本正常,新版本坏了
- No, it never worked / 一直如此
validations:
required: true
- type: dropdown
id: reproducibility
attributes:
label: Can you reproduce it?
label: Can you reproduce it? / 复现概率
options:
- Always (100%)
- Often (>50%)
- Sometimes
- Once / not sure
- Always (100%) / 必现
- Often (>50%) / 经常
- Sometimes / 偶尔
- Once / not sure / 仅一次
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: Use numbered steps and include the shell or connection type when relevant.
label: Steps to reproduce / 复现步骤
description: |
Numbered, from a fresh launch, ending at the exact moment it goes wrong. "It's laggy" or "it doesn't work" cannot be acted on.
从启动开始按序编号,写到出错的那一步为止。"很卡"、"不好用"这种一句话我们没法处理。
placeholder: |
1. Open Nebula
2. Create or connect to ...
3. ...
1. Launch Pebrel, open a new PowerShell 7 tab
2. Run `codex` and ask it to ...
3. Split the pane with Ctrl+Shift+D
4. Output overlaps the input box at the bottom (see screenshot)
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
label: Expected behavior / 期望结果
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual behavior
label: Actual behavior / 实际结果
description: What you see instead, including any error text verbatim. / 实际看到什么,错误文字原样贴。
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs / screenshots
description: Include relevant lines or screenshots. Remove passwords, private keys, tokens, host addresses, and other secrets.
label: Logs / screenshots / 日志或截图
description: |
Required. Screenshot or short recording for anything visual; log lines for crashes, hangs and connection problems. Remove passwords, keys, tokens and host addresses first.
必填。显示类问题贴截图或短录屏;崩溃、卡死、连接类问题贴日志。请先删掉密码、私钥、token、主机地址。
Logs / 日志位置:
- Windows: `%APPDATA%\Pebrel\pebrel_debug.log` and `%TEMP%\Pebrel-<pid>.log`
- macOS: `~/Library/Application Support/Pebrel/pebrel_debug.log` and `$TMPDIR/Pebrel-<pid>.log`
- Linux: `~/.config/pebrel/pebrel_debug.log` and `/tmp/Pebrel-<pid>.log`
placeholder: Paste redacted logs or drag screenshots here.
validations:
required: true
- type: textarea
id: settings
attributes:
label: Non-default settings / 改过的设置
description: Theme, font, transparency/blur, GPU, shell profile, hooks — anything changed from defaults. Attach settings.txt if unsure. / 改过的主题、字体、透明/模糊、GPU、Shell 配置、hooks;不确定就贴 settings.txt。
placeholder: "Acrylic blur on, opacity 85%, font JetBrains Mono 13"
- type: checkboxes
id: checklist
attributes:
label: Before submitting
label: Before submitting / 提交前确认
options:
- label: I searched existing issues and did not find a duplicate
- label: I searched existing issues and did not find a duplicate / 已搜索,无重复
required: true
- label: I removed passwords, private keys, tokens, and other secrets from this report
- label: I tested with the latest release / 已用最新版本试过
required: true
- label: I removed passwords, private keys, tokens and other secrets / 已删除敏感信息
required: true
+9 -6
View File
@@ -1,8 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Existing issues
url: https://github.com/Kuddev/nebula/issues
about: Search existing reports before opening a new issue.
- name: Latest release
url: https://github.com/Kuddev/nebula/releases/latest
about: Check whether the problem is already fixed in the latest version.
- name: Existing issues / 已有 issue
url: https://github.com/Kuddev/pebrel/issues
about: Search existing reports before opening a new issue. / 先搜一下是否已有人反馈。
- name: Latest release / 最新版本
url: https://github.com/Kuddev/pebrel/releases/latest
about: Check whether the problem is already fixed in the latest version. / 看看最新版是否已修复。
- name: Discord
url: https://discord.gg/VFn4rcxmhn
about: Quick questions and discussion. / 快速提问与交流。
+41 -24
View File
@@ -1,5 +1,5 @@
name: Feature Request
description: Suggest an improvement or new capability for Nebula
name: Feature Request / 功能建议
description: Suggest an improvement or new capability for Pebrel / 提出改进或新功能
title: "[Feature] "
labels: ["enhancement", "triage"]
body:
@@ -7,12 +7,13 @@ body:
attributes:
value: |
Describe the workflow problem first, then the change you want. Concrete examples make the request easier to evaluate.
先说现在哪里不顺手,再说希望怎么改。具体的使用场景最有说服力。
- type: textarea
id: problem
attributes:
label: Problem / pain point
description: What is hard, missing, or repetitive today?
label: Problem / pain point — 现在的痛点
description: What is hard, missing, or repetitive today? / 什么事难做、缺失、或者每天重复?
placeholder: When I work with multiple terminals or hosts, I cannot ...
validations:
required: true
@@ -20,50 +21,66 @@ body:
- type: textarea
id: solution
attributes:
label: Proposed solution
description: What would you like Nebula to do?
label: Proposed solution — 希望的做法
description: What would you like Pebrel to do? / 希望 Pebrel 怎么做?
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: List workarounds, other tools, or designs you considered.
validations:
required: true
label: Alternatives considered — 现在的替代办法
description: Workarounds, other tools, or designs you considered. / 目前怎么绕过去的,别的终端怎么做的。
- type: dropdown
id: platform
attributes:
label: Platform / 平台
description: Leave empty if it applies everywhere. / 全平台通用可不选。
multiple: true
options:
- Windows
- Windows (WSL)
- macOS
- Linux
- type: dropdown
id: area
attributes:
label: Related area
label: Related area / 相关范围
multiple: true
options:
- Local terminal / shell
- SSH / SFTP
- Tabs / splits / sessions
- AI assistant / providers
- Settings / backup / sync
- UI / UX
- Other
- Terminal rendering / scrolling / selection — 终端渲染、滚动、选区
- Shell startup / PATH / start directory / context menu — Shell 启动、环境变量、起始目录、右键菜单
- SSH / proxy / jump host — SSH、代理、跳板
- File tree / drag-and-drop upload — 文件树、拖拽上传
- Tabs / splits / session restore / window size — 标签页、分屏、会话恢复、窗口尺寸
- AI agent (Claude Code / Codex / Pi / OMP) — AI Agent 集成与状态
- Markdown / LaTeX / image reader — 阅读页与内联渲染
- Settings / theme / layout / blur — 设置页、主题、布局、模糊背景
- Keyboard / IME / mouse / shortcuts / clipboard — 键盘、输入法、鼠标、快捷键、剪贴板
- Git / SVN
- Performance / lag — 性能、卡顿
- Installer / update / packaging — 安装包、更新、打包
- Other / 其他
validations:
required: true
- type: dropdown
id: priority
attributes:
label: How important is this to you?
label: How important is this to you? / 对你有多重要
options:
- Nice to have
- Would improve my daily workflow
- Blocking / critical for my use case
- Nice to have / 有更好
- Would improve my daily workflow / 每天都会用到
- Blocking / critical for my use case / 没有就没法用
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: Before submitting
label: Before submitting / 提交前确认
options:
- label: I searched existing issues for similar requests
- label: I searched existing issues for similar requests / 已搜索,无重复
required: true
+154
View File
@@ -0,0 +1,154 @@
name: Issue labels
# Turns the dropdown answers of the issue forms (.github/ISSUE_TEMPLATE/*.yml)
# into labels and flags bug reports that are too thin to act on. Labels are
# only added; maintainers' manual labels are never removed.
on:
issues:
types: [opened, edited]
permissions:
issues: write
concurrency:
group: issue-labels-${{ github.event.issue.number }}
cancel-in-progress: true
jobs:
label-from-form:
name: label-from-form
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Map form answers to labels
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const body = issue.body || "";
// Issue forms render every field as "### <label>\n\n<value>";
// multi-select dropdowns join their values with ", ".
const fields = {};
const re = /^### (.+?)\s*\n+([\s\S]*?)(?=^### |\s*$(?![\s\S]))/gm;
for (const m of body.matchAll(re)) {
const value = m[2].trim();
if (value && value !== "_No response_") fields[m[1].trim()] = value;
}
const field = (prefix) => {
const key = Object.keys(fields).find((k) => k.startsWith(prefix));
return key ? fields[key] : "";
};
const answers = (prefix) => {
const value = field(prefix);
return value ? value.split(", ").map((s) => s.trim()) : [];
};
// Match on the English lead of each option so wording tweaks in
// the Chinese half never silently break the mapping.
const OS = [
["Windows (WSL)", ["os: windows", "os: wsl"]],
["Windows", ["os: windows"]],
["macOS", ["os: macos"]],
["Linux", ["os: linux"]],
];
const AREA = [
["Terminal rendering", ["area: terminal"]],
["Shell startup", ["area: shell"]],
["SSH", ["area: ssh"]],
["File tree", ["area: files"]],
["Tabs / splits", ["area: session"]],
["AI agent", ["area: agents"]],
["Markdown", ["area: reader"]],
["Settings", ["area: ui"]],
["Keyboard", ["area: input"]],
["Git", ["area: git"]],
["Startup crash", ["crash"]],
["Performance", ["performance"]],
["Installer", ["area: ci-release"]],
];
const PROGRAM = [
["Claude Code", ["area: agents"]],
["Codex", ["area: agents"]],
["Pi", ["area: agents"]],
["Gemini", ["area: agents"]],
];
const CONNECTION = [
["SSH", ["area: ssh"]],
["WSL", ["os: wsl"]],
];
const SHELL = [["WSL", ["os: wsl"]]];
const pick = (values, table) => {
const out = [];
for (const v of values) {
const hit = table.find(([lead]) => v.startsWith(lead));
if (hit) out.push(...hit[1]);
}
return out;
};
const labels = new Set([
...pick([...answers("Operating system"), ...answers("Platform")], OS),
...pick([...answers("Affected area"), ...answers("Related area")], AREA),
...pick(answers("Program running in the pane"), PROGRAM),
...pick(answers("Connection type"), CONNECTION),
...pick(answers("Shell in the affected pane"), SHELL),
]);
if (answers("Did it work in a previous version").some((v) => v.startsWith("Yes"))) {
labels.add("regression");
}
// Thin bug reports: no usable steps or no evidence. Ask once, on
// open; an edit that fills the gaps clears the label.
const existing = new Set(issue.labels.map((l) => l.name));
const isBug = existing.has("bug");
const steps = field("Steps to reproduce");
const logs = field("Logs / screenshots");
const missing = [];
// CJK carries more per character; weigh it double. Numbered
// multi-line steps count as an attempt regardless of length.
const compact = steps.replace(/\s+/g, "");
const weight = compact.length + (compact.match(/[ -鿿豈-﫿]/g) || []).length;
const numbered = (steps.match(/^\s*\d+[.、)]/gm) || []).length >= 2;
if (isBug && weight < 60 && !numbered) {
missing.push("**Steps to reproduce / 复现步骤**: numbered steps from launch to the failing moment / 从启动开始按序写到出错那一步");
}
if (isBug && !logs) {
missing.push("**Logs / screenshots / 日志或截图**: a screenshot for visual issues, `pebrel_debug.log` for crashes, hangs and connection problems / 显示类问题贴截图,崩溃卡死连接类贴日志");
}
if (isBug && !/\d+\.\d+/.test(field("Pebrel version"))) {
missing.push("**Pebrel version / 版本号**: e.g. `1.9.0` (Settings > Home)");
}
const toAdd = [...labels].filter((l) => !existing.has(l));
if (missing.length) {
if (!existing.has("needs-info")) toAdd.push("needs-info");
if (context.payload.action === "opened") {
await github.rest.issues.createComment({
...context.repo,
issue_number: issue.number,
body: [
"Thanks for the report. It is missing details we need before anyone can reproduce it, so it is parked as `needs-info`. Please edit the issue and fill in:",
"感谢反馈。目前缺少复现所需的信息,已标记 `needs-info`。请**编辑 issue** 补齐以下内容,补齐后标签会自动移除:",
"",
...missing.map((m) => `- ${m}`),
"",
"Reports without these are closed after 14 days of silence. / 14 天无补充将关闭。",
].join("\n"),
});
}
} else if (isBug && existing.has("needs-info") && context.payload.action === "edited") {
await github.rest.issues.removeLabel({
...context.repo, issue_number: issue.number, name: "needs-info",
}).catch(() => {});
}
core.info(`form labels: ${[...labels].join(", ") || "(none)"}; adding: ${toAdd.join(", ") || "(none)"}; missing: ${missing.length}`);
if (toAdd.length) {
await github.rest.issues.addLabels({
...context.repo,
issue_number: issue.number,
labels: toAdd,
});
}
+73 -12
View File
@@ -11,7 +11,9 @@ on:
push:
branches: [main]
# Include docs, catalogs, assets and workflow edits: every PR gets test results.
# ready_for_review starts the scarce-runner jobs that draft pushes skip.
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
merge_group:
concurrency:
@@ -29,13 +31,61 @@ jobs:
with:
runtime_run: ${{ inputs.windows_diagnostics_run }}
lint:
if: ${{ inputs.windows_diagnostics_run == '' }}
name: lint (fmt, clippy)
runs-on: ubuntu-24.04
timeout-minutes: 30
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v4
- name: Install native dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends build-essential cmake pkg-config libasound2-dev libdbus-1-dev libudev-dev libfontconfig1-dev libfreetype6-dev libegl1-mesa-dev libgl1-mesa-dev libwayland-dev libxkbcommon-dev libxkbcommon-x11-dev libx11-xcb-dev libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxcb-xinput-dev libxcb-xkb-dev libxi-dev
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.97.1"
components: rustfmt, clippy
- name: rustfmt
run: cargo fmt --all -- --check
- uses: ./.github/actions/rust-cache
id: rust-cache
with:
key: clippy
target: target/ci
- name: clippy
# Advisory until the existing warning backlog is cleared; then drop
# continue-on-error and add -D warnings.
continue-on-error: true
run: cargo clippy --locked --config .github/ci-profile.toml --profile ci --workspace --all-targets
- name: Save compiled workload
if: ${{ always() && steps.rust-cache.outcome == 'success' && steps.rust-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v4
with:
path: target/ci
key: ${{ steps.rust-cache.outputs.cache-key }}
# Draft pull requests run the three fast platforms (about 20 minutes) on
# every push. The scarce runners (Intel Mac, Windows ARM64) join once the
# pull request is marked ready for review and on main, so every merge is
# verified on all five platforms without paying their queue time while a
# change is still being iterated. Skipped jobs report success, which keeps
# all five names usable as required checks; a ready PR cannot merge red.
native-tests:
if: ${{ inputs.windows_diagnostics_run == '' }}
name: Tests (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, windows-2022, windows-11-arm, macos-26, macos-26-intel]
os: [ubuntu-24.04, windows-2022, macos-26]
tier: [core]
include:
- os: windows-11-arm
tier: full
- os: macos-26-intel
tier: full
runs-on: ${{ matrix.os }}
timeout-minutes: 120
env:
@@ -45,13 +95,15 @@ jobs:
RUST_TOOLCHAIN: "1.97.1"
steps:
- uses: actions/checkout@v4
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
with:
fetch-depth: 0
- uses: actions/setup-python@v5
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
with:
python-version: "3.13"
- name: Install native dependencies
if: runner.os == 'Linux'
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) && (runner.os == 'Linux') }}
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
@@ -62,15 +114,16 @@ jobs:
libx11-xcb-dev libxcb1-dev libxcb-render0-dev \
libxcb-shape0-dev libxcb-xfixes0-dev libxcb-xinput-dev libxcb-xkb-dev libxi-dev
- name: Require the AppKit SDK used by this PR
if: runner.os == 'macOS'
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) && (runner.os == 'macOS') }}
run: |
sdk_version="$(xcrun --sdk macosx --show-sdk-version)"
test "${sdk_version%%.*}" -ge 26
- uses: dtolnay/rust-toolchain@stable
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
with:
toolchain: "1.97.1"
- name: Require a native Windows ARM64 compiler
if: matrix.os == 'windows-11-arm'
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) && (matrix.os == 'windows-11-arm') }}
shell: pwsh
run: |
if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -ne 'Arm64') {
@@ -81,6 +134,7 @@ jobs:
throw 'The Windows ARM job must compile and run ARM64 tests.'
}
- uses: ./.github/actions/rust-cache
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
id: rust-cache
with:
key: native
@@ -90,25 +144,26 @@ jobs:
${{ runner.os != 'macOS' && 'target/release' || '' }}
legacy-key: ${{ runner.os == 'Windows' && 'windows' || runner.os == 'Linux' && 'linux' || 'macos' }}
- name: Prepare pinned Windows console runtime
if: runner.os == 'Windows'
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) && (runner.os == 'Windows') }}
shell: pwsh
run: |
$architecture = if ('${{ runner.arch }}' -eq 'ARM64') { 'arm64' } else { 'x64' }
./scripts/prepare-windows-runtime.ps1 -Destination assets/windows/conhost -Architecture $architecture
./scripts/tests/windows-runtime.tests.ps1 -Architecture $architecture
- name: Test complete workspace and native harness
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
run: python scripts/ci_native_tests.py
- name: Check source architecture
if: ${{ !cancelled() && runner.os == 'Linux' }}
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) && (!cancelled() && runner.os == 'Linux') }}
run: python scripts/check_architecture.py
- name: Test translation and allocation contracts
if: ${{ !cancelled() && runner.os == 'Linux' }}
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) && (!cancelled() && runner.os == 'Linux') }}
run: cargo test --locked --manifest-path tools/i18n-contract/Cargo.toml
- name: Check the release workspace
if: ${{ !cancelled() && runner.os != 'macOS' }}
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) && (!cancelled() && runner.os != 'macOS') }}
run: cargo check --locked --workspace --release
- name: Save compiled workload
if: ${{ always() && steps.rust-cache.outcome == 'success' && steps.rust-cache.outputs.cache-hit != 'true' }}
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) && (always() && steps.rust-cache.outcome == 'success' && steps.rust-cache.outputs.cache-hit != 'true') }}
uses: actions/cache/save@v4
with:
path: |
@@ -116,7 +171,7 @@ jobs:
${{ runner.os != 'macOS' && 'target/release' || '' }}
key: ${{ steps.rust-cache.outputs.cache-key }}
- name: Upload native compilation timings
if: always()
if: ${{ (matrix.tier != 'full' || github.event_name != 'pull_request' || !github.event.pull_request.draft) && (always()) }}
uses: actions/upload-artifact@v4
with:
name: native-timings-${{ matrix.os }}
@@ -145,32 +200,38 @@ jobs:
RUST_TOOLCHAIN: "1.97.1"
steps:
- uses: actions/checkout@v4
if: ${{ (matrix.os != 'macos-26-intel' || github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
- uses: actions/setup-python@v5
if: ${{ (matrix.os != 'macos-26-intel' || github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
with:
python-version: "3.13"
- name: Require the AppKit SDK used by this PR
if: ${{ (matrix.os != 'macos-26-intel' || github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
run: |
sdk_version="$(xcrun --sdk macosx --show-sdk-version)"
test "${sdk_version%%.*}" -ge 26
- uses: dtolnay/rust-toolchain@stable
if: ${{ (matrix.os != 'macos-26-intel' || github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
with:
toolchain: "1.97.1"
- uses: ./.github/actions/rust-cache
if: ${{ (matrix.os != 'macos-26-intel' || github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
id: rust-cache
with:
key: native-release-check
target: target/release
legacy-key: macos
- name: Check the release workspace
if: ${{ (matrix.os != 'macos-26-intel' || github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
run: cargo check --locked --workspace --release --timings
- name: Save compiled workload
if: ${{ always() && steps.rust-cache.outcome == 'success' && steps.rust-cache.outputs.cache-hit != 'true' }}
if: ${{ (matrix.os != 'macos-26-intel' || github.event_name != 'pull_request' || !github.event.pull_request.draft) && always() && steps.rust-cache.outcome == 'success' && steps.rust-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v4
with:
path: target/release
key: ${{ steps.rust-cache.outputs.cache-key }}
- name: Upload release check compilation timings
if: always()
if: ${{ (matrix.os != 'macos-26-intel' || github.event_name != 'pull_request' || !github.event.pull_request.draft) && always() }}
uses: actions/upload-artifact@v4
with:
name: release-check-timings-${{ matrix.os }}
+81
View File
@@ -0,0 +1,81 @@
name: PR size
# Labels every pull request by the number of changed source lines and fails
# oversized ones so they get split before review. Generated and documentation
# files do not count. Maintainers bypass the limit with the `size-exempt`
# label (a release integration branch is the usual case).
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
# Required checks must also report inside the merge queue; the size was
# already enforced on the pull request, so this is a pass-through.
merge_group:
permissions:
pull-requests: write
concurrency:
group: pr-size-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
pr-size:
name: pr-size
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/github-script@v7
if: github.event_name == 'pull_request'
with:
script: |
const pr = context.payload.pull_request;
const LIMIT = 1500;
const IGNORED = [
/^Cargo\.lock$/, /(^|\/)package-lock\.json$/, /(^|\/)yarn\.lock$/,
/^docs\//, /\.md$/, /\.snap$/, /\.svg$/, /\.png$/, /\.ico$/,
/^assets\//, /^locales\//, /\.lock$/,
];
const files = await github.paginate(github.rest.pulls.listFiles, {
...context.repo, pull_number: pr.number, per_page: 100,
});
let counted = 0, ignored = 0;
for (const f of files) {
const lines = f.additions + f.deletions;
if (IGNORED.some((re) => re.test(f.filename))) ignored += lines; else counted += lines;
}
const size = counted <= 100 ? "S" : counted <= 500 ? "M" : counted <= LIMIT ? "L" : "XL";
const labels = new Set(pr.labels.map((l) => l.name));
const wanted = `size/${size}`;
for (const l of labels) {
if (l.startsWith("size/") && l !== wanted) {
await github.rest.issues.removeLabel({ ...context.repo, issue_number: pr.number, name: l }).catch(() => {});
}
}
if (!labels.has(wanted)) {
await github.rest.issues.addLabels({ ...context.repo, issue_number: pr.number, labels: [wanted] });
}
core.info(`counted ${counted} lines (${ignored} ignored) in ${files.length} files -> ${wanted}`);
if (size !== "XL" || labels.has("size-exempt")) return;
const marker = "<!-- pr-size-guard -->";
const comments = await github.paginate(github.rest.issues.listComments, {
...context.repo, issue_number: pr.number, per_page: 100,
});
if (!comments.some((c) => c.body && c.body.includes(marker))) {
await github.rest.issues.createComment({
...context.repo, issue_number: pr.number,
body: [
marker,
`This pull request changes **${counted}** source lines (limit ${LIMIT}; docs, lockfiles and assets excluded). Changes this large cannot be reviewed reliably, so please split it into independent pull requests — one behaviour change each, with its own tests.`,
"",
`本 PR 改动 **${counted}** 行源码(上限 ${LIMIT},不计文档、lockfile、资源)。这么大的改动无法可靠审查,请拆成多个相互独立的 PR,每个只做一件事并附测试。`,
"",
"Maintainers can add the `size-exempt` label for integration branches. / 维护者可对集成分支加 `size-exempt` 放行。",
].join("\n"),
});
}
core.setFailed(`${counted} changed source lines exceed the ${LIMIT}-line limit; split the pull request or add size-exempt.`);
+12 -3
View File
@@ -74,6 +74,8 @@ raise any existing tracked private artifact for an explicit maintainer decision.
`architecture/notes/`; ordinary fixes do not create a note.
2. Keep one conceptual change per PR. A necessary extraction and its behavior
tests may accompany the feature; unrelated rewrites and formatting may not.
The `pr-size` check fails above 1500 changed source lines (docs, lockfiles
and assets excluded); split instead of asking for an exemption.
3. Put shared rules in their existing authority. UI modules adapt those rules;
they must not fork persistence, state transitions or domain behavior.
4. Add regression tests that fail for the defect, and test error/cancellation
@@ -122,9 +124,14 @@ compile check into a claim that UI tests or a packaged application were run.
## Review and enforcement
`architecture-contracts` is the stable PR job name. Maintainers must enable it as
a required check and require Code Owner approval in the target branch ruleset;
see the [activation checklist](docs/project-constraints.md#server-side-activation).
`architecture-contracts`, `lint (fmt, clippy)`, `pr-size`, the five
`Tests (<os>)` jobs and both `Release workspace (<os>)` jobs are required checks
on `main`, together with Code Owner approval; see the
[activation checklist](docs/project-constraints.md#server-side-activation).
Open pull requests as drafts while iterating: drafts run Linux, Windows x64 and
Apple Silicon on every push (about 20 minutes). Marking the PR ready for review
adds Intel Mac and Windows ARM64, so every merge is verified on all five
platforms without paying for the scarce runners on each push.
Local hooks are convenient, but bypassable; they are not the enforcement boundary.
Submitting a workflow or `CODEOWNERS` file does not configure server-side rules.
@@ -137,6 +144,8 @@ feature work; there is no routine `--skip-architecture` option.
## 中文摘要
- 先读架构图、工程合同和决策记录;按职责拆分,不按行号切片。
- 一个 PR 只做一件事;改动超过 1500 行源码(不计文档、lockfile、资源)`pr-size` 会失败,请拆分。
- 迭代期间用 Draft PR:每次 push 跑 Linux / Windows x64 / Apple Silicon(约 20 分钟);标记 Ready for review 后补跑 Intel Mac 与 Windows ARM64,十项必需检查全绿才能合并。
- 2000 行是现有仓库的防灾上限,800 行只提示审查,不是“大厂标准”。
- 普通功能 PR 不得增加存量债务;有问题的规则可以修订,但要有反例、测试和维护者审批。
- 新增核心抽象、依赖方向、持久化或线程模型改变要先说明设计,不强迫每个小修复写 ADR。