From 31c0589439ed9d35edde8320da1bf9a005c41a53 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:37:20 +0800 Subject: [PATCH] chore: keep local git hooks fast --- ...cutable-sdk-runtime-distribution.i18n.yaml | 4 +- ...ile-executable-sdk-runtime-distribution.md | 2 +- ...-executable-sdk-runtime-distribution.zh.md | 2 +- .../2026-06-11-doc-sync-enforcement.md | 4 +- .../process/2026-06-11-quality-gates.md | 8 +- .../2026-06-18-markdown-cross-link-lint.md | 4 +- .../2026-06-20-agent-note-classification.md | 2 +- ...2026-06-20-core-data-structures-catalog.md | 4 +- .../2026-06-20-generated-cordis-catalog.md | 2 +- .../process/2026-07-02-tool-schema-catalog.md | 4 +- ...26-07-04-cordis-jsdoc-completeness-gate.md | 4 +- .../2026-07-04-persistence-log-catalog.md | 2 +- .../2026-07-06-export-surface-jsdoc-gate.md | 2 +- .../2026-07-06-generated-config-catalog.md | 2 +- .../2026-07-06-parallel-pre-push-gates.md | 35 +++----- ...-doc-sync-through-gate-scheduler.i18n.yaml | 4 +- ...6-07-21-doc-sync-through-gate-scheduler.md | 6 +- ...7-21-doc-sync-through-gate-scheduler.zh.md | 6 +- .../2026-07-22-fast-local-git-hooks.i18n.yaml | 6 ++ .../2026-07-22-fast-local-git-hooks.md | 36 ++++++++ .../2026-07-22-fast-local-git-hooks.zh.md | 36 ++++++++ .agents/skills/dsh-code-review/SKILL.md | 2 +- .../skills/dsh-find-simplifications/SKILL.md | 2 +- .agents/skills/dsh-pre-push-checks/SKILL.md | 85 ++++++------------- .../dsh-pre-push-checks/agents/openai.yaml | 2 +- AGENTS.md | 29 ++----- docs/development.i18n.yaml | 4 +- docs/development.md | 12 +-- docs/development.zh.md | 12 +-- docs/i18n/README.i18n.yaml | 4 +- docs/i18n/README.md | 2 +- docs/i18n/README.zh.md | 2 +- lefthook.yml | 16 ++-- package.json | 1 - .../hooks-claude/tests/coverage-cases.ts | 2 +- .../hooks/hooks-codex/tests/coverage-cases.ts | 2 +- packages/support/loader-smoke/src/index.ts | 2 +- scripts/run-gates.ts | 52 ++---------- 38 files changed, 196 insertions(+), 210 deletions(-) create mode 100644 .agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.i18n.yaml create mode 100644 .agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.md create mode 100644 .agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.zh.md diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml index 6db1ef4ece..b76ed1a9ae 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.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-10-single-file-executable-sdk-runtime-distribution.md: 0d4686a5a233785ca4832ef068a118b484a872fe -2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: dcc9213c6b3a088b8b8bce2a442c5232ed5b7d0b +2026-07-10-single-file-executable-sdk-runtime-distribution.md: 43ba5708d1216c37a7ad7e2904df7d2a6baf016d +2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 3b33ff870d745584d2988bb6a7eb1a31e56ec3da diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md index 0d4686a5a2..43ba5708d1 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md @@ -36,7 +36,7 @@ Config discovery has two channels and fails loudly when both are missing: the `D Inside the exe's VFS sits a **real package tree in build-artifact form** (each package's `lib/` plus a real `node_modules`); the Loader resolves plugin names through standard dynamic `import()`: bare specifiers resolve upward along `node_modules` from the Loader's position inside the VFS, and land inside the VFS naturally. The closed set needs no allowlist code — the set is whatever the VFS has installed, and importing a name outside the set fails. -The deploy root is [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json) (`dsh-jsonrpc-agent-pkg`, a pnpm workspace member and a zero-code pure dependency manifest) — the unified source of truth for "which plugins the exe ships" and "what the Python runtime distributes". Adding a plugin to the exe = adding one dependency line to the manifest and repackaging. [`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) traverses every workspace package covered by that manifest and requires every non-optional workspace peer at the runtime root, reporting the complete referencing-package → missing-peer chain; CI static, pre-push, and the single-exe build run it before packaging. Deploy also packs by each package's `files`, so the shared chunks tsdown splits out must be covered by `files`. +The deploy root is [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json) (`dsh-jsonrpc-agent-pkg`, a pnpm workspace member and a zero-code pure dependency manifest) — the unified source of truth for "which plugins the exe ships" and "what the Python runtime distributes". Adding a plugin to the exe = adding one dependency line to the manifest and repackaging. [`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) traverses every workspace package covered by that manifest and requires every non-optional workspace peer at the runtime root, reporting the complete referencing-package → missing-peer chain; `pnpm run hygiene`, CI static, and the single-exe build run it before packaging. Deploy also packs by each package's `files`, so the shared chunks tsdown splits out must be covered by `files`. ### Build pipeline and artifacts diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md index dcc9213c6b..3b33ff870d 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md @@ -36,7 +36,7 @@ exe 使用 [@yao-pkg/pkg](https://github.com/yao-pkg/pkg)(vercel/pkg 归档后 exe 的 VFS 内是**构建产物形态的真实包树**(各包的 `lib/` + 真实 `node_modules`)。loader 通过标准动态 `import()` 解析插件名:裸包名从 VFS 内 loader 所在位置沿 `node_modules` 向上解析,自然落在 VFS 内。封闭集不需要白名单代码——VFS 中安装了什么,集合中就有什么;`import()` 集合外的名称会失败。 -部署根目录是 [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json)(`dsh-jsonrpc-agent-pkg`,pnpm 工作区成员、零代码纯依赖清单),也是“exe 安装哪些插件”与“Python 运行时分发什么”的统一事实源。向 exe 添加插件,就是在清单中增加一行依赖后重新打包。[`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) 遍历该清单覆盖的全部工作区包,要求每个非可选的工作区对等依赖(peer dependency)都显式列在运行时根目录,并报告“引用包 → 缺失对等依赖”的完整链路;CI 静态检查、pre-push 与 single-exe 构建都会在打包前运行该门禁。部署还会依据各包的 `files` 字段打包,因此 tsdown 拆出的共享分片必须被 `files` 覆盖。 +部署根目录是 [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json)(`dsh-jsonrpc-agent-pkg`,pnpm 工作区成员、零代码纯依赖清单),也是“exe 安装哪些插件”与“Python 运行时分发什么”的统一事实源。向 exe 添加插件,就是在清单中增加一行依赖后重新打包。[`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) 遍历该清单覆盖的全部工作区包,要求每个非可选的工作区对等依赖(peer dependency)都显式列在运行时根目录,并报告“引用包 → 缺失对等依赖”的完整链路;`pnpm run hygiene`、CI 静态检查与 single-exe 构建都会在打包前运行该门禁。部署还会依据各包的 `files` 字段打包,因此 tsdown 拆出的共享分片必须被 `files` 覆盖。 ### 构建管线与产物 diff --git a/.agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.md b/.agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.md index 8f32202c8f..67cdfd6771 100644 --- a/.agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.md +++ b/.agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.md @@ -13,7 +13,7 @@ Two gates, mirroring the existing `scripts/` style (tsx ESM, one job each): 1. **`doc-typecheck`** extracts every fenced ` ```ts ` block from `README.md`, `docs/**`, and `packages/*/README.md`, writes them to a temp project extending the root `tsconfig.json`, and compiles it with `tsc -b`. The temp project reuses the source `paths` map and the root project references, so documentation examples see source while vendored code remains checked under its own tsconfig settings. A block that is a deliberate sketch opts out with an explicit ` ```ts ignore-check ` info string; the script reports the opt-out ratio and fails if it exceeds half, so the escape hatch can't quietly become the norm. 2. **`verify-event-taxonomy`** extracts the event names from the `interface Events` blocks across `packages/*/src` and from the taxonomy table in `docs/architecture.md`, and asserts the two sets match exactly. Verify, don't generate: the table keeps its hand-written Mode/Purpose columns; only the set of names is checked. (Landing this surfaced three events the table had been missing — `tools/change`, `llm/adapter-change`, `system-prompt/change`.) **Superseded** by [the generated cordis catalog](2026-06-20-generated-cordis-catalog.md): this gate and its `architecture.md` table are retired in favor of the fully-generated `docs/cordis-catalog/events.md` + `docs/cordis-catalog/services.md` and their `verify-cordis-catalog` freshness gate. The other gates here (`doc-typecheck`, and the `verify-md-wrap` amendment below) are unaffected. -Both run via a shared `doc-sync` package.json script that the lefthook pre-push hook and CI both invoke ([mechanical quality gates](2026-06-11-quality-gates.md): hooks and CI call the same scripts, so the gate fires locally before a push — not only after it). They run after `pnpm run typecheck`, which validates the package/vendor build graph that doc-typecheck references. +Both run via a shared `doc-sync` package.json script that contributors invoke for relevant documentation changes and CI invokes exhaustively. The [fast local Git hooks](2026-07-22-fast-local-git-hooks.md) decision keeps this surface-selected work out of commit and push hooks. **Amendment (2026-06-17):** a third gate, **`verify-md-wrap`**, was later folded into `doc-sync`. It parses each in-scope Markdown file (`README.md`, `docs/**`, `packages/*/README.md`, plus `AGENTS.md` / `packages/AGENTS.md`) with `mdast-util-from-markdown` + GFM and fails on any `paragraph` node spanning more than one source line, enforcing the docs/AGENTS.md "one physical line per paragraph" writing rule. Same verify-don't-generate principle: it reports hard-wraps and never rewrites, so it adds no formatting churn. `doc-sync` is now three gates. @@ -24,7 +24,7 @@ Both run via a shared `doc-sync` package.json script that the lefthook pre-push ## Consequences -- Doc drift in the checkable classes now fails the pre-push hook and CI instead of waiting for a reviewer to notice. This is an instance of the "mechanical gates over prose" principle. +- Doc drift in the checkable classes fails `doc-sync` and CI instead of waiting for a reviewer to notice. This is an instance of the "mechanical gates over prose" principle. - Making doc snippets compile costs a few stub imports/`declare`s; the `ignore-check` ratio must stay low or the gate is theater (the ratio guard enforces this). - The taxonomy check is name-only — a wrong Mode or Purpose column still needs human review. - API reports remain available to revisit if the packages are ever published externally. diff --git a/.agents/notes/implemented/process/2026-06-11-quality-gates.md b/.agents/notes/implemented/process/2026-06-11-quality-gates.md index 84c3da7b95..5e1db16e52 100644 --- a/.agents/notes/implemented/process/2026-06-11-quality-gates.md +++ b/.agents/notes/implemented/process/2026-06-11-quality-gates.md @@ -2,24 +2,26 @@ Status: implemented +The hook/CI symmetry in this record is superseded by [Fast local Git hooks](2026-07-22-fast-local-git-hooks.md); CI remains the exhaustive enforcement path. + ## Problem This codebase is developed primarily by coding agents. Agents follow enforced gates far more reliably than prose conventions, and "a lot of work" is not a cost argument when agents do the labor. Early evidence: tests that didn't typecheck shipped (vitest doesn't typecheck) and were only caught by a review. ## Decision -Every AGENTS.md promise gets a command that exits non-zero, wired into git hooks and CI both calling the same package.json scripts: +Every mechanically checkable AGENTS.md promise gets a command that exits non-zero. CI invokes the exhaustive set, while Git hooks reserve their latency budget for cheap local defects: - Max-strict TypeScript (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, …); examples, tests, and scripts typecheck in CI via the root no-emit `tsconfig.json` while package/vendor code stays behind its own project-reference boundary. - ESLint strict-type-checked + @stylistic (the house style, enforced), including file-local duplicated logic checks; vendored code excluded. - jscpd detects cross-file clones in package production TypeScript and repository scripts; narrow source-range exceptions document deliberately parallel implementations. - Per-file 100% coverage on `packages/*/*/src` (v8); unreachable defensive guards carry `/* v8 ignore */ ` with stated reasons instead of deletion. - knip (dead code/deps), publint (package correctness), workspace constraints (workspace rules: private, cordis peer+dev, uniform version, ESM), and a NodeNext consumer typecheck for built package declarations. -- lefthook pre-commit (lint staged, typecheck, vendor-manifest guard) and pre-push (tests, hygiene); CI runs the full matrix on node 22.19/24/26 plus built application smokes for the Headless, TUI, ACP, JSON-RPC, workflow, and code-runtime entry paths. +- lefthook pre-commit fixes staged lint, rejects staged whitespace, and checks the vendor manifest; pre-push runs incremental typecheck. CI runs the full matrix on node 22.19/24/26 plus built application smokes for the Headless, TUI, ACP, JSON-RPC, workflow, and code-runtime entry paths. ## Consequences -- Conventions survive agent turnover; violations fail fast and locally. +- Conventions survive agent turnover; cheap commit/push defects fail locally and exhaustive violations fail in CI. - The gates themselves are code to maintain; config changes are reviewed like any change. - 100%-coverage pressure can produce assertion-free tests — mutation testing is the planned counterweight (see [the mutation-testing proposal](../../proposed/testing/2026-06-11-mutation-testing.md)). diff --git a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md index 05dfed2453..e57c75575b 100644 --- a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md +++ b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md @@ -16,7 +16,7 @@ A fourth `doc-sync` gate, `verify-md-links` (`scripts/verify-md-links.ts`), mirr - Check a target only when it is a **relative path**. Skip scheme-qualified URLs (`https:`, `mailto:`, …), protocol-relative (`//host`), root-absolute (`/path` — no stable base in a checkout), and pure in-page anchors (`#section`). Strip any `#fragment`/`?query`, resolve the path against the linking file's directory, and assert it exists on disk. - Report and never rewrite; exit non-zero on the first broken link found. -Scope matches the other gates plus the AGENTS.md pair and the repo-authored agent-skill Markdown under `.agents/skills/` (those skill files cross-link into the docs tree, so this reorg rewrote links in them too): `README.md`, `docs/**/*.md`, `packages/*/README.md`, `AGENTS.md`, `packages/AGENTS.md`, `.agents/skills/**/*.md`, deduped by real path (the `CLAUDE.md` symlinks resolve onto the AGENTS.md files). It is wired into the `doc-sync` script that the lefthook pre-push hook and CI both run, so a broken link fails locally before a push — consistent with [mechanical quality gates](2026-06-11-quality-gates.md). +Scope matches the other gates plus the AGENTS.md pair and the repo-authored agent-skill Markdown under `.agents/skills/` (those skill files cross-link into the docs tree, so this reorg rewrote links in them too): `README.md`, `docs/**/*.md`, `packages/*/README.md`, `AGENTS.md`, `packages/AGENTS.md`, `.agents/skills/**/*.md`, deduped by real path (the `CLAUDE.md` symlinks resolve onto the AGENTS.md files). It is wired into `doc-sync`, so relevant documentation changes and CI exercise the same broken-link check. This gate checks *existence*, not anchor validity: a link to a real file with a `#wrong-heading` fragment still passes (the file resolves; the fragment is stripped). @@ -26,6 +26,6 @@ This gate checks *existence*, not anchor validity: a link to a real file with a ## Consequences -- Renames and moves that orphan a cross-link now fail the pre-push hook and CI instead of waiting for a reader to click a dead link. This made the Agent Note reorganization that introduced the gate self-verifying: the same PR that rewrote forty links also added the check that proves none dangle. +- Renames and moves that orphan a cross-link fail `doc-sync` and CI instead of waiting for a reader to click a dead link. This made the Agent Note reorganization that introduced the gate self-verifying: the same PR that rewrote forty links also added the check that proves none dangle. - One more fast tsx script in the `doc-sync` chain; no new dependency (the mdast/GFM stack is already in devDependencies for `verify-md-wrap`). - The convention this enforces — cross-reference docs by machine-checkable relative link, never by bare prose or a number — is documented in [docs/AGENTS.md](../../../../docs/AGENTS.md) so authors know the gate exists and why. diff --git a/.agents/notes/implemented/process/2026-06-20-agent-note-classification.md b/.agents/notes/implemented/process/2026-06-20-agent-note-classification.md index 42c4523e0c..750a3586e0 100644 --- a/.agents/notes/implemented/process/2026-06-20-agent-note-classification.md +++ b/.agents/notes/implemented/process/2026-06-20-agent-note-classification.md @@ -43,4 +43,4 @@ Both are `doc-sync` members, in the `verify-md-wrap` style (tsx ESM, verify-don' - Every Agent Note sits under a class folder. A reader can browse one folder to see all simplifications or all testing decisions within a lifecycle. - Two more fast tsx scripts in the `doc-sync` chain; no new dependency (the mdast/GFM stack was already present for `verify-md-wrap`/`verify-md-links`). - Adding a class is a deliberate act: amend the `const` in `scripts/agent-note-tree.ts` and the [Classification section](../../README.md#classification), not just `mkdir` a folder. The gate rejects an unknown folder, so an ad-hoc class can't slip in. -- Source-comment doc references are now gated too — a moved or renamed doc that a `.ts` comment cites fails the pre-push hook, closing a drift class `verify-md-links` structurally could not see. +- Source-comment doc references are gated too — a moved or renamed doc that a `.ts` comment cites fails `verify-doc-refs` in `doc-sync` and CI, closing a drift class `verify-md-links` structurally could not see. diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md index 21b86b1812..da2b0e8c51 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md @@ -32,7 +32,7 @@ The durability requirement was specific: the doc shows the **literal** current t - Complete type declarations and their JSDoc are pasted verbatim into a dedicated ` ```ts type-equiv ` fence. A concise ` ```ts public-api ` fence carries the source-equivalent ambient projection for a class whose implementation bodies do not belong in the catalog. `doc-typecheck` recognizes both and skips them (the bare declarations are not standalone-compilable), and **excludes them from the opt-out ratio** — they are a separately-checked category, not unchecked sketches. - A new `scripts/verify-type-equiv.ts` extracts each block via the TypeScript parser and asserts that its declaration structure and every JSDoc comment match the declared symbol, ignoring only formatting whitespace and non-JSDoc comments. Ordinary blocks retain the complete declaration. A `public-api` projection retains a class's public fields, constructor, accessors, and methods with their original JSDoc while removing implementation bodies and private or protected members. This is chosen over a compiled `_Check` assertion because source names and documentation identity, not assignability, are the properties the catalog preserves. - Provenance lives in a central `scripts/type-equiv.manifest.json` (`{ doc, symbol, source }` entries), **not** in directive comments in the prose. The script enforces a **1:1 correspondence**: every type-equiv block has exactly one manifest entry and vice versa, so a block can never be silently unchecked and an entry can never rot. -- Wired into `doc-sync`, so it runs in the same lefthook pre-push and CI paths as the other doc gates. +- Wired into `doc-sync`, so relevant documentation changes run it locally and CI runs it with the other documentation checks. ### Maintenance is the author's job, with a gate backstop @@ -52,7 +52,7 @@ The spine-vs-seam rule was tested against `BashExecRequest`, tool schemas and de ## Consequences -- The vocabulary now has a single home that **cannot silently drift**: a field or public class-member change in source fails `verify-type-equiv` in the pre-push hook and CI until the paste is refreshed. Cordis service methods remain owned by the generated services catalog rather than being duplicated here. +- The vocabulary now has a single home that **cannot silently drift**: a field or public class-member change in source fails `verify-type-equiv` in `doc-sync` and CI until the paste is refreshed. Cordis service methods remain owned by the generated services catalog rather than being duplicated here. - The spine-vs-seam line is a reusable scoping tool, not a one-off: the same "the thing you write/hold/receive is core; the machinery that types/renders/persists it is a detail" rule is what later scoped the events/services catalog's harness-vs-inherited tiering. - The `ts type-equiv` fence is a third doc-block category alongside ` ```ts ` (compiled) and ` ```ts ignore-check ` (sketch). A later sibling added a fourth, ` ```ts cordis-catalog ` (generated signature), reusing the same skip-and-exclude treatment. - Adding or reshaping a core type now carries a documentation obligation the author must honor (the gate cannot detect a missing *new* type), backstopped by the `dsh-code-review` checklist. diff --git a/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.md b/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.md index 7de7056b33..4ca9b7e42c 100644 --- a/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.md +++ b/.agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.md @@ -33,7 +33,7 @@ This **supersedes the event-taxonomy half** of [doc-sync enforcement](2026-06-11 ## Consequences -- The catalog cannot drift: a source change that the committed file doesn't reflect fails `verify-cordis-catalog` in the pre-push hook and CI. A new event with no `@mode` tag, a tag that contradicts its signature, or an unclassified signature type fails the generator outright. +- The catalog cannot drift: a source change that the committed file doesn't reflect fails `verify-cordis-catalog` in `doc-sync` and CI. A new event with no `@mode` tag, a tag that contradicts its signature, or an unclassified signature type fails the generator outright. - Event and service-method contracts have a single home — the JSDoc at the declaration. The catalog repeats that original JSDoc inside its generated signature block and uses its description portion as entry prose, so thin source documentation yields a thin catalog entry. - The inherited tier is hand-summarized, so a vendor sync that adds/renames a cordis-core event or `ctx` member needs a matching edit to the curated table in `gen-cordis-catalog.ts`. This is the deliberate cost of not walking pinned vendor source; it changes rarely and is called out in the generator. - `verify-event-taxonomy.ts` is deleted and the `docs/architecture.md` event table is gone; anyone who linked to a specific table row now lands on the generated catalog instead. diff --git a/.agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md b/.agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md index 06ab61732c..814331af8b 100644 --- a/.agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md +++ b/.agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md @@ -8,7 +8,7 @@ The repository had no single reference for the names, descriptions, and JSON Sch ## Decision -Generate the catalog by **booting each tool plugin and reading its registered schemas**, not by parsing source. `scripts/gen-tool-catalog.ts` mounts each shipped tool package on a fresh cordis `Context` (with `SystemPrompt` + `ToolRegistry` and the injected seams the plugin's `apply` reads), calls `ctx.tools.schemas()` — exactly the `ToolSchema[]` the model is sent — disposes the context, and renders one `## ` section per package with a ` ```json ` `parameters` block per tool. It mirrors the `gen-cordis-catalog` / `gen-module-graph` CLI shape: default `--write` regenerates, `--check` fails if the committed copy is stale, output is deterministic (manifest-ordered, tools sorted by name). `verify-tool-catalog` (the `--check`) runs inside `doc-sync`, so the freshness gate fires in the same lefthook pre-push and CI paths as every other doc gate. +Generate the catalog by **booting each tool plugin and reading its registered schemas**, not by parsing source. `scripts/gen-tool-catalog.ts` mounts each shipped tool package on a fresh cordis `Context` (with `SystemPrompt` + `ToolRegistry` and the injected seams the plugin's `apply` reads), calls `ctx.tools.schemas()` — exactly the `ToolSchema[]` the model is sent — disposes the context, and renders one `## ` section per package with a ` ```json ` `parameters` block per tool. It mirrors the `gen-cordis-catalog` / `gen-module-graph` CLI shape: default `--write` regenerates, `--check` fails if the committed copy is stale, output is deterministic (manifest-ordered, tools sorted by name). `verify-tool-catalog` (the `--check`) runs inside `doc-sync`, so relevant documentation changes and CI exercise the same freshness check. ### Why boot, not parse (the crux) @@ -47,7 +47,7 @@ Schema blocks use ` ```json `, not a bespoke `ts`-family fence. `doc-typecheck` ## Consequences -- The catalog cannot drift: a tool schema change the committed file doesn't reflect fails `verify-tool-catalog` in the pre-push hook and CI. A new `tool-*` package not added to the manifest fails the completeness guard outright. +- The catalog cannot drift: a tool schema change the committed file doesn't reflect fails `verify-tool-catalog` in `doc-sync` and CI. A new `tool-*` package not added to the manifest fails the completeness guard outright. - Tool description prose has a single home — the `defineTool` `description` at the source — and the generated entry is only as good as it, the same forcing function the cordis catalog applies to event JSDoc. - The generator imports and executes workspace packages (the first repo script to do so; the others only read text). It runs under `tsx` via the root `tsconfig` `paths` map, the same unbuilt-source path the demos and tests use, so it needs no build step. - A new capability seam behind a future tool means a new manifest recipe entry (which seams to mount). This is the deliberate hand-written cost called out above; it changes only when a tool package is added. diff --git a/.agents/notes/implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md b/.agents/notes/implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md index 882ab8cf82..3945382217 100644 --- a/.agents/notes/implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md +++ b/.agents/notes/implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md @@ -10,7 +10,7 @@ The AGENTS.md rule ("every export has a JSDoc explaining semantics") is prose-ch ## Decision -Extend `scripts/gen-cordis-catalog.ts` — the same walk, the same `@mode` precedent — to enforce JSDoc COMPLETENESS on everything it catalogs. `verify-cordis-catalog` runs inside `doc-sync`, which both CI and the lefthook pre-push hook already execute, so the gate needs zero new wiring (quality-gates principle: one source of truth). +Extend `scripts/gen-cordis-catalog.ts` — the same walk, the same `@mode` precedent — to enforce JSDoc COMPLETENESS on everything it catalogs. `verify-cordis-catalog` runs inside `doc-sync`, so relevant documentation changes and CI exercise the same gate without separate wiring. The contract: @@ -32,7 +32,7 @@ Negative-path tests in `packages/core/agent/tests/gen-cordis-catalog.spec.ts` dr ## Consequences -- A new event or service method cannot land with an undocumented parameter or result: the generator refuses to regenerate and `verify-cordis-catalog` fails pre-push and in CI. The ~139 gaps found at adoption were filled in the same change, so the gate landed green. +- A new event or service method cannot land with an undocumented parameter or result: the generator refuses to regenerate and `verify-cordis-catalog` fails `doc-sync` and CI. The ~139 gaps found at adoption were filled in the same change, so the gate landed green. - The service surface must annotate return types explicitly and use identifier parameters. Neither constraint bound at adoption (every method already annotated; no destructured seam parameters existed); both are now load-bearing requirements a violating change will discover mechanically. - The general AGENTS.md JSDoc rule ("one-liners when one line suffices") acquires a stricter carve-out on this surface: a one-line summary still suffices only when the method has no parameters and a void result. - `@param` on `next` or `this` stays legal but unchecked — a deliberate asymmetry: the gate enforces the payload contract and refuses to demand boilerplate. diff --git a/.agents/notes/implemented/process/2026-07-04-persistence-log-catalog.md b/.agents/notes/implemented/process/2026-07-04-persistence-log-catalog.md index bd16baf8f6..bde0f08497 100644 --- a/.agents/notes/implemented/process/2026-07-04-persistence-log-catalog.md +++ b/.agents/notes/implemented/process/2026-07-04-persistence-log-catalog.md @@ -28,7 +28,7 @@ This supersedes the hand-copies: the session.md `hook/*` table, the compact READ ## Consequences -- The catalog cannot drift: a vocabulary or envelope change the committed file doesn't reflect fails `verify-persistence-catalog` in the pre-push hook and CI, and a new merged event with no JSDoc fails the generator outright — a plugin can no longer add an undocumented on-disk record type. +- The catalog cannot drift: a vocabulary or envelope change the committed file doesn't reflect fails `verify-persistence-catalog` in `doc-sync` and CI, and a new merged event with no JSDoc fails the generator outright — a plugin can no longer add an undocumented on-disk record type. - Event prose has a single home, the JSDoc at the declaration; the catalog preserves that JSDoc and any nested field comments without flattening or paraphrasing them. - The `SurfaceEventType` union is now structurally load-bearing for docs: renaming an event without updating the union (or vice versa) fails the generator, not just the compiler. - The badge derivation assumes the union stays a closed set of string literals with exactly one owner; a refactor away from that shape must update the generator in the same change. diff --git a/.agents/notes/implemented/process/2026-07-06-export-surface-jsdoc-gate.md b/.agents/notes/implemented/process/2026-07-06-export-surface-jsdoc-gate.md index 452c877182..734fbeab21 100644 --- a/.agents/notes/implemented/process/2026-07-06-export-surface-jsdoc-gate.md +++ b/.agents/notes/implemented/process/2026-07-06-export-surface-jsdoc-gate.md @@ -36,7 +36,7 @@ Three exemption families keep the gate from demanding boilerplate, in the spirit ## Consequences -- A new export cannot land undocumented: `verify-export-jsdoc` fails `doc-sync`, which pre-push and CI already run. The 203 gaps found at adoption were filled in the same change, so the gate landed green. +- A new export cannot land undocumented: `verify-export-jsdoc` fails `doc-sync` and CI. The 203 gaps found at adoption were filled in the same change, so the gate landed green. - Exported functions must annotate return types (universal at adoption, now load-bearing) and use identifier parameters where `@param` must name them. - Seam docs are canonical: an implementation inherits its heritage docs, and behavior notes worth keeping on the implementation are additions, not requirements. - The gate builds a `ts.Program` (~6s) — the one doc gate that pays for type resolution; acceptable inside `doc-sync`, which already compiles doc snippets. diff --git a/.agents/notes/implemented/process/2026-07-06-generated-config-catalog.md b/.agents/notes/implemented/process/2026-07-06-generated-config-catalog.md index 82355ab511..f876191be4 100644 --- a/.agents/notes/implemented/process/2026-07-06-generated-config-catalog.md +++ b/.agents/notes/implemented/process/2026-07-06-generated-config-catalog.md @@ -32,7 +32,7 @@ The package README `## Config` sections stay. The overlap is accepted deliberate ## Consequences -- The catalog cannot drift: a source change the committed file does not reflect fails `verify-config-catalog` in pre-push and CI. An undocumented config field, an unresolvable referenced type name, or a schema key missing from the config type fails the generator outright. +- The catalog cannot drift: a source change the committed file does not reflect fails `verify-config-catalog` in `doc-sync` and CI. An undocumented config field, an unresolvable referenced type name, or a schema key missing from the config type fails the generator outright. - Config prose now has a forcing function at the declaration: writing a new config field means writing its JSDoc, which becomes the catalog entry verbatim. - The generator hard-errors on shapes it cannot walk statically — an aliased package-local config import, a schema built by anything other than `object`/`intersect` composition, an unlisted global type name. Introducing such a shape includes teaching the generator (or the shape stays out of the repo), which is the point: the catalog stays the whole truth. - `gen-cordis-catalog.ts` exports its JSDoc/pointer helpers and `LINK_MAP` for reuse, so the two catalogs cross-link types identically and a link-map addition serves both. 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 710c37cb3a..a46b729a4d 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 @@ -2,39 +2,30 @@ Status: implemented +The local-hook portion of this record is superseded by [Fast local Git hooks](2026-07-22-fast-local-git-hooks.md). The bounded gate scheduler and package-level `publint` parallelism remain in force for CI, `doc-sync`, and explicit local commands. + ## Problem -The pre-push hook is the last local checkpoint before a branch leaves the machine, so its wall clock directly shapes whether contributors keep it enabled and trust its signal. Lefthook already runs top-level jobs in parallel, but aggregate jobs such as `pnpm run hygiene` and `pnpm run doc-sync` hide long sequential chains inside one job. The hook can therefore be configured as parallel while still waiting on serial subcommands whose members are independent. - -Flattening those members directly into `lefthook.yml` solves the local hook only. CI has the same scheduling problem, and duplicating a long leaf list in YAML gives future script changes two places to drift. - -`publint` has the same shape one level lower. Each package is linted independently against its own manifest and built output, but the runner loops through every package in order. On this repo that makes one package-publication gate consume time proportional to the number of packages even though the checks do not share mutable state. +Aggregate jobs such as documentation synchronization hide long sequential chains whose members are read-only and independent. Duplicating their leaf inventory in workflow YAML gives future script changes multiple places to drift, while running package publication checks serially makes one gate consume time proportional to the package count. ## Decision -[lefthook.yml](../../../../lefthook.yml) keeps one pre-push job named `full check` and runs `pnpm run check:pre-push`. That package script delegates to [scripts/run-gates.ts](../../../../scripts/run-gates.ts), the same bounded scheduler CI uses. +[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. -The `pre-push` mode expands into leaf gates for the unit suite, snapshot suite, build, `hygiene` members, `doc-sync` members, and module-graph freshness. The leaf list keeps the same gate vocabulary as the package scripts, including Agent Note classification and Agent Note format, while the runner schedules independent checks with four active top-level workers by default; `DSH_GATE_CONCURRENCY` overrides that 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. -The build gate makes the hook self-contained from a clean worktree. `publint`, `verify-node-next-types`, and the pre-push form of `doc-typecheck` wait for that build output, while source-only gates continue in parallel. - -[scripts/publint-all.ts](../../../../scripts/publint-all.ts) discovers the package list 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. - -The per-gate package scripts remain the vocabulary for ad hoc local runs. `hygiene` stays an aggregate `&&` chain the scheduler mirrors, while `doc-sync` has since moved its member list into the scheduler itself ([doc-sync through the gate scheduler](2026-07-21-doc-sync-through-gate-scheduler.md)). +The per-gate package scripts remain the vocabulary for ad hoc local runs. `hygiene` stays an aggregate `&&` chain, while `doc-sync` owns its member list in the scheduler ([doc-sync through the gate scheduler](2026-07-21-doc-sync-through-gate-scheduler.md)). ## Alternatives considered -- **Keep aggregate `hygiene` and `doc-sync` jobs in the hook** - simpler config, but it leaves most of the pre-push wall clock inside serial command chains that lefthook cannot see or schedule. -- **Declare one lefthook job per leaf gate** - exposes parallelism through lefthook's native job model, but it makes the hook file carry a long member list that CI cannot reuse. -- **Require developers to build before pushing** - avoids one hook gate, but it makes `publint` fail in a clean worktree and turns the final local checkpoint into a convention instead of a runnable check. -- **Background subcommands inside shell scripts** - can parallelize work, but it loses lefthook's job names, per-job timing, and failure grouping, and makes signal handling harder to reason about. -- **Declare one publint lefthook job per package** - exposes maximum parallelism, but it turns the hook into a hand-maintained package inventory that drifts exactly when new packages are added. -- **Run publint with unbounded concurrency** - minimizes elapsed time on small machines only by gambling with process count, memory pressure, package tarball creation, and readable logs. +- **Keep aggregate jobs serial** — simpler execution but makes wall clock equal the sum of independent checks and repeats command-wrapper startup. +- **Declare one CI job per leaf gate** — exposes maximum workflow parallelism but repeats checkout, setup, and install overhead and duplicates the scheduler inventory in YAML. +- **Background subcommands inside shell scripts** — parallelizes work but loses per-gate timing, deterministic failure grouping, and straightforward signal handling. +- **Declare one `publint` job per package** — exposes maximum package parallelism but creates a hand-maintained package inventory that drifts when packages change. +- **Run `publint` with unbounded concurrency** — minimizes elapsed time on small repositories only by gambling with process count, memory pressure, package tarball creation, and readable logs. ## Consequences -The hook's critical path becomes the slowest real gate instead of the sum of hidden gate chains. Lefthook reports one `full check` job, and the runner reports per-gate timing inside that job, so a slow local checkpoint still points at the gate that dominates the run. +Scheduler-backed commands take the slowest dependency chain instead of the sum of independent gates and report the gate that dominates. The cost is a custom scheduler with an explicit mode inventory. -The hook file stays short, and the duplicated member list lives in [scripts/run-gates.ts](../../../../scripts/run-gates.ts), where CI and pre-push can share it. The cost is a custom scheduler script instead of pure lefthook configuration, plus a build in the local pre-push path. - -`publint-all.ts` becomes asynchronous code and buffers command output instead of inheriting stdio live. The payoff is package-level parallelism with stable output order and one environment variable for resource tuning. +`publint-all.ts` is asynchronous and buffers command output instead of inheriting stdio live. The payoff is package-level parallelism with stable output order and one environment variable for resource tuning. diff --git a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml b/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml index 9deebd1da3..8bd4745529 100644 --- a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.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-21-doc-sync-through-gate-scheduler.md: b79df2dd7d3515cb0434ac672f7f87c3271d900b -2026-07-21-doc-sync-through-gate-scheduler.zh.md: 9395244e3c7700166ad87c49219073210c66bc7e +2026-07-21-doc-sync-through-gate-scheduler.md: b7e41ba4aeac8ea03c706acadd481eee26abd5c2 +2026-07-21-doc-sync-through-gate-scheduler.zh.md: 56699747b1ba97fd90f7d53ab0deebc73ac775ef diff --git a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.md b/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.md index b79df2dd7d..b7e41ba4ae 100644 --- a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.md +++ b/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.md @@ -6,13 +6,13 @@ English | [中文](2026-07-21-doc-sync-through-gate-scheduler.zh.md) ## Problem -`pnpm run doc-sync` was a `&&` chain of 24 `pnpm run` subcommands. Each link paid a full pnpm wrapper start (workspace resolution, script lookup, tsx boot) before its script ran; measured on a development host, the 24 script bodies together finish in about 34 seconds while the chained form takes around 3 minutes, and the wrapper stall reproduces on local disk, so every developer and CI lane pays it, not just network-filesystem checkouts. The chain also ran serially even though the member gates are read-only and independent, and it silently drifted from [scripts/run-gates.ts](../../../../scripts/run-gates.ts): `verify-cordis-api` joined the chain when the runtime API catalog landed but was never added to `docSyncLeafGates`, so CI and pre-push never enforced that catalog's freshness. +`pnpm run doc-sync` was a `&&` chain of 24 `pnpm run` subcommands. Each link paid a full pnpm wrapper start (workspace resolution, script lookup, tsx boot) before its script ran; measured on a development host, the 24 script bodies together finish in about 34 seconds while the chained form takes around 3 minutes, and the wrapper stall reproduces on local disk, so every developer and CI lane pays it, not just network-filesystem checkouts. The chain also ran serially even though the member gates are read-only and independent, and it silently drifted from [scripts/run-gates.ts](../../../../scripts/run-gates.ts): `verify-cordis-api` joined the chain when the runtime API catalog landed but was never added to `docSyncLeafGates`, so CI never enforced that catalog's freshness. ## Decision -`doc-sync` in `package.json` now delegates to the existing bounded scheduler — `tsx scripts/run-gates.ts doc-sync` — the same way `check:pre-push` and the `check:ci:*` scripts already do ([parallel pre-push gates](2026-07-06-parallel-pre-push-gates.md), [parallel GitHub CI gates](2026-07-06-parallel-github-ci-gates.md)). The new `doc-sync` mode expands to exactly `docSyncLeafGates()`, making the leaf list in `run-gates.ts` the single source of truth for the member set; the chain that could drift from it is gone. Like `pre-push`, the mode caps default concurrency at four workers because several doc gates each build a full `ts.Program`; `DSH_GATE_CONCURRENCY` still overrides. +`doc-sync` in `package.json` delegates to the existing bounded scheduler — `tsx scripts/run-gates.ts doc-sync` — like the `check:ci:*` scripts ([parallel gate scheduling](2026-07-06-parallel-pre-push-gates.md), [parallel GitHub CI gates](2026-07-06-parallel-github-ci-gates.md)). The `doc-sync` mode expands to exactly `docSyncLeafGates()`, making the leaf list in `run-gates.ts` the single source of truth for the member set. The local mode caps default concurrency at four workers because several doc gates each build a full `ts.Program`; `DSH_GATE_CONCURRENCY` still overrides. -The drift this consolidation surfaced is fixed in the same change: `docSyncLeafGates` gains the missing `verify-cordis-api` leaf, so CI and pre-push now gate the generated runtime API catalog alongside the other generated docs. +`docSyncLeafGates` includes `verify-cordis-api`, so relevant local documentation checks and CI gate the generated runtime API catalog alongside the other generated docs. ## Alternatives considered diff --git a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.zh.md b/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.zh.md index 9395244e3c..56699747b1 100644 --- a/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.zh.md +++ b/.agents/notes/implemented/process/2026-07-21-doc-sync-through-gate-scheduler.zh.md @@ -6,13 +6,13 @@ Status: implemented ## 问题 -`pnpm run doc-sync` 原本是把 24 个 `pnpm run` 子命令用 `&&` 串起来的链。每一环都要先付一次完整的 pnpm 包装层启动(workspace 解析、脚本查找、tsx 启动)才轮到脚本本体;在开发机上实测,24 个脚本本体合计约 34 秒即可跑完,而链式形态耗时约 3 分钟,且包装层的停顿在本地磁盘上同样复现,因此每位开发者和每条 CI 车道都在付这笔开销,并非只有网络文件系统上的检出受影响。这条链还是串行执行的,尽管各成员门禁只读且相互独立;它也在悄悄偏离 [scripts/run-gates.ts](../../../../scripts/run-gates.ts):运行时 API 目录落地时 `verify-cordis-api` 加入了链,却从未加进 `docSyncLeafGates`,导致 CI 和 pre-push 从未把关该目录的新鲜度。 +`pnpm run doc-sync` 原本是把 24 个 `pnpm run` 子命令用 `&&` 串起来的链。每一环都要先付一次完整的 pnpm 包装层启动(workspace 解析、脚本查找、tsx 启动)才轮到脚本本体;在开发机上实测,24 个脚本本体合计约 34 秒即可跑完,而链式形态耗时约 3 分钟,且包装层的停顿在本地磁盘上同样复现,因此每位开发者和每条 CI 车道都在付这笔开销,并非只有网络文件系统上的检出受影响。这条链还是串行执行的,尽管各成员门禁只读且相互独立;它也在悄悄偏离 [scripts/run-gates.ts](../../../../scripts/run-gates.ts):运行时 API 目录落地时 `verify-cordis-api` 加入了链,却从未加进 `docSyncLeafGates`,导致 CI 从未把关该目录的新鲜度。 ## 决策 -`package.json` 中的 `doc-sync` 现在委托给既有的有界调度器——`tsx scripts/run-gates.ts doc-sync`——与 `check:pre-push` 和各 `check:ci:*` 脚本的做法一致([并行 pre-push 门禁](2026-07-06-parallel-pre-push-gates.md)、[并行 GitHub CI 门禁](2026-07-06-parallel-github-ci-gates.md))。新增的 `doc-sync` 模式恰好展开为 `docSyncLeafGates()`,使 `run-gates.ts` 里的叶子列表成为成员集合的唯一真源;那条可能与之漂移的链不复存在。与 `pre-push` 一样,该模式把默认并发上限设为四个 worker,因为多个文档门禁各自要构建完整的 `ts.Program`;`DSH_GATE_CONCURRENCY` 仍可覆盖。 +`package.json` 中的 `doc-sync` 委托给既有的有界调度器——`tsx scripts/run-gates.ts doc-sync`——与各 `check:ci:*` 脚本的做法一致([并行门禁调度](2026-07-06-parallel-pre-push-gates.md)、[并行 GitHub CI 门禁](2026-07-06-parallel-github-ci-gates.md))。`doc-sync` 模式恰好展开为 `docSyncLeafGates()`,使 `run-gates.ts` 里的叶子列表成为成员集合的唯一真源。本地模式把默认并发上限设为四个 worker,因为多个文档门禁各自要构建完整的 `ts.Program`;`DSH_GATE_CONCURRENCY` 仍可覆盖。 -这次整合暴露出的漂移在同一变更中修复:`docSyncLeafGates` 补上缺失的 `verify-cordis-api` 叶子,CI 和 pre-push 从此与其他生成文档一起把关生成的运行时 API 目录。 +`docSyncLeafGates` 包含 `verify-cordis-api`,因此相关的本地文档检查与 CI 会同其他生成文档一起把关生成的运行时 API 目录。 ## 考虑过的替代方案 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 new file mode 100644 index 0000000000..a21afc8e6a --- /dev/null +++ b/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# 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 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 new file mode 100644 index 0000000000..bab47c6479 --- /dev/null +++ b/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.md @@ -0,0 +1,36 @@ +# Agent Note: Fast local Git hooks + +Status: implemented + +English | [中文](2026-07-22-fast-local-git-hooks.zh.md) + +## Problem + +An agent already runs the tests and checks that exercise its change, while commit, push, and CI can each repeat increasingly broad subsets of the same work. A full pre-push suite therefore delays every publication, amplifies unrelated local flakes, and gives no new signal when CI immediately runs the exhaustive matrix again. + +Fast hooks still need to reject cheap, high-confidence defects before work leaves the machine. Staged formatting, whitespace errors, missing vendored-source metadata, and repository type errors fit that boundary; unit suites, snapshots, documentation checks, builds, and package hygiene vary with the changed surface and do not. + +## Decision + +[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. + +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. + +## Supersedes + +This decision supersedes the local-hook portion of [Parallel pre-push gates](2026-07-06-parallel-pre-push-gates.md) and the hook/CI symmetry in [Mechanical quality gates over prose guidelines](2026-06-11-quality-gates.md). Their CI scheduler, package-gate, and mechanical-enforcement decisions remain in force. + +## Alternatives considered + +- **Keep the full pre-push suite and optimize its scheduler** — preserves the earliest exhaustive signal but still repeats agent-selected evidence and CI, while unrelated failures continue blocking publication. +- **Remove pre-push entirely** — makes pushes cheapest but loses the fast cross-file guarantee that TypeScript provides after several commits. +- **Keep typecheck in pre-commit** — catches type errors earlier but charges every intermediate commit instead of one push; staged lint already covers the commit-local syntax and style boundary. +- **Make staged lint check-only** — avoids hook-side mutation, but contributors intentionally retain the existing auto-fix workflow; Lefthook's `stage_fixed` owns re-staging so the command does not duplicate `git add`. + +## 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. + +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 new file mode 100644 index 0000000000..7b279b1a9a --- /dev/null +++ b/.agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.zh.md @@ -0,0 +1,36 @@ +# Agent Note: 快速本地 Git 钩子 + +Status: implemented + +[English](2026-07-22-fast-local-git-hooks.md) | 中文 + +## 问题 + +agent(智能体)已经会运行能够覆盖自身改动的测试和检查,而提交、推送与 CI 可能分别重复其中范围越来越广的子集。因此,全量 pre-push 套件会拖慢每次推送,放大与当前改动无关的本地偶发失败,而且 CI 紧接着再次运行完整矩阵时不会提供新信号。 + +快速钩子仍需在工作离开本机之前拦下检查成本低且把握高的缺陷。暂存文件格式问题、空白错误、vendor 源码元数据缺失与仓库类型错误符合这条边界;单元测试套件、快照、文档检查、构建与包(package)的 `hygiene` 检查则随改动范围而异,不符合这条边界。 + +## 决策 + +[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` 调度。 + +agent 检查待推送的 diff,并仅运行一次能够覆盖其行为的最小范围测试和检查。CI 负责全量覆盖率门禁、构建产物检查与平台矩阵。只有在明确要求、诊断 CI,或涉及全仓库的改动无法由范围更窄的证据得到可信验证时,才完整运行一遍本地检查矩阵。 + +## 取代关系 + +本决策取代[并行 pre-push 门禁](2026-07-06-parallel-pre-push-gates.md)中涉及本地钩子的部分,以及[以机械质量门禁代替文字规范](2026-06-11-quality-gates.md)中关于钩子与 CI 对称性的部分。上述记录中关于 CI 调度器、包门禁与机械化强制执行的决策继续有效。 + +## 考虑过的替代方案 + +- **保留全量 pre-push 套件并优化其调度器**——能够最早提供全面信号,但仍会重复 agent 已选取的证据和 CI,且无关失败仍会阻塞推送。 +- **完全移除 pre-push**——推送成本最低,但会失去 TypeScript 在多个提交之后提供的快速跨文件保证。 +- **在 pre-commit 中保留类型检查**——更早捕获类型错误,但每次中间提交都要承担开销,而不是只在推送时运行一次;暂存文件 lint 已经覆盖提交本身的语法与风格边界。 +- **将暂存文件 lint 设为仅检查模式**——避免钩子修改文件,但贡献者有意保留现有的自动修复工作流;Lefthook 的 `stage_fixed` 负责重新暂存,因此命令无需重复执行 `git add`。 + +## 结果 + +普通提交的关键路径是暂存文件 lint,缓存已预热时推送的关键路径是增量类型检查。钩子耗时只作为开发观察数据和 PR(Pull Request)证据记录,不设置会受主机负载与缓存状态影响的计时测试。 + +从本地推送成功不再能证明仓库完整矩阵已通过。agent 必须选择相关的行为证据,评审人必须判断该选择是否与 diff 相符,CI 则对每个推送版本提供一次全面信号。 diff --git a/.agents/skills/dsh-code-review/SKILL.md b/.agents/skills/dsh-code-review/SKILL.md index 925dc5b6ac..2c9fd86df5 100644 --- a/.agents/skills/dsh-code-review/SKILL.md +++ b/.agents/skills/dsh-code-review/SKILL.md @@ -24,7 +24,7 @@ description: Use when reviewing a pull request in the deepseek-harness repo — 3. **Core type docs match.** Changes to spine or seam vocabulary update the appropriate [core-data-structures](../../../docs/core-data-structures/core.md) page and any `type-equiv` entry. Internal types need no catalog entry. 4. **Registrations clean up.** Verify each new registry contribution satisfies the disposal-test contract in [packages/AGENTS.md](../../../packages/AGENTS.md). 5. **Invariant companions are semantic.** For every touched `./invariant`, require an owner event-stream or mutable-data relationship at its authoritative boundary; service or method presence, plugin metadata or effects, and fixed pure examples belong in type, load, or unit tests. Accept an empty installer when its package-specific reason establishes that no plausible runtime relationship exists; do not demand an invented check merely to eliminate emptiness ([repository rule](../../../AGENTS.md#conventions); [package contract](../../../packages/AGENTS.md)). -6. **Required gates pass.** Trust the [current readiness sequence](../../../AGENTS.md#run-the-ci-gates-locally-before-marking-a-pr-ready) and `pnpm run check:pre-push` for their enforced inventory; review the semantic gaps they cannot detect. +6. **Required evidence exists.** Verify the author ran the [relevant local checks](../../../AGENTS.md#run-relevant-checks-locally) for the diff and that CI covers the exhaustive matrix; review the semantic gaps neither can detect. ## Manual checks diff --git a/.agents/skills/dsh-find-simplifications/SKILL.md b/.agents/skills/dsh-find-simplifications/SKILL.md index 2ce80f3f74..2e1ccdc3c8 100644 --- a/.agents/skills/dsh-find-simplifications/SKILL.md +++ b/.agents/skills/dsh-find-simplifications/SKILL.md @@ -102,7 +102,7 @@ Diff the sibling branch against `origin/master`, not against the current PR bran ## Validation And PR Hygiene -For docs-only Agent Note work, run at least `pnpm run doc-sync`, `pnpm run lint`, and `git diff --check`. For code comments or skill changes, also run the relevant validator when one exists. Before pushing, expect the pre-push hook to run module graph freshness, unit tests, snapshots, doc-sync, and hygiene. +For docs-only Agent Note work, run at least `pnpm run doc-sync`, `pnpm run lint`, and `git diff --check`. For code comments or skill changes, also run the relevant validator when one exists. Select any other evidence from the outgoing diff; the pre-push hook contributes typecheck only. When opening or updating a PR, summarize: diff --git a/.agents/skills/dsh-pre-push-checks/SKILL.md b/.agents/skills/dsh-pre-push-checks/SKILL.md index a138c5b4b4..b2ecc6362d 100644 --- a/.agents/skills/dsh-pre-push-checks/SKILL.md +++ b/.agents/skills/dsh-pre-push-checks/SKILL.md @@ -1,13 +1,13 @@ --- name: dsh-pre-push-checks -description: Use before pushing, force-pushing, marking ready for review, claiming checks pass, or bypassing a local hook on a deepseek-harness branch, especially after merges, review fixes, package graph changes, docs/catalog updates, snapshots, e2e behavior, or built artifact changes. +description: Use before pushing, force-pushing, marking ready for review, or claiming checks pass on a deepseek-harness branch to select the smallest tests and checks that cover the outgoing diff without reflexively running the full repository suite. --- # DSH Pre-Push Checks -Use this skill to choose and run the smallest sufficient verification set before a `deepseek-harness` push. Do not treat the local pre-push hook as the full CI contract: CI also runs coverage, build, and built-bin smoke. +Use this skill to run relevant local evidence once before a `deepseek-harness` push. Git hooks are intentionally narrow: pre-commit fixes staged lint, checks staged whitespace, and guards vendored-source metadata; pre-push runs only the incremental repository typecheck. CI owns exhaustive coverage and the platform matrix. -## First Steps +## Inspect the outgoing change 1. Confirm the checkout and branch. @@ -16,88 +16,57 @@ git status --short --branch git rev-parse --show-toplevel ``` -2. Inspect the outgoing diff. +2. Inspect the diff against its actual base. ```sh git diff --stat git diff --name-only origin/$(git branch --show-current)...HEAD ``` -If the branch has no upstream or the command is not meaningful for the stack shape, use `git diff --name-only origin/master...HEAD` or the PR base branch. +If the branch has no upstream or that range is not meaningful for the stack, compare with the PR base branch. After merging a changed base, reassess which behavior the combined diff can affect and rerun only checks invalidated by the merge. -3. If the branch was just merged with `master`, or the user says master changed, run the gates after resolving the merge and before pushing or marking ready. Do not present a conflict-resolution commit as ready with only typecheck/lint evidence. +## Select relevant evidence -## Required Baseline +There is no universal local baseline beyond the hooks. Every behavior change needs the narrowest available test or purpose-built check that would fail for its regression; add broader checks only for surfaces the diff actually reaches. -Run these before every non-trivial push: +- **Package or script behavior:** run the owning Vitest file or focused test name. Add adjacent package tests when a shared contract changes; leave repository-wide coverage to CI unless the change is genuinely cross-cutting or the user requests it. +- **Documentation, Agent Notes, catalogs, or doc-linked comments:** run `pnpm run doc-sync`; run full lint when the documentation workflow requires it. +- **Model-, editor-, CLI-, or terminal-visible output:** run the focused keyless snapshot or real runnable-example scenario that owns the output. +- **Package manifests, public exports, build configuration, worker/bin entries, or built runtime paths:** run `pnpm run build`, the relevant hygiene checks, and the owning built-artifact smoke. +- **Real provider or agent behavior:** run the relevant `pnpm run test:e2e` target when credentials are available; never print secrets. -```sh -pnpm run typecheck -pnpm run lint -pnpm run test:coverage -``` +Do not manually repeat a passing check merely because commit or push follows. In particular, do not run typecheck immediately before pushing solely to duplicate the pre-push hook. -Why `test:coverage`, not only `test`: CI enforces per-file 100% coverage. A branch can pass `pnpm run test` and still fail CI. +## Full local rehearsal -## Add Gates By Touched Surface +Run the complete local approximation only when the user explicitly requests it, while diagnosing a CI failure, or when the change spans the repository so broadly that no narrower set is credible. Use the current workflow and package scripts as the inventory; do not recreate the removed `check:pre-push` aggregate. -Run `pnpm run doc-sync` and `pnpm run verify-module-graph` when the diff touches Markdown docs, package manifests, package imports/exports, generated catalogs, Agent Notes, architecture docs, translation pairs, Mermaid diagrams, or comments that cite docs/packages. +## Handle failures -Run `pnpm run build` and `pnpm run hygiene` when the diff touches any package `package.json`, dependency graph, public exports, build config, declaration surface, bundled runtime path, or code that will be consumed from built `lib/`. - -Run snapshot tests when the diff changes ACP/editor-facing transcript behavior: ACP bridge updates, agent-loop observable output, tool call/result presentation, session log rendering, stdout/stderr protocol output, or snapshot fixtures. - -```sh -pnpm run test:snapshot -``` - -Run built-bin smoke tests after `pnpm run build` when app packages, app boot, package runtime imports, bin entries, loader behavior, or published artifact paths change. - -```sh -DSH_EXAMPLE_MODE=lib pnpm exec vitest run --config vitest.e2e.config.ts examples/headless-agent/tests/keyless-smoke.e2e.ts examples/tui-agent/tests/tui-keyless-smoke.e2e.ts packages/examples/cli-demo/tests/built-bin.e2e.ts packages/examples/acp-demo/tests/built-bin.e2e.ts -``` - -Run real e2e when behavior depends on a real model/API, tool-use loop, ACP integration, prompt injection, or end-to-end agent UX. If `.env` is available, use it; do not print secrets. - -```sh -pnpm run test:e2e -``` - -Run a targeted test first for the changed package, but never use targeted tests as the only push evidence unless the change is test-only and cannot affect shared behavior. - -## Full Local CI Approximation - -Use this before high-risk pushes, after large merges, before asking for review on a major PR, or when prior pushes have caused CI churn. The authoritative command list is the root [AGENTS.md § Run the CI gates locally before marking a PR ready](../../../AGENTS.md#run-the-ci-gates-locally-before-marking-a-pr-ready); run that block rather than copying a local variant into this skill. Add `pnpm run test:e2e` when a key is available and the feature has real-agent behavior. - -## Handling Failures - -If a gate fails, stop and fix or explain the blocker. Do not push and hope CI differs. +If a relevant check fails, stop and fix or explain the blocker. Do not push and hope CI differs. If a failure looks environment-specific, prove it: - Record the exact command, failing test, and platform-specific mismatch. -- Confirm the relevant non-platform gates pass. -- Prefer fixing the test for cross-platform determinism if the test is part of the required local gate. -- Bypass a local hook only when the user explicitly asks to push or agrees, and state exactly which hook failed and why it is not expected to fail on CI. +- Confirm the relevant non-platform evidence. +- Prefer fixing cross-platform nondeterminism when the check is required. +- Bypass a local hook only when the user explicitly asks or agrees, and report exactly what failed and why CI is expected to differ. -Known pattern to watch for: Linux CI and macOS local behavior can differ for shell utilities such as `sed -i`. Treat this as evidence to investigate, not as automatic permission to bypass. +## Push procedure -## Push Procedure - -1. Local commits may happen before the full gate set, but do not push, mark ready, or claim checks pass until the relevant gates pass or any blocker is explicitly documented. -2. Let the normal pre-commit hook run. If it changes files, inspect and commit or amend the change intentionally rather than hiding it. -3. Push normally first so the pre-push hook can run. -4. If a local hook is bypassed after user approval, use the narrow bypass and say so in the final response. -5. After push, verify the remote ref matches local HEAD. +1. Run the selected relevant checks once. +2. Commit normally and inspect any files changed by the pre-commit fixer before continuing. +3. Push normally so the incremental typecheck hook runs. +4. Verify the remote ref matches local `HEAD`. ```sh git rev-parse HEAD origin/$(git branch --show-current) ``` -For GitHub PRs, check CI after push: +For GitHub PRs, inspect remote CI after the push: ```sh gh pr checks ``` -If checks are pending, say pending. If checks fail, inspect logs before claiming the push is good. +Report pending checks as pending. Inspect failures before attributing them to the branch or the environment. diff --git a/.agents/skills/dsh-pre-push-checks/agents/openai.yaml b/.agents/skills/dsh-pre-push-checks/agents/openai.yaml index 6ad9b63935..4a38ea4da8 100644 --- a/.agents/skills/dsh-pre-push-checks/agents/openai.yaml +++ b/.agents/skills/dsh-pre-push-checks/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "DSH Pre-Push Checks" - short_description: "Run the right DeepSeek Harness gates before push" + short_description: "Run the relevant DeepSeek Harness checks before push" default_prompt: "Use $dsh-pre-push-checks before pushing this DeepSeek Harness branch." diff --git a/AGENTS.md b/AGENTS.md index cf1763d3d1..a3b673b0b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,7 +48,7 @@ Package groups: [packages/README.md](packages/README.md). ```sh pnpm install # pnpm workspaces, node ^22.19 || >=24 pnpm run test # vitest unit tests -pnpm run test:coverage # THE gating test run: per-file 100% coverage on packages/*/*/src +pnpm run test:coverage # CI coverage gate: per-file 100% on packages/*/*/src pnpm run test:e2e # real-API tests; self-skip without DEEPSEEK_API_KEY pnpm run test:snapshot # keyless ACP/headless/TUI replay vs expected outputs; filter: -t pnpm run test:snapshot:record # re-record expected outputs (needs key) @@ -69,26 +69,13 @@ pnpm run demo:acp # ACP server agent (needs DEEPSEEK_API_KEY) When required `gh`, `pnpm`, build, test, or generator commands fail because the agent sandbox blocks credentials, network, IPC, file watching, or nested `sandbox-exec`, retry unchanged with the narrowest host escalation before diagnosing authentication or project failure. Require sandbox evidence; never bypass genuine test failures or the product sandbox under test. -### Run the CI gates locally before marking a PR ready +### Run relevant checks locally -Run narrow checks during implementation and this CI-equivalent sequence before marking a PR ready. Fresh worktrees need `pnpm run build` before publint and NodeNext inspect `lib/`: +Agents MUST run the tests and checks relevant to changed behavior before pushing; use [dsh-pre-push-checks](.agents/skills/dsh-pre-push-checks/SKILL.md) to select them and report only commands actually run. -```sh -set -euo pipefail -pnpm run typecheck -pnpm run lint -pnpm run duplication -pnpm run test:coverage -pnpm run test:snapshot -pnpm run doc-sync -pnpm run website:build -pnpm run verify-module-graph -pnpm run build -pnpm run hygiene -DSH_EXAMPLE_MODE=lib pnpm exec vitest run --config vitest.e2e.config.ts examples/headless-agent/tests/keyless-smoke.e2e.ts examples/tui-agent/tests/tui-keyless-smoke.e2e.ts packages/examples/cli-demo/tests/built-bin.e2e.ts packages/examples/acp-demo/tests/built-bin.e2e.ts packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts packages/workflow/workflow-workerthread/tests/built-worker.e2e.ts packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts -``` - -`test:coverage`, not `test`, is the gate ([why](docs/testing.md)); report only commands actually run. +- For product or tooling behavior, start with the narrowest unit, e2e, or purpose-built check that exercises the change. Add focused snapshots for model- or human-visible output, `doc-sync` for documentation, build/hygiene or built-artifact smoke for published runtime paths, and real-API e2e only when those surfaces change. +- Do not default to the entire repository suite or rerun a passing check solely because commit or push occurs. CI owns exhaustive coverage and the platform matrix; run a full local rehearsal only when the user explicitly requests it, while diagnosing CI, or when a repository-wide change cannot be validated narrowly. +- `test:coverage`, not `test`, is the CI coverage gate ([why](docs/testing.md)). ## Secrets / .env @@ -115,12 +102,12 @@ Real-API tests and demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, - **Prefer symmetry for parallel values**; unexplained asymmetry usually signals a missed extraction. - **Tests describe behavior, not correctness.** Change obsolete behavior with its tests; explain why in the PR. - **Every non-trivial change MUST include at least one Agent Note in the same PR.** Update the owning note or add one, validate its premises against code, and exempt only mechanical/local edits ([scope](.agents/notes/README.md#when-to-write-one)). -- **Testing policy** — [docs/testing.md](docs/testing.md). Every non-trivial model- or human-visible change adds or updates a keyless snapshot through a real runnable example in the same PR; package tests, e2e-only assertions, and mock-only fixtures do not substitute for the assembled application transcript. Fixtures must replay on macOS/Linux; fix fixtures, not normalizers. +- **Testing policy** — [docs/testing.md](docs/testing.md). Every non-trivial model- or product-user-visible behavior change adds or updates a keyless snapshot through a real runnable example in the same PR; package tests, e2e-only assertions, and mock-only fixtures do not substitute for the assembled application transcript. Fixtures must replay on macOS/Linux; fix fixtures, not normalizers. - **A tool's ACP render intent is part of its design**, decided up front (`generic`/`terminal`/`diff`, `locations`); presentation methods are pure functions of `args` ([cookbook](docs/cookbook/adding-a-tool.md)). - **Plan unit, e2e, and snapshot coverage** for new seams, lifecycle shapes, and transcript surfaces; missing snapshot-harness support is part of the implementation, not deferred follow-up. - **Keep PRs coherent and merge with merge commits.** Split an independently meaningful feature or design decision into a separate or stacked PR when combining it obscures ownership, intent, or verification. Never squash/rebase or rewrite pushed branches; put a review fix on its introducing PR, then merge down the stack ([guide](docs/cookbook/responding-to-pr-review-on-a-stack.md)). - TODO markers: `FIXME`/`TODO`/`XXX` by urgency ([semantics](docs/development.md)). -- Files end with exactly one trailing newline; `git diff --check` (pre-push) gates it. +- Files end with exactly one trailing newline; `git diff --cached --check` (pre-commit) gates it. ## Defensive patterns diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 6f3fa83d94..20eb3b4ca1 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: f0db7fbcb4a9df98e83d6c1edd5610e5cc4dd517 -development.zh.md: 62e16479a49d5548e1fbd773dabca5bd741a24fe +development.md: 10406cebae1bf83fff663903b1478c9acb8476a1 +development.zh.md: 50051ffd631518b37c3ad96f5fd3830cf6893ec9 diff --git a/docs/development.md b/docs/development.md index f0db7fbcb4..10406cebae 100644 --- a/docs/development.md +++ b/docs/development.md @@ -35,13 +35,13 @@ pnpm run typecheck That first typecheck runs the package/vendor build graph and the root no-emit `tsconfig.json` graph for examples, tests, and scripts. The root graph uses the same source `paths` map but relies on project references so vendored code is checked under its own tsconfig settings. -If you are preparing to push from a fresh clone or worktree, also build once: +If a relevant local check consumes built package output, build once first: ```sh pnpm run build ``` -`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files, and `verify-node-next-types`, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until `pnpm run build` runs. +`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files, and `verify-node-next-types`, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until `pnpm run build` runs; ordinary commits and pushes do not require that build unless their selected checks consume it. ## Environment variables @@ -56,14 +56,14 @@ DEEPSEEK_BASE_URL=https://... # optional ## Git hooks -lefthook is configured in `lefthook.yml` as an early local checkpoint before review: +lefthook is configured in `lefthook.yml` as a fast local checkpoint: -- `pre-commit` runs staged-file ESLint fixes, `pnpm run typecheck`, and the vendor manifest guard. -- `pre-push` runs `pnpm run check:pre-push`, whose scheduler runs runtime-closure verification, unit tests, duplication detection, snapshot tests, build, module-graph freshness, and the member gates of `pnpm run hygiene` and `pnpm run doc-sync` concurrently. +- `pre-commit` runs staged-file ESLint fixes, checks the staged diff for whitespace errors, and runs the vendor manifest guard. +- `pre-push` runs only the incremental repository typecheck. The vendor manifest guard checks that changes under `vendor/*/src` are staged with the matching `vendor/README.md` manifest update. See `vendor/README.md` before editing vendored code. -These hooks do not exactly mirror CI. Notably, `pre-push` runs unit tests without coverage, while CI runs `pnpm run test:coverage`; CI also runs built-bin smoke tests and exercises the compatibility matrix on Node 22.19, 24, and 26. +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. ## CI gates diff --git a/docs/development.zh.md b/docs/development.zh.md index 62e16479a4..50051ffd63 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -35,13 +35,13 @@ pnpm run typecheck 首次类型检查会执行 package/vendor 的构建图,以及根目录下用于示例、测试和脚本的 no-emit `tsconfig.json` 项目图。根图使用同一份源码 `paths` 映射,但依赖 project references,因此 vendor 代码在它自己的 tsconfig 设置下被检查。 -如果准备从新克隆或新 worktree 推送,还需要构建一次: +如果相关的本地检查需要使用构建后的包产物,请先构建一次: ```sh pnpm run build ``` -`pnpm run hygiene` 包含 `publint`(用构建出的 `lib/*.js` 文件校验 package 入口点)和 `verify-node-next-types`(用一个临时的 NodeNext 消费方校验构建出的声明文件)。新 worktree 在 `pnpm run build` 运行之前没有打包的 JS 和声明文件。 +`pnpm run hygiene` 包含 `publint`(用构建出的 `lib/*.js` 文件校验 package 入口点)和 `verify-node-next-types`(用一个临时的 NodeNext 消费方校验构建出的声明文件)。新 worktree 在 `pnpm run build` 运行之前没有打包的 JS 和声明文件;普通提交和推送无需构建,除非所选检查会使用这些产物。 ## 环境变量 @@ -56,14 +56,14 @@ DEEPSEEK_BASE_URL=https://... # optional ## Git 钩子 -lefthook 在 `lefthook.yml` 中配置,作为评审前的本地早期检查点: +lefthook 在 `lefthook.yml` 中配置,作为快速的本地检查点: -- `pre-commit` 运行对暂存文件的 ESLint 修复、`pnpm run typecheck` 和 vendor manifest(元数据清单)守卫; -- `pre-push` 运行 `pnpm run check:pre-push`,其调度器并发运行 runtime-closure 校验、单元测试、重复代码检查、快照测试、构建、module-graph 新鲜度,以及 `pnpm run hygiene` 与 `pnpm run doc-sync` 的各成员门禁。 +- `pre-commit` 运行对暂存文件的 ESLint 修复,检查暂存 diff 中的空白错误,并运行 vendor manifest(元数据清单)守卫; +- `pre-push` 只运行仓库增量类型检查。 vendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `vendor/README.md` manifest 更新一起暂存。请在编辑 vendor 代码前先阅读 `vendor/README.md`。 -这些钩子并不与 CI 完全一致。特别是:`pre-push` 运行不带覆盖率的单元测试,而 CI 运行 `pnpm run test:coverage`;CI 还会运行 built-bin 冒烟测试,并在 Node 22.19、24 和 26 上执行兼容性矩阵。 +这些钩子有意不运行测试、快照、文档检查、构建或 `hygiene`。贡献者只运行一次[与改动行为相关的检查](../AGENTS.md#run-relevant-checks-locally);CI 负责全量覆盖率门禁、构建产物冒烟测试,以及 Node 22.19、24 和 26 兼容性矩阵。 ## CI 门禁 diff --git a/docs/i18n/README.i18n.yaml b/docs/i18n/README.i18n.yaml index 401813b088..2a72aaa53e 100644 --- a/docs/i18n/README.i18n.yaml +++ b/docs/i18n/README.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 -README.md: bd5d8c08a4c474a13342b6b60800cfe0d31e110b -README.zh.md: a53ab8d9d6053b39def34505038504fefc80a3f9 +README.md: c4ddf44ad2497b4ff371918356ab1ec0698c7049 +README.zh.md: 4a31af4fdee4db2d0362cf9117a6eef4fea32393 diff --git a/docs/i18n/README.md b/docs/i18n/README.md index bd5d8c08a4..c4ddf44ad2 100644 --- a/docs/i18n/README.md +++ b/docs/i18n/README.md @@ -21,7 +21,7 @@ This repo's documentation is read by people and agents both inside and outside t ## The gate: verify-translation-pairing -`pnpm run verify-translation-pairing` (part of `doc-sync`, so CI and the pre-push hook run it) enforces the contract mechanically: +`pnpm run verify-translation-pairing` (part of `doc-sync`, which contributors run locally for documentation changes and CI runs exhaustively) enforces the contract mechanically: 1. Every file listed as `required` in [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) has a complete pair. 2. Every pair that exists at all — required or not — is complete and consistent: all three files present, each side's current blob hash equals the recorded one (editing either side without re-confirming the pair goes red), both sides carry the language switcher, and the structural signatures match in order — heading depths, verbatim code blocks (info string and content), table row and column counts, list kinds, ordered-list starts, item counts, and every link target apart from the switcher. diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md index a53ab8d9d6..4a31af4fde 100644 --- a/docs/i18n/README.zh.md +++ b/docs/i18n/README.zh.md @@ -21,7 +21,7 @@ ## 门禁:verify-translation-pairing -`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,因此 CI 和 pre-push 钩子都会运行)机械地强制执行这份契约: +`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,贡献者会针对文档变更在本地运行,CI 则会完整运行)机械地强制执行这份契约: 1. [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) 中 `required` 列出的每个文件都有完整配对。 2. 任何已存在的配对——无论是否 required——都完整且一致:三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)、双方都带语言切换行、结构签名按序一致——标题深度、逐字节一致的代码块(信息字符串与内容)、表格行列数、列表类型、有序列表起始编号、列表项数量,以及除切换行之外的每个链接目标。 diff --git a/lefthook.yml b/lefthook.yml index 53b8cc84c2..49a1499976 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,25 +1,23 @@ -# Git hooks (lefthook). Hooks call the same package.json scripts CI runs — -# one source of truth; the hook is just an earlier, faster checkpoint. +# Git hooks (lefthook). Keep these local checkpoints fast; CI owns the full +# repository-wide gate matrix. # Install: `pnpm exec lefthook install` (runs automatically via postinstall). pre-commit: - parallel: true jobs: - name: lint (staged) glob: '*.{ts,mts,cts,mjs}' exclude: - 'vendor/*/src/**' - run: node_modules/.bin/eslint --fix {staged_files} && git add {staged_files} + run: node_modules/.bin/eslint --fix {staged_files} stage_fixed: true - - name: typecheck - glob: '*.ts' - run: pnpm run typecheck + - name: whitespace (staged) + run: git diff --cached --check - name: vendor manifest guard run: scripts/check-vendor-manifest.sh pre-push: jobs: - - name: full check - run: pnpm run check:pre-push + - name: typecheck + run: node_modules/.bin/tsc -b tsconfig.json --pretty false diff --git a/package.json b/package.json index c3998bad0e..90b502cc55 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "check:ci:snapshot": "tsx scripts/run-gates.ts ci-snapshot", "check:ci:artifacts": "tsx scripts/run-gates.ts ci-artifacts", "check:node-compat": "tsx scripts/run-gates.ts node-compat", - "check:pre-push": "tsx scripts/run-gates.ts pre-push", "knip": "knip --treat-config-hints-as-errors", "publint": "tsx scripts/publint-all.ts", "doc-typecheck": "tsx scripts/doc-typecheck.ts", diff --git a/packages/hooks/hooks-claude/tests/coverage-cases.ts b/packages/hooks/hooks-claude/tests/coverage-cases.ts index df6d55180a..e303c589be 100644 --- a/packages/hooks/hooks-claude/tests/coverage-cases.ts +++ b/packages/hooks/hooks-claude/tests/coverage-cases.ts @@ -85,7 +85,7 @@ export function defineCoverageCases(group: CoverageGroup): void { const located = await capture(dir()) expect(located.payload.transcript_path).toBe(located.expected) expect((await capture()).payload.transcript_path).toBe('') - }, 15_000) // Two real agent/hook subprocess loops need loaded pre-push runner headroom. + }, 15_000) // Two real agent/hook subprocess loops need process startup and teardown headroom. it('honors pluginRoot + projectDir substitution and warns on a skipped non-command hook', async () => { const d = dir() diff --git a/packages/hooks/hooks-codex/tests/coverage-cases.ts b/packages/hooks/hooks-codex/tests/coverage-cases.ts index 9bf14da46a..d0f0df92f6 100644 --- a/packages/hooks/hooks-codex/tests/coverage-cases.ts +++ b/packages/hooks/hooks-codex/tests/coverage-cases.ts @@ -76,7 +76,7 @@ export function defineCoverageCases(groups: CoverageGroup | readonly CoverageGro const located = await capture(dir()) expect(located.payload.transcript_path).toBe(located.expected) expect((await capture()).payload.transcript_path).toBeNull() - }, 15_000) // Two real agent/hook subprocess loops need loaded pre-push runner headroom. + }, 15_000) // Two real agent/hook subprocess loops need process startup and teardown headroom. it('UserPromptSubmit block (exit 2) → rejected turn; default reason on empty stderr', async () => { const d = dir() diff --git a/packages/support/loader-smoke/src/index.ts b/packages/support/loader-smoke/src/index.ts index 36ab137f32..bcb27eb9d3 100644 --- a/packages/support/loader-smoke/src/index.ts +++ b/packages/support/loader-smoke/src/index.ts @@ -24,7 +24,7 @@ export const LOADER_SMOKE_TEST_TIMEOUT_MS = DEFAULT_PROCESS_TIMEOUT_MS + 15_000 /** Which artifact an example bin is booted from: unbuilt `src` via tsx, or built `lib` via plain Node. */ export type ExampleMode = 'src' | 'lib' -/** Environment variable selecting the mode; CI and pre-push set it to `lib`, dev leaves it unset (`src`). */ +/** Environment variable selecting the mode; CI sets it to `lib`, dev leaves it unset (`src`). */ export const EXAMPLE_MODE_ENV = 'DSH_EXAMPLE_MODE' /** diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index 2de30b76a7..91c6d5228b 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -17,7 +17,6 @@ type Mode = | 'ci-snapshot' | 'ci-artifacts' | 'node-compat' - | 'pre-push' | 'doc-sync' type GateStatus = 'pending' | 'running' | 'passed' | 'failed' | 'skipped' @@ -87,21 +86,20 @@ function parseMode(raw: string | undefined): Mode { case 'ci-snapshot': case 'ci-artifacts': case 'node-compat': - case 'pre-push': 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 | node-compat | pre-push | doc-sync, got ${JSON.stringify(raw)}.`, + `run-gates: expected mode ci-primary | ci-static | ci-lint | ci-coverage | ci-snapshot | ci-artifacts | node-compat | doc-sync, got ${JSON.stringify(raw)}.`, ) } } function defaultConcurrency(selectedMode: Mode, total: number): ConcurrencyDefault { const available = availableParallelism() - // Local modes cap workers: several doc gates each build a full ts.Program, + // The local doc mode caps workers: several 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 === 'doc-sync' const modeLimit = localCap ? Math.min(4, available) : available return { workers: Math.min(total, modeLimit), @@ -191,21 +189,6 @@ function gatesForMode(selected: Mode): Gate[] { 'packages/session-persistence/session-persistence-jsonl/tests/zstd.compat.spec.ts', ], { label: 'JSONL Zstandard smoke' }), ] - case 'pre-push': - return [ - pnpmScript('runtime-closure', 'verify-runtime-closure', { label: 'runtime closure' }), - pnpmScript('cordis-config', 'verify-cordis-config', { label: 'Cordis config' }), - pnpmScript('test', 'test'), - pnpmScript('duplication', 'duplication'), - snapshotGate(), - pnpmScript('build', 'build'), - ...hygieneLeafGates({ artifactNeeds: ['build'] }), - ...docSyncLeafGates({ - docTypecheckNeeds: ['build'], - docTypecheckEnv: { DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1' }, - }), - pnpmScript('module-graph', 'verify-module-graph', { label: 'module graph' }), - ] case 'doc-sync': return docSyncLeafGates() } @@ -295,8 +278,8 @@ 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 -// than the tsx/source path dev uses. It therefore waits on `build`. +// plugins via real exports). CI pairs it with `build`, so it exercises what ships rather than +// the tsx/source path dev uses and therefore waits on `build`. function snapshotGate(): Gate { return pnpmScript('snapshot', 'test:snapshot', { env: { DSH_EXAMPLE_MODE: 'lib' }, @@ -321,30 +304,9 @@ function positiveIntArg(envName: string, flag: string): string[] { return [`${flag}=${raw}`] } -function hygieneLeafGates(options: { artifactNeeds?: string[] } = {}): Gate[] { - const artifactOptions = options.artifactNeeds === undefined ? {} : { needs: options.artifactNeeds } +function docSyncLeafGates(): Gate[] { return [ - pnpmScript('knip', 'knip'), - pnpmScript('publint', 'publint', artifactOptions), - pnpmScript('constraints', 'constraints'), - pnpmScript('package-invariants', 'verify-package-invariants', { label: 'package invariants' }), - builtPackageInvariantsGate(options.artifactNeeds), - pnpmScript('node-next-types', 'verify-node-next-types', { - label: 'node-next types', - ...artifactOptions, - }), - ] -} - -function docSyncLeafGates(options: { - docTypecheckNeeds?: string[] - docTypecheckEnv?: Record -} = {}): Gate[] { - const docTypecheckOptions: Partial = {} - if (options.docTypecheckNeeds !== undefined) docTypecheckOptions.needs = options.docTypecheckNeeds - if (options.docTypecheckEnv !== undefined) docTypecheckOptions.env = options.docTypecheckEnv - return [ - pnpmScript('doc-typecheck', 'doc-typecheck', docTypecheckOptions), + pnpmScript('doc-typecheck', 'doc-typecheck'), pnpmScript('cordis-catalog', 'verify-cordis-catalog', { label: 'cordis catalog' }), pnpmScript('cordis-api', 'verify-cordis-api', { label: 'cordis api' }), pnpmScript('export-jsdoc', 'verify-export-jsdoc', { label: 'export jsdoc' }),