diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index fded5dc..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -See [AGENTS.md](./AGENTS.md). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/docs/contributor-card-debugging.md b/docs/contributor-card-debugging.md deleted file mode 100644 index 8505a45..0000000 --- a/docs/contributor-card-debugging.md +++ /dev/null @@ -1,215 +0,0 @@ -# Contributor Card — debugging state & open problems - -> Handoff doc so a future session can resume. Branch `feat/contributor-card`, -> PR #34. The data layer + card render work. -> -> (1) **FLICKER — RESOLVED 2026-06-17.** Root cause was *not* in `sync()` (all of -> §5's H1–H4 were wrong). `navigation.ts` re-runs every `onPageReady` handler -> on a **2-second poll** (`setInterval(runHandlers, 2000)`) and on every -> `turbo:render`; each run re-called `injectContributorCard()`, whose first -> act was `cleanupContributorCard()` — which **removed the live panel and -> cleared the cache mid-hover** (this is the H5 family). Fixed by making -> `injectContributorCard()` idempotent and non-destructive. **Verified -> zero-flicker across 18+ poll cycles** with the real-CDP-hover timeline. See §9. -> (2) **Still open** — a design decision: the user wants the facts as a **seamless -> continuation of GitHub's native card**, not the separate floating panel -> currently shipped (§7). Resolve next. -> -> Last updated 2026-06-17. - ---- - -## 1. What the feature is - -On hovering a username, GitHub shows a hovercard. We attach a panel of objective -facts (account age, relation to this repo, historical merge rate, activity). -Code: `src/features/contributor-card.ts` (+ `src/lib/contributor-signals.ts`, -service-worker `fetchContributorInfo`). Toggle: `feature-contributor-card`. - -## 2. The bug (RESOLVED — see §9 for the fix) - -**Symptom (user, real mouse, logged-in):** hovering the avatar or username area, -our panel **appears for ~0.5–1s then disappears on its own**, while GitHub's -native card stays. Confirmed the current build IS loaded (panel is a direct child -of `.js-hovercard-content`, showing real data). - -**Root cause (confirmed 2026-06-17):** an *external* teardown, not a `sync()` -decision. The real-CDP-hover timeline caught the panel being removed while the -hydro node was unchanged and `card.login` still valid and the container still -`display:block` — which `sync()` would never do. The remover was -`cleanupContributorCard()`, re-triggered by `navigation.ts`'s 2s poll / -`turbo:render` re-running `injectContributorCard()`. See §9. - -**Why my "fixes" kept missing:** I validated with **synthetic events** and a -**mimic**, which showed 0 flicker — but the real mouse does something synthetic -events don't reproduce. **Do not trust synthetic-event or screenshot validation -for this.** Use a real-mouse DOM timeline (see §6). - -## 3. Verified GitHub hovercard DOM facts (live, chrome-devtools) - -- **Trigger:** ``. - The **avatar and the username are SEPARATE triggers** for the same user (a PR - page had 4 triggers for one user). Moving between them is the key interaction. -- **Container:** `.js-hovercard-content` — a `.Popover.position-absolute` - directly under ``. **Persistent / reused.** `overflow:visible`, height auto. -- **Body box:** `.js-hovercard-content > .Popover-message` (inline `width:360px`, - `position:relative`). **Same node across hovers**, BUT GitHub **replaces its - innerHTML on every hover / trigger change.** -- **Content root:** `.Popover-message > div > div[data-hydro-view='{...}']`. The - JSON payload has `event_type:"user-hovercard-hover"` and `payload.card_user_login` - — this is how we read which user (and that it's a *user* card vs repo/issue). -- **Avatar↔username move:** GitHub **removes the old `[data-hydro-view]` node and - adds a new one** (`-hydro#1 … +hydro#2`), with a **~500ms network gap** between. - Same `.Popover-message`, same container. -- **Anchor survival (tested):** a child appended **inside `.Popover-message`** is - **wiped** on swap; a child appended to **`.js-hovercard-content`** (sibling - after `.Popover-message`) **survives**. → current code anchors in the container. -- **Hide mechanism:** GitHub hides on `mouseleave`/`mouseout` of - `.js-hovercard-content` whose `relatedTarget` is outside it → sets - `display:none` and clears `.Popover-message` (children → 0). There is a hide - delay. Containment IS respected (moving into a child keeps it open, in the tests - done so far). **NOT yet tested:** whether GitHub *removes/recreates* the - container on hide vs just `display:none` — this matters a lot (see H1). -- No reposition on content growth (top stays, grows downward). - -## 4. Fixes attempted (chronological) and why each fell short - -1. **Append into `[data-hydro-view]`, mark node done.** → vanished on the node - swap; the "done" marker then blocked re-adding → "flash then gone forever". -2. **Drop the loading→fill `replaceChildren`** (it tore out the node under the - cursor). → did not fix it. -3. **Re-inject whenever our block is missing; per-login cache; no marker.** - (commit `3379c78`) → re-injects, but **flickers ~500ms** every swap because the - block lived inside the volatile content node and there's nothing to inject into - during GitHub's rebuild gap. -4. **Anchor in stable `.js-hovercard-content`, login-keyed, rebuild only on login - change** (commit `a84082d`, CURRENTLY LOADED). Synthetic/mimic = 0 flicker, but - **user still sees it disappear after ~0.5–1s with a real mouse.** ← current state. - -## 5. Hypotheses for the remaining disappearance (ranked, to test next) - -- **H1 — GitHub destroys/recreates `.js-hovercard-content` on hide.** If, when the - pointer leaves, GitHub *removes* the container (not just `display:none`), our - panel (its child) is destroyed. On re-show GitHub makes a fresh container; we - rebuild — but if the per-login fetch isn't warm at that instant, the panel is - absent for a while → reads as "disappeared". **Test:** does the container node - identity change across a hide/show cycle? (stamp it, hide, re-show, compare). - *Most likely culprit given "disappears while still hovering / shortly after".* -- **H3 — moving onto our panel dismisses the card.** Our panel sits BELOW - `.Popover-message` (outside the visible bubble, in the container). Real pointer - moving down onto it may cross a region GitHub treats as "left the card" (its hit - region may be `.Popover-message`, not the whole container). Earlier real-CDP test - only moved onto a child *inside* `.Popover-message`. **Test:** real-hover trigger - → real-move pointer onto the panel → does the card hide? -- **H4 — sync() removes it when `card.login` is momentarily null.** During a - re-render the fresh `[data-hydro-view]` might briefly lack `card_user_login` (or - a different `event_type`). `sync()` then hits `!card.login` → `existing.remove()` - and doesn't re-add until a "good" render. **Test:** log `card.login` on every - sync pass over time. -- **H5 — SPA navigation cleanup.** PR pages do partial/turbo updates. If - `onPageReady` fires, `injectContributorCard()` → `cleanupContributorCard()` - clears the panel + cache. **Test:** log when inject/cleanup run during a hover. -- **H2 — delayed contextual re-render** ("Opened this PR (their first)", "Joined - this month" appear ~1s after). Same login → should be a no-op for us, but verify. - -## 6. How to resume (real-mouse DOM timeline — the right method) - -MCP auto-connect to the user's Chrome is now fixed permanently (SessionStart hook, -see `reference-chrome-devtools-autoconnect` memory). On next start, with the user's -Chrome open (remote debugging on), `list_pages` shows their real tabs. - -Repro page: a PR conversation with a first-time external contributor, e.g. -`https://github.com/CodyTseng/jumble/pull/809` (logged in). Hover a comment -author's avatar/username. - -**Recorder to paste via `evaluate_script` (records add/remove of our panel + hydro -nodes + container style, with timestamps):** - -```js -window.__rec = []; -const t0 = performance.now(); -const log = (m) => window.__rec.push(Math.round(performance.now()-t0)+'ms '+m); -let s=0; -const isPanel=(n)=>n.matches?.('.better-github-contributor-card')||n.querySelector?.('.better-github-contributor-card'); -const hv=(n)=>n.matches?.('[data-hydro-view]')?n:n.querySelector?.('[data-hydro-view]'); -window.__recObs?.disconnect?.(); -window.__recObs=new MutationObserver((muts)=>{for(const mu of muts){ - if(mu.type==='attributes'){ if(mu.target.classList?.contains('js-hovercard-content')) log('container['+mu.attributeName+']='+(mu.target.getAttribute('style')||'').slice(0,40)); continue; } - for(const n of mu.addedNodes) if(n.nodeType===1){ if(isPanel(n))log('+PANEL'); const h=hv(n); if(h){if(!h.__id)h.__id=++s; log('+hydro#'+h.__id);} } - for(const n of mu.removedNodes) if(n.nodeType===1){ if(isPanel(n))log('-PANEL from '+(mu.target.className||mu.target.tagName)); const h=hv(n); if(h)log('-hydro#'+(h.__id||'?')); } -}}); -window.__recObs.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['style','class','hidden']}); -'armed'; -``` - -Then either (a) ask the user to hover with their real mouse for a few seconds, or -(b) drive a **real** CDP hover yourself: `take_snapshot` → find a user-link uid → -`hover` it → wait ~3s held → `hover` the panel's uid (test H3) → read -`window.__rec`. Real CDP input (the `hover`/`click` tools) ≠ synthetic JS events; -use the real tools. Also stamp the container node identity to settle H1: -`const c=document.querySelector('.js-hovercard-content'); c.__id ??= Math.random();` -then re-check `c.__id` after a hide/show. - -## 7. Design decision still open (user feedback) - -The user wants the facts shown as a **seamless continuation of GitHub's native -card**, NOT the separate floating panel currently shipped. The tension: -- Inside `.Popover-message` (seamless) → **wiped on every swap → flicker** (§3). -- In `.js-hovercard-content` (stable, no flicker) → renders as a **separate panel** - below the card (what's shipped; user dislikes). - -Options to explore: -- **(A, preferred) Make the container-anchored panel visually merge** with the - card: remove the gap, match width/background, suppress its own shadow, square the - top corners so it reads as one taller card. Have to handle `.Popover-message`'s - rounded bottom + shadow seam (maybe overlap it slightly upward). Keeps zero - flicker. **Verify visually with the user.** -- (C) Inject into `.Popover-message` and re-inject on every wipe — rejected: the - ~500ms rebuild gap can't be filled, so flicker is unavoidable there. -- (D) A fully custom standalone hovercard — user explicitly does NOT want this - ("不是在 github card 上做接续吗"). - -Resolve the flicker (§5/§6) FIRST; don't polish styling on something that vanishes. - -## 8. State / pointers - -- Branch `feat/contributor-card`, PR #34. Commits: docs `2120557`, data `8bc9d58`, - DOM `85e52ae`, fixes `d296798` / `3379c78` / `a84082d`. -- `dist/` is built from the latest; user reloads the unpacked extension to test. -- All unit tests pass (225). Tests use happy-dom + synthetic DOM — they DON'T - catch the real-mouse flicker; treat them as logic guards, not proof it works. -- Design rationale & signal taxonomy: `docs/pr-signals-plan.md`. - -## 9. Flicker resolution (2026-06-17) - -**Method that finally worked:** real CDP `hover` (not synthetic events) on the -PR author's username + a `MutationObserver`/sampler timeline (§6). The clean -warm-cache run showed: `+PANEL` at T, `-PANEL` at T+~375ms, *with the hydro node -unchanged, `login` still valid, container still `display:block`*. That combination -is impossible from `sync()` (it only removes on `!card.login` or a login change), -so the remover had to be external → `cleanupContributorCard()`. - -**Why it fired:** `src/lib/navigation.ts` runs `runHandlers()` on a **2s -`setInterval`** and on every `turbo:render`. That re-runs the `onPageReady` -handler → `injectFeature("feature-contributor-card")` → `injectContributorCard()`, -which used to start with `cleanupContributorCard()` (remove panel + clear cache + -disconnect observer). So roughly every 2 seconds, any in-flight hover got wiped. -The native card survived because it's GitHub's, untouched by our cleanup. Synthetic -tests missed it because no real 2s poll runs across a single synthetic hover. - -**The fix** (`src/features/contributor-card.ts`): -- `injectContributorCard()` is now **idempotent + non-destructive**: it returns - early when the observer is already attached to the current `document.body`, and - only re-attaches (without clearing cache or removing panels) when Turbo swaps - the ``. One long-lived observer; no per-poll churn. -- The `infoCache` now persists across navigations, so it's **keyed by - `repo#login`** (the repo-relation row `repoMerged` is repo-specific). -- Regression test: "survives a navigation re-inject without wiping a live panel" - — re-calls `injectContributorCard()` mid-hover and asserts the panel survives - and no refetch happens. The old code would have failed it. - -**Verification (real CDP hover, new build, 2026-06-17):** held a hover and -alternated avatar↔username over a ~36s window spanning **18+ poll ticks**; the -sampler showed `panel=YES` in **every** sample — never removed. When GitHub closes -its own card the panel is hidden *with* the container (correct) but not destroyed, -and re-shows instantly on the next hover with no refetch. 226 tests pass. diff --git a/docs/contributor-profile-card.md b/docs/contributor-profile-card.md new file mode 100644 index 0000000..e2d2d15 --- /dev/null +++ b/docs/contributor-profile-card.md @@ -0,0 +1,56 @@ +# Contributor Profile Card + +Contributor Profile Card 在 GitHub 原生用户 hovercard 底部追加一组贡献者资料, +帮助用户快速了解不熟悉的贡献者。它只展示来自 GitHub 的客观信息,不打分、不贴 +标签,也不判断账号或贡献质量。 + +## 展示内容 + +| 项目 | 内容 | 数据来源 | +| ---------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | +| 账号年龄 | 账号创建至今的时间及创建月份 | REST `GET /users/{login}` | +| 本仓库关系 | Owner、Member、Collaborator、Contributor 或 First-time contributor | REST `GET /repos/{owner}/{repo}/issues?creator={login}` 返回的 `author_association` | +| 历史 PR | PR 总数、已合并数、关闭但未合并数 | Search API | +| 活跃度 | 过去一年的贡献数 | GraphQL `contributionsCollection` | + +本仓库关系仅在仓库页面且 GitHub 返回有效关系时展示。历史 PR 总数为零或查询失败 +时不展示该行。 + +活跃度需要 GitHub token。未配置 token 时,卡片保留该行并提示用户连接 token; +其他公开资料仍可匿名获取,但受 GitHub API 匿名限额约束。 + +## 交互行为 + +- 功能默认开启,可在扩展设置的 Profile 分组中关闭。 +- 只有用户 hovercard 出现时才按需请求当前用户的数据。 +- 请求期间先展示与最终布局一致的 skeleton,避免内容加载造成布局跳动。 +- 卡片接在 GitHub 原生 hovercard 下方,并跟随原卡片宽度、位置和明暗主题。 +- GitHub 在头像与用户名之间切换或进行 SPA 导航时会重建 hovercard 内容;卡片挂载 + 在稳定的外层容器上,避免重复请求和闪烁。 +- 非用户 hovercard 不会被修改。 + +## 缓存与失败处理 + +- Service worker 使用 `chrome.storage.session` 缓存每项 GitHub API 结果 5 分钟,并 + 合并相同 key 的并发请求。 +- Content script 按 `仓库 + 用户` 缓存成功结果,避免跨仓库复用错误的贡献者关系。 +- 网络错误、API 限流或未知用户不会渲染错误数据;失败请求冷却 60 秒后允许重试。 +- 关闭功能时会断开 observer、清除内存缓存并移除已注入的卡片。 + +## 数据边界 + +- 所有资料都直接从 GitHub API 获取,不发送给第三方服务。 +- GitHub token 保存在 Chrome 本地存储中,只用于请求 GitHub API。 +- 功能不生成风险分数、可疑标签或颜色警告。 +- Followers 和 public repository 数量虽然随用户资料一起返回,但当前不会展示。 + +## 代码位置 + +- 卡片渲染与 hovercard 集成:`src/features/contributor-card.ts` +- 账号年龄与仓库关系转换:`src/lib/contributor-signals.ts` +- GitHub API 请求与缓存:`src/service-worker.ts` +- Content script 请求桥接:`src/lib/github-api.ts` +- 请求和响应类型:`src/lib/messages.ts` +- 样式:`src/styles/content.css` +- 设置入口:`static/options.html` +- 文案:`src/_locales/*/messages.json` diff --git a/docs/pr-signals-plan.md b/docs/pr-signals-plan.md deleted file mode 100644 index bb03edf..0000000 --- a/docs/pr-signals-plan.md +++ /dev/null @@ -1,174 +0,0 @@ -# 可疑账号识别 · 贡献者背景卡 — 设计 - -> 状态:**MVP 已实现并上线**(分支 `feat/contributor-card` / PR #34)。本文是 -> 共识记录 + 信号知识库,后续相关 feature 都回到这里对照,不重新发散。 -> -> **实现说明 / 与初版设计的演进(2026-06-17):** -> - **拒绝率已实现**:历史 PR 行现为 `总数 PR · N merged · M closed`(closed = 关闭 -> 未合并,Search `is:closed is:unmerged`)——即计划点名的"单信号最强之一"(§3.2 / §4.2)。 -> - **改用 `author_association`**:"与本仓库关系"现显示 Owner / Member / Collaborator / -> Contributor / First-time(从该用户在本仓库的 issue/PR 读取,REST、非 DOM),替代了 -> 原先的 merged 计数,补上了"维护者 / 组织成员"身份信号(§3.2 的本意)。 -> - **默认开**(经确认):4 个信号中 3 个免 token,活跃度行已内置"🔒 连接 token"提示, -> 故保持默认开(§3.5 的"默认行为待定"定为默认开)。 -> - 实现期间另做了:合并进原生 hovercard 成一张卡、行内去图标重排、加载骨架屏, -> 并修复了 nav-poll 重注入导致的 flicker(详见 `docs/contributor-card-debugging.md`)。 - ---- - -## 0. 一句话 - -在 hover 用户名时,往 GitHub 原生 hovercard 底部**追加一块客观事实**,帮看的人 -判断"**这个账号可不可疑**"——而不是替他下结论。 - ---- - -## 1. 目标与边界 - -**要解决的真实痛点**:GitHub 上大量用 AI 批量生成的低质量("vibe code"/slop) -PR,消耗维护者注意力。我们想帮人快速识别**可疑账号**。 - -**明确的边界(MVP 只做左边):** - -| MVP 做 | MVP 不做(待定,见第 6 节) | -|--------|--------------------------| -| 识别**可疑账号**(关于"人"的事实) | 识别**可疑 PR**(关于"这次改动"的事实) | -| 展示客观事实 | 打分 / 风险等级 | -| 单个账号按需查询(hover) | 跨账号网络/团伙分析 | - -> 为什么先做"账号"不做"PR":选了 hovercard 形态(见第 3 节),它是**作者卡**, -> 只能放关于人的信号;PR 级信号(是否关联 issue、体量、描述质量)是 PR 页才有的 -> 落点,属于另一个 feature。 - ---- - -## 2. 核心原则(伦理护栏 — 任何阶段都不能破) - -1. **展示事实,不下结论。** 形态是"背景卡 / context card",把客观事实低调摆出, - 判断权交给看的人。**绝不**贴"vibe coder"之类标签——那本身可能成为骚扰/诽谤 - 载体,也极易误伤。 -2. **MVP 不打分。** 不输出"可疑分 / 风险等级"。打分既易误伤,又把工具推向网暴。 - 纯事实、中性配色、不标红。 -3. **新 ≠ 垃圾。** 学生、隐藏私有贡献者、网络差地区开发者、刚换号的老手,画像 - 都和刷子重合。所以"账号 3 天"只平铺直叙地显示,不染红、不暗示可疑。 -4. **不持久化、不对外发布个人画像。** 缓存只为省 rate limit,且仅本地、短时。 -5. **贴合 better-github 纪律**:fetch lazily/scoped(hover 才查、一次一人);不改 - 原生 DOM(只追加);请求走 service worker 的 `cachedFetch`(缓存 + 合并)。 - ---- - -## 3. MVP 设计(逐项已确认) - -### 3.1 形态:增强原生 hovercard - -hover 用户名时 GitHub 自己会弹原生 hovercard(头像/名字/bio/followers/Follow)。 -我们**往它底部追加一块**,而不是另做一张卡。 - -- ✅ 体验原生、不会两张卡打架、契合"只追加不改原生"。 -- ⚠️ **已知技术风险点**:原生 hovercard 异步加载,DOM 结构与出现时机刁钻,需用 - observer 等它出现再注入。**实现时务必先在真实页面核对 DOM 再写代码**(吸取 - 上一轮"臆测选择器"的教训)。 -- 覆盖面广:PR 列表、Issue、评论区——任何出现用户名的地方都生效。 - -### 3.2 信号(4 个) - -| 信号 | 数据来源 | 是否要 token | -|------|----------|-------------| -| **账号年龄** | REST `/users/{login}` 的 `created_at` | 否 | -| **与本仓库/组织关系** | PR 的 `author_association`(`FIRST_TIME_CONTRIBUTOR` / `NONE` / `CONTRIBUTOR` / `MEMBER` / `OWNER`) | 否 | -| **历史 merge 率 / 拒绝率** | Search API:`type:pr author:X is:merged` vs `is:closed is:unmerged` 的 `total_count` | 否(匿名限额低) | -| **活跃度 / 绿点总数** | GraphQL `contributionsCollection` | **是** | - -(followers + 公开 repo 数:信号弱,暂不纳入;需要可随时加。) - -### 3.3 token 降级 - -- **无 token**:显示前三个免费事实;活跃度行显示"🔒 连接 token 查看"。 -- **有 token**:活跃度行显示真实贡献数。 - -### 3.4 卡片样子(追加块,纯事实/中性) - -``` - (GitHub 原生 hovercard:头像 / 名字 / bio / followers…) - ─── Better GitHub ─────────────── - 账号年龄 3 天(2026-06 创建) - 本仓库 首次贡献者 - 历史 PR 8 提交 · 2 合并(25%) - 活跃度 🔒 连接 token 查看 ← 无 token 时 -``` - -有 token 时末行:`活跃度 近一年 1,240 次贡献`。 - -### 3.5 落地接入提示(供实现时参考,尚未写) - -- 复用 `src/lib/page-detect.ts`、service worker 的 `cachedFetch`(**每个 user 缓存 - 一次**,hover 重复同一人直接命中)。 -- i18n 走 `src/lib/i18n.ts`,中/英/繁三套。 -- 作为可开关 feature 注册(`content.ts` 的 FEATURE_KEYS + `options.ts` + options.html - + locale 文案),默认行为待定(考虑到要 token,可能默认关或首次提示)。 -- 测试:数据解析(年龄/比率计算)纯函数优先做成可单测的;DOM 注入用 happy-dom。 - ---- - -## 4. 信号知识库(完整,大多待定) - -> 讨论中梳理过的全部信号,留档备用。**权重方向仅为记录,启用前需重新校准。** - -### 4.1 账号画像(关于"人")—— 权重低,易误伤 - -账号年龄(单看无意义,要"新 + 突然爆发")、粉丝/关注比异常、空 bio/默认头像/ -buzzword bio、仓库全是 fork/模板、pinned 空壳。**MVP 只取"账号年龄"**,其余待定且 -权重要低。 - -### 4.2 行为/时间(关于"人")—— 比静态画像强 - -集中只打高星项目、velocity 异常/时间点规律(自动化)、**提完不跟进 review**(判别 -力强)、**拒绝率 closed-unmerged/total**(单信号最强之一)、被打 spam 标签史。 -**MVP 取"历史 merge/拒绝率"。** 其余待定。 - -### 4.3 内容(关于"PR 本身")—— 最值钱,但属 PR 级,MVP 不做 - -改动琐碎、**描述与 diff 错位**(AI 强指纹)、模板腔、**幻觉引用**(不存在的 issue/ -函数)、跨仓库雷同、CI 直接挂/误提交 node_modules。**全部待定**(需 PR 页落点)。 - -### 4.4 网络/团伙 —— 最高阶,成本最高,最后考虑 - -同日注册、命名类似、互相点赞评论的 sockpuppet ring。需图分析。**待定。** - -### 4.5 大 PR —— 待定 - -有效体量 vs 名义体量(剥离生成物/vendored/lockfile)、加法 vs churn、流程信号 -(是否关联 issue/RFC、是否首次给本仓库)、review 成本不对称。属 PR 级,**待定。** - ---- - -## 5. 与本目标无关的旁支(显式记录,避免再走偏) - -- **Load-bearing 文件高亮**(在大 PR diff 里标 build/CI/deps/install 文件):它是 - **通用安全审查辅助**,对所有 PR 一视同仁,**不判断可疑性**,因此**不属于"可疑 - 账号识别"这个目标**。曾被探索并实现后回滚。若将来想做,应作为**独立的安全向 - feature**,不要混进本设计。 - ---- - -## 6. 待定(Deferred — 看 MVP 效果再决定,不是放弃) - -| 项 | 内容 | 门槛 | -|----|------|------| -| D1 | **PR 级"垃圾 PR"信号**:是否关联 issue、有效体量、描述与 diff 错位、模板腔、幻觉引用 | 需 PR 页落点;描述类靠启发式,准确度待验 | -| D2 | 更多账号画像信号(粉丝比、仓库组合等) | 权重低、易误伤,需谨慎 | -| D3 | followers + 公开 repo 数 | 信号弱 | -| D4 | 置信分模型(若做,size 作乘数,带触发原因 + 灵敏度调节) | 需多数信号就位才有意义;且与"不打分"原则冲突,需重新决策 | -| D5 | 网络/sockpuppet 分析 | 图分析,成本最高 | - ---- - -## 7. API / 成本备忘 - -- **REST 匿名可用**:`/users/{login}` → `created_at`、`followers`、`public_repos`; - PR 对象自带 `author_association`(常已在页面 DOM 里)。 -- **Search API**:merge/拒绝数用 `total_count`;匿名约 10 req/min,带 token 30/min。 - hovercard 按需(一次一人)+ `cachedFetch`,够用。 -- **GraphQL**:`contributionsCollection`(绿点)**强制 token**。 -- 所有"按 user"的结果**必须缓存**;匿名 REST 仅 60/hr。 -- 不持久化对外画像(见第 2 节)。 diff --git a/src/features/contributor-card.ts b/src/features/contributor-card.ts index c76791f..235a699 100644 --- a/src/features/contributor-card.ts +++ b/src/features/contributor-card.ts @@ -1,9 +1,9 @@ -// Contributor background card. +// Contributor profile card. // // GitHub shows a hovercard when you hover a username. We attach a panel of // *objective facts* to it — account age, relation to this repo, historical merge -// rate, activity — so the reader can judge whether an account looks suspicious. -// Facts only: no score, no "suspicious" label, no red. See docs/pr-signals-plan.md. +// history, and activity. Facts only: no score, labels, or warnings. +// See docs/contributor-profile-card.md. // // DOM verified against a live page (2026-06): // .js-hovercard-content ← the reused popover root; STABLE diff --git a/src/lib/contributor-signals.ts b/src/lib/contributor-signals.ts index a09713e..8158170 100644 --- a/src/lib/contributor-signals.ts +++ b/src/lib/contributor-signals.ts @@ -1,4 +1,4 @@ -// Pure derivations for the contributor background card. No DOM, no network — +// Pure derivations for the contributor profile card. No DOM, no network — // these turn the raw ContributorInfo (fetched in the service worker) into the // values the card displays. Kept separate so the logic is unit-testable and the // DOM/i18n layer only formats. diff --git a/src/lib/github-api.ts b/src/lib/github-api.ts index 11166c4..d6b9750 100644 --- a/src/lib/github-api.ts +++ b/src/lib/github-api.ts @@ -55,7 +55,7 @@ export type { ContributorInfo, }; -/** Fetch objective facts about an account for the contributor card. Returns null +/** Fetch objective facts about an account for the contributor profile card. Returns null * for an unknown user, a rate-limited request, or any failure (the caller then * renders nothing). owner/repo add the repo-relation row when on a repo page. */ export async function fetchContributorInfo( diff --git a/src/lib/messages.ts b/src/lib/messages.ts index eb51875..c49fec7 100644 --- a/src/lib/messages.ts +++ b/src/lib/messages.ts @@ -76,8 +76,8 @@ export interface ForkInfo { stargazersCount: number; } -/** Objective facts about a GitHub account, for the contributor background card. - * Pure-fact only — no scoring. See docs/pr-signals-plan.md. */ +/** Objective facts about a GitHub account, for the contributor profile card. + * Pure-fact only — no scoring. See docs/contributor-profile-card.md. */ export interface ContributorInfo { login: string; /** ISO timestamp of account creation (REST `created_at`). */ diff --git a/src/service-worker.ts b/src/service-worker.ts index f34b468..39fff12 100644 --- a/src/service-worker.ts +++ b/src/service-worker.ts @@ -630,13 +630,13 @@ async function fetchForks(owner: string, repo: string): Promise { }); } -// --- Contributor background card --- +// --- Contributor profile card --- // // Objective facts about an account, fetched lazily on hover (one user at a // time). Each piece is cached independently so hovering the same user again — // or on a different repo — reuses what it can. Search/GraphQL failures degrade // to 0/null, which makes the card simply omit that row rather than show a wrong -// number. See docs/pr-signals-plan.md. +// number. See docs/contributor-profile-card.md. // Run a Search-issues query and return its total_count; 0 on any failure so the // card omits the affected row instead of rendering a misleading value. diff --git a/src/styles/content.css b/src/styles/content.css index 41ed26b..c77b994 100644 --- a/src/styles/content.css +++ b/src/styles/content.css @@ -748,7 +748,7 @@ a.better-github-review-popover-item:hover { background: var(--bgColor-muted, var(--color-canvas-subtle, #f6f8fa)); } -/* Contributor background card — a section of objective facts that continues +/* Contributor profile card — a section of objective facts that continues GitHub's native user hovercard so the two read as ONE card. Anchored in the stable popover root (a sibling after .Popover-message, not inside the body GitHub re-renders), so it never flickers. The merged look: no gap, the panel's