From 7dfb8a5f035d201085da596e9590a5343c3c882b Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:17:40 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=93=84=20=E4=BF=AE=E6=AD=A3=20AGENTS.?= =?UTF-8?q?md=20=E4=B8=8E=20develop.md=20=E4=B8=AD=E7=9A=84=E8=BF=87?= =?UTF-8?q?=E6=9C=9F=E4=BA=8B=E5=AE=9E=E4=B8=8E=E6=8E=AA=E8=BE=9E=E6=AD=A7?= =?UTF-8?q?=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 23 ++++++++++++++--------- docs/develop.md | 4 ++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 63d90e238..024e21ad1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,12 +2,16 @@ This file provides guidance to AI coding agents (Claude Code, etc.) when working with code in this repository. -> **Note:** This is the single source of truth. `CLAUDE.md` only contains `@AGENTS.md` and re-imports this file — do not add content to `CLAUDE.md`; put all guidance here in `AGENTS.md`. +> **Note:** This is the single source of truth relative to `CLAUDE.md` — `CLAUDE.md` only contains `@AGENTS.md` +> and re-imports this file; don't split guidance between the two, put it here. Detailed guidance beyond +> engineering principles and the architecture map is owned by the docs linked below (see +> [`docs/DOC-MAINTENANCE.md`](docs/DOC-MAINTENANCE.md)'s ownership table) — cross-link them, don't duplicate +> their content here. > **Before writing any code, read [`docs/develop.md`](docs/develop.md)** — the development spec (commands, > project structure, coding style, UI & theme rules, testing mechanics, i18n, and the commit/PR workflow). This > file keeps only the non-negotiable engineering principles and the architecture map; the concrete "how" lives -> in `DEVELOP.md`, and deep internals in [`docs/architecture.md`](docs/architecture.md). +> in that same guide, and deep internals in [`docs/architecture.md`](docs/architecture.md). > **To manually verify a feature actually works, read [`docs/verification.md`](docs/verification.md)** — drive > the real built extension end-to-end with one-shot throwaway scratch scripts (not the committed test suite). @@ -32,14 +36,15 @@ ScriptCat — Manifest V3 browser extension that runs Tampermonkey-compatible us > **UI stack.** The presentation layer (`src/pages/`) is built with **shadcn/ui + Tailwind CSS v4** on > **React 19** (migrated from Arco Design + UnoCSS). The concrete UI/theme rules live in > [`docs/develop.md`](docs/develop.md); the design system (color tokens, components, layout/motion/state -> patterns, new-page recipe) lives in [`docs/design.md`](docs/design.md) — read it before building any page. +> patterns, new-page recipe) lives in [`docs/design.md`](docs/design.md) — read it before building any new +> page, dialog, or block. ## Engineering Principles -These are non-negotiable and apply to every change, regardless of what `docs/develop.md` says about mechanics. +These are non-negotiable and apply to every code change, regardless of what `docs/develop.md` says about mechanics. -- **Fix root causes, not symptoms — refactor over patch.** No `as any` / `// @ts-ignore` / try-catch swallow / defensive skips to make errors disappear. When the clean fix needs restructuring, refactor rather than bolt on a patch (宁愿重构也不要打补丁). If a test fails, fix the code, not the test. -- **Confirm before you fix.** Before touching a reported bug, reproduce it and confirm it actually exists — never fix from assumption. Then capture it in a failing test, then fix, **in that order** (确定 bug 存在 → 写测试 → 修复). +- **Fix root causes, not symptoms — refactor over patch.** No `as any` / `// @ts-ignore` / try-catch swallow / defensive skips to make errors disappear. When the clean fix needs restructuring, refactor rather than bolt on a patch (宁愿重构也不要打补丁). If a test fails, fix the code, not the test — unless the test itself is wrong (tautological, obsolete, or asserting an incorrect contract; see [`docs/references/develop-testing.md`](docs/references/develop-testing.md)'s "writing meaningful tests" section for what qualifies), in which case fix the test and say why. +- **Confirm before you fix.** Before touching a reported bug, reproduce it and confirm it actually exists — never fix from assumption. [`docs/verification.md`](docs/verification.md) has the scratch-script workflow for driving the real extension to reproduce it. Then capture it in a failing test, then fix, **in that order** (确定 bug 存在 → 写测试 → 修复). - **TDD/BDD first.** Write failing tests **before** implementation, using BDD-style Chinese `describe`/`it` titles. (Runner, mocks, and how to run tests are in `docs/develop.md`.) - **SOLID, high cohesion & low coupling — applied to the existing extension points.** `Repo` for new entities, `Group.on(...)` for new messages. Inject `Group` / `IMessageQueue` / DAOs via constructor; don't `new` them inside methods. Depend on narrow interfaces (`IMessageQueue`, not `MessageQueue`). - **Direct replacement over adapter sandwiches.** When swapping a backend/library, replace in place — no `interface Foo + LegacyImpl + NewImpl` unless both must coexist at runtime. @@ -89,14 +94,14 @@ Execution paths: page scripts → `chrome.userScripts`; background → SW → Of ### Key Packages -`message/` (with mocks), `filesystem/` (WebDAV + local), `cloudscript/`, `eslint/` (userscript lint config — `eslint-plugin-userscripts`-based `defaultConfig` for the in-app editor), `chrome-extension-mock/`. +`message/` (with mocks), `filesystem/` (WebDAV, cloud drive providers, zip export — see [`docs/cloud-sync.md`](docs/cloud-sync.md)), `cloudscript/`, `eslint/` (userscript lint config — `eslint-plugin-userscripts`-based `defaultConfig` for the in-app editor), `chrome-extension-mock/`. > The project's own custom ESLint rules live in `eslint-rules/` at the repo root (wired in `eslint.config.mjs`, **not** in `packages/eslint/`) and act as a **mechanical harness** for conventions that would otherwise rely on memory: -> - `require-last-error-check` — enforces `chrome.runtime.lastError` handling. +> - `chrome-error/require-last-error-check` — enforces `chrome.runtime.lastError` handling. Not covered by `harness.test.mjs` (see below). > - `scriptcat/no-i18n-default-value` — bans `t(key, { defaultValue })` inline fallbacks (they leak hardcoded text to every language and bypass the `i18n-usage` key check); add the key to `src/locales//*.json` instead. > - `scriptcat/no-raw-color-classname` (`src/pages/**/*.tsx`) — bans raw palette/hex colors in `className` (`bg-white`, `text-gray-500`, `dark:bg-gray-800`, `bg-[#fff]`); use design tokens (`bg-background`/`text-foreground`/…) so light & dark both work. > -> Two conventions are enforced via built-in rules in `eslint.config.mjs`: `no-restricted-imports` bans `@radix-ui/react-*` single packages (use the merged `radix-ui`) and the `sonner` `toast` export (use `notify`); `no-restricted-syntax` bans `forwardRef` across `src/pages/**` (use React 19 `function` + ref-prop). All four syntax-based harness rules are covered by `eslint-rules/harness.test.mjs`. +> Two conventions are enforced via built-in rules in `eslint.config.mjs`: `no-restricted-imports` bans `@radix-ui/react-*` single packages (use the merged `radix-ui`) and the `sonner` `toast` export (use `notify`); `no-restricted-syntax` bans `forwardRef` across `src/pages/**` (use React 19 `function` + ref-prop). `eslint-rules/harness.test.mjs` covers exactly four of these: `no-i18n-default-value`, `no-raw-color-classname`, the `radix-ui` pattern of `no-restricted-imports`, and `no-restricted-syntax` — not `require-last-error-check`, and not the `sonner` pattern of `no-restricted-imports`. > > Separately, type-aware rules run on `src/pages/**` (tests excluded) via `projectService` — `@typescript-eslint/no-floating-promises`, `no-misused-promises` (with `checksVoidReturn.attributes: false`, so `async` JSX handlers are allowed), and `await-thenable`, all `error` — to catch missing `await`s and promises misused as void callbacks. These need type information, so they are *not* part of `harness.test.mjs`. diff --git a/docs/develop.md b/docs/develop.md index 0ce0ab4bd..8fd790c2c 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -68,7 +68,7 @@ React 19 + shadcn/ui (Radix UI primitives, "new-york" style) + Tailwind CSS v4 + - No hard-coded colors. - **Design system** — the full color-token reference (light/dark values), component palette, layout & responsive patterns, motion guidance, state patterns, and a new-page recipe live in - [`DESIGN.md`](./design.md). Read it before building a new page, dialog, or block. + [`design.md`](./design.md). Read it before building a new page, dialog, or block. ## Testing @@ -78,7 +78,7 @@ This project uses Vitest for unit tests and Playwright for end-to-end tests. ## i18n -i18next, 8 locales (`src/locales/`: en-US, zh-CN, zh-TW, ja-JP, de-DE, vi-VN, ru-RU, tr-TR); extension strings in `src/assets/_locales/`. ESLint `react/jsx-no-literals: warn` enforces translation. Each locale is split by namespace into multiple `*.json` files (`common.json`, `popup.json`, `script.json`, …), re-exported via the locale's `index.ts` and merged in `src/locales/locales.ts`. `defaultNS` is `common`; keys in any other namespace need the `ns:` prefix (e.g. `t("script:tags")`). For localization, edit the relevant namespace `*.json` under `src/locales//`; new locales must also be registered in `src/locales/locales.ts`. +i18next, 9 locales (`src/locales/`: en-US, zh-CN, zh-TW, ja-JP, de-DE, vi-VN, ru-RU, tr-TR, pt-BR); extension strings in `src/assets/_locales/`. ESLint `react/jsx-no-literals: warn` enforces translation. Each locale is split by namespace into multiple `*.json` files (`common.json`, `popup.json`, `script.json`, …), re-exported via the locale's `index.ts` and merged in `src/locales/locales.ts`. `defaultNS` is `common`; keys in any other namespace need the `ns:` prefix (e.g. `t("script:tags")`). For localization, edit the relevant namespace `*.json` under `src/locales//`; new locales must also be registered in `src/locales/locales.ts`. **Before translating, read [`docs/translation.md`](./translation.md)** — the translation/localization guide (terminology rules + per-locale `terminology-.md` specs). From 9c96f0d2452753e1dadf01150a153f68c9d9b714 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:31:59 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=93=84=20=E4=BF=AE=E6=AD=A3=20react/j?= =?UTF-8?q?sx-no-literals=20=E7=BA=A7=E5=88=AB=E9=99=88=E8=BF=B0=EF=BC=8C?= =?UTF-8?q?=E6=B6=88=E9=99=A4=20TDD=20=E8=8C=83=E5=9B=B4=E4=B8=8E=E6=89=80?= =?UTF-8?q?=E6=9C=89=E6=9D=83=E5=A3=B0=E6=98=8E=E7=9A=84=E8=87=AA=E7=9B=B8?= =?UTF-8?q?=E7=9F=9B=E7=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/develop.md:两处 "react/jsx-no-literals: warn" 均为过期表述, eslint.config.mjs 实际配置为 "error";一并改为不重复硬编码 locale 数量/列表,改为指向 docs/translation.md 这一权威来源,避免该计数 再次漂移(此前已从 8 漂移到 9)。 - AGENTS.md: - 将 "apply to every code change" 的整体范围限定去掉,改为按条目 各自标注适用范围——原有的"every code change"表述本身仍会把 TDD 要求错误地套用到行为保持型重构、类型清理、配置/依赖变更、死代码 删除、机械改名等不需要先写失败测试的改动上;同时又可能让"Scope discipline"这类本应通用于文档/翻译/配置改动的原则被误读为仅限 代码改动。现改为 TDD 条目自带范围说明,其余条目不再受统一限定词 约束。 - "If a test fails, fix the code, not the test" 的例外表述中 "tautological"归类有误——重言式测试通常恒为真,不会"失败",因此 不适合作为"测试失败时可能是测试本身错了"的例子;已改为区分两类 不同情况:测试失败因其自身断言的契约有误(应修正测试)、与测试 从一开始就不该存在因而应主动清理(不依赖是否失败)。 - "Confirm before you fix" 补充:并非所有复现都需要走 docs/verification.md 的浏览器端 scratch-script 流程——纯逻辑/解析 /工具函数类 bug 用最小的失败单元测试复现即可,只有依赖真实扩展、 浏览器 API 或跨上下文行为的 bug 才需要该工作流。 - 新增的"详细指南不放在 AGENTS.md、避免重复"表述与文件末尾仍保留 的 ESLint 规则详细清单(含插件命名空间、精确文件范围、harness 覆盖 情况)直接冲突——已在该清单前补充一句说明:这是有意为之的窄范围 例外,因为这些规则由 lint/测试机制强制执行,不会像纯文字文档那样 悄悄漂移。 - docs/design.md 的阅读触发条件从嵌在 "UI stack" 段落中的被动陈述, 提升为与其他四条文档提示同级的顶部 "Before X, read Y" 引用块(并把 UI stack 段落中原本重复的触发语句删除,避免两处各说一次)。 Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 17 ++++++++++------- docs/develop.md | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 024e21ad1..bc24a219c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,9 @@ This file provides guidance to AI coding agents (Claude Code, etc.) when working > **To manually verify a feature actually works, read [`docs/verification.md`](docs/verification.md)** — drive > the real built extension end-to-end with one-shot throwaway scratch scripts (not the committed test suite). +> **Before building any new page, dialog, or block, read [`docs/design.md`](docs/design.md)** — the design +> system: color tokens, component palette, layout/motion/state patterns, and the new-page recipe. + > **Before any translation/localization work, read [`docs/translation.md`](docs/translation.md)** — > the single source of truth for translation. Whenever you add or change localized content > (`src/locales//*.json` namespace files, per-language docs, UI copy, or test snapshots), you must first @@ -36,16 +39,16 @@ ScriptCat — Manifest V3 browser extension that runs Tampermonkey-compatible us > **UI stack.** The presentation layer (`src/pages/`) is built with **shadcn/ui + Tailwind CSS v4** on > **React 19** (migrated from Arco Design + UnoCSS). The concrete UI/theme rules live in > [`docs/develop.md`](docs/develop.md); the design system (color tokens, components, layout/motion/state -> patterns, new-page recipe) lives in [`docs/design.md`](docs/design.md) — read it before building any new -> page, dialog, or block. +> patterns, new-page recipe) lives in [`docs/design.md`](docs/design.md). ## Engineering Principles -These are non-negotiable and apply to every code change, regardless of what `docs/develop.md` says about mechanics. +These are non-negotiable, regardless of what `docs/develop.md` says about mechanics — where a principle's scope +isn't universal, that's called out in the item itself. -- **Fix root causes, not symptoms — refactor over patch.** No `as any` / `// @ts-ignore` / try-catch swallow / defensive skips to make errors disappear. When the clean fix needs restructuring, refactor rather than bolt on a patch (宁愿重构也不要打补丁). If a test fails, fix the code, not the test — unless the test itself is wrong (tautological, obsolete, or asserting an incorrect contract; see [`docs/references/develop-testing.md`](docs/references/develop-testing.md)'s "writing meaningful tests" section for what qualifies), in which case fix the test and say why. -- **Confirm before you fix.** Before touching a reported bug, reproduce it and confirm it actually exists — never fix from assumption. [`docs/verification.md`](docs/verification.md) has the scratch-script workflow for driving the real extension to reproduce it. Then capture it in a failing test, then fix, **in that order** (确定 bug 存在 → 写测试 → 修复). -- **TDD/BDD first.** Write failing tests **before** implementation, using BDD-style Chinese `describe`/`it` titles. (Runner, mocks, and how to run tests are in `docs/develop.md`.) +- **Fix root causes, not symptoms — refactor over patch.** No `as any` / `// @ts-ignore` / try-catch swallow / defensive skips to make errors disappear. When the clean fix needs restructuring, refactor rather than bolt on a patch (宁愿重构也不要打补丁). If a test fails, fix the code, not the test — unless the *test's own contract* is wrong (a stale fixture, an assertion that was incorrect from the start, or a contract that legitimately changed), in which case fix the test and say why. That's distinct from proactively deleting a test that never carried value regardless of pass/fail (tautological, duplicate, framework-only, mislabeled — see [`docs/references/develop-testing.md`](docs/references/develop-testing.md)'s "writing meaningful tests" section). +- **Confirm before you fix.** Before touching a reported bug, reproduce it and confirm it actually exists — never fix from assumption. Use the smallest reproduction that faithfully shows it: a failing unit test for pure logic/parser/utility bugs, or [`docs/verification.md`](docs/verification.md)'s scratch-script workflow when it depends on the built extension, browser APIs, or cross-context behavior. Then capture it in a failing test, then fix, **in that order** (确定 bug 存在 → 写测试 → 修复). +- **TDD/BDD first, for behavior changes.** Write failing tests **before** implementing new or changed behavior, using BDD-style Chinese `describe`/`it` titles. Behavior-preserving refactors, type cleanup, config/dependency changes, dead-code removal, and mechanical renames don't need a new failing test — verify them instead. (Runner, mocks, and how to run tests are in `docs/develop.md`.) - **SOLID, high cohesion & low coupling — applied to the existing extension points.** `Repo` for new entities, `Group.on(...)` for new messages. Inject `Group` / `IMessageQueue` / DAOs via constructor; don't `new` them inside methods. Depend on narrow interfaces (`IMessageQueue`, not `MessageQueue`). - **Direct replacement over adapter sandwiches.** When swapping a backend/library, replace in place — no `interface Foo + LegacyImpl + NewImpl` unless both must coexist at runtime. - **Scope discipline — stay in your lane.** Bug fix ≠ cleanup PR. Touch only the files the task requires; leave unrelated files untouched (不要动和任务不相干的文件). Don't add helpers, abstractions, validation, or backwards-compat shims you don't need today. Three similar lines beats a premature abstraction. @@ -96,7 +99,7 @@ Execution paths: page scripts → `chrome.userScripts`; background → SW → Of `message/` (with mocks), `filesystem/` (WebDAV, cloud drive providers, zip export — see [`docs/cloud-sync.md`](docs/cloud-sync.md)), `cloudscript/`, `eslint/` (userscript lint config — `eslint-plugin-userscripts`-based `defaultConfig` for the in-app editor), `chrome-extension-mock/`. -> The project's own custom ESLint rules live in `eslint-rules/` at the repo root (wired in `eslint.config.mjs`, **not** in `packages/eslint/`) and act as a **mechanical harness** for conventions that would otherwise rely on memory: +> The project's own custom ESLint rules live in `eslint-rules/` at the repo root (wired in `eslint.config.mjs`, **not** in `packages/eslint/`) and act as a **mechanical harness** for conventions that would otherwise rely on memory. This list is a deliberate, narrow exception to the "detailed guidance lives in the owning doc" rule above: these rules are enforced by lint/tests, not prose, so — unlike freeform documentation — they can't silently drift out of sync with what's actually enforced: > - `chrome-error/require-last-error-check` — enforces `chrome.runtime.lastError` handling. Not covered by `harness.test.mjs` (see below). > - `scriptcat/no-i18n-default-value` — bans `t(key, { defaultValue })` inline fallbacks (they leak hardcoded text to every language and bypass the `i18n-usage` key check); add the key to `src/locales//*.json` instead. > - `scriptcat/no-raw-color-classname` (`src/pages/**/*.tsx`) — bans raw palette/hex colors in `className` (`bg-white`, `text-gray-500`, `dark:bg-gray-800`, `bg-[#fff]`); use design tokens (`bg-background`/`text-foreground`/…) so light & dark both work. diff --git a/docs/develop.md b/docs/develop.md index 8fd790c2c..bf7839385 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -42,7 +42,7 @@ Core entry points live in `src` (`service_worker.ts`, `content.ts`, `inject.ts`, ## Coding Style & Naming Conventions -Use strict TypeScript, React JSX runtime, 2-space indentation, semicolons, double quotes, trailing commas where valid, and a 120-column Prettier width. Prefer aliases from `tsconfig.json`: `@App/*`, `@Packages/*`, and `@Tests/*`. ESLint requires type-only imports, allows `_`-prefixed unused variables, warns on literal JSX text, and enforces `chrome.runtime.lastError` checks. Use `pnpm run lint-fix` for mechanical fixes. +Use strict TypeScript, React JSX runtime, 2-space indentation, semicolons, double quotes, trailing commas where valid, and a 120-column Prettier width. Prefer aliases from `tsconfig.json`: `@App/*`, `@Packages/*`, and `@Tests/*`. ESLint requires type-only imports, allows `_`-prefixed unused variables, errors on literal JSX text, and enforces `chrome.runtime.lastError` checks. Use `pnpm run lint-fix` for mechanical fixes. ### Language Conventions @@ -78,7 +78,7 @@ This project uses Vitest for unit tests and Playwright for end-to-end tests. ## i18n -i18next, 9 locales (`src/locales/`: en-US, zh-CN, zh-TW, ja-JP, de-DE, vi-VN, ru-RU, tr-TR, pt-BR); extension strings in `src/assets/_locales/`. ESLint `react/jsx-no-literals: warn` enforces translation. Each locale is split by namespace into multiple `*.json` files (`common.json`, `popup.json`, `script.json`, …), re-exported via the locale's `index.ts` and merged in `src/locales/locales.ts`. `defaultNS` is `common`; keys in any other namespace need the `ns:` prefix (e.g. `t("script:tags")`). For localization, edit the relevant namespace `*.json` under `src/locales//`; new locales must also be registered in `src/locales/locales.ts`. +i18next; extension strings in `src/assets/_locales/`. The current locale list is owned by [`docs/translation.md`](./translation.md). ESLint `react/jsx-no-literals: error` enforces translation. Each locale is split by namespace into multiple `*.json` files (`common.json`, `popup.json`, `script.json`, …), re-exported via the locale's `index.ts` and merged in `src/locales/locales.ts`. `defaultNS` is `common`; keys in any other namespace need the `ns:` prefix (e.g. `t("script:tags")`). For localization, edit the relevant namespace `*.json` under `src/locales//`; new locales must also be registered in `src/locales/locales.ts`. **Before translating, read [`docs/translation.md`](./translation.md)** — the translation/localization guide (terminology rules + per-locale `terminology-.md` specs). From f616abad37c8011b1576a0d4890bf56cb226cb05 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:44:00 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=93=84=20=E6=B6=88=E9=99=A4=20TDD=20?= =?UTF-8?q?=E4=BE=8B=E5=A4=96=E5=88=86=E7=B1=BB=E8=BF=87=E5=AE=BD=E4=B8=8E?= =?UTF-8?q?=20ESLint=20=E6=B8=85=E5=8D=95"=E4=B8=8D=E4=BC=9A=E6=BC=82?= =?UTF-8?q?=E7=A7=BB"=E5=A3=B0=E6=98=8E=E7=9A=84=E8=87=AA=E8=AF=81?= =?UTF-8?q?=E7=9F=9B=E7=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TDD 例外原按"config/dependency changes"整类豁免失败测试要求,但此类 改动本身可能改变可观察行为(解析器升级修复缺陷、打包配置影响产物、 manifest/permission 变更);改为以"是否改变可观察行为"作为判断标准, 并给出反例防止被当作豁免借口。 ESLint 规则清单此前声称"lint/tests 强制执行,不会像文档一样悄悄漂移", 但该清单本身是人工维护的 prose(哪条规则覆盖什么范围、哪些测试验证 它),lint 只强制规则本身的行为,不强制这段描述的准确性——本 PR 上一轮 就是在修正这段描述的过期内容,这本身就反驳了"不会漂移"的说法。改为 明确该清单仍是需要用 grep 复核的陈述,而非既定事实。 Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bc24a219c..80d3e5a42 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,7 +48,7 @@ isn't universal, that's called out in the item itself. - **Fix root causes, not symptoms — refactor over patch.** No `as any` / `// @ts-ignore` / try-catch swallow / defensive skips to make errors disappear. When the clean fix needs restructuring, refactor rather than bolt on a patch (宁愿重构也不要打补丁). If a test fails, fix the code, not the test — unless the *test's own contract* is wrong (a stale fixture, an assertion that was incorrect from the start, or a contract that legitimately changed), in which case fix the test and say why. That's distinct from proactively deleting a test that never carried value regardless of pass/fail (tautological, duplicate, framework-only, mislabeled — see [`docs/references/develop-testing.md`](docs/references/develop-testing.md)'s "writing meaningful tests" section). - **Confirm before you fix.** Before touching a reported bug, reproduce it and confirm it actually exists — never fix from assumption. Use the smallest reproduction that faithfully shows it: a failing unit test for pure logic/parser/utility bugs, or [`docs/verification.md`](docs/verification.md)'s scratch-script workflow when it depends on the built extension, browser APIs, or cross-context behavior. Then capture it in a failing test, then fix, **in that order** (确定 bug 存在 → 写测试 → 修复). -- **TDD/BDD first, for behavior changes.** Write failing tests **before** implementing new or changed behavior, using BDD-style Chinese `describe`/`it` titles. Behavior-preserving refactors, type cleanup, config/dependency changes, dead-code removal, and mechanical renames don't need a new failing test — verify them instead. (Runner, mocks, and how to run tests are in `docs/develop.md`.) +- **TDD/BDD first, for changes that alter observable behavior.** Write failing tests **before** implementing new or changed behavior, using BDD-style Chinese `describe`/`it` titles. The exception is genuinely behavior-preserving work — refactors, type cleanup, dead-code removal, mechanical renames — not a category of file: a config or dependency change that *does* alter behavior (a parser upgrade fixing a parsing bug, a bundler config change affecting output, a manifest/permission bump) still needs one; verify the behavior-preserving cases instead. (Runner, mocks, and how to run tests are in `docs/develop.md`.) - **SOLID, high cohesion & low coupling — applied to the existing extension points.** `Repo` for new entities, `Group.on(...)` for new messages. Inject `Group` / `IMessageQueue` / DAOs via constructor; don't `new` them inside methods. Depend on narrow interfaces (`IMessageQueue`, not `MessageQueue`). - **Direct replacement over adapter sandwiches.** When swapping a backend/library, replace in place — no `interface Foo + LegacyImpl + NewImpl` unless both must coexist at runtime. - **Scope discipline — stay in your lane.** Bug fix ≠ cleanup PR. Touch only the files the task requires; leave unrelated files untouched (不要动和任务不相干的文件). Don't add helpers, abstractions, validation, or backwards-compat shims you don't need today. Three similar lines beats a premature abstraction. @@ -99,7 +99,7 @@ Execution paths: page scripts → `chrome.userScripts`; background → SW → Of `message/` (with mocks), `filesystem/` (WebDAV, cloud drive providers, zip export — see [`docs/cloud-sync.md`](docs/cloud-sync.md)), `cloudscript/`, `eslint/` (userscript lint config — `eslint-plugin-userscripts`-based `defaultConfig` for the in-app editor), `chrome-extension-mock/`. -> The project's own custom ESLint rules live in `eslint-rules/` at the repo root (wired in `eslint.config.mjs`, **not** in `packages/eslint/`) and act as a **mechanical harness** for conventions that would otherwise rely on memory. This list is a deliberate, narrow exception to the "detailed guidance lives in the owning doc" rule above: these rules are enforced by lint/tests, not prose, so — unlike freeform documentation — they can't silently drift out of sync with what's actually enforced: +> The project's own custom ESLint rules live in `eslint-rules/` at the repo root (wired in `eslint.config.mjs`, **not** in `packages/eslint/`) and act as a **mechanical harness** for conventions that would otherwise rely on memory. This list is a deliberate, narrow exception to the "detailed guidance lives in the owning doc" rule above, kept here because it's short and directly useful during review. Lint enforces the *rules themselves* — code violating them fails CI — but this summary of exactly which rule covers which scope and which tests exercise it is still prose, written and maintained by hand, and can drift from `eslint.config.mjs`/`eslint-rules/` like any other doc; treat specifics below as claims to re-verify with `grep`, not settled fact: > - `chrome-error/require-last-error-check` — enforces `chrome.runtime.lastError` handling. Not covered by `harness.test.mjs` (see below). > - `scriptcat/no-i18n-default-value` — bans `t(key, { defaultValue })` inline fallbacks (they leak hardcoded text to every language and bypass the `i18n-usage` key check); add the key to `src/locales//*.json` instead. > - `scriptcat/no-raw-color-classname` (`src/pages/**/*.tsx`) — bans raw palette/hex colors in `className` (`bg-white`, `text-gray-500`, `dark:bg-gray-800`, `bg-[#fff]`); use design tokens (`bg-background`/`text-foreground`/…) so light & dark both work. From b3f897151dac7981daba996f8694086201622e9c Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:51:56 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=93=84=20=E8=A1=A5=E5=85=85=20TDD=20?= =?UTF-8?q?=E4=B8=8D=E5=8F=AF=E8=A1=8C=E8=A6=86=E7=9B=96=E4=BE=8B=E5=A4=96?= =?UTF-8?q?=EF=BC=8C=E7=BB=9F=E4=B8=80=20design.md=20=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=E8=8C=83=E5=9B=B4=E8=87=B3"=E6=96=B0=E5=BB=BA=E6=88=96?= =?UTF-8?q?=E4=BF=AE=E6=94=B9"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TDD/Confirm-before-you-fix 此前对"改变可观察行为"的改动一律要求先写 失败测试,未考虑纯视觉/动画调整、仅特定浏览器版本或扩展生命周期阶段 才能复现的问题、文案/翻译改动、无法可靠自动化的平台行为等场景——这类 改动缺乏可行的自动化覆盖手段,硬性要求容易诱导写空跑/不稳定的低价值 测试,与 develop-testing.md 反对无价值测试的既有规则冲突。补充第二类 例外:改用 docs/verification.md 的人工验证并记录证据。 design.md 触发提示(AGENTS.md、docs/develop.md、docs/design.md 自身 开头)此前均写"建任意新 page/dialog/block 前读",但 design.md 的 Core Constraints 一节明确"every UI change must satisfy all of these" ——范围比"新建"更广,修改已有 UI 同样适用。三处触发文字统一改为 "building or modifying",消除范围不一致。 Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 9 +++++---- docs/design.md | 2 +- docs/develop.md | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 80d3e5a42..ccac1c389 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,8 +16,9 @@ This file provides guidance to AI coding agents (Claude Code, etc.) when working > **To manually verify a feature actually works, read [`docs/verification.md`](docs/verification.md)** — drive > the real built extension end-to-end with one-shot throwaway scratch scripts (not the committed test suite). -> **Before building any new page, dialog, or block, read [`docs/design.md`](docs/design.md)** — the design -> system: color tokens, component palette, layout/motion/state patterns, and the new-page recipe. +> **Before building or modifying any page, dialog, or block, read [`docs/design.md`](docs/design.md)** — the +> design system: color tokens, component palette, layout/motion/state patterns, and the new-page recipe. Its +> Core Constraints apply to every UI change, not just new ones. > **Before any translation/localization work, read [`docs/translation.md`](docs/translation.md)** — > the single source of truth for translation. Whenever you add or change localized content @@ -47,8 +48,8 @@ These are non-negotiable, regardless of what `docs/develop.md` says about mechan isn't universal, that's called out in the item itself. - **Fix root causes, not symptoms — refactor over patch.** No `as any` / `// @ts-ignore` / try-catch swallow / defensive skips to make errors disappear. When the clean fix needs restructuring, refactor rather than bolt on a patch (宁愿重构也不要打补丁). If a test fails, fix the code, not the test — unless the *test's own contract* is wrong (a stale fixture, an assertion that was incorrect from the start, or a contract that legitimately changed), in which case fix the test and say why. That's distinct from proactively deleting a test that never carried value regardless of pass/fail (tautological, duplicate, framework-only, mislabeled — see [`docs/references/develop-testing.md`](docs/references/develop-testing.md)'s "writing meaningful tests" section). -- **Confirm before you fix.** Before touching a reported bug, reproduce it and confirm it actually exists — never fix from assumption. Use the smallest reproduction that faithfully shows it: a failing unit test for pure logic/parser/utility bugs, or [`docs/verification.md`](docs/verification.md)'s scratch-script workflow when it depends on the built extension, browser APIs, or cross-context behavior. Then capture it in a failing test, then fix, **in that order** (确定 bug 存在 → 写测试 → 修复). -- **TDD/BDD first, for changes that alter observable behavior.** Write failing tests **before** implementing new or changed behavior, using BDD-style Chinese `describe`/`it` titles. The exception is genuinely behavior-preserving work — refactors, type cleanup, dead-code removal, mechanical renames — not a category of file: a config or dependency change that *does* alter behavior (a parser upgrade fixing a parsing bug, a bundler config change affecting output, a manifest/permission bump) still needs one; verify the behavior-preserving cases instead. (Runner, mocks, and how to run tests are in `docs/develop.md`.) +- **Confirm before you fix.** Before touching a reported bug, reproduce it and confirm it actually exists — never fix from assumption. Use the smallest reproduction that faithfully shows it: a failing unit test for pure logic/parser/utility bugs, or [`docs/verification.md`](docs/verification.md)'s scratch-script workflow when it depends on the built extension, browser APIs, or cross-context behavior. Then capture it in a failing test (or, if the TDD exception below applies, recorded verification evidence), then fix, **in that order** (确定 bug 存在 → 写测试 → 修复). +- **TDD/BDD first, for changes that alter observable behavior.** Write failing tests **before** implementing new or changed behavior, using BDD-style Chinese `describe`/`it` titles. Two exceptions, neither a blanket file/task category: (1) genuinely behavior-preserving work — refactors, type cleanup, dead-code removal, mechanical renames, or a config/dependency change confirmed not to alter behavior — verify it instead; (2) a behavior change where meaningful automated coverage is genuinely infeasible — a pure visual/animation tweak, a bug reproducible only in a specific browser version or extension lifecycle stage, a copy/translation wording change, platform behavior that can't be automated reliably — verify it via [`docs/verification.md`](docs/verification.md) and record the evidence instead of committing a pass-through or low-value test just to satisfy this rule. (Runner, mocks, and how to run tests are in `docs/develop.md`.) - **SOLID, high cohesion & low coupling — applied to the existing extension points.** `Repo` for new entities, `Group.on(...)` for new messages. Inject `Group` / `IMessageQueue` / DAOs via constructor; don't `new` them inside methods. Depend on narrow interfaces (`IMessageQueue`, not `MessageQueue`). - **Direct replacement over adapter sandwiches.** When swapping a backend/library, replace in place — no `interface Foo + LegacyImpl + NewImpl` unless both must coexist at runtime. - **Scope discipline — stay in your lane.** Bug fix ≠ cleanup PR. Touch only the files the task requires; leave unrelated files untouched (不要动和任务不相干的文件). Don't add helpers, abstractions, validation, or backwards-compat shims you don't need today. Three similar lines beats a premature abstraction. diff --git a/docs/design.md b/docs/design.md index 54edc4f75..c293ceb52 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,6 +1,6 @@ # ScriptCat Design System -> **A reuse-oriented design reference.** It consolidates the visual language that lives in `src/index.css` and the shadcn component layer into one place you can copy from: **color tokens (full light/dark values), the theming mechanism, the component palette, layout & responsive patterns, motion, state patterns, and an end-to-end new-page recipe.** Read this before building any new page, dialog, or block so it stays visually and behaviorally consistent with the rest of the app. +> **A reuse-oriented design reference.** It consolidates the visual language that lives in `src/index.css` and the shadcn component layer into one place you can copy from: **color tokens (full light/dark values), the theming mechanism, the component palette, layout & responsive patterns, motion, state patterns, and an end-to-end new-page recipe.** Read this before building or modifying any page, dialog, or block so it stays visually and behaviorally consistent with the rest of the app. > **Stack in one line:** React 19 + shadcn/ui (Radix primitives, `new-york` style) + Tailwind CSS v4 + React Router. Colors and motion are defined in the `@theme inline` block of `src/index.css`. **There is no `tailwind.config.js`** (Tailwind v4); PostCSS runs through `@tailwindcss/postcss` (`postcss.config.mjs`); **class names have no prefix** (`bg-background`, not `tw-bg-background`). diff --git a/docs/develop.md b/docs/develop.md index bf7839385..bd9789fd7 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -68,7 +68,7 @@ React 19 + shadcn/ui (Radix UI primitives, "new-york" style) + Tailwind CSS v4 + - No hard-coded colors. - **Design system** — the full color-token reference (light/dark values), component palette, layout & responsive patterns, motion guidance, state patterns, and a new-page recipe live in - [`design.md`](./design.md). Read it before building a new page, dialog, or block. + [`design.md`](./design.md). Read it before building or modifying any page, dialog, or block. ## Testing From d94b83282dcde302cc41efc3ab0d604a3159b0c3 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 18 Jul 2026 11:04:54 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=93=84=20=E6=B6=88=E9=99=A4=20TDD=20?= =?UTF-8?q?=E4=B8=89=E6=96=87=E6=A1=A3=E5=86=B2=E7=AA=81=EF=BC=8CESLint=20?= =?UTF-8?q?=E7=BB=86=E5=88=99=E8=BF=81=E8=87=B3=20develop.md=20=E7=B2=BE?= =?UTF-8?q?=E7=AE=80=20AGENTS.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENTS.md 此前新增的失败测试例外未同步到 docs/verification.md 与 docs/references/develop-testing.md:verification.md 仍无条件要求 "scratch reproduction 必须晋升为已提交的失败测试",develop-testing.md 仍无条件写"write failing tests before implementation",AGENTS.md 自身 "确定 bug 存在 → 写测试 → 修复"的中文摘要也未反映新例外——三处对"失败 测试是否强制"给出不同答案。已让 verification.md/develop-testing.md 改为指向 AGENTS.md 作为该策略的唯一权威(不再各自重复并可能过期的 断言),中文摘要补上"或记录验证证据"分支。 ESLint 自定义规则清单(规则名、范围、harness 覆盖情况)此前直接嵌在 AGENTS.md 里,与文件开头"keeps only the non-negotiable engineering principles and the architecture map"的范围声明矛盾,且是本 PR 反复 被指出"新增文本过多、应下放到其他文档"的主要来源。已整体迁移到 docs/develop.md 新增的 "ESLint custom rules" 小节(该文档本就是 "concrete how"的权威归属),AGENTS.md 改为四行指针,不再需要额外的 "例外"措辞。 TDD 第二类例外(自动化覆盖不可行)此前一律指向 docs/verification.md 的浏览器扩展 scratch-script 工作流,但该工作流是为依赖真实扩展/浏览器 API 的场景设计的,对纯文案/翻译改动而言过重——已改为按是否需要真实 扩展区分:需要时用该工作流,否则用更轻量的记录说明即可。 Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 16 ++++++---------- docs/develop.md | 29 +++++++++++++++++++++++++++++ docs/references/develop-testing.md | 2 +- docs/verification.md | 6 +++--- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ccac1c389..93417eaaf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,8 +48,8 @@ These are non-negotiable, regardless of what `docs/develop.md` says about mechan isn't universal, that's called out in the item itself. - **Fix root causes, not symptoms — refactor over patch.** No `as any` / `// @ts-ignore` / try-catch swallow / defensive skips to make errors disappear. When the clean fix needs restructuring, refactor rather than bolt on a patch (宁愿重构也不要打补丁). If a test fails, fix the code, not the test — unless the *test's own contract* is wrong (a stale fixture, an assertion that was incorrect from the start, or a contract that legitimately changed), in which case fix the test and say why. That's distinct from proactively deleting a test that never carried value regardless of pass/fail (tautological, duplicate, framework-only, mislabeled — see [`docs/references/develop-testing.md`](docs/references/develop-testing.md)'s "writing meaningful tests" section). -- **Confirm before you fix.** Before touching a reported bug, reproduce it and confirm it actually exists — never fix from assumption. Use the smallest reproduction that faithfully shows it: a failing unit test for pure logic/parser/utility bugs, or [`docs/verification.md`](docs/verification.md)'s scratch-script workflow when it depends on the built extension, browser APIs, or cross-context behavior. Then capture it in a failing test (or, if the TDD exception below applies, recorded verification evidence), then fix, **in that order** (确定 bug 存在 → 写测试 → 修复). -- **TDD/BDD first, for changes that alter observable behavior.** Write failing tests **before** implementing new or changed behavior, using BDD-style Chinese `describe`/`it` titles. Two exceptions, neither a blanket file/task category: (1) genuinely behavior-preserving work — refactors, type cleanup, dead-code removal, mechanical renames, or a config/dependency change confirmed not to alter behavior — verify it instead; (2) a behavior change where meaningful automated coverage is genuinely infeasible — a pure visual/animation tweak, a bug reproducible only in a specific browser version or extension lifecycle stage, a copy/translation wording change, platform behavior that can't be automated reliably — verify it via [`docs/verification.md`](docs/verification.md) and record the evidence instead of committing a pass-through or low-value test just to satisfy this rule. (Runner, mocks, and how to run tests are in `docs/develop.md`.) +- **Confirm before you fix.** Before touching a reported bug, reproduce it and confirm it actually exists — never fix from assumption. Use the smallest reproduction that faithfully shows it: a failing unit test for pure logic/parser/utility bugs, or [`docs/verification.md`](docs/verification.md)'s scratch-script workflow when it depends on the built extension, browser APIs, or cross-context behavior. Then capture it in a failing test (or, if the TDD exception below applies, recorded verification evidence), then fix, **in that order** (确定 bug 存在 → 写测试或记录验证证据 → 修复). +- **TDD/BDD first, for changes that alter observable behavior.** Write failing tests **before** implementing new or changed behavior, using BDD-style Chinese `describe`/`it` titles. Two exceptions, neither a blanket file/task category: (1) genuinely behavior-preserving work — refactors, type cleanup, dead-code removal, mechanical renames, or a config/dependency change confirmed not to alter behavior — verify it instead; (2) a behavior change where meaningful automated coverage is genuinely infeasible — a pure visual/animation tweak, a bug reproducible only in a specific browser version or extension lifecycle stage, a copy/translation wording change, platform behavior that can't be automated reliably — verify it manually and record the evidence instead of committing a pass-through or low-value test just to satisfy this rule — [`docs/verification.md`](docs/verification.md)'s scratch-extension workflow when the change needs the built extension or browser APIs, a simpler noted check otherwise. (Runner, mocks, and how to run tests are in `docs/develop.md`.) - **SOLID, high cohesion & low coupling — applied to the existing extension points.** `Repo` for new entities, `Group.on(...)` for new messages. Inject `Group` / `IMessageQueue` / DAOs via constructor; don't `new` them inside methods. Depend on narrow interfaces (`IMessageQueue`, not `MessageQueue`). - **Direct replacement over adapter sandwiches.** When swapping a backend/library, replace in place — no `interface Foo + LegacyImpl + NewImpl` unless both must coexist at runtime. - **Scope discipline — stay in your lane.** Bug fix ≠ cleanup PR. Touch only the files the task requires; leave unrelated files untouched (不要动和任务不相干的文件). Don't add helpers, abstractions, validation, or backwards-compat shims you don't need today. Three similar lines beats a premature abstraction. @@ -100,14 +100,10 @@ Execution paths: page scripts → `chrome.userScripts`; background → SW → Of `message/` (with mocks), `filesystem/` (WebDAV, cloud drive providers, zip export — see [`docs/cloud-sync.md`](docs/cloud-sync.md)), `cloudscript/`, `eslint/` (userscript lint config — `eslint-plugin-userscripts`-based `defaultConfig` for the in-app editor), `chrome-extension-mock/`. -> The project's own custom ESLint rules live in `eslint-rules/` at the repo root (wired in `eslint.config.mjs`, **not** in `packages/eslint/`) and act as a **mechanical harness** for conventions that would otherwise rely on memory. This list is a deliberate, narrow exception to the "detailed guidance lives in the owning doc" rule above, kept here because it's short and directly useful during review. Lint enforces the *rules themselves* — code violating them fails CI — but this summary of exactly which rule covers which scope and which tests exercise it is still prose, written and maintained by hand, and can drift from `eslint.config.mjs`/`eslint-rules/` like any other doc; treat specifics below as claims to re-verify with `grep`, not settled fact: -> - `chrome-error/require-last-error-check` — enforces `chrome.runtime.lastError` handling. Not covered by `harness.test.mjs` (see below). -> - `scriptcat/no-i18n-default-value` — bans `t(key, { defaultValue })` inline fallbacks (they leak hardcoded text to every language and bypass the `i18n-usage` key check); add the key to `src/locales//*.json` instead. -> - `scriptcat/no-raw-color-classname` (`src/pages/**/*.tsx`) — bans raw palette/hex colors in `className` (`bg-white`, `text-gray-500`, `dark:bg-gray-800`, `bg-[#fff]`); use design tokens (`bg-background`/`text-foreground`/…) so light & dark both work. -> -> Two conventions are enforced via built-in rules in `eslint.config.mjs`: `no-restricted-imports` bans `@radix-ui/react-*` single packages (use the merged `radix-ui`) and the `sonner` `toast` export (use `notify`); `no-restricted-syntax` bans `forwardRef` across `src/pages/**` (use React 19 `function` + ref-prop). `eslint-rules/harness.test.mjs` covers exactly four of these: `no-i18n-default-value`, `no-raw-color-classname`, the `radix-ui` pattern of `no-restricted-imports`, and `no-restricted-syntax` — not `require-last-error-check`, and not the `sonner` pattern of `no-restricted-imports`. -> -> Separately, type-aware rules run on `src/pages/**` (tests excluded) via `projectService` — `@typescript-eslint/no-floating-promises`, `no-misused-promises` (with `checksVoidReturn.attributes: false`, so `async` JSX handlers are allowed), and `await-thenable`, all `error` — to catch missing `await`s and promises misused as void callbacks. These need type information, so they are *not* part of `harness.test.mjs`. +> The project's own custom ESLint rules (`eslint-rules/` at the repo root, wired in `eslint.config.mjs` — +> **not** `packages/eslint/`, which is the unrelated userscript lint config) are documented in +> [`docs/develop.md`](docs/develop.md#eslint-custom-rules): exact rule names, scopes, and which are covered by +> `eslint-rules/harness.test.mjs`. ## Pull Request Description Format diff --git a/docs/develop.md b/docs/develop.md index bd9789fd7..af3fe0c49 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -44,6 +44,35 @@ Core entry points live in `src` (`service_worker.ts`, `content.ts`, `inject.ts`, Use strict TypeScript, React JSX runtime, 2-space indentation, semicolons, double quotes, trailing commas where valid, and a 120-column Prettier width. Prefer aliases from `tsconfig.json`: `@App/*`, `@Packages/*`, and `@Tests/*`. ESLint requires type-only imports, allows `_`-prefixed unused variables, errors on literal JSX text, and enforces `chrome.runtime.lastError` checks. Use `pnpm run lint-fix` for mechanical fixes. +### ESLint custom rules + +The project's own custom rules live in `eslint-rules/` at the repo root (wired in `eslint.config.mjs`, **not** +`packages/eslint/`, which is the unrelated userscript lint config for the in-app editor) and act as a mechanical +harness for conventions that would otherwise rely on memory. Lint enforces the rules themselves — code violating +them fails CI — but this list of exactly which rule covers which scope, and which are covered by +`eslint-rules/harness.test.mjs`, is hand-maintained prose; re-verify specifics with `grep` rather than trusting +it as settled fact: + +- `chrome-error/require-last-error-check` — enforces `chrome.runtime.lastError` handling. Not covered by + `harness.test.mjs`. +- `scriptcat/no-i18n-default-value` — bans `t(key, { defaultValue })` inline fallbacks (they leak hardcoded text + to every language and bypass the `i18n-usage` key check); add the key to `src/locales//*.json` instead. +- `scriptcat/no-raw-color-classname` (`src/pages/**/*.tsx`) — bans raw palette/hex colors in `className` + (`bg-white`, `text-gray-500`, `dark:bg-gray-800`, `bg-[#fff]`); use design tokens (`bg-background`/ + `text-foreground`/…) so light & dark both work. + +Two conventions are enforced via built-in rules in `eslint.config.mjs`: `no-restricted-imports` bans +`@radix-ui/react-*` single packages (use the merged `radix-ui`) and the `sonner` `toast` export (use `notify`); +`no-restricted-syntax` bans `forwardRef` across `src/pages/**` (use React 19 `function` + ref-prop). +`eslint-rules/harness.test.mjs` covers exactly four of these: `no-i18n-default-value`, `no-raw-color-classname`, +the `radix-ui` pattern of `no-restricted-imports`, and `no-restricted-syntax` — not `require-last-error-check`, +and not the `sonner` pattern of `no-restricted-imports`. + +Separately, type-aware rules run on `src/pages/**` (tests excluded) via `projectService` — +`@typescript-eslint/no-floating-promises`, `no-misused-promises` (with `checksVoidReturn.attributes: false`, so +`async` JSX handlers are allowed), and `await-thenable`, all `error` — to catch missing `await`s and promises +misused as void callbacks. These need type information, so they are *not* part of `harness.test.mjs`. + ### Language Conventions - Comments in Simplified Chinese. diff --git a/docs/references/develop-testing.md b/docs/references/develop-testing.md index 2f381c40d..6e9507b90 100644 --- a/docs/references/develop-testing.md +++ b/docs/references/develop-testing.md @@ -14,7 +14,7 @@ budget at once. Chrome APIs mocked via invalid-selector `DOMException` lazily — the upstream eager construction captures a deep stack on every `matches()`/`querySelector()` call and cost ~15% of TSX suite time. -- Write failing tests **before** implementation; co-locate `*.test.ts`/`*.test.tsx` next to source (or place in `tests`). +- Co-locate `*.test.ts`/`*.test.tsx` next to source (or place in `tests`). - BDD-style Chinese `describe`/`it` titles. Use `describe.concurrent()` / `it.concurrent()` where independent. - Single file: `pnpm test -- --run path/to/file.test.ts`. - Playwright tests are `*.spec.ts` files in `e2e`; they run with one worker and retain failure artifacts. Run targeted tests while iterating, then run `pnpm run lint` plus the relevant full suite before a PR. diff --git a/docs/verification.md b/docs/verification.md index 5a1af17b3..7a720a9b3 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -23,9 +23,9 @@ Promoting a scenario into the permanent suite is a *separate, deliberate* decisi permanent regression coverage. That path is owned by [`DEVELOP.md`](./develop.md), not this guide. **Reproducing a bug you intend to fix is *not* "casual verification."** A scratch reproduction is the *确定 bug -存在* step from [`../AGENTS.md`](../AGENTS.md) (确定 bug 存在 → 写测试 → 修复): it confirms the bug is real but does -**not** replace the test. Next, promote it into a *failing* committed test, then fix, then confirm it goes green — -that permanent test is owned by [`DEVELOP.md`](./develop.md) / [`../AGENTS.md`](../AGENTS.md). +存在* step in [`../AGENTS.md`](../AGENTS.md)'s TDD / Confirm-before-you-fix policy — it confirms the bug is real +but is not itself the required test or evidence. That policy (including its narrow exceptions) is owned by +`../AGENTS.md`; follow its next step — a committed failing test in the general case — before fixing. ## Prerequisite gate (cheap signals first) From c2a53a30a0a83b3c8cd98577c7303f41da8f9817 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 18 Jul 2026 11:09:24 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=93=84=20=E7=B2=BE=E7=AE=80=20AGENTS.?= =?UTF-8?q?md=20Engineering=20Principles=EF=BC=8C=E4=BE=8B=E5=A4=96?= =?UTF-8?q?=E7=BB=86=E8=8A=82=E4=B8=8B=E6=94=BE=E5=88=B0=E4=B8=93=E5=B1=9E?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Engineering Principles 一节里 "Fix root causes"、"Confirm before you fix"、"TDD/BDD first" 三条经过五轮修正,逐条累积了大量例外场景与反例 说明,成为 AGENTS.md(每次会话都会加载)里最重的部分。这些例外/反例 本质上是"如何执行"的机制细节,而非不可协商的原则本身,按 AGENTS.md 自己的所有权声明应归属专属文档: - "Fix root causes" 的测试例外(契约本身有误 / 从未有价值)细节迁移 到 docs/references/develop-testing.md 的 "Writing meaningful tests" 小节开头,AGENTS.md 保留原则本身 + 一处指针。 - "Confirm before you fix" 的复现方式选择(单元测试 vs 扩展级 scratch-script)迁移到 docs/verification.md,该文档本就以复现 bug 为职责范围。 - "TDD/BDD first" 的两类例外(行为保持型改动;自动化覆盖不可行)整段 迁移到 docs/references/develop-testing.md 新增的 "When TDD doesn't apply" 小节。 AGENTS.md 该节三条从长段落收窄为原则 + 指针,文件总字节数减少约 12%(10347 → 9132 字节),例外内容本身未删减,只是搬到了各自的所有权 文档,不损失约束力(harness)。 Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 6 +++--- docs/references/develop-testing.md | 9 +++++++++ docs/verification.md | 4 ++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 93417eaaf..86a1e0ce3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,9 +47,9 @@ ScriptCat — Manifest V3 browser extension that runs Tampermonkey-compatible us These are non-negotiable, regardless of what `docs/develop.md` says about mechanics — where a principle's scope isn't universal, that's called out in the item itself. -- **Fix root causes, not symptoms — refactor over patch.** No `as any` / `// @ts-ignore` / try-catch swallow / defensive skips to make errors disappear. When the clean fix needs restructuring, refactor rather than bolt on a patch (宁愿重构也不要打补丁). If a test fails, fix the code, not the test — unless the *test's own contract* is wrong (a stale fixture, an assertion that was incorrect from the start, or a contract that legitimately changed), in which case fix the test and say why. That's distinct from proactively deleting a test that never carried value regardless of pass/fail (tautological, duplicate, framework-only, mislabeled — see [`docs/references/develop-testing.md`](docs/references/develop-testing.md)'s "writing meaningful tests" section). -- **Confirm before you fix.** Before touching a reported bug, reproduce it and confirm it actually exists — never fix from assumption. Use the smallest reproduction that faithfully shows it: a failing unit test for pure logic/parser/utility bugs, or [`docs/verification.md`](docs/verification.md)'s scratch-script workflow when it depends on the built extension, browser APIs, or cross-context behavior. Then capture it in a failing test (or, if the TDD exception below applies, recorded verification evidence), then fix, **in that order** (确定 bug 存在 → 写测试或记录验证证据 → 修复). -- **TDD/BDD first, for changes that alter observable behavior.** Write failing tests **before** implementing new or changed behavior, using BDD-style Chinese `describe`/`it` titles. Two exceptions, neither a blanket file/task category: (1) genuinely behavior-preserving work — refactors, type cleanup, dead-code removal, mechanical renames, or a config/dependency change confirmed not to alter behavior — verify it instead; (2) a behavior change where meaningful automated coverage is genuinely infeasible — a pure visual/animation tweak, a bug reproducible only in a specific browser version or extension lifecycle stage, a copy/translation wording change, platform behavior that can't be automated reliably — verify it manually and record the evidence instead of committing a pass-through or low-value test just to satisfy this rule — [`docs/verification.md`](docs/verification.md)'s scratch-extension workflow when the change needs the built extension or browser APIs, a simpler noted check otherwise. (Runner, mocks, and how to run tests are in `docs/develop.md`.) +- **Fix root causes, not symptoms — refactor over patch.** No `as any` / `// @ts-ignore` / try-catch swallow / defensive skips to make errors disappear (宁愿重构也不要打补丁). If a test fails, fix the code, not the test — the narrow exceptions (a wrong test contract; a test that never carried value) are in [`docs/references/develop-testing.md`](docs/references/develop-testing.md#writing-meaningful-tests-what-to-clean-up--not-write). +- **Confirm before you fix.** Before touching a reported bug, reproduce it and confirm it actually exists — never fix from assumption. Capture the reproduction, then fix, **in that order** (确定 bug 存在 → 写测试或记录验证证据 → 修复); how to reproduce and what counts as capture are in [`docs/verification.md`](docs/verification.md) and the TDD entry below. +- **TDD/BDD first, for changes that alter observable behavior.** Write failing tests **before** implementing new or changed behavior, using BDD-style Chinese `describe`/`it` titles. Two narrow exceptions — neither a blanket file/task category — are in [`docs/references/develop-testing.md`](docs/references/develop-testing.md#when-tdd-doesnt-apply). (Runner, mocks, and how to run tests are in `docs/develop.md`.) - **SOLID, high cohesion & low coupling — applied to the existing extension points.** `Repo` for new entities, `Group.on(...)` for new messages. Inject `Group` / `IMessageQueue` / DAOs via constructor; don't `new` them inside methods. Depend on narrow interfaces (`IMessageQueue`, not `MessageQueue`). - **Direct replacement over adapter sandwiches.** When swapping a backend/library, replace in place — no `interface Foo + LegacyImpl + NewImpl` unless both must coexist at runtime. - **Scope discipline — stay in your lane.** Bug fix ≠ cleanup PR. Touch only the files the task requires; leave unrelated files untouched (不要动和任务不相干的文件). Don't add helpers, abstractions, validation, or backwards-compat shims you don't need today. Three similar lines beats a premature abstraction. diff --git a/docs/references/develop-testing.md b/docs/references/develop-testing.md index 6e9507b90..fc4c1c6aa 100644 --- a/docs/references/develop-testing.md +++ b/docs/references/develop-testing.md @@ -19,8 +19,17 @@ invalid-selector `DOMException` lazily — the upstream eager construction captu - Single file: `pnpm test -- --run path/to/file.test.ts`. - Playwright tests are `*.spec.ts` files in `e2e`; they run with one worker and retain failure artifacts. Run targeted tests while iterating, then run `pnpm run lint` plus the relevant full suite before a PR. +### When TDD doesn't apply + +Two exceptions to the TDD/BDD-first principle, neither a blanket file/task category — write a failing test for everything else: + +- **Genuinely behavior-preserving work** — refactors, type cleanup, dead-code removal, mechanical renames, or a config/dependency change confirmed not to alter behavior. Verify it instead of testing it. +- **Automated coverage is genuinely infeasible** — a pure visual/animation tweak, a bug reproducible only in a specific browser version or extension lifecycle stage, a copy/translation wording change, platform behavior that can't be automated reliably. Verify it manually and record the evidence instead of committing a pass-through or low-value test just to satisfy the rule — [`../verification.md`](../verification.md)'s scratch-extension workflow when the change needs the built extension or browser APIs, a simpler noted check otherwise. + ### Writing meaningful tests (what to clean up / not write) +**Two distinct situations — don't conflate them.** A test that fails because *its own asserted contract* is wrong (a stale fixture, an assertion that was incorrect from the start, a contract that legitimately changed) — fix the test and say why. A test that never carried value regardless of pass/fail (see below) — clean it up independent of whether it's currently failing. Neither is license to weaken a valid regression test just to make CI pass. + A test earns its place by exercising **our own logic** and failing on a real regression. Don't write the "tests nothing" kinds below — and clean them up when you find them (delete the test; don't touch business logic): - **Tautology** — asserting a constant equals its own literal definition (source `const FOO = [Type.BAR]`, test `expect(FOO).toEqual([Type.BAR])`). diff --git a/docs/verification.md b/docs/verification.md index 7a720a9b3..3ad4cc4de 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -27,6 +27,10 @@ permanent regression coverage. That path is owned by [`DEVELOP.md`](./develop.md but is not itself the required test or evidence. That policy (including its narrow exceptions) is owned by `../AGENTS.md`; follow its next step — a committed failing test in the general case — before fixing. +Choose the reproduction method by what the bug depends on: a failing unit test for pure logic/parser/utility +bugs; this guide's scratch-script workflow (above) when it depends on the built extension, browser APIs, or +cross-context behavior. + ## Prerequisite gate (cheap signals first) Driving a browser is the *last* check, not the first. Confirm the cheap signals are green before you build: From 0bf071ba9b207af11f42d7c74ceb99bf48a5271c Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 18 Jul 2026 11:19:16 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=93=84=20=E6=B6=88=E9=99=A4=20verific?= =?UTF-8?q?ation.md=20=E4=B8=AD=20scratch=20=E8=AF=81=E6=8D=AE=E4=B8=8E?= =?UTF-8?q?=E4=BE=8B=E5=A4=96=E7=9A=84=E6=8E=AA=E8=BE=9E=E6=AD=A7=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "a scratch reproduction... is not itself the required test or evidence" 此前无条件表述,但 AGENTS.md 的自动化覆盖不可行例外场景下 scratch 复现(report.md、截图、观察记录)本身就是要求的证据——两处 表述冲突。已补充"一般情况下"限定,并明确例外场景下 scratch 复现即为 所需证据,无需再补一个已提交的测试。 同时修正所有权表述:"该策略(含其例外)归属 AGENTS.md" 已不准确—— 例外细节在上一轮迁移到了 docs/references/develop-testing.md,AGENTS.md 只保留原则本身。改为分别说明两份文档各自的所有权范围。 Co-Authored-By: Claude Sonnet 5 --- docs/verification.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/verification.md b/docs/verification.md index 3ad4cc4de..81db6718c 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -23,9 +23,13 @@ Promoting a scenario into the permanent suite is a *separate, deliberate* decisi permanent regression coverage. That path is owned by [`DEVELOP.md`](./develop.md), not this guide. **Reproducing a bug you intend to fix is *not* "casual verification."** A scratch reproduction is the *确定 bug -存在* step in [`../AGENTS.md`](../AGENTS.md)'s TDD / Confirm-before-you-fix policy — it confirms the bug is real -but is not itself the required test or evidence. That policy (including its narrow exceptions) is owned by -`../AGENTS.md`; follow its next step — a committed failing test in the general case — before fixing. +存在* step in [`../AGENTS.md`](../AGENTS.md)'s TDD / Confirm-before-you-fix policy. In the general case it +confirms the bug is real but is not itself the required test — promote it into a committed failing test before +fixing. Under that policy's infeasible-automated-coverage exception (criteria in +[`references/develop-testing.md`](./references/develop-testing.md#when-tdd-doesnt-apply)), this scratch +reproduction — its `report.md`, screenshots, and observations — *is* the required evidence; no committed test +is needed. `AGENTS.md` owns the governing principle; `references/develop-testing.md` owns the exception +criteria — don't expect the exception boundary spelled out in `AGENTS.md` itself. Choose the reproduction method by what the bug depends on: a failing unit test for pure logic/parser/utility bugs; this guide's scratch-script workflow (above) when it depends on the built extension, browser APIs, or