diff --git a/.github/required-ci-ruleset.json b/.github/required-ci-ruleset.json new file mode 100644 index 00000000..8ff7b5cb --- /dev/null +++ b/.github/required-ci-ruleset.json @@ -0,0 +1,33 @@ +{ + "name": "Require validated pull requests", + "target": "branch", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "ref_name": { + "include": ["refs/heads/main", "refs/heads/0.3.2", "refs/heads/release/0.4.0"], + "exclude": [] + } + }, + "rules": [ + { + "type": "pull_request", + "parameters": { + "dismiss_stale_reviews_on_push": false, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_approving_review_count": 0, + "required_review_thread_resolution": false + } + }, + { + "type": "required_status_checks", + "parameters": { + "required_status_checks": [{ "context": "CI required", "integration_id": 15368 }], + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false + } + }, + { "type": "non_fast_forward" } + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 016df8d4..f6bfb6b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,11 +6,9 @@ on: # staging branch before the final release PR. push: branches: [main, 0.3.2, release/0.4.0] - # And a pull request has to show its result before it is merged, not after. GitHub reads this - # file from the pull request's own head, so listing the release branch here is what makes a - # pull request against a release branch run the gate at all. + # Every PR needs evidence, including stacked changes targeting a feature branch. + # No target-branch or path filter: an omitted run cannot satisfy CI required. pull_request: - branches: [main, 0.3.2, release/0.4.0] # A merge queue builds the queued commits before they reach `main`, and it only accepts a group # whose required checks passed there. Without this trigger the workflow simply never runs for a # queued group, so enabling a merge queue would silently un-gate every merge. @@ -37,8 +35,8 @@ concurrency: # because a half-built release branch is worse than a queued one. cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: - # The POSIX legs are the gate: a red Linux or macOS run fails `ci-required` and blocks the merge. - # Windows is split into its own, non-blocking job directly below (#316). + # The blocking POSIX legs are Linux x64 and macOS arm64: a red one fails `ci-required`. + # macOS Intel and Windows are separate non-blocking signals below (#316). validate-posix: strategy: fail-fast: false @@ -103,6 +101,56 @@ jobs: if-no-files-found: error retention-days: 7 + # macOS Intel is a signal, not a gate: this leg measured 318 s on the PR run that added + # it, past the approved ">300 s -> non-blocking" line (#316). It still runs on every push, + # pull request and merge group, and uploads the darwin-x64 artifact for the release job. + validate-macos-intel: + runs-on: macos-15-intel + continue-on-error: true + timeout-minutes: 35 + env: + CARGO_BUILD_JOBS: '3' + DSH_OPS_SUITE_TIMEOUT_MS: '300000' + DSH_OPS_RETRY_TIMEOUTS: '1' + steps: + - uses: actions/checkout@v7 + with: + # The provenance regression verifies exact blobs from the pinned 0.3.1 commit. + fetch-depth: 0 + - uses: actions/setup-node@v7 + with: + node-version: '24' + - name: Verify runner identity + run: node -e "if(process.platform+'-'+process.arch!=='darwin-x64')throw Error('Unexpected runner'); console.log(process.platform,process.arch)" + - name: Set up pinned Rust + shell: bash + run: rustup toolchain install 1.97.1 --profile minimal && rustup default 1.97.1 + - name: Reuse exact locked Rust build cache + uses: actions/cache@v6 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + native/target + key: ops-031-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/Cargo.lock') }} + restore-keys: ops-031-${{ runner.os }}-${{ runner.arch }}- + - name: Install locked dependencies + run: npm ci --ignore-scripts --registry=https://registry.npmjs.org + - name: Functional gate (skips, suite inventory, packed artifact) + shell: bash + run: node scripts/verify-gate.mjs --skip-install + - name: Verify native staged closure + run: node bin/native-runtime.mjs check --platform darwin-x64 + - name: Check shell payload platform filtering + shell: bash + run: node -e "const fs=require('fs');if(process.platform!=='win32'){for(const n of ['bash','pwsh'])if(fs.existsSync('node_modules/@dsh-ops/'+n+'-win32-x64'))throw Error('Windows shell installed on POSIX')}" + - uses: actions/upload-artifact@v7 + with: + name: native-darwin-x64 + path: runtime/darwin-x64/ + if-no-files-found: error + retention-days: 7 + # Windows still runs on every push, pull request and merge group — it is never skipped — but its # result is a **signal, not a gate**. The job is `continue-on-error: true`, and `ci-required` does # not list it in `needs`, so the aggregate neither waits for it nor fails with it. That is what @@ -238,8 +286,9 @@ jobs: retention-days: 7 # The bench smoke: `workflow_dispatch` only, off by default, and informational (like `tools-trial` - # above). One platform is enough, because it exercises the harness rather than the platform — the - # `validate-posix` matrix is the gate and `validate-windows` its non-blocking counterpart. Both + # above). Three host platforms exercise the portable harness; the + # `validate-posix` matrix is the gate, with `validate-windows` and `validate-macos-intel` as + # non-blocking counterparts. Both # sides are the *same* staged worker, so the run # proves only what a harness self-check can prove: the corpus builds and is removed, the worker # answers identically twice, and every query lands on `MATCH` or a declared `EXPECTED_*`. It cannot @@ -248,7 +297,20 @@ jobs: # **never** run in CI. bench-smoke: if: ${{ github.event_name == 'workflow_dispatch' && inputs.bench }} - runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + target: linux-x64 + worker: dsh-ops-worker + - os: windows-latest + target: win32-x64 + worker: dsh-ops-worker.exe + - os: macos-latest + target: darwin-arm64 + worker: dsh-ops-worker + runs-on: ${{ matrix.os }} continue-on-error: true timeout-minutes: 30 env: @@ -293,7 +355,7 @@ jobs: - name: Tool-level comparison, one binary on both sides shell: bash run: | - worker="runtime/linux-x64/dsh-ops-worker" + worker="runtime/${{ matrix.target }}/${{ matrix.worker }}" mkdir -p bench/runs/smoke/fixtures node bench/cli.mjs tools \ --baseline "$worker" --candidate "$worker" \ @@ -309,7 +371,7 @@ jobs: - uses: actions/upload-artifact@v7 if: always() with: - name: bench-smoke-report + name: bench-smoke-${{ matrix.target }}-report path: | bench/runs/smoke/** bench/runs/*/run.json @@ -383,7 +445,8 @@ jobs: # names in sync — and silently losing the gate when a runner label changes. This job collapses them. # # It waits **only** on blocking jobs. Every `continue-on-error: true` job — `validate-windows`, - # `tools-trial`, `bench-smoke` — is deliberately absent from `needs` and from the loop below: + # `validate-macos-intel`, `tools-trial`, `bench-smoke` — is deliberately absent from `needs` and + # from the loop below: # listing one here would keep the required check pending until that job finished, which is a gate # in everything but name. `scripts/check-workflows.mjs` enforces both halves of that rule. ci-required: @@ -410,7 +473,7 @@ jobs: done # Not in `needs` on purpose: this check must never wait for them, so their result is not # available here. Read them on their own jobs; a red one is a signal, not a merge blocker. - echo "validate-windows, tools-trial, bench-smoke: non-blocking (continue-on-error), not waited on by this check" + echo "validate-windows, validate-macos-intel, tools-trial, bench-smoke: non-blocking (continue-on-error), not waited on by this check" if [ "$failed" != 0 ]; then echo "::error::a blocking job did not succeed; read the failing job above" exit 1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 92c486af..81787446 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,8 +26,8 @@ name: Release # checksum manifest (docs/PROVENANCE.md), not a second, unmaintained mechanism, and an # attestation would need `id-token: write` / `attestations: write` on the repository. # -# SCOPE: exactly the three declared targets in lib/ptc/runtime.js — win32-x64, -# linux-x64, darwin-arm64. Intel macOS, Windows arm64 and Linux arm64 are outside the +# SCOPE: exactly the four declared targets in lib/ptc/runtime.js — win32-x64, +# linux-x64, darwin-arm64 and darwin-x64. Windows arm64 and Linux arm64 are outside the # matrix; `bin/native-runtime.mjs` refuses them, and `bin/check-release.mjs` fails the # run if any other target appears under `runtime/`. on: @@ -57,6 +57,7 @@ jobs: - { runner: windows-latest, target: win32-x64, triple: x86_64-pc-windows-msvc } - { runner: ubuntu-24.04, target: linux-x64, triple: x86_64-unknown-linux-gnu } - { runner: macos-latest, target: darwin-arm64, triple: aarch64-apple-darwin } + - { runner: macos-15-intel, target: darwin-x64, triple: x86_64-apple-darwin } runs-on: ${{ matrix.runner }} timeout-minutes: 45 env: @@ -166,6 +167,11 @@ jobs: name: native-darwin-arm64 path: runtime/darwin-arm64 + - uses: actions/download-artifact@v8 + with: + name: native-darwin-x64 + path: runtime/darwin-x64 + - name: Prepare the host worker for functional suites # Boundary suites intentionally resolve their checkout-local host build. # This build is test-only; the staged release runtimes remain the cold matrix artifacts. @@ -184,7 +190,7 @@ jobs: # Publishing must not start before this step is green — it is the only reason # the `publish` job sits behind `needs`. `verify:release` is # `validate-manifest` + the full repository suite + `bin/check-release.mjs`, - # which demands all three declared targets, matches every receipt hash against + # which demands all four declared targets, matches every receipt hash against # the staged bytes, and re-checks the Windows IPC binary and source digests. # A partial download, a foreign-architecture binary, a stale receipt or a # manifest/target disagreement fails here instead of shipping. diff --git a/README.en.md b/README.en.md index 0123cde1..2a41ea39 100644 --- a/README.en.md +++ b/README.en.md @@ -25,6 +25,9 @@ OPS adds bounded file and document tools to DSH. Native mode returns compact, li PTC code receives typed Outcome values from the same one-shot Rust worker. Host integration is strict TypeScript, compiled to the existing JavaScript package paths. +Supported: Windows x64, Linux x64, and macOS 13+ on Apple Silicon or Intel +(`darwin-arm64`, `darwin-x64`). See [macOS](docs/macos.md). + > [!IMPORTANT] > This branch is the **0.4.0 release candidate**, not a published release. Approved acceptance requires > at least 40% less BM-01 output than OPS 0.3.x; the official reader is a separate comparison. @@ -97,6 +100,10 @@ A regular DSH CLI cannot manage the reserved `desktop` profile; do not confuse i #### Update +Host compatibility is declared for DSH **0.2.0-rc.2 and 0.2.1-rc.2**. See +[host versions and display metadata](docs/usage.md#host-compatibility-and-display-metadata) +for component icon exports, profile refresh and config/schema checks. + Re-run the same `npx --yes dsh-ops@latest install --profile ...`, or use the official CLI `add dsh-ops@latest`. Reload when the app asks; restart the app when replacing code that is already loaded. #### Uninstall and status @@ -166,4 +173,4 @@ Versions before 0.3.2 bundled and distributed [yc-duan](https://github.com/yc-du ## Star trend -[![Star trend chart](https://api.star-history.com/svg?repos=T-Auto/dsh-ops&type=Date)](https://star-history.com/#T-Auto/dsh-ops&Date) \ No newline at end of file +[![Star trend chart](https://api.star-history.com/svg?repos=T-Auto/dsh-ops&type=Date)](https://star-history.com/#T-Auto/dsh-ops&Date) diff --git a/README.md b/README.md index ad77e185..ccf70dc3 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,10 @@ dsh plugin --profile dsh-tui add dsh-ops #### 更新方法 +宿主兼容范围明确声明为 DSH **0.2.0-rc.2 与 0.2.1-rc.2**。 +组件图标导出、profile 刷新及配置/schema 检查见 +[宿主兼容范围与展示元数据](docs/使用指南.md#宿主兼容范围与展示元数据)。 + 重新运行相同的 `npx --yes dsh-ops@latest install --profile ...`,或用官方 CLI `add dsh-ops@latest`。按应用提示重载;替换已加载代码时重启应用。 #### 卸载与查看状态 diff --git a/README.zh.md b/README.zh.md index 1e8bfa6e..7eb4fe02 100644 --- a/README.zh.md +++ b/README.zh.md @@ -23,6 +23,8 @@ OPS 为 DSH 增加有界文件与文档工具。Native 模式返回紧凑、带行号的文本;PTC 代码从同一个按调用启动的 Rust worker 获取类型化 Outcome。宿主集成使用严格 TypeScript,编译后保持已有 JavaScript 包路径。 +支持:Windows x64、Linux x64,以及 macOS 13+ 的 Apple Silicon 与 Intel(`darwin-arm64`、`darwin-x64`)。见 [macOS](docs/macos.zh.md)。 + > [!IMPORTANT] > 本分支是 **0.4.0 发布候选**,尚未发布。已批准的验收范围为:BM-01 相比 OPS 0.3.x 输出减少至少 40%,官方读取另列对照;BM-03 对文件/发现 schema 执行 400 token 上限,同时保留 Bash 启动常驻并单列完整总数。最终实测证据与功能 CI 缺一不可。 > 详情见[发布状态与限制](docs/0.4.0发布候选.md)。下方 `@latest` 命令安装已发布版本;评估候选版本需自行构建本检出目录并打包。 @@ -77,6 +79,10 @@ dsh plugin --profile dsh-tui add dsh-ops #### 更新方法 +宿主兼容范围明确声明为 DSH **0.2.0-rc.2 与 0.2.1-rc.2**。 +组件图标导出、profile 刷新及配置/schema 检查见 +[宿主兼容范围与展示元数据](docs/使用指南.md#宿主兼容范围与展示元数据)。 + 重新运行相同的 `npx --yes dsh-ops@latest install --profile ...`,或用官方 CLI `add dsh-ops@latest`。按应用提示重载;替换已加载代码时重启应用。 #### 卸载与查看状态 diff --git a/bench/README.md b/bench/README.md index 1718ae85..1db50ff4 100644 --- a/bench/README.md +++ b/bench/README.md @@ -1,262 +1,104 @@ -# bench +# Benchmark Manual -轻量、零依赖、自包含的测量工装:一套入口、两种测量模式、一份数据契约。它**只在仓库里跑** —— 不随 -npm 包发布、不参与构建、不进 `verify:release` / `release.yml`,也从不成为默认门禁(设计议题 #205)。 +## 测试前的准备 -本套收编了两份来源:**#103(@OldLigant)** 的无模型工具级确定性对比,和 **#183(@Richeir)** 的模型 -驱动 token A/B 骨架;口径对齐 **#173** 的意图 —— 本地性能测量只作人类证据,不进 CI。归属记录见 -`docs/CHANGELOG.md`。 +Linux / macOS:使用当前平台构建的 worker;`setup --dry-run` 会说明跳过 Windows PowerShell pin、PSModulePath、Windows Bash IPC 和随包 Windows shell 检查的原因。当前 shell 行已支持各平台,不要再从早期 Windows-only 默认值推断配置。模型 sweep 仍需自己的 OpenAI 兼容服务与凭据;`tools` 自检不调用模型。 -## 三种入口形态 +Linux / macOS: build the worker for the current host. `setup --dry-run` explains skipped Windows-only checks. `ab`, `compare`/`report`, `panel` and `tools` use the same entry point on every platform. A model sweep needs your own OpenAI-compatible endpoint; the tools self-check needs no model. Every sweep now fingerprints the composed profiles even without `--seal`, recording platform and component configuration in the arm summaries. `enabled` is a configuration fact, not proof of tool readiness; unresolved expressions remain `unknown`. -| 形态 | 命令 | 用途 | -| --- | --- | --- | -| 唯一跨平台入口 | `node bench/cli.mjs ` | 全部子命令: `setup \| ab \| report \| panel \| tools` | -| npm scripts | `npm run bench:ab` 等 | 日常使用;装包后 `bench/` 不存在,脚本给出明确报错而不是 ENOENT | -| 底层单脚本 | `node bench/setup.mjs` / `bench/compare.mjs` / `bench/report/panel.mjs` / `bench/tools/index.mjs` | 排障与单步重跑;`ab` 只是按顺序把 runner 与 compare 连起来 | +`compare` 默认拒绝不同平台(或已知/未知平台)混配,退出码为 2;诊断时可加 `--allow-cross-platform`,报告会明确标记差异不是插件效应。旧数据两侧都缺平台时仍可读取,显示 `unknown`,不能据此声称同平台。CI 的可选 tools-only smoke 覆盖 Linux / Windows / macOS,各自上传 artifact;不运行模型 sweep,不产出跨平台结论。 -`node bench/cli.mjs --help` 列出全部子命令,` --help` 给出该命令自己的旗标。任何路径都从 -模块位置解析(在哪个工作目录下调用都一样),且**不经过 shell** —— `.cmd`/`.ps1` shim 无法被 Node 管道, -而 shell 会重新解释每个参数。 +`compare` rejects mismatched platforms with exit 2. `--allow-cross-platform` is an explicit diagnostic override and adds a “not a plugin effect” warning. Both-unknown legacy archives remain readable. The optional, non-blocking tools smoke runs on Linux, Windows and macOS with separate artifacts. -## 退出码 +```console +npm ci --ignore-scripts +npm run build && npm run build:ptc # lib/ + 原生 worker +``` -| 码 | 含义 | -| --- | --- | -| `0` | 正常;或一个**弃权**的成本门(噪声底未知 ⇒ 不读方向) | -| `1` | 失败:`report` 的判决门失败 · `tools` 出现被接受状态之外的项 · `setup` 的某个检查或步骤失败 | -| `2` | 用法/输入错误:未知命令、未知旗标、缺值、越界参数;装包后缺 `bench/` 也走这里 | -| `3` | **已声明但尚未接线**:目标文件不存在,或存在但没导出 `main(argv, io)` —— 打印所属切片与目标路径,绝不静默成功 | +### 再准备 dsh + 两个 profile -`report`(以及 `ab` 的最后一步)的退出码**就是裁决**:`1` 表示门禁失败;一个没能跑起来的成本门 -(噪声底未知等)弃权并退 `0`。 +```console +npm i -g @deepseek-ai/dsh +node bench/cli.mjs setup # 会自动添加 2 个 headless profile(base、dsh),然后在 dsh 上面安装本地代码 build 出来的插件 +``` -## 两种测量模式(以及各自不该断言什么) +### 配 LLM 链接 -| 模式 | 入口 | 用模型 | 断言什么 | 不该断言什么 | -| --- | --- | --- | --- | --- | -| 成本 A/B | `bench ab` → `bench report` | 是 | **通过率先行**(任一任务 ✅→❌ 就是回归);只有**两侧都通过**的配对参与成本统计;方向必须超过 A/A 噪声底 | 时间;落在噪声带内的 Δ;没有噪声底时的任何方向 | -| 工具级确定性对比 | `bench tools` | 否 | 只有 `MATCH / EXPECTED_DIFFERENCE / EXPECTED_REJECTION` 算通过,其余状态(含 `MISMATCH` / `UNSTABLE`)都退 `1` | 时间;同二进制两侧的"提速";任何性能结论 | -| 本地性能(#173 的意图) | 各命令打印的 median | 否 | **什么都不裁决** —— median 只作人类证据 | 进 CI、进门禁、进任何性能回归判定 | - -三者共同的一条:**永不裁决时间**。 +跑 `node bench/cli.mjs setup` 会在自动创建;也可以手动创建(会覆盖已有的): -### 成本 A/B 的口径 +```console +cp bench/llm.patch.example.yml bench/llm.patch.yml # bash +``` -1. **通过率先行**:`--fail-on both`(默认)先判通过率,任一任务从 ✅ 变 ❌ 即失败;`--fail-on pass-drop` - 只判通过率,`regression` 只判 token,`none` 只出报告; -2. **配对**:按 `task × repeat` 配对;`--regression-scope task|total|both` 决定 token 回归判每个任务、 - 判总量还是两者都判;`--tolerance-pct N`(默认 `2`)是回归容忍带; -3. **只有两侧都通过的配对参与成本统计** —— 一侧失败的配对连 token 一起被排除,并在报告里单列; -4. **A/A 噪声底**:没有噪声底时,所有成本方向弃权(`noise-floor-unknown`),报告只给测到的 Δ。 +填 `bench/llm.patch.yml`: -### 工具级确定性对比的口径 +- `baseURL`:服务器地址,如 `https://host/v1` +- `` :模型 id,两处必须一致 +- `apiKeyEnv`:放密钥的环境变量名,如 `BENCH_SERVER_API_KEY` +- `contextWindow` / `maxTokens`:模型真实窗口 / 单次输出上限(默认最大 256K 上下文,单次最大 32K 输出) -- `tools/measure.mjs` 的 `compare()` **判断顺序本身就是语义**:`TIMEOUT → ERROR → UNSTABLE → - (!sameAsBaseline ? (expectedDifference ? EXPECTED_DIFFERENCE : MISMATCH)) → expectedError 命中 ? - EXPECTED_REJECTION → (!eligible ? WORKER_ERROR) → MATCH`。其中"两侧返回同一个 error 信封"若不在 - 任务里显式声明为 `expectedError`,必须落 `WORKER_ERROR`,**不能**当成 `MATCH`(否则一次崩溃会被报告成提速); -- `speedup` 只在 `MATCH` 且 candidate median > 0 时才有意义;任一轮超时或不稳定,整项不给方向; -- 夹具在**计时之外**用 `mkdtemp` 生成,跑完删除;删除**之前**先整树盘点,遇到不是自己创建的内容就报错并保留现场; -- 报告**独占创建**(`flag: 'wx'`):重跑不覆盖上一次的证据,而是退 `1` 并提示 `Refusing to overwrite`; -- 每次调用都显式带 `outputMode`(native 默认是 content):省略参数两侧就不可比。 +密钥只放在 `~/.dsh/.credentials.yaml` 的 `ref` 节点下面的环境变量里面,这样避免出现在项目源文件、对话的上下文 -## 子命令 +## 怎么跑全量 -| 子命令 | 目标 | 做什么 | -| --- | --- | --- | -| `setup` | `bench/setup.mjs` | 自检 + 幂等准备两侧 profile(`bench-base` / `bench-ops`),打印跑 sweep 的那一条命令 | -| `ab` | `bench/run.mjs` → `bench/compare.mjs` | 先基线、再候选、再比较,三步共用**一个** `bench/runs/benchmark-test-NN` 组目录 | -| `report` | `bench/compare.mjs` | 从已封存的 `run.json` 渲染报告(`DSH-OPS Benchmark Report .md`) | -| `panel` | `bench/report/panel.mjs` | 生成/打开一个只读 `run.json` 的 `panel.html` | -| `tools` | `bench/tools/index.mjs` | 无模型的工具级确定性对比 | +```console +node bench/cli.mjs ab +``` -### `setup` +## 怎么跑单个 task -`--base-profile` / `--cand-profile` / `--template`(默认 `headless`),`--pin-shell ` / `--no-pin`, -`--permission-mode ` / `--no-permission-mode`(`danger-full-access`(默认)/ `workspace-write` / -`read-only` / `none`),`--plugin `(`local` 默认装本 checkout 的本地构建 / npm spec / `none`), -`--reinstall`,`--dsh-home `,`--dsh `,`--force`,`--dry-run`,`--no-build-check`。 +```console +node bench/cli.mjs ab --task csv-sum +``` -顺序是故意的:先跑 preflight(权限组合、`PSModulePath`、本地构建),**检查失败就在碰 profile 之前拒绝**; -一套配置一旦会被写成"量的是权限模式"或"跑的是旧构建",改 profile 也修不好,只会在下一次运行里让报错更难读。 -`--dry-run` 打印每一步写入与命令,什么也不改。 +## 结果在哪看 -`setup` 会把 `bench/llm.patch.example.yml` 复制成 `bench/llm.patch.yml`(仅当后者不存在),之后的每次 dsh -调用都以 `--patch` 带上它;把服务器、模型 id 与 `apiKeyEnv` 填进副本即可,**密钥只放环境变量或 -`~/.dsh/.credentials.yaml`,绝不写进这个文件**。副本是操作员的,存在就一字不改。 - -### `ab` +- 在 bench/runs 文件夹里面可以看到 +- 文件夹名称格式为 `benchmark-YYYY-MM-DD-NN`(按当天日期编号,从 `-00` 起) +- 跑完自动在组目录写 `DSH-OPS Benchmark Report .md` -`--task `(可重复)、`--repeat `、`--folder `、`--base