android-ad-cleaner
CLAUDE.md7.1k starsChanged 19 days agoMITView on GitHub
本仓库的维护规范就是本文件:开工前读完,判断标准在 CODING_STANDARDS.md。AGENTS.md 不在这里——它是交付给用户侧执行 agent 的手机操作手册,不是维护规范(ADR-0028);但改引擎行为(挂包拒绝名单、极性词表、off() 的走向)时,§1 的 11 条产品契约就是这条改动的边界,先读一遍。 本文件只写会改变 agent 行为的指令。逐次变更记录见 CHANGELOG.md,用户看的更新说明与发版流程见 docs/agents/release.md,上报归类见 docs/09,不在这里复述。
Adopted by
Steady: 0 new projects took it in the last 4 weeks, none the 4 weeks before.thin data
Who's talking about rddmwmddr/android-ad-cleaner
- @metasprojects.bsky.social“Python script cleans vendor adware off Android devices without root, fully reversible, with AI agent …”Bluesky · Oct 27, 2026 · 3 likes
- BLACKIELF/qingjing-aigoodbro“清静 AiGoodBro:Mac 与 Android 品牌适配及可下载构建”GitHub · Nov 8, 2026
- mrxcode-debug/pdt-github-hot-radar“[GitHub Radar] 2026-11-01: 3 repo đáng xem”GitHub · Nov 1, 2026
- ultrametricai/ultrametric“vendor-radar: daily scan 2026-10-28”GitHub · Oct 28, 2026
The biggest 4 of 4 mentions linking to the repository that our crawl found. Each line is their own headline.
In this repositoryOne other file it gives its agents.
- AGENTS.mdAGENTS.md
The file
Files
CLAUDE.md
本仓库的维护规范就是本文件:开工前读完,判断标准在 CODING_STANDARDS.md。AGENTS.md 不在这里——它是交付给用户侧执行 agent 的手机操作手册,不是维护规范(ADR-0028);但改引擎行为(挂包拒绝名单、极性词表、off() 的走向)时,§1 的 11 条产品契约就是这条改动的边界,先读一遍。
本文件只写会改变 agent 行为的指令。逐次变更记录见 CHANGELOG.md,用户看的更新说明与发版流程见 docs/agents/release.md,上报归类见 docs/09,不在这里复述。
开发
- 回归:
python -m unittest discover -s tests;本机用仓库根的.venv/bin/python(系统 python 缺 adbutils,会报一串 ModuleNotFoundError)。只装requirements.txt就够,不需要 pywebview / pillow。并行会话在各自的 worktree 里干活时,那个目录没有.venv,用主副本.venv/bin/python的绝对路径;Read/Edit 也一律用该 worktree 的绝对路径。只跑一个文件时加PYTHONPATH=tests:PYTHONPATH=tests python -m unittest tests.test_row(tests/里的用例按顶层模块 importfixtures)。 - 本机
.venv是 3.13(仓库根的.python-version钉着这一版),与 CI 的 Linux job 同一版,上面那条回归命令就是本机口径,不必再用别的解释器跑一遍;重建时照.python-version走,别升到更新的版本。3.12 只剩 Windows job 与发版打包在跑,而那两边会 skip 掉 POSIX-only 的一批用例(包括 CLI 逐字节对拍),macOS 打包不跑回归——解释器会改写部分输出(3.13 起编译器剥掉文档字符串的公共缩进,异常 repr、argparse 帮助同理),别假设两版逐字相同。 - 数基线总数用干净副本:先
git rev-parse origin/main记下<sha>,再git archive <sha> | tar -x -C /tmp/x,在/tmp/x里跑同一套。主副本里可能躺着别的会话没提交的用例。干净副本与新 worktree 都没有node_modules,跑之前先npm ci,否则前端用例那条会红在Cannot find module 'jsdom'。 - 改动面与审查对比用分支基点的固定 SHA(
git merge-base HEAD origin/main的结果,git diff <sha>),不用origin/main尖端——并行会话会推进它。 - 主副本的当前分支随时被别的会话切走(
main只是其中一种状态),所以切分支、丢弃改动(git checkout --、restore、reset、stash)都在git worktree add出来的目录里做;主副本只做合并与只读操作。 - 推送前再
git fetch origin main并 rebase:并行会话会在开工到推送之间推进 main,基于旧 SHA 推送会让 PR 直接CONFLICTING,CI 也不触发。 - CI:
.github/workflows/test.yml在 PR 和 main 推送时自动跑同一套,Linux(unittest,3.13)与 Windows(unittest-windows,3.12)各一个 job,两版解释器各跑一次;两边 skip 的用例不一样(Windows 侧 skip 掉 POSIX-only 那批,含 CLI 逐字节对拍),判断覆盖时看-v输出里的 skipped 行,别假设两边跑的是同一批。build-windows.yml只在v*标签或手动触发时打包,不守 PR。 - 静态检查:
uvx ruff@0.17.3 check --select F,E9 tool tests gui android/app/src/main/python,本地与 CI 同一个版本(CI 就钉 0.17.3,.venv里没有 ruff)。不查recipes/——机型脚本靠注入作用域执行,静态分析看到的全是未定义名;也不开格式与行长规则。 - 找「前端行为测试」先看 tests/js/:用例用
node --test+ jsdom 加载真实的gui/web/index.html与官网根目录的index.html(下载页;手机版界面是 Compose,不在 jsdom 里测,判断在aac_android/view.py,用 Python 测;ADR-0017)。由 tests/test_web_client.py 在unittest里代跑,回归仍是上面那一条命令;单跑先npm ci,再npm test(等价于node --test --test-timeout=30000 --test-force-exit "tests/js/*.test.js";Node 22 不认目录参数,--test-force-exit防漏写window.close()时挂住)。缺 node、用例数为 0 都判红,不跳过。jsdom 覆盖不到的真渲染 / 真定时器再退到真浏览器:python -m http.server起静态服务,页面加载后桩window.pywebview.api并 dispatchpywebviewready,之后直接调showRun()这类顶层函数。 - CHANGELOG 一条一行、≤ 80 字,落在
## 未发布的### 新增/### 修复/### 内部之一,结尾挂一个编号(有 PR 挂 PR 号,只有 issue 的挂 issue 号;内部重构再加 ADR 号)。内部条目允许点一个符号名,但不展开机制;测试数、基线 SHA、对拍组数一律不写——那些在 PR 描述与 ADR 里。## 未发布期间不写验证行,发版时才补。rebase 后先git rebase --continue把提交落完、再跑回归与对拍,并确认## 未发布没有被已发版的小节吸收(main 刚发过版时要重新开出来)。 - 审查标准:见 CODING_STANDARDS.md,只放判断项;机械规则交给 CI。
- 社区问题的归类与真机缺口见 docs/09:那里只记 GitHub 上看不到的判据与缺口,逐条回复与 issue 编号留在 issue 里。
- 找「命令怎么分派、退出码怎么定」先看 tool/run.py 的
dispatch()与COMMANDS:一条命令一个cmd_*(serial, rest) -> int,sys.exit只在__main__一处;退出码语义见 ADR-0005。tool/adapt.py的三条子命令同形状:取run.fail()与返回码(#708),改它们时别把sys.exit加回 helper。 - 找「开关所属的行」先看 tool/row.py:
line结构定界、own多开关容器收窄、title_of平铺列表按竖直距离分带、title_hit给出同一段标题文字的落点(off()回退用)、dedupe同一水平带(整行留最靠右、双列各成一行)、control开关本体(卡片整体 checkable 时落点取内层 Switch);fast.py的switches()消费dedupe/control,chan_fast.py共用SAME_LINE与own,disabled_at判开关是不是灰的(整卡或内层开关带enabled="false"),off()与chan_fast共用它,不要另写一份。设置入口(齿轮)的判据是icon_hit,机型脚本从fast取这一份、不再各留副本。 - 找「某个品牌某个分组的入口怎么进」先看 tool/entries.py:品牌家族
FAMILY只在那里定义(run.py的广告包、参考脚本与版本比较都从它取,别再建第二份),ENTRIES是品牌 × 分组的三档入口,reach()按直达 → 搜索回退、都不通返回None,markdown()生成 docs/清单/入口速查.md。条目必须有出处(机型脚本里用过,或某份品牌清单里写着),used_by()从脚本现算「用过的机型」,speedups()给入口速查表尾的「可提速提示」(同族有人用过、这台没用过的直达),tests/test_entries.py守着这几条。 - 找「这个开关该开还是该关」先看 tool/polarity.py:
NEG_PREFIX/AD_WORDS/TRACK_WORDS与want_state()只在那里定义,tool/fast.py只是显式 re-export;加一个否定词会同时改变引擎与词库(ADR-0008 的增补段)。 - 找「某个开关叫什么、哪一页要留着什么」先看 tool/vocab.py:从机型脚本现算每个
(家族, 分组)的词条与同页保留项,生成 docs/清单/词库.md;只认p.off()/close_named()的字面量与模块级常量,一条词条都没读出来的脚本列在「读不到的脚本」。方向(要关 / 要开)取自polarity.want_state(),不按调用名猜。同页保留项只在那一页成立,不产出跨页禁词表(ADR-0008)。写完脚本跑python3 tool/vocab.py gaps <目录名>查漏:同家族别的机型关过、这份没写的词条。 - 找「按文字导航该用哪个方法」先看 docs/04-给新机型写脚本.md 的「该用哪个导航方法」:
start/entry/enter/setting/go/tap/tab失败时是raise还是返回False,选错会点错地方;setting失败只返回False,这一段没兜底时必须raise,否则本段空跑还按成功记(#776)。Phone.focus()是当前前台窗口(多数是包名/页面名,锁屏与通知栏抢焦点时是裸窗口名),不是焦点控件。 - 找「适配新机型从哪一步开始」先看 tool/adapt.py:
survey只读试入口(只试entries.ENTRIES的直达、不走Phone.start,落点用entries.landed()),explore/gen/check是后三段;-s 序列号 adapt <子命令>的转发只认ADAPT_SERIAL_SUBS(survey/explore/check);gen(不用带-s)与未知子命令按这种形态调时,dispatch()直接fail(),不再静默打印帮助。 - 改「各品牌在哪儿连点 7 下」先看 docs/手机版教程/brand-taps.json:这四行是单一来源,跑
python tool/gen_brand_taps.py渲染进android-tutorial.html、docs/手机版教程/README.md、docs/00-打开开发者模式.md的brand-taps标记区,漏跑由tests/test_doc_consistency.py抓。标记区外的正文(如页面里「小米连点的是「OS 版本」」)仍是手写。 - 改「官网两页的外观」先看仓库根的 site.css:调色板、深色档、顶栏与页脚只在那里,
python tool/gen_site_css.py把它渲染进index.html与android-tutorial.html的site-css标记区,页面不引外链(单文件副本取不到,ADR-0025)。两页在共享样式上的差异值只在:root.reading一条里(页面专属样式仍各自持有),漏跑由tests/test_doc_consistency.py的SiteCssTests抓。 - 找「这一页该拨哪些开关」先看 tool/fast.py 的
plan_switches()与SwitchPlan:目标选择、屏带过滤、checked/kept/disabled只在那里定义;灰态由调用方注入的disabled=谓词给出(off()会复读一页确认),off()只做点击 → 回读 → 重试 → 报告(连点前若屏上盖着确认框,先点掉再继续,判据是_confirm_target;补点两轮仍不动时回退到点行标题,switch_rows()的第四项与pick_title_rows(),ADR-0024)。回读的结论由readback()给出(每条目标一个结局,词表见 CONTEXT.md 的「回读」),补点该点哪一行由pick_retry_rows()给出;两个函数都不读屏、不点击,page_same与disabled由调用方注入。 - 找「启动后算不算还在目标 App」先看 tool/fast.py 的
_launch()/check_foreground():unknown_focus()判「读不出 ≠ 离开」,连续FOREIGN_STREAK帧外占才停段,启动等待按LAUNCH_WAIT计、顺延不超过LAUNCH_HARD_STOP;只有确认落在目标 App 时才读页面文字、关弹窗(ADR-0022)。- 找「等页面认得出」先看 tool/wait.py:settle/setting/ChannelPage.refresh/off/confirm都消费until(),不要另写轮询循环;「等机主解锁」的文案、轮询间隔、站稳时间与默认预算只在同一文件的wait_unlocked()里,两处调用点(run.py起脚本之前、Phone)只传锁屏判据与亮屏命令。 - 找「怎么走到渠道页、认出哪一行的开关」先看 tool/chan_fast.py 的
ChannelPage.open():直达 / 转应用通知页 / 内联行 / 同名放弃四类只在那里定义(ADR-0007);channel_switch/inline_switch/wait_page/via_app_page是一行前向壳,不要往里加逻辑。 - 找「引擎报给客户端的状态」先看 tool/events.py:六个 kind 与事件行格式只在那里定义(ADR-0004),引擎侧
emit()、gui/app.py的_log()都走它;failed_steps是唯一带载荷的 kind,客户端用parse_data()取整份字典,其余仍用parse()取 kind。不要再按中文文案判断状态。 - 找「这句话该说电脑还是说本 App」先看 tool/host.py:
ON_PHONE/CLICKER/CLICK只在那里定义(手机版由aac_android/main.py的boot()设AAC_HOST=phone),tool/troubleshooting.py是显式转发、tool/adb_link.py直接取;客户端自己产出的诊断行同理,常量在gui/app.py的TAP_REFUSED,由boot()覆盖,传给引擎的宿主标记只认ENGINE_HOST、不继承用户 shell 的AAC_HOST。分叉的只有措辞,唯一的控制流例外是手机版不写插电常亮(chan_fast.keep_awake()、run.py的recipe与finish),别让它变成按宿主分叉的判定(ADR-0019 的增补段)。读屏失败分两种结局,判据在adb_link:LIB_GONE认「命令服务断开」这一族(走link_lost),read_failed()认「读屏请求超时」(只报这一步没完成,后面的照常跑)。 - 找「渠道怎么判营销 / 服务 / 受保护」先看 tool/channel_policy.py 的
classify():营销候选、扫描排除、默认不勾、受保护、厂商营销 ID、广告网络 ID、前台服务七组判定只在那里定义(ADR-0006);chan_fast的扫描分支与gui/app.py的chan_scan()都消费它,不要另写词表。前台服务的证据来自渠道行的mFgServiceShown(Android 14 起叫mUserVisibleTaskShown,两个都认,见FG_SERVICE_MARKS;parse()用foregroundout 参数带回),不在通知记录段。 - 找「怎么点 App 底部标签」先看 tool/fast.py 的
Phone.tab():底部带top > 0.85H与「精确 → 逗号末段等于 → 包含」三层匹配只在那里定义(ADR-0012);它不重试、不确认落点、找不到不回退,需要文字确认的普通导航仍用Phone.go()。 - 改「一行输出怎么进界面」先看两处:
gui/app.py的_log()决定它进Job.lines还是只当事件;gui/web/index.html 的summarize()在认不出模式时直接显示results[].tail末行——tail是第二个落点,机器行不能进。手机版同样的判断在android/app/src/main/python/aac_android/view.py,改口径时两边都要看。 - 找「没勾的项为什么出现在结果里」先看
gui/app.py的plan_tasks():勾了的排Task,没勾的排一条skip_reason非空的,由not_run_result()记成rc=0+skipped的结果行(ADR-0023);原因分三档——没勾、这台机本来就没有或之前已做、通知没扫成,取自Api._offered()(last_status+last_chans),不从plan的空字段反推。真因故障跳过的行仍是rc≠0,别把它并进这一档。 - 找「哪一步还能重试、重试重跑什么」先看
gui/app.py的retryable()/retry_plan():可重试 =rc≠0且没stopped(#577 的rc=0跳过与用户停止都不算),广告开关步只取结果行里的failed_steps段名,快照成功过就不重存(ADR-0026)。合并按Task.kind在Job.add_result()就地覆盖,两个宿主读同一个poll()的retry计数,别在前端或view.py再算一份;段名来自引擎的failed_steps事件(ADR-0004 增补段)。 - 找「手机版界面」先看
android/app/src/main/java/com/ahuamiao/adcleaner/ui/AppModel.kt:页面流程只有Screen一个状态,每次切换都经过它的方法(ADR-0018);带判断的逻辑放aac_android/view.py,别写进 Kotlin。手机版不再加载gui/web/。 - 找「手机版替换层」先看 android/app/src/main/python/aac_android/:
adb.py接管subprocess并按 adb 语义回答,bridge.py是 shell 门面(Shizuku 或内置无线调试),inproc.py在本进程线程里跑脚本(清tool/模块、换环境变量、按线程分流输出、SystemExit当退出码、看门狗只写 logcat),u2.py顶掉uiautomator2/adbutils并在本地匹配选择器(ADR-0019)。引擎、tool/、recipes/与桌面版同一份;新增读屏能力要先加进u2.py与tool/reader.py两边。 - 造不连手机的
Phone用 tests/fixtures.py 的phone_stub()(fake_phone()就是它加一条时间轴),装配只此一处。客户端用例要假Runner(命令返回什么、spawn 交什么)同样用它:FakeRunner/FakeProc,Api(runner=…)注入,不要 patchapi._run与app.subprocess。纯重构要给对拍证据时,同一文件还有all_pages()取tests/里的页面样本、Clock假时钟、Timeline+Device按时间轴换页。 - 无设备用例不许真等:
chan_fast的 scan 分支会读型号,用例要 patch 掉phone_model,否则对着不存在的序列号等 adb 超时;机型脚本一律用fixtures.load_recipe()装载(它把脚本的time三个读数一起换成假钟),自己exec一份脚本就会真 sleep。躲不开真实进程的环节(跨进程锁、真起播放器)用Clock注入或把等待压到毫秒级。tests/test_recipe_loader.py 守着装载这一条。 - 改「认渠道页」这类读屏逻辑要做改前改后对拍时,驱动器是 tests/compare_chan_pages.py:认页 / 进页 / 编排三层 × 两种屏尺寸,同一份探针分别跑冻结基线与当前
tool/,守卫数字非零才算测到。 - 找「读屏 adapter」先看 tool/reader.py:
Phone.reader是手机读屏库或假设备的统一接口,Phone.d是它的历史别名(机型脚本与测试用),新增读屏能力先加进Reader。 - 手机上「有」的读屏异常名,电脑上未必有:
aac_android/u2.py的install()把模块里定义的类动态注册成uiautomator2.exceptions,电脑上装的是真库 +adbutils。在tool/、gui/里新增u2.exceptions.X或adbutils.X之前先确认桌面那份也有,tests/test_runtime_symbols.py守着这一条。 - adb 固定 platform-tools 37.0.1(
gui/app.py的ADB_PINNED);升级时改那张表,校验值按 Googlerepository2-3.xml的 SHA-1 核对。 - 客户端数据路径看
gui/app.py的ClientData,备份目录看 tool/backups.py 的Backups(root 由调用方给),工具模块用_tool()惰性取。 - 找「Windows 启动失败是缺 WebView2、被 MOTW 拦还是托管的 dll 被判成远程来源」先看 gui/winpreflight.py:注册表三态、两个关键 dll 的
Zone.Identifier判定与load_rejected()认的那两个原文子串只在那里定义(ADR-0003);gui/app.py的startup_advice()只按结论选文案,顺序是缺运行时 → MOTW → 原文拒载 → 未确认 → 兜底(带 MOTW 的 dll 也报同一句Failed to resolve,所以 MOTW 在前);startup_failed()先经win_clipboard()把整段写进剪贴板再弹窗,写不进去才退回「按 Ctrl+C」(#811)。随包的<exe>.config由 gui/dotnet_config.py 写,windows.spec收集完产物后调用。
错误日志上报
- 用户说明见 docs/12。服务器、数据库、部署与运维信息只记在本机
CLAUDE.local.md和.private/,不进仓库。
发版
- 完整流程与 Release 正文的写法见 docs/agents/release.md:正文是用户看的唯一权威,repo 里不留副本,
README.md的首段从它复制。 - 新版本提示读官网的
android-ad-cleaner-latest.json,发版后要更新它(步骤记在本机CLAUDE.local.md)。 - 官网另有一份放在机主的服务器上,发版后同步,方法记在本机
CLAUDE.local.md。 - 国内下载走 Gitee:
python3 gui/upload_gitee.py <版本>从公开的 GitHub Release 下载安装包并核对 SHA256,推代码和标签,建发行版传附件,再匿名下载回来核对。令牌在本机~/.config/gitee/token,不进仓库和对话。百度网盘暂停使用;Gitee 不可用时把index.html的REL.gitee留空,改跑gui/upload_baidu.sh [版本],它回写网盘链接、按钮随之恢复,#dl-lead那句文案要手动改回「国内下载走百度网盘,链接里带提取码」,后半句「安装版会原地替换旧版,原来的备份保留;免安装版不会」原样留着。 - 发完版按 docs/agents/release-followup.md 把
waiting-for-verify的 issue 逐条发给报告者复测。新版不送到报告者手上,这些 issue 永远关不掉。
Agent skills
Issue tracker
GitHub Issues(gh CLI)。开工前先有票:任何实现 / 重构 / 文档改造先开 issue,分支名带 issue 号。见 docs/agents/issue-tracker.md。
Issue claiming
一张票在动之前先打 in-progress(gh issue edit <N> --add-label in-progress),同时摘掉 ready-for-agent / ready-for-human / needs-triage。动手前先 gh issue list --label in-progress 查重:命中一张不是本会话认领的票,先看它有没有开着的 PR 或在改的分支,别开第二份实现。合入 main 之前不摘:PR 开着没合并,别人仍可能拿同一张票再做一遍。摘只有两个时机——合入 main 且不需要真机复测就直接摘,合入 main 但等真机复测就摘 in-progress、加 waiting-for-verify(两者不并存);放弃或改派则摘掉并把原来的 ready-* 或 needs-triage 加回,票面写一句为什么停。细节见 docs/agents/issue-tracker.md 的「票的生命周期」。
Triage labels
默认五个角色标签,标签名即角色名;另加本仓库自有的两个状态标签:waiting-for-verify(已修、等真机复测)与 in-progress(已认领、正在改)。见 docs/agents/triage-labels.md。
Release followup
发版后要催哪些复测、报告者确认前不关闭哪些 issue,见 docs/agents/release-followup.md。每次发版都要过一遍这张清单。
Code review
派审查的方法与规则见 docs/agents/code-review.md:基线钉 SHA、评审期间不改工作区、默认三轴。
Domain docs
single-context:根目录 CONTEXT.md + docs/adr/。见 docs/agents/domain.md。CONTEXT.md 只放术语表(命令 / 脚本 / 清单与词库 / 执行 / 连接 / 事件 / 等待 / 通知 / Issue 状态 / 规则 / 文档十一组定义);模块边界的决定写进 docs/adr/(现有 0001 手机连接、0002 Phone 状态、0003 客户端 import 与依赖、0004 引擎状态事件、0005 命令与退出码、0006 渠道判定、0007 渠道到达、0008 拨动计划、0009 引擎载荷通道、0010 执行状态、0011 Api 拆分、0012 底部标签、0013 设置入口、0014 手机 shell 通道、0015 配对码识别、0016 设置搜索直达、0017 前端测试基座、0018 手机版界面、0019 手机版执行宿主、0020 不做读写拆分、0021 回读结局、0022 启动前台归属、0023 没执行的项进结果、0024 落点回退、0025 官网共享样式、0026 结果页重试、0027 守护模式、0028 两个 agent 受众)。整体形状看 docs/ARCHITECTURE.md:三个宿主、四条跨边界契约、模块清单;机制与取舍仍在 ADR 里,总览不复述。
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

