| feature_ids |
|
||||||
|---|---|---|---|---|---|---|---|
| topics |
|
||||||
| doc_kind | guide | ||||||
| created | 2026-07-25 |
本文面向继续开发本项目的人,记录当前实现的模块边界、不可破坏的统计口径、常见改动的落点、验证方法和发布流程。
用户操作、Token 权限、统计口径及已知限制见 README.md。本文件与生产代码 index.html、生产代码测试 test.html 共同构成后续迭代的仓库内真相源。
当前交付物是一个可直接打开的静态单页应用:
- 所有 HTML、CSS、JavaScript 和 favicon 都内联在
index.html。 - 无服务端、数据库、构建工具、运行时依赖或 CDN。
localStorage只保存 Token、仓库和用户配置。- 当前查询投影只存在于页面内存;完整已合入 PR 在
http(s)下按 PR 持久化到浏览器 IndexedDB,file://下使用页面内存缓存。 - CSV 是统计结果的可移植持久交付物。
- GitHub Pages 只托管静态文件;浏览器直接请求 GitHub / GitCode API。
除非需求明确改变产品形态,否则不要为局部功能引入后端、框架、第三方依赖或新的持久化服务。
| 阶段 | 最终 SHA | 浏览器测试 | Review / 发布证据 |
|---|---|---|---|
| F001 PR 代码变更统计 | 6523cc0 |
65 passed | sol APPROVE |
| F002 Comments / Approve 与三类详情 | b4b57a5 |
122 passed | sol APPROVE,Pages 验收通过 |
| 最终 UI、导出诊断、Token 保存与隐私说明 | 876bec3 |
137 passed | kimi APPROVE,Pages 验收通过 |
| F003 已合入 PR 缓存与查询加速 | b3b877a |
222 passed | kimi + Opus APPROVE;PR #1 已合入,Pages release gate open |
| 完整性 bug 修复 + 分支配置 + since 优化 + 缓存清除 + 分支筛选 | (pending) | 254 passed | Pages 部署中 |
本文创建时线上生产基线为 876bec3。后续发布应把新的最终 SHA、测试数量和线上验收结果补到本表或后续变更记录中。
index.html 仍是单文件,但 JavaScript 用固定 section comment 划分模块。查找这些标题即可定位:
| Section | 职责 |
|---|---|
Utilities |
HTML 转义、仓库 URL 解析、日期半开区间、并发限制器 |
Storage |
版本化 localStorage 读写 |
CacheStore |
版本化 merged PR cache;HTTP IndexedDB / file Memory 后端 |
ProviderRequestBudget |
所有真实网络请求的平台级并发预算(GitHub 8 / GitCode 16) |
CSV Helper |
RFC 4180 解析/生成、BOM、公式注入防护、下载 |
GitHub Provider |
默认分支、merged PR、代码量、Comments、Reviews |
GitCode Provider |
默认分支、merged PR、代码量、Comments、当前有效 Approve |
Statistics Engine |
仓库去重、用户映射、活动聚合、筛选、完整性 |
Query Controller |
查询编排、进度、取消、错误与部分统计传播 |
CSV Export |
从当前可见投影导出汇总或明细 |
Token Management |
保存、清除、测试并保存 Token |
User CRUD / Repo Management |
本地配置维护 |
Initialization |
恢复配置、默认日期、事件绑定 |
主数据流:
localStorage(配置、用户)
→ collectFilters / buildRepoList
→ Provider.getDefaultBranch
→ Provider.fetchMergedPRs(仓库级发现 → PR cache hit / core hydration)
→ fetchCollaborationForPR(cache hit / Provider.fetchCollaboration → full-only cache write)
→ 规范化 activity
→ mapActivitiesToUsers
→ applyDisplayFilters
→ aggregate
→ 页面 / 汇总 CSV / 明细 CSV
页面和导出必须共享同一批筛选后的活动及汇总数据。不要在导出函数中另写一套业务筛选。
配置使用两个版本化 localStorage Key:
code-statistics.config.v1
code-statistics.users.v1
等价数据形态:
// code-statistics.config.v1
{
githubToken: "…",
gitcodeToken: "…",
repositories: [
{ url: "https://github.com/owner/repo", branch: "main" },
{ url: "https://gitcode.com/owner/repo", branch: "" }
// 旧格式纯字符串仍兼容,normalizeRepoEntry() 自动迁移
]
}
// code-statistics.users.v1
[
{
user_key: "alice",
display_name: "Alice",
email: "alice@example.com",
github_login: "alice-gh",
gitcode_login: "alice-gc",
enabled: true
}
]完整已合入 PR 使用 IndexedDB:
Database: code-statistics.pr-cache.v1
Object store: prs
Key: platform:repository-lowercase#pr-number
{
schemaVersion: 1,
cachedAt,
platform,
repository,
prNumber,
core,
activities, // raw activity;禁止 user_key / matched / display_name
completeness: "full",
partialReason: ""
}缓存规则:
- 只有当前 schema、结构一致且
core/activities全部为full的条目可命中。 - schema 不匹配、结构损坏、partial 或 failed 一律 miss;损坏/旧 schema 条目在读取时删除。
- 已合入 PR 的 core + collaboration 整体不设时间 TTL;产品明确不追踪少数 merge 后评论/审批变化。
http(s)使用 IndexedDB;初始化失败时降级为当前页面 Memory,不影响统计正确性。file://不尝试 IndexedDB。- IndexedDB 写入只在 readwrite transaction
complete后返回成功。 - 用户资料不是 cache key;用户变化只重新投影当前 raw activities,不使 PR cache 失效。
演进规则:
- 新增可选字段时必须为旧数据提供默认值。
- 破坏性结构变更必须使用新 Key(如
.v2)并实现可回滚迁移。 - 迁移完整成功前不得覆盖或删除旧 Key。
- Token 不得进入 URL、日志、CSV 或错误详情;界面只显示掩码。
- 清除浏览器站点数据会清除全部配置与 PR cache,此行为必须继续在界面和 README 中说明。
每个平台 Provider 都必须实现以下边界:
testConnection(token)
getDefaultBranch(token, owner, repo, signal)
fetchMergedPRs(token, owner, repo, defaultBranch, startDate, endDate, signal, onProgress, cacheStore?)
fetchCollaboration(token, owner, repo, pr, signal, onProgress)fetchMergedPRs 返回:
{
prs: [
{
platform, repo, number, title, author,
mergedAt, baseBranch,
additions, deletions, changedFiles,
completeness, url
}
],
partial: false,
partialReason: "",
cacheHits: 0,
networkPRs: 0
}fetchCollaboration 返回:
{
activities: [/* 规范化 activity */],
partial: false,
partialReason: ""
}规范化 activity 的稳定字段:
{
activity_type: "development" | "comment" | "approval",
platform,
repository,
pr_number,
pr_title,
pr_url,
merged_at,
actor_login,
user_key,
matched,
display_name,
event_at,
source_type,
source_id,
activity_url,
body_summary,
additions,
deletions,
changed_files,
completeness: "full" | "partial" | "failed",
partial_reason
}新增平台时应先适配这两个返回结构,不要让平台特有字段泄漏到 StatsEngine。
下列规则属于产品契约,修改前必须先更新需求与测试:
- 只统计合入仓库默认分支的已合并 PR。
- 日期按用户本地日期解释,内部使用
[开始日 00:00, 结束日次日 00:00)。 - 三类活动都随 PR 的
merged_at归入查询周期。 - 用户归属只按
platform + actor_login匹配;邮箱不参与归属。 - 禁用用户不参与匹配,未匹配账号保留为独立诊断行。
- 开发 PR 的代码量只归 PR 作者;Comment 和 Approve 不增加代码量。
变更行 = 新增行 + 删除行,净增行 = 新增行 - 删除行。- GitHub Comment 来源包含 issue comment、review comment、非空 review body。
- GitHub Approve 取同一 PR、同一用户最后一个决定性 Review 状态。
- GitCode Approve 只表示查询时
accept === true的当前有效状态。 - 不同来源按
activity_type + source_type + source_id去重。 - 失败、限流、字段不可解析或
too_large不得静默当作 0;必须传播为“部分”或“失败”。 - 同一查询每个仓库只执行一次 PR 发现流程;协作来源按每个入选 PR 拉取一次。
- 汇总页面、详情和两种 CSV 必须遵守相同的平台、用户、仓库及用户范围筛选。
- cache hit 必须是同 schema、同 PR identity 的完整条目;partial/failed 不能命中。
- 缓存只保存 raw activity;用户归属永远从当前 users 重新投影。
- 所有实际网络请求必须经过 Provider
_fetch的共享预算;PR 内部 fan-out 不得绕过 GitHub 8 / GitCode 16 上限。 - 进度完成数只在 cache hit 或网络任务 settle 后递增;最终标题和仓库行不得保留“正在获取”。
- 所有 API 或用户输入写入
innerHTML前必须经过escapeHtml。 - 外部链接保持
target="_blank"与rel="noopener"。 - CSV 文本继续通过
CsvHelper.sanitize防止公式注入。 - Token 只能放在认证请求头,不得放进 query string。
- 不在仓库、截图、测试夹具或文档中写入真实 Token。
file://页面直接请求平台 API;新增网络目标时必须在配置页和 README 明示。- API 失败信息可以展示状态和平台返回摘要,但不得拼入认证请求头或 Token。
- 扩展
parseRepoUrl和仓库平台展示。 - 增加 Token 配置、测试连接和版本化存储字段。
- 实现完整 Provider 契约,输出规范化 PR 与 activity。
- 在
runQuery中只增加 Provider 选择,不复制查询编排。 - 为用户模型增加该平台账号字段及 CSV 导入兼容。
- 增加分页、限流、字段缺失、部分失败和用户匹配测试。
- 更新 README 的权限、网络目标、口径和限制。
- 先定义归属用户、时间口径、去重键和完整性传播。
- Provider 只负责生成新的规范化 activity。
- 在
StatsEngine.aggregate增加聚合,不在 UI 临时计算业务口径。 - 同步汇总列、详情、CSV 字段和空态。
- 用固定夹具覆盖“只有该活动、没有开发 PR”的用户。
- 优先修改
StatsEngine.applyDisplayFilters或可见数据投影。 - 页面、详情和 CSV 都消费该投影。
- 至少覆盖
matched / all / unmatched × platform × user × repo。 - 检查 partial/failed 诊断只跟随当前可见仓库,不生成空平台行。
- 写旧数据加载测试。
- 增加迁移,不直接改变旧 Key 的语义。
- 验证刷新后配置仍在、失败迁移不丢数据。
- 更新本文件“持久化契约”和 README 的用户可见说明。
- 先修改
CACHE_SCHEMA_VERSION与 entry validation 测试;不在旧 schema 上猜测兼容。 - 缓存只接受 full 数据,禁止为了命中率放宽 partial/failed 门禁。
- 不把
user_key、matched、display_name或 Token 写入 entry。 - IndexedDB mutation 必须等待 transaction complete;用 close → reopen 测试持久性。
- 同步 README 的协议边界与隐私披露。
- 保持“配置 / 用户信息 / 统计数据”三个一级页签。
- 保持单文件与无外部静态资源。
- 桌面优先,同时检查窄屏布局、键盘操作、空态和错误态。
- 若修改 DOM 标识或文案,同步
test.html的生产 DOM 断言。
测试页通过隐藏 iframe 加载真实 index.html,不是复制生产函数。
启动本地静态服务器:
python3 -m http.server 8901 --bind 127.0.0.1然后访问:
http://127.0.0.1:8901/test.html
F003 fresh-context 处置后的开发分支基线为 222 passed, 0 failed;新增行为时测试总数应增加,不应通过删除断言维持全绿。
静态检查:
sed -n '/^<script>$/,/^<\/script>$/p' index.html | sed '1d;$d' | node --check -
sed -n '/^<script>$/,/^<\/script>$/p' test.html | sed '1d;$d' | node --check -
git diff --check行为变更采用红绿流程:
- 在
test.html增加能复现问题或表达新契约的失败断言。 - 确认旧生产代码失败。
- 修改
index.html。 - 用同一断言确认转绿,再跑全部测试。
浏览器手工验收至少覆盖:
- 成功测试 Token 后自动保存;失败测试不覆盖旧 Token。
- 用户和仓库 CRUD,刷新页面后仍可恢复。
- 日期、平台、用户、仓库、用户范围组合筛选。
- 开发 PR / Comments / 审核 PR 三个详情标签及键盘操作。
- 汇总和明细 CSV 与当前页面一致,且不含 Token。
- 单仓库失败、部分统计、取消查询时其他状态不被伪装成完整。
- 浏览器控制台无新增错误。
任何代码或测试行为变更都需要非作者 Review,且 Review 必须覆盖准备发布的最终 SHA。
建议流程:
- 建立功能分支或隔离 checkout。
- 红测试 → 实现 → 全量测试 → 静态检查。
- 由非作者复核原始需求、完整 diff、测试和浏览器行为。
- Review 明确 APPROVE 后再合入或推送
main。 main更新会触发.github/workflows/deploy.yml。- 等
Deploy to GitHub Pages成功后,再检查线上页面包含本次变更。
查看部署:
gh run list --commit "$(git rev-parse HEAD)" --limit 5线上地址:
https://mindfn.github.io/code-statistics/
部署成功不等于功能验收完成;至少做一次线上 smoke test。若 Actions 失败,线上仍是上一次成功版本。
每次迭代完成前逐项检查:
- 用户操作、权限、口径或限制变化 → 更新
README.md。 - 模块边界、数据契约、扩展方式或门禁变化 → 更新本文件。
- 生产行为变化 → 更新
test.html。 - API 行为变化 → 更新 Provider、失败传播测试及已知限制。
- 发布后 → 记录最终 SHA、测试结果和线上验收证据。
docs/features/、docs/discussions/ 与 feature-specs/ 已纳入版本控制;产品口径变更必须同步 feature truth,不能只改 README 或生产代码。