diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md index a46b729a4d..87b1c0847b 100644 --- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md +++ b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md @@ -10,7 +10,7 @@ Aggregate jobs such as documentation synchronization hide long sequential chains ## Decision -[scripts/run-gates.ts](../../../../scripts/run-gates.ts) owns the bounded scheduler used by CI and `doc-sync`. It expands named modes into leaf gates, respects artifact dependencies, buffers attributable output, and accepts `DSH_GATE_CONCURRENCY` when a caller needs a different worker bound. +[scripts/run-gates.ts](../../../../scripts/run-gates.ts) owns the bounded scheduler used by CI, `doc-sync`, and the opt-in `check:all` command. It expands named modes into leaf gates, respects artifact dependencies, buffers attributable output, and accepts `DSH_GATE_CONCURRENCY` when a caller needs a different worker bound. [scripts/publint-all.ts](../../../../scripts/publint-all.ts) discovers packages from `packages//` and runs `publint` with a worker pool sized from `availableParallelism()`. `DSH_PUBLINT_CONCURRENCY` can cap or raise the worker count for local machines and CI runners with different resource profiles. Results are buffered per package and printed in deterministic package order, so parallel execution does not scramble each package's log block. diff --git a/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.i18n.yaml b/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.i18n.yaml index a21afc8e6a..361f7a0bd0 100644 --- a/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-22-fast-local-git-hooks.md: bab47c6479f1a2c01cbfa7152b1d610917fb6175 -2026-07-22-fast-local-git-hooks.zh.md: 7b279b1a9ad86e09ed5cf7d2470cb61ff17e09b7 +2026-07-22-fast-local-git-hooks.md: a07af1cd424c86f7fa80ea946cd5012362cc66eb +2026-07-22-fast-local-git-hooks.zh.md: 78d4ea8980476609a9140737a75152eba123b308 diff --git a/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.md b/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.md index bab47c6479..a07af1cd42 100644 --- a/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.md +++ b/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.md @@ -14,7 +14,7 @@ Fast hooks still need to reject cheap, high-confidence defects before work leave [lefthook.yml](../../../../lefthook.yml) keeps both hooks as bounded local checkpoints. Pre-commit runs sequentially: ESLint fixes and re-stages changed JavaScript and TypeScript, `git diff --cached --check` rejects staged whitespace errors, and the vendor manifest guard checks vendored-source metadata. Pre-push invokes the repository TypeScript binary directly in incremental build mode. -Neither hook runs tests, snapshots, documentation checks, builds, hygiene, or the gate scheduler. The `check:pre-push` package script and `pre-push` scheduler mode do not exist; [scripts/run-gates.ts](../../../../scripts/run-gates.ts) continues to own CI and `doc-sync` scheduling. +Neither hook runs tests, snapshots, documentation checks, builds, hygiene, or the gate scheduler. The opt-in `check:all` package script selects the `check-all` scheduler inventory in [scripts/run-gates.ts](../../../../scripts/run-gates.ts) independently of the hooks; it is a contributor command, not an agent instruction. Agents inspect the outgoing diff and run the narrowest tests and checks that cover its behavior once. CI owns exhaustive coverage, built-artifact checks, and the platform matrix. A complete local rehearsal is reserved for an explicit request, CI diagnosis, or a repository-wide change that cannot be validated credibly by narrower evidence. @@ -31,6 +31,6 @@ This decision supersedes the local-hook portion of [Parallel pre-push gates](202 ## Consequences -Normal commits take the staged-file lint critical path, and warm pushes take the incremental typecheck critical path. Hook latency is observed in development and PR evidence rather than enforced by a timing test whose result would depend on host load and cache state. +Normal commits take the staged-file lint critical path, and warm pushes take the incremental typecheck critical path. Contributors retain a one-command opt-in rehearsal without widening the hook critical paths or the agent-required validation set. Hook latency is observed in development and PR evidence rather than enforced by a timing test whose result would depend on host load and cache state. Local publication no longer proves the exhaustive repository matrix. Agents must select relevant behavioral evidence, reviewers must evaluate whether that selection matches the diff, and CI supplies the comprehensive signal once per pushed revision. diff --git a/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.zh.md b/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.zh.md index 7b279b1a9a..78d4ea8980 100644 --- a/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.zh.md +++ b/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.zh.md @@ -14,7 +14,7 @@ agent(智能体)已经会运行能够覆盖自身改动的测试和检查, [lefthook.yml](../../../../lefthook.yml) 将两个钩子都保留为有界的本地检查点。Pre-commit 按顺序运行:ESLint 修复改动过的 JavaScript 和 TypeScript 文件并重新暂存,`git diff --cached --check` 拒绝暂存 diff 中的空白错误,vendor manifest(元数据清单)守卫检查 vendor 源码元数据。Pre-push 直接调用仓库内的 TypeScript 二进制,并启用增量构建模式。 -两个钩子都不运行测试、快照、文档检查、构建、`hygiene` 或门禁调度器。`check:pre-push` 包脚本与调度器的 `pre-push` 模式不存在;[scripts/run-gates.ts](../../../../scripts/run-gates.ts) 继续负责 CI 和 `doc-sync` 调度。 +两个钩子都不运行测试、快照、文档检查、构建、`hygiene` 或门禁调度器。可选运行的 `check:all` 包脚本独立于这些钩子,从 [scripts/run-gates.ts](../../../../scripts/run-gates.ts) 中选择 `check-all` 调度器清单;它是贡献者命令,而非对 agent 的指令。 agent 检查待推送的 diff,并仅运行一次能够覆盖其行为的最小范围测试和检查。CI 负责全量覆盖率门禁、构建产物检查与平台矩阵。只有在明确要求、诊断 CI,或涉及全仓库的改动无法由范围更窄的证据得到可信验证时,才完整运行一遍本地检查矩阵。 @@ -31,6 +31,6 @@ agent 检查待推送的 diff,并仅运行一次能够覆盖其行为的最小 ## 结果 -普通提交的关键路径是暂存文件 lint,缓存已预热时推送的关键路径是增量类型检查。钩子耗时只作为开发观察数据和 PR(Pull Request)证据记录,不设置会受主机负载与缓存状态影响的计时测试。 +普通提交的关键路径是暂存文件 lint,缓存已预热时推送的关键路径是增量类型检查。贡献者仍可选择用一条命令完整演练,且不会扩展钩子关键路径或 agent 必须运行的验证集合。钩子耗时只作为开发观察数据和 PR(Pull Request)证据记录,不设置会受主机负载与缓存状态影响的计时测试。 从本地推送成功不再能证明仓库完整矩阵已通过。agent 必须选择相关的行为证据,评审人必须判断该选择是否与 diff 相符,CI 则对每个推送版本提供一次全面信号。 diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 20eb3b4ca1..4ea0bafef3 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -development.md: 10406cebae1bf83fff663903b1478c9acb8476a1 -development.zh.md: 50051ffd631518b37c3ad96f5fd3830cf6893ec9 +development.md: 3559b09d86395707f222aad0a281c9db1246c24f +development.zh.md: 8664a3291c04a5338fdadbce8f24b160cc9ec0a8 diff --git a/docs/development.md b/docs/development.md index 10406cebae..3559b09d86 100644 --- a/docs/development.md +++ b/docs/development.md @@ -65,6 +65,8 @@ The vendor manifest guard checks that changes under `vendor/*/src` are staged wi The hooks intentionally do not run tests, snapshots, documentation checks, builds, or hygiene. Contributors run the [checks relevant to the changed behavior](../AGENTS.md#run-relevant-checks-locally) once; CI owns exhaustive coverage, built-artifact smokes, and the Node 22.19, 24, and 26 compatibility matrix. +Contributors can opt into the comprehensive local gate set with `pnpm run check:all`. The command is independent of both Git hooks and is not an agent instruction. + ## CI gates The keyless [CI workflow](../.github/workflows/ci.yml) groups independent gates into broad lanes and runs a smaller compatibility signal across supported Node versions. Artifact consumers wait for one build within their lane. The separate real-API workflow runs `pnpm run test:e2e` with its configured worker bound. See [scripts/run-gates.ts](../scripts/run-gates.ts) and the workflow files for the current gate and job inventory. @@ -77,6 +79,7 @@ Use these from the repo root: pnpm run test # unit tests pnpm run test:coverage # unit tests with per-file coverage gates pnpm run test:e2e # real-API tests; self-skips without DEEPSEEK_API_KEY +pnpm run check:all # comprehensive opt-in gate set; not wired to Git hooks pnpm run typecheck # build package/vendor outputs, then typecheck examples, tests, and scripts pnpm run lint # eslint . pnpm run lint:fix # eslint . --fix diff --git a/docs/development.zh.md b/docs/development.zh.md index 50051ffd63..8664a3291c 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -65,6 +65,8 @@ vendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `v 这些钩子有意不运行测试、快照、文档检查、构建或 `hygiene`。贡献者只运行一次[与改动行为相关的检查](../AGENTS.md#run-relevant-checks-locally);CI 负责全量覆盖率门禁、构建产物冒烟测试,以及 Node 22.19、24 和 26 兼容性矩阵。 +贡献者可以选择运行 `pnpm run check:all`,执行全面的本地门禁集。该命令独立于两个 Git 钩子,也不是对 agent 的指令。 + ## CI 门禁 keyless [CI 工作流](../.github/workflows/ci.yml) 将独立门禁分组到若干宽粒度 lane,并在受支持的 Node 版本上运行一组较小的兼容性检查。产物消费方在各自 lane 内等待一次 build。单独的真实 API 工作流按其配置的 worker 上限运行 `pnpm run test:e2e`。当前门禁和 job 清单以 [scripts/run-gates.ts](../scripts/run-gates.ts) 和工作流文件为准。 @@ -77,6 +79,7 @@ keyless [CI 工作流](../.github/workflows/ci.yml) 将独立门禁分组到若 pnpm run test # unit tests pnpm run test:coverage # unit tests with per-file coverage gates pnpm run test:e2e # real-API tests; self-skips without DEEPSEEK_API_KEY +pnpm run check:all # comprehensive opt-in gate set; not wired to Git hooks pnpm run typecheck # build package/vendor outputs, then typecheck examples, tests, and scripts pnpm run lint # eslint . pnpm run lint:fix # eslint . --fix diff --git a/package.json b/package.json index cab2592fea..611e1d49e2 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "test:snapshot:refresh": "DSH_SNAPSHOT=refresh vitest run --config vitest.snapshot.config.ts", "test:web": "npm run build:web && vitest run --config vitest.web.config.ts", "test:gui": "vitest run packages/client packages/host", + "check:all": "tsx scripts/run-gates.ts check-all", "check:ci": "tsx scripts/run-gates.ts ci-primary", "check:ci:static": "tsx scripts/run-gates.ts ci-static", "check:ci:lint": "tsx scripts/run-gates.ts ci-lint", diff --git a/packages/ui/tui/tests/tui.spec.ts b/packages/ui/tui/tests/tui.spec.ts index d942dfef2b..c0f7962d91 100644 --- a/packages/ui/tui/tests/tui.spec.ts +++ b/packages/ui/tui/tests/tui.spec.ts @@ -665,13 +665,24 @@ describe('pi-tui chat lifecycle and transcript', () => { }) it('refreshes the running status elapsed time on its own timer', async () => { - const result = await setup({ status: 'running' }) - result.terminal.output = '' - // The loader repaints "0s" until the controller's own interval fires; a - // non-zero elapsed proves the refresh, not just the loader's animation. - await new Promise(resolve => setTimeout(resolve, 1_300)) - expect(result.terminal.output).toMatch(/Waiting for the first token [1-9]s/) - await dispose(result) + let now = 0 + const intervals = vi.spyOn(globalThis, 'setInterval') + let result: Awaited> | undefined + try { + result = await setup({ status: 'running', now: () => now }) + const refresh = intervals.mock.calls.find(([, interval]) => interval === 1_000)?.[0] + if (typeof refresh !== 'function') throw new Error('TUI did not register its elapsed-status refresh interval') + result.terminal.output = '' + // The loader repaints "0s" until the controller's own interval fires; a + // non-zero elapsed proves the refresh, not just the loader's animation. + now = 1_000 + refresh() + await tick() + expect(result.terminal.output).toContain('Waiting for the first token 1s') + } finally { + if (result !== undefined) await dispose(result) + intervals.mockRestore() + } }) it('shows minutes and seconds once a step passes a minute', async () => { diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index 59ee171572..1c3958434c 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -21,7 +21,7 @@ type Mode = | 'ci-windows-observational' | 'node-compat' | 'pre-push' - | 'manual-push' + | 'check-all' | 'doc-sync' type GateStatus = 'pending' | 'running' | 'passed' | 'failed' | 'skipped' @@ -98,12 +98,12 @@ function parseMode(raw: string | undefined): Mode { case 'ci-windows-observational': case 'node-compat': case 'pre-push': - case 'manual-push': + case 'check-all': case 'doc-sync': return raw default: throw new Error( - `run-gates: expected mode ci-primary | ci-static | ci-lint | ci-coverage | ci-snapshot | ci-artifacts | ci-windows-blocking | ci-windows-complete | ci-windows-observational | node-compat | pre-push | manual-push | doc-sync, got ${JSON.stringify(raw)}.`, + `run-gates: expected mode ci-primary | ci-static | ci-lint | ci-coverage | ci-snapshot | ci-artifacts | ci-windows-blocking | ci-windows-complete | ci-windows-observational | node-compat | pre-push | check-all | doc-sync, got ${JSON.stringify(raw)}.`, ) } } @@ -112,7 +112,7 @@ function defaultConcurrency(selectedMode: Mode, total: number): ConcurrencyDefau const available = availableParallelism() // Local modes cap workers: several doc gates each build a full ts.Program, // so an uncapped default on a large host trades wall clock for memory blowups. - const localCap = selectedMode === 'pre-push' || selectedMode === 'doc-sync' + const localCap = selectedMode === 'pre-push' || selectedMode === 'check-all' || selectedMode === 'doc-sync' const modeLimit = localCap ? Math.min(4, available) : available return { workers: Math.min(total, modeLimit), @@ -191,7 +191,7 @@ function gatesForMode(selected: Mode): Gate[] { case 'node-compat': return nodeCompatGates() case 'pre-push': return [] - case 'manual-push': + case 'check-all': return [ pnpmScript('runtime-closure', 'verify-runtime-closure', { label: 'runtime closure' }), pnpmScript('cordis-config', 'verify-cordis-config', { label: 'Cordis config' }), @@ -383,7 +383,7 @@ function coverageGate(): Gate { } // The snapshot suite boots the example bins in `lib` mode (built artifact under plain Node, -// plugins via real exports) — CI and pre-push already build, so they exercise what ships rather +// plugins via real exports) — CI and check-all already build, so they exercise what ships rather // than the tsx/source path dev uses. It therefore waits on `build`. function snapshotGate(): Gate { return pnpmScript('snapshot', 'test:snapshot', {