From 106818cfc24f67e79c3d6a0aab3bb173faf3b0f0 Mon Sep 17 00:00:00 2001 From: study-tool Date: Sun, 1 Nov 2026 20:04:52 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B6=88=E8=A7=A3=20AGENTS.md=20?= =?UTF-8?q?=E8=A7=84=E5=88=99=E5=86=B2=E7=AA=81=EF=BC=8C=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=A5=91=E7=BA=A6=E8=BF=81=E5=85=A5=E6=9E=B6=E6=9E=84=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现象:AGENTS.md 里 5 处规则互相打架——逐次询问 vs 认领/提交/升版本;不构建 vs 前端必须 build;全部提交 vs 禁 add -A;每次提交 bump 版本号,而实际 24 次提交版本仍是 0.0.8。 docs/agent_board.md 没有 Agent 列,认领规则无法落地;docs/agent_workflow.md 是 0 字节; docs/todo.md 与 docs/progress/todo.md 双账本漂移,旧文件还写 platforms/gdgxk.py。 根因:规则按「想防的事」逐条追加,没有回头消解冲突;项目契约和 Agent 行为混在同一个入口文件里。 边界: - AGENTS.md 只留行为规则;日志 / 目录 / 插件契约指向 docs/architecture.md(两节内容已并入,未丢失) - 版本号改为只在发版提交时 bump,普通提交不动 - 看板改四列表头,状态带时间戳「进行中 (MM-DD HH:mm)」,超 24h 可接管 - 删除 docs/todo.md;其中 2 条仍未修的代码问题已迁入 docs/progress/todo.md,待办按 待做 / 已知偏差 / 未验证 重排 - 不改代码、不改版本号、不打 tag 运行了什么:git status 自查(只提交这 6 个文档文件,uv.lock 是原有改动未提交);提交时 pre-commit 跑 ruff / web typecheck / commitizen 没跑什么:没跑测试、没构建、没跑 pnpm build(本次无代码改动) --- AGENTS.md | 90 +++++++++++++++++++++------------- docs/agent_board.md | 24 ++++----- docs/agent_workflow.md | 108 +++++++++++++++++++++++++++++++++++++++++ docs/architecture.md | 13 +++-- docs/progress/todo.md | 27 ++++++++--- docs/todo.md | 61 ----------------------- 6 files changed, 205 insertions(+), 118 deletions(-) delete mode 100644 docs/todo.md diff --git a/AGENTS.md b/AGENTS.md index faa4c29..4f55726 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,60 +1,80 @@ # AGENTS.md -本文档用于约束本项目中的 AI / 自动化开发行为。开发时优先遵循本文件,其次遵循用户当前消息。 +本文档用于约束本项目中的 AI / 自动化开发行为。 + +**优先级:用户当前消息 > 本文件 > 其他文档。** 与本文件冲突时以用户消息为准,并在收尾时问一句要不要同步改本文件。 ## 基本原则 - 先读现有代码,再动手修改,优先沿用项目已有结构和写法。 -- 先声明后动手:AI 开工前,必须在 docs/agent_board.md 或任务看板中认领路径。如果发现路径已被其他 Agent 占用,必须停下等待,不能硬改。 -- 禁止 git add -A:在共享仓库中,必须按具体文件名暂存,防止误提交其他 Agent 未完成的改动。 -- 任何任务,必须在独立的 git worktree 或独立工作区中执行,严禁在 main 工作区直接并行修改。 - 写代码保持最少行数,能简单实现就不要引入复杂抽象。 - 标准格式、协议、解析、压缩、加密、日期等通用能力优先使用成熟稳定的库,不要手写底层实现,除非用户明确要求或项目已有实现必须沿用。 - 不要为了“兼容更多场景”写大量分支,只实现当前明确需要的功能。 - 项目尚未上线,不需要兼容旧数据;表结构或字段调整时直接按新设计修改,不写旧字段兼容、数据迁移兜底或删除旧表的清理逻辑,除非用户明确要求。 -- 每次写完代码,不需要检查语法,不需要执行构建,用户会自己做。 -- 不要改无关文件,不要顺手重构。 -- 任何文件编辑、改动、删除或格式化前,必须先询问用户是否允许;只有得到明确同意后才能修改。 +- 不要改无关文件,不要顺手重构;顺手发现的 bug 不修,记进 `docs/progress/todo.md` 并注明位置。 - 如果工作区已有用户改动,不要回滚,不要覆盖;只在必要范围内追加修改。 +### 动文件之前(Plan-first) + +- 修改**已存在文件**前,先给一次改动清单:路径 / 改什么 / 为什么 / 影响面;同意后本次任务内连续执行,不逐文件问。 +- 新建文件、新建目录不用逐次问,但要列进清单。 +- 删除、覆盖、格式化、`git reset --hard`、`git checkout .`、`git push -f`、删分支 / 删 worktree,必须单独列出、单独同意。 +- 用户当条消息里点名的改动,视为已同意。 + +### 验证口径 + +- **产物生成必须做**:前端改动 → `cd web && pnpm build`,`static/` 一起提交,不直接改 `static/` 里的产物。 +- **质量验证不做**:不跑测试、不跑全量 lint / typecheck,由用户执行。例外:用户明确要求、发版、或改到核心逻辑(认证 / 计费 / 写库 / 定时任务 / 并发 / 对外协议)。 +- 提交时 pre-commit 会跑 `ruff-check` / `ruff-format` / `web typecheck`,失败就修;这不算“检查语法”。 +- 结论必须带依据:命令、`文件:行`、报错原文;没核实就写「未核实」。 +- 命令失败、文件读不到、解析失败 → 结论是「未知」,不是「空」或「没问题」。 +- 没跑的验证写 `Not tried: <原因>`。 + +## 多 Agent 协作 + +- 看板表头统一为 `| 路径 | 任务 | Agent | 状态 |`,历史行 `Agent` 列留空。 +- 开工前在 `docs/agent_board.md` 追加一行,状态写 `进行中 (MM-DD HH:mm)`(如 `进行中 (11-01 19:40)`)。同一路径已有别人的 `进行中` → 停下问,不硬改。 +- 完成后只改自己那行:状态改 `已完成,待实测`,**保留行**(它是完成记录)。不动别人 Agent 的行。 +- 超过 24 小时仍是 `进行中` 的行(按状态里的时间算)视为过期,可以接管,但要注明接管人。 +- 只有一个 Agent 在顺序开发时,认领行可以省,但改前必须先看有没有人动过同一文件。 +- **并行开发(≥2 个任务同时改代码)** 时,每个任务用独立 worktree: + + ```bash + git worktree add ../study-tool-<任务名> -b <任务名> + cp .env ../study-tool-<任务名>/ + uv sync + pnpm --dir web install + ``` + + 合并回 master 后 `git worktree remove ../study-tool-<任务名>`。单人顺序开发不强制建 worktree。 +- 禁止 `git add -A` / `git add .`:必须按具体文件名暂存,防止误提交其他 Agent 未完成的改动。 + ## 反复提醒沉淀 -- 如果开发过程中总是遇到某个问题,或者用户反复提醒或同一个问题或同一个注意事项被用户纠正2次以上,需要把该注意事项补充到文档`docs/agent_workflow.md`中。 +- 同一个坑出现 ≥2 次(或同一个注意事项被用户纠正 2 次以上)才写成规则;只出现 1 次的先记 `docs/agent_workflow.md` 的「观察中」。 +- `docs/agent_workflow.md` 每条格式:规则 / 为什么 / 证据(提交 sha、报错原文、`文件:行`)/ Seen N×。 +- Seen ≥3 且用户同意,才升级进本文件;写入前先全文搜重,有同义条目就改旧的,不新增重复条目。 - 补充时写成明确、可执行的规则,避免只写模糊描述。 -- 关于“遇到重复问题时的总结沉淀流程”及“自我进化机制”,详见 `docs/agent_workflow.md`。 -- - -## 日志规范 - -- 所有日志只走 `runtime.log(key, msg, level="info")`:它统一写控制台、内存(`/api/log`)和 SQLite(`/api/log/history`),三处内容必须一致。 -- 禁止在新代码里自己 `print()` 写业务日志,也不要直接调 `store.run_log_add()`;模块只负责组织 `msg`。 -- key 约定:账号相关用账号 id(页面卡片「课程日志」直接显示);全局 / 系统事件用 `"system"`(右上角「系统日志」弹窗显示)。 -- 时间戳、级别、格式都由 `runtime.log` 统一拼(`时间 [级别] 内容`),模块里不要自己加时间前缀。 -- 前端读日志统一用 `/api/log?key=&since=`(实时)+ `/api/log/history?key=&tail=`(历史兜底),不要另起一套日志接口。 - -## 代码结构 / 平台插件 - -- 业务代码都在 `src/study_tool/` 包里;根目录只放启动壳 `app.py`、`pyproject.toml`、`static/`、`data/`、`script/` 这类运行时资源。通用脚本收进 `script/`,不参与运行的参考脚本放 `script/reference/`;只服务某个平台的脚本(含运行必需的 JS)放该插件目录的 `script/` 下(`plugins/<名字>/script/`),不放根目录,保证删掉插件目录不留残件。插件专属的常量、请求模型、提示词放插件自己的 `config.py` / 模块里,核心目录不写具体平台的内容,也不写死平台 id。 -- 平台实现只能放在 `src/study_tool/plugins`:`plugin.json` 只放 `id` / `title` / `name` 注册信息,`main.py` 放 `Platform` 子类,实现复杂的再拆 `jspx.py` / `study.py` 等同目录模块;`load_plugins()` 扫目录 import,新增插件不用改任何 Python 代码(`GET /api/plugins` 看清单,`POST /api/plugins/{name}/enable|disable` 启停,`POST /api/plugins/reload` 热重载)。`src/study_tool/platforms/` 只放运行时(`base` / `contract` / `errors` / `registry`),不再放具体平台。能力只能通过「在子类里实现同名方法」绑定,禁止再写 `Platform(id=..., study=..., session=...)` 这种逐项传函数的注册;模块末尾 `PLATFORM = registry.register(<子类>())`,一个平台只注册一次。同一平台的不同页面代际拆成插件目录内的实现模块,由平台模块按 `course["generation"]` 分发。 -- 新增平台的标准步骤:建 `plugins/<名字>/`,写 `plugin.json`(`id` / `title` / `name`),实现 `study(self, key, s, d, **opts)`,需要登录 / 课程列表 / 发现时再实现 `session` / `dump_cookies` / `login` / `list_courses` / `discover` / `renew_session` / `profile` 对应方法,没有的阶段不写,末尾 `PLATFORM = registry.register(X())`)→ 启动即可,不用改 `load_builtin()`、`engine.py`、`routes/`、前端。 -- 引擎、路由、前端都不允许直接 import 某个具体平台模块;需要平台能力时用 `registry.get(course["platform"])`;账号级操作(登录 / 刷新课程 / 发现课程 / 会话)按 `accounts.platform` 用 `registry.get()` 取 `Platform` 上的 `session` / `dump_cookies` / `login` / `list_courses` / `discover` / `renew_session` / `profile`;「添加账号」的平台下拉来自 `GET /api/platforms`。 -- 前端源码在 `web/`(React + TypeScript + Vite + Tailwind CSS),构建产物输出到 `static/` 并提交进仓库;改完前端必须执行 `cd web && pnpm build`,不要直接改 `static/` 里的产物。 -- 新代码的 import 一律显式(如 `from study_tool.config import JSPX`),不要再新增 `from xxx import *`;历史遗留的 star import 只保留在拆分前就存在的模块里。 -- `docs/architecture.md` 维护分层与插件契约,改动结构时同步更新。 +- Agent 只提议并附证据,**不自己改本文件**。 ## 文档规范 - README 保持简洁,只放项目介绍、核心功能、快速开始和文档入口。 -- 后续待办写到 `docs/progress/todo.md`。 +- 后续待办写到 `docs/progress/todo.md`,分三段:待做 / 已知偏差 / 未验证。 - 已实现但还需要用户测试确认的事项写到 `OPTIMIZATION.md`。 -- 每次任务完成前,都要根据实际变更检查并更新 `OPTIMIZATION.md` 和 `OPTIMIZATION.md`;如果功能或待办没有变化,也要确认无需修改。 +- 每次任务完成前,根据实际变更检查并更新 `OPTIMIZATION.md` 与 `docs/progress/todo.md`;没有变化也要确认无需修改。 +- 文档里的命令、路径、字段名必须与代码一致;引用代码写 `路径 + 符号名`,不写行号。 - 文档不要写过期日期;除非用户明确要求记录具体时间。 ## 发版本流程 - 发版本时,先把 `CHANGELOG.md` 的 `Unreleased` 变更整理成新的版本记录,并保留空的 `Unreleased` 标题。 -- 每次提交必须将当前版本号进行提升,更新 `metadata.yaml`里面的`version`中的版本号。 -- 将当前未提交的代码全部提交到 Git。 -- 提交完成后,给当前提交打最新版本号对应的 tag,例如 `v0.0.5`。 -- 发版本流程中不要执行编译、测试或构建,除非用户明确要求。 -- 如果触发重要的决策,你需要将代码提交到`git`中,并且将本次已经修改的内容进行总结 \ No newline at end of file +- **只在发版提交时提升版本号**:`metadata.yaml` 的 `version` 与 `pyproject.toml` 的 `version` 一起改(bumpversion);普通提交不动版本号。 +- 提交范围:只提交本次任务的文件(`git add <具体路径>`)。提交前 `git status` 必须只剩本次任务的文件;出现别人的或无关的改动 → 停下问,不顺手提交。 +- 提交完成后,给当前提交打最新版本号对应的 tag(如 `v0.0.8`)。打之前 `git tag -l` 查重、`git rev-parse HEAD` 确认,不覆盖已有 tag。 +- 发版本流程中不要执行编译、测试或构建,除非用户明确要求;前端改动仍需 `pnpm build` 并把 `static/` 一起提交。 +- 完成一个能独立回滚的改动就提交一次(一个 commit 一个目标)。提交信息:标题 `<区域>: <用户可见的变化>`;body 写 现象 → 根因 → 边界(哪些变了、哪些没变)→ 运行了什么 / 没跑什么。 + +## 项目契约 + +- 日志约定、目录分层、平台插件契约统一维护在 `docs/architecture.md`,改动结构时同步更新;本文件不再重复这些内容。 \ No newline at end of file diff --git a/docs/agent_board.md b/docs/agent_board.md index a61dda8..6c92af2 100644 --- a/docs/agent_board.md +++ b/docs/agent_board.md @@ -1,14 +1,14 @@ # Agent 任务看板 -| 路径 | 任务 | 状态 | -| --- | --- | --- | -| src/study_tool/plugins/gdgxk/main.py、utils/learn.py | 广东公需课:进度页重取任务点状态 + 未完成自动重学 | 已完成,待实测 | -| src/study_tool/plugins/gdgxk/utils/exam.py、main.py | 广东公需课:课程考核解析 + 题库/AI 答题 + 交卷(finishTest2) | 已完成,待实测 | -| web/src/App.tsx、src/study_tool/routes/accounts.py、src/study_tool/plugins/gdgxk/main.py、src/study_tool/database/__init__.py | 修复刷新课程空平台报错 + 旧会话 cookie 回写导致的反复重登 | 已完成,待实测 | -| src/study_tool/platforms/base.py、contract.py、plugins/gdgxk/main.py、plugins/gdgxk/utils/exam.py、plugins/guanyixue/quiz.py、main.py、web/src/components/QuizDialog.tsx | 公需课接入交互式测验:quiz / quiz_submit 槽位 + 按平台分发交卷 | 已完成,待实测 | -| web/src/components/PluginsDialog.tsx、src/study_tool/plugins/__init__.py、src/study_tool/platforms/contract.py | 插件管理卡片:能力名改中文 + 显示适配网站 | 已完成,待实测 | -| src/study_tool/notify.py、plugins/daidai_push/、plugins/__init__.py、config/settings.py、models.py、routes/settings.py、routes/accounts.py、engine.py、plugins/gdgxk/main.py、plugins/guanyixue/utils/spa.py、web/src/components/SettingsDialog.tsx、PluginsDialog.tsx | 故障推送:呆呆面板推送渠道插件 + `notify` 设置项 + 进度异常 / 多次重试 / 登录与运行异常埋点 | 已完成,待实测 | -| web/src/components/SettingsDialog.tsx、web/src/components/ui/Modal.tsx | 系统设置改成左侧分类 + 右侧内容的两栏弹窗(大模型 / 学习模式 / 故障推送) | 已完成,已截图核对 | -| web/src/components/AccountEditDialog.tsx、ui/ConfirmDialog.tsx、ui/Modal.tsx、ui/Button.tsx、AccountCard.tsx、QuizDialog.tsx、App.tsx | 账号卡片加「修改账号信息」弹窗(账号 / 密码 / 备注 / 平台,保存后自动重登)+ 自绘确认弹窗替换 window.confirm | 已完成,待实测 | -| src/study_tool/plugins/gdgxk/main.py、utils/learn.py、web/src/components/ui/LogLines.tsx、AccountCard.tsx、SystemLogDialog.tsx | 卡片标题显示学员姓名(profile 解析个人中心 name)+ 进度上报日志改清楚 + 日志面板改明亮模式并按级别配色 | 已完成,待实测 | -| src/study_tool/app.py、routes/settings.py、notify.py、web/src/components/SettingsDialog.tsx、docs/plugin.md | 发版体检:启动日志不再打印 AUTH_TOKEN、密钥可清空、推送失败不占整段冷却、目录名文案统一 | 已完成,待实测 | +| 路径 | 任务 | Agent | 状态 | +| --- | --- | --- | --- | +| src/study_tool/plugins/gdgxk/main.py、utils/learn.py | 广东公需课:进度页重取任务点状态 + 未完成自动重学 | | 已完成,待实测 | +| src/study_tool/plugins/gdgxk/utils/exam.py、main.py | 广东公需课:课程考核解析 + 题库/AI 答题 + 交卷(finishTest2) | | 已完成,待实测 | +| web/src/App.tsx、src/study_tool/routes/accounts.py、src/study_tool/plugins/gdgxk/main.py、src/study_tool/database/__init__.py | 修复刷新课程空平台报错 + 旧会话 cookie 回写导致的反复重登 | | 已完成,待实测 | +| src/study_tool/platforms/base.py、contract.py、plugins/gdgxk/main.py、plugins/gdgxk/utils/exam.py、plugins/guanyixue/quiz.py、main.py、web/src/components/QuizDialog.tsx | 公需课接入交互式测验:quiz / quiz_submit 槽位 + 按平台分发交卷 | | 已完成,待实测 | +| web/src/components/PluginsDialog.tsx、src/study_tool/plugins/__init__.py、src/study_tool/platforms/contract.py | 插件管理卡片:能力名改中文 + 显示适配网站 | | 已完成,待实测 | +| src/study_tool/notify.py、plugins/daidai_push/、plugins/__init__.py、config/settings.py、models.py、routes/settings.py、routes/accounts.py、engine.py、plugins/gdgxk/main.py、plugins/guanyixue/utils/spa.py、web/src/components/SettingsDialog.tsx、PluginsDialog.tsx | 故障推送:呆呆面板推送渠道插件 + `notify` 设置项 + 进度异常 / 多次重试 / 登录与运行异常埋点 | | 已完成,待实测 | +| web/src/components/SettingsDialog.tsx、web/src/components/ui/Modal.tsx | 系统设置改成左侧分类 + 右侧内容的两栏弹窗(大模型 / 学习模式 / 故障推送) | | 已完成,已截图核对 | +| web/src/components/AccountEditDialog.tsx、ui/ConfirmDialog.tsx、ui/Modal.tsx、ui/Button.tsx、AccountCard.tsx、QuizDialog.tsx、App.tsx | 账号卡片加「修改账号信息」弹窗(账号 / 密码 / 备注 / 平台,保存后自动重登)+ 自绘确认弹窗替换 window.confirm | | 已完成,待实测 | +| src/study_tool/plugins/gdgxk/main.py、utils/learn.py、web/src/components/ui/LogLines.tsx、AccountCard.tsx、SystemLogDialog.tsx | 卡片标题显示学员姓名(profile 解析个人中心 name)+ 进度上报日志改清楚 + 日志面板改明亮模式并按级别配色 | | 已完成,待实测 | +| src/study_tool/app.py、routes/settings.py、notify.py、web/src/components/SettingsDialog.tsx、docs/plugin.md | 发版体检:启动日志不再打印 AUTH_TOKEN、密钥可清空、推送失败不占整段冷却、目录名文案统一 | | 已完成,待实测 | diff --git a/docs/agent_workflow.md b/docs/agent_workflow.md index e69de29..21a521b 100644 --- a/docs/agent_workflow.md +++ b/docs/agent_workflow.md @@ -0,0 +1,108 @@ +# Agent 工作流 · 经验账本 + +> 规则在 `AGENTS.md`;这里只放「还没被证实」或「刚被证实一次」的经验。 +> 写入前先全文搜重:有同义条目就改旧的,不新增重复条目。 +> Agent 可以写本文件,但**不能自己改 `AGENTS.md`**——只能提议 + 附证据。 + +## 怎么用 + +1. 遇到问题,先搜本文件有没有同一条。 +2. 没有 → 在「观察中」加一条,`Seen:1×`。 +3. 又遇到 → 同一条 `Seen +1`,把新证据追加进去(不要另开一条)。 +4. `Seen ≥3` 且用户同意 → 升级进 `AGENTS.md`,本文件那条标 `已升级`;判断为「不该加」的 → 移到「已否决」并写清原因。 + +## 条目格式 + +```markdown +### <一句话规则,写成能执行的动作> + +- 为什么:<不这么做的具体代价> +- 证据: + - `<版本 / commit sha>` <现象,带文件:行 或报错原文> +- Seen:N× +- 状态:观察中 | 已升级 → AGENTS.md「<章节>」 | 已否决(原因) +``` + +## 观察中(Seen 1–2) + +### 文档里不写具体的版本号 / 行号示例,它们会过期 + +- 为什么:示例一旦过期,Agent 会照着过期的值做事,且没人会主动去改示例。 +- 证据: + - `v0.0.8 (aa48145)`:旧 `AGENTS.md` 写「打 tag,例如 `v0.0.5`」,而当时最新 tag 已是 `v0.0.8`。 +- Seen:1× +- 状态:观察中(新版本改成了 `v0.0.8`,同样会过期;下次改成 `<最新 tag>` 或直接删示例) + +### ruff-format 会重排文档里的 python 代码块 + +- 为什么:以为只改 `.py`,结果提交时它把文档里 ```python 代码块的格式也改了,pre-commit 直接拦下这次提交;不知道这点会反复「改了又变」。 +- 证据: + - `06d3206` 提交时:`docs/architecture.md` 的 `Channel` 示例被 `ruff-format` 加了空行,pre-commit 报 `files were modified by this hook` 并中止提交。 +- Seen:1× +- 状态:观察中(文档里的 python 代码块以钩子结果为准,别手改对抗) + +## 已升级 → AGENTS.md + +### 修改已存在文件:一次清单、一次同意 + +- 为什么:逐文件询问会让「认领 → 改代码 → 升版本 → 提交」每一步都卡住,长任务直接跑不动;但完全不问又会动到用户的文件。 +- 证据: + - `v0.0.8 (aa48145)`:旧规则「任何文件编辑…必须先询问」与同文件的「认领看板」「提交代码」「升版本号」三条互相冲突,且没写清「一次同意管几次」。 +- Seen:1× +- 状态:已升级 → AGENTS.md「动文件之前(Plan-first)」 + +### 不构建 ≠ 不验证 + +- 为什么:把「不跑构建」写成「不用检查」,会让 Agent 把没验证的结论当成已验证的结论报上来。 +- 证据: + - `v0.0.8 (aa48145)`:同文件一边写「不需要检查语法、不需要执行构建」,一边写「改完前端必须 `cd web && pnpm build`」;`.pre-commit-config.yaml` 又会在提交时跑 `ruff-check` / `ruff-format` / `web typecheck`。 +- Seen:1× +- 状态:已升级 → AGENTS.md「验证口径」 + +### 版本号只在发版提交时提升 + +- 为什么:每次提交都 bump 时,bump 同时改 `metadata.yaml` + `pyproject.toml`,并行分支必然冲突;而且这条从来没被执行过。 +- 证据: + - `v0.0.8 (aa48145)`:旧规则「每次提交必须提升版本号」,实际 24 次提交、版本仍是 0.0.8,tag 只有 `v0.0.1` / `v0.0.8`。 +- Seen:1× +- 状态:已升级 → AGENTS.md「发版本流程」 + +### 看板按 Agent 认领,完成后改状态、保留行 + +- 为什么:规则说「按 Agent 认领」,表里却没有 Agent 列,认领动作无从落地;删行又会把完成记录一起删掉。 +- 证据: + - `v0.0.8 (aa48145)`:`docs/agent_board.md` 表头只有「路径 / 任务 / 状态」,12 行状态全是「已完成,待实测」,没有 Agent 列。 +- Seen:1× +- 状态:已升级 → AGENTS.md「多 Agent 协作」 + +### 项目契约和 Agent 行为规则分开 + +- 为什么:行为规则和项目契约混在一个文件里,改契约时没人会想到 review 规则文件,两边都不好维护。 +- 证据: + - `v0.0.8 (aa48145)`:`AGENTS.md` 93 行,其中「日志规范」+「代码结构 / 平台插件」约 4KB 是项目契约,而 `docs/architecture.md` 已在维护同一套分层与插件契约。 +- Seen:1× +- 状态:已升级 → AGENTS.md「项目契约」(两节已并入 `docs/architecture.md`,AGENTS.md 降到 79 行) + +### 一个文件只留一份权威账本 + +- 为什么:两份待办并存时,Agent 会读旧的、写新的,两边慢慢分叉。 +- 证据: + - `v0.0.8 (aa48145)`:`docs/todo.md` 仍写 `../src/study_tool/platforms/gdgxk.py`,而平台早已迁到 `src/study_tool/plugins/gdgxk/`。 + - `v0.0.8` CHANGELOG:文档里的旧目录名 `pugins/` 是另一次同类漂移,靠一次性全量替换才修掉。 +- Seen:2× +- 状态:已升级 → AGENTS.md「文档规范」(`docs/todo.md` 已删除;其中 2 条仍未修的代码问题已迁入 `docs/progress/todo.md`) + +## 已否决(不要再提议) + +| 规则 | 原因 | +| --- | --- | +| 每个任务都强制建 git worktree | 单人顺序开发时是纯摩擦:新 worktree 没有 `.env` / `.venv` / `node_modules`,每次要重装;已在 AGENTS.md 限定为「并行开发时」。 | +| 六条验收硬拦发布(全量测试 / 多平台 vet / 真用) | 与「用户自己构建验证」冲突,本项目没有对应的测试体系。 | +| 每个失败都在同一 commit 开 issue | 用 `docs/progress/todo.md` 等价实现,个人项目不引入 issue 流程。 | +| 只合并 reviewed head(`--match-head-commit`) | 没有外部 PR 流程,不适用。 | + +## 维护 + +- 「观察中」+「已升级」合计超过 15 条就该清理:合并同义条目、删掉已否决的、把过期的观察降级或删掉。 +- 每条都必须能落到「证据」;写不出证据的,先别写进来。 +- 不写会议纪要、不写「注意质量」这类无法执行的表述。 diff --git a/docs/architecture.md b/docs/architecture.md index f6a723a..fe14cb1 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -73,6 +73,8 @@ script/reference/ 不参与运行的参考脚本:dgjy-toolkit.user.js 依赖方向单向:接口 → 领域 → 基础设施。基础设施不反向 import 接口。 +新代码的 import 一律显式(如 `from study_tool.config import JSPX`),不要再新增 `from xxx import *`;历史遗留的 star import 只保留在拆分前就存在的模块里。 + `engine.py` 与 `routes/*` 只用 `registry.get(...)` 取平台实现,不 import 具体平台模块; 平台实现之间只有同平台不同代际会互相 import(`course.py` → `spa.py`),依赖保持单向,避免成环。 @@ -212,8 +214,10 @@ from study_tool.platforms import registry class Channel: # notify.Channel id = "" label = "" + def send(self, title, content, context=None): ... # 失败抛异常 + NOTIFY = notify.register(DaidaiPush()) # 插件 main.py 末尾注册 ``` @@ -245,6 +249,9 @@ NOTIFY = notify.register(DaidaiPush()) # 插件 main.py 末尾注册 ## 日志 -统一入口 `runtime.log(key, msg, level="info")`,一次写三处:控制台、内存 -(`/api/log` 给前端轮询)、SQLite(`/api/log/history`)。key 用账号 id 或 `system`。 -规范见 `AGENTS.md` 的「日志规范」,不要自己 `print()`。 +所有日志只走 `runtime.log(key, msg, level="info")`:它统一写控制台、内存(`/api/log`)和 SQLite(`/api/log/history`),三处内容必须一致。 + +- 禁止在新代码里自己 `print()` 写业务日志,也不要直接调 `store.run_log_add()`;模块只负责组织 `msg`。 +- key 约定:账号相关用账号 id(页面卡片「课程日志」直接显示);全局 / 系统事件用 `"system"`(右上角「系统日志」弹窗显示)。 +- 时间戳、级别、格式都由 `runtime.log` 统一拼(`时间 [级别] 内容`),模块里不要自己加时间前缀。 +- 前端读日志统一用 `/api/log?key=&since=`(实时)+ `/api/log/history?key=&tail=`(历史兜底),不要另起一套日志接口。 diff --git a/docs/progress/todo.md b/docs/progress/todo.md index 1dc078c..74756ff 100644 --- a/docs/progress/todo.md +++ b/docs/progress/todo.md @@ -1,20 +1,33 @@ # 后续待办 -- 接入第二个学习平台时,建 `plugins/<名字>/`(`plugin.json` + `main.py` 的 `Platform` 子类),实现 - `session` / `login` / `list_courses` / `discover` / `study` 并注册;账号在「添加账号」里选平台后即可用,登录与课程发现不需要再改路由。 +## 待做 + +- 登录日志的 key 用错维度:`routes/accounts.py` 的 `_log_login()` 拿用户名(手机号)当 key 写 `run_log`,而卡片 + 「课程日志」按账号 id 读,两边对不上——登录行只有「登录记录」页能看到。要么统一成账号 id,要么卡片同时聚合两个 key。 +- `ddddocr` 的报错提示会误导:`plugins/gdgxk/utils/login.py` 把任何导入失败都写成「需要先装 ddddocr(pip install ddddocr)」, + `uv sync` 在应用运行期间改写包造成的 `DLL load failed` 也会被说成「没装」。应区分 ImportError 与 OSError(DLL), + 并把真实异常写进 `runtime.log`。 - 前端补一条自动化回归(Playwright):空态、账号卡片、测验弹窗、设置保存、平台下拉。 - pre-commit 首次接入还没跑过全量:`ruff-format` 会重排约 30 个文件,`ruff check` 会报 `plugins/guanyixue/script/study_video.py:34` 的 E402(脚本先改 `sys.path` 再 import,加 `# noqa: E402` 即可)。 找一次专门的提交跑 `pre-commit run --all-files`,别混进功能改动里。 - 构建检查进 CI:`cd web && pnpm install --frozen-lockfile && pnpm typecheck && pnpm build`, 并校验 `static/` 与源码同步。 -- 广东公需课:用一门**未学完**的课实测进度上报,确认平台端「已学时长 / 已完成」状态跟着变。 -- 广东公需课:**试卷考核**已接入(`utils/exam.py`:解析内嵌题面 → 题库/AI 答题 → `finishTest2` 交卷),待用 - 一门只差考核的课实测交卷响应与得分。 - 广东公需课:视频弹题仍未接入;`answer.py` 的两套按序答案留作考核备选,暂未使用。 +- 推送渠道目前只有呆呆面板(`plugins/daidai_push/`):要接 WxPusher / Telegram / 邮件,按 + `"kind": "notify"` 再建一个插件目录即可,核心不用改;`notify.send()` 会发给所有已加载渠道。 +- 接入第二个学习平台时,建 `plugins/<名字>/`(`plugin.json` + `main.py` 的 `Platform` 子类),实现 + `session` / `login` / `list_courses` / `discover` / `study` 并注册;账号在「添加账号」里选平台后即可用,登录与课程发现不需要再改路由。 + +## 已知偏差 + - 测验 `/api/quiz/submit` 已改成按 `registry.get(platform).quiz_submit` 分发(公需课也能交卷),但 `/api/quiz*` 三个接口仍由莞易学插件注册: 停用莞易学后公需课的「打开测验」会 404。要让接口彻底跟插件解耦,需把它们挪到 `routes/`,并把 AI 答题的提示词 / 解析一起下沉到核心。 - `engine.py` 的 legacy / spa 日志标签、`enable_quiz` / `optQuiz` 字段、`snapshots` 表仍带着莞易学的概念, 有第二个平台再决定是否抽象。 -- 推送渠道目前只有呆呆面板(`plugins/daidai_push/`):要接 WxPusher / Telegram / 邮件,按 - `"kind": "notify"` 再建一个插件目录即可,核心不用改;`notify.send()` 会发给所有已加载渠道。 + +## 未验证 + +- 广东公需课:用一门**未学完**的课实测进度上报,确认平台端「已学时长 / 已完成」状态跟着变。 +- 广东公需课:**试卷考核**已接入(`utils/exam.py`:解析内嵌题面 → 题库/AI 答题 → `finishTest2` 交卷),待用 + 一门只差考核的课实测交卷响应与得分。 diff --git a/docs/todo.md b/docs/todo.md deleted file mode 100644 index ebf42c7..0000000 --- a/docs/todo.md +++ /dev/null @@ -1,61 +0,0 @@ -# TODO - -## 待评估:接入广东公需课平台(jsxx.gds.edu.cn) - -**状态**:已接入,见 `../src/study_tool/platforms/gdgxk.py` 与 `OPTIMIZATION.md`;登录 / 课程列表 / 进度心跳已实测,待用未学完课程确认。 - -### 背景 - -参考脚本:`2026广东公需课·全自动完美版(后台保活+10分钟刷新).js`(油猴 v1.3,`@match https://jsxx.gds.edu.cn/*`)。 - -它做的事:悬浮面板 + 后台保活 + 10 分钟整页刷新 + 视频题目题库命中(约 200 条)+ 未命中时 12 轮 -暴力试错 + 考核页按课程 id 硬编码两套 30 题答案自动勾选(不代交卷)。 - -注意:这是**广东专业技术人员继续教育公需课**,跟本项目的 dgjy(东莞教师培训)不是一套系统, -连登录入口都不同。 - -### 结论:不能直接融合 - -| 维度 | 该脚本 | 本项目 | 能不能用 | -| --- | --- | --- | --- | -| 站点 | jsxx.gds.edu.cn | jspx / study.dgjy.net | 无交集 | -| 实现层 | 页面 DOM 点击、layui/mylayer 弹层 | 服务端接口 + Playwright 真播 | 不同层 | -| 答案 | 硬编码题库 + 按课程 id 两套答案 | 大模型现算 | 数据不可复用 | -| 保活 | setInterval、伪造 mousemove、整页 reload | 服务端跑逻辑,浏览器只播视频 | reload 会打断播放 | -| 兜底 | 12 轮暴力试错 | LLM + 交卷前人工核对 | 平台限次,会烧光重考机会 | - -### 真要接入时的顺序 - -1. **抓包取证**(阻塞项,必须有公需课账号) - - 登录链路:账号密码还是统一身份认证?落哪些 cookie / token - - 课程列表与课时树接口 - - 课时上报接口:视频进度怎么记账,是否要心跳 - - 考试接口:取卷、交卷、重考次数(对应 dgjy 的 `surplusExamCount` 那一套) - - 视频是不是直链,需不需要 Playwright 真播到结尾 -2. **适配层分派**:`app.py` 现在 `login()` / `discover()` / `detect_platform()` / `StudyClient` - 全是 dgjy 专用,得按平台分支;`accounts` 表加平台标识,控制台卡片显示平台 -3. **答题**:本地题库命中优先、LLM 兜底(题库得自己攒 dgjy 的,脚本那 200 条是公需课内容,搬不过来) -4. **UI**:课程卡片区分平台;公需课的「考核」页结构跟 dgjy 完全不同,不能复用测验弹窗 - -### 明确不照抄的部分 - -- 暴力试错答题:dgjy 平台一般只给 3 次重考,试错直接废掉机会 -- 10 分钟整页刷新:打断正在播放的视频,进度白费 -- 按课程 id 硬编码答案:换课程批次即失效,脚本注释自己写了「已反转,解决反了问题」,说明是试错出来的 -- `.layui-layer` / `.mylayer-wrap` / `#studySelectAct .next` / `#topicList .m-topic-item` 这些选择器 -- 用 `innerText` 包含「正确」判断已答弹层:题干带「正确」二字就会被误点 -- 把「下一节」按钮 `pointer-events:none` 锁死:平台一改版就失效 - -### 可以借鉴的 - -- 题库命中优先、LLM 兜底(省 token、命中时更准) -- 强制播完才放开「下一节」—— 项目已有等价实现(播到 `ended` + `completeTime` 校验) - -## 待办:日志 key 与 ddddocr 报错提示 - -- **登录日志的 key 用错维度**:`routes/accounts.py` 的 `_log_login()` 拿**用户名(手机号)**当 key 写 run_log,而卡片「课程日志」读的是**账号 id**,两边对不上——登录行只有登录记录页(`login_log` 表)能看到。要么统一成账号 id,要么卡片同时聚合两个 key。 -- **ddddocr 的报错提示会误导**:`platforms/gdgxk.py` 把任何导入失败都写成「需要先装 ddddocr(pip install ddddocr)」,`uv sync` 在应用运行期间改写包造成的 `DLL load failed` 也会被说成「没装」。应区分 ImportError 与 OSError(DLL),并把真实异常写进 `runtime.log`。 - -## 已完成 - -- 「首次进入课程弹『未加入课堂』」已修复,实测确认见 `OPTIMIZATION.md`。