Commit 06ef685
feat(activity): 信任 + 控制 + 风格 follow-up(隐私 + override + 配置外置 + tone + 游戏二维度) (Project-N-E-K-O#1026)
* feat(activity): 信任 + 控制 + 风格 follow-up(隐私黑名单 + 用户 override + 配置外置 + tone modifier + 游戏二维度)
PR Project-N-E-K-O#1015 落了 activity tracker 基础后的 follow-up。截图比对里"他们"那份设计的 5 项可借鉴差距一次性吃下:
skip_probability:替代之前提的 silent propensity。derive_skip_probability 给 (state, intensity, genre) 派生概率默认值(competitive 0.3、immersive horror 0.3、其余 0),proactive_chat Phase 1 起点掷骰跳过;unfinished_thread 守卫 + propensity=closed 短路;用户可以从 preferences 调到 1.0 实现"完全静音"或 0.0 关闭概率门。
doc 重写:状态表加 private 行;新增 5 节(Skip probability / Tone modifier / Game intensity & genre / Privacy blacklist / Own-app exclusion / User overrides & externalized config)。
测试:tests/test_activity_tracker_followup.py 34 个用例,覆盖隐私分类(5 个参数化)/ 私密窗口脱敏 / 私密 vs voice / 私密 yields to away / own-app 透传 + GPU 抑制 / app override + 不可降级 / game override / 阈值外置 / 配置 validator 丢坏值 / tone 全表派生(16 参数化)/ skip 默认值 + override clamp + 特定 combo 优先级。lint clean。
关联 issue:N.E.K.O#1020(系统压力自请退出,改日单独做)、N.E.K.O#1023(跨平台 OS 信号 - Electron 推送方案,单独 PR B)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(activity): 9 条 review 反馈一并吞下(Codex 3 + CodeRabbit 4 + github-code-quality 2)
Codex P1 — 隐私 + stale_returning 互动漏洞:
当用户从 away 回来时正好打开 KeePass,_classify_state 给出 'private'
但 get_snapshot 的 stale_returning 复盖逻辑会把 effective_state 改成
'stale_returning' → propensity 'greeting_window',proactive_chat 就
能跑了。给 stale_returning 复盖加 private 例外,配回归测试覆盖。
Codex P2 / CR Major — 偏好热重载:
prefs 当年只在 ActivityStateMachine.__init__ 里抓一次,长会话看不到
user_preferences.json 的编辑(30s mtime cache 实质失效)。
UserActivityTracker 加 _refresh_prefs hook,每次 get_snapshot /
get_snapshot_sync 起点拉一次(loader 自带 mtime cache,开销几乎为零),
identity-compare 检测到 reload 就 swap 进 state_machine。
阈值仍冻结到 __init__(通常是一次性调优常量,热重载意义不大)。
配热重载测试。
Codex P2 / CR Major — 解析失败保留上次好配置:
_load_from_file 现在返回 Optional[ActivityPreferences]:
- None → 读/解析失败(文件正在被编辑、JSON 半成品等)
- ActivityPreferences() → 文件 OK 但没 activity 段
- 实例 → 正常解析
调用方 get_activity_preferences 看到 None 就保留 _cache.prefs 不动,
只 bump fetched_at 防 retry 风暴。配两条 loader 测试(坏文件保留 +
正常文件无 activity 段返回默认)。
CR Major — `NEKO` 别名太泛:
word-boundary 匹配下 'NEKO' 会撞 "Neko Atsume" 等无关标题,整条
追踪链就走 own_app 特殊分支了。改成只保留点号形式 'N.E.K.O' /
'Project N.E.K.O',差异化够强不会误伤。
CR Critical — closed propensity 仍把状态信息渲染进 prompt:
format_activity_state_section 之前只跳了 tone 行,state 名 / reason
templates / 时间 / unfinished_thread / enrichment 都还在渲染。
proactive_chat 已经在更上层 short-circuit 了 closed propensity,但
其它消费者(debug logging / future side panels)可能透传。
Defense in depth:propensity == 'closed' 直接返回空串,谁也别想看到
"用户在 KeePass 里" 这件事的任何蛛丝马迹。
CR Major — _VALID_CATEGORIES 允许用户 override 到 private/own_app:
state_machine 把 private/own_app 定义成"只能来自静态 keyword DB"的
特例,但 loader 这边却把它们列入了 _VALID_CATEGORIES。结果是用户写
``{"MyApp.exe": {"category": "private"}}`` 在 loader 端通过、但在
state_machine 端因为 static result 是 'unknown' 不算 locked、override
会真的让 MyApp 跑 private 流程——绕过了 privacy 的"只信静态库"承诺。
从 _VALID_CATEGORIES 里移除 private 和 own_app,loader 直接拒收。
CR Minor — docstring 示例 JSON shape 写反:
写成了 ``{"model_path": ..., "activity": ...}`` 单对象,但 loader
和 user_preferences.json 实际是 list of dict 形态(line 245 处理
``isinstance(data, list)``)。改成数组示例 + 说明顶层结构。
github-code-quality — _cached_* "unused global" 误报:
4 个 module-level globals 改成单个 _CacheState 类的属性写入。这样
CodeQL 的"intra-function 流分析"看到的就是 attribute write 而非
global rebind,不再触发 unused-global 规则。状态封装也更显式。
invalidate_activity_preferences_cache 走 reset_metadata() 方法。
github-code-quality — tests/...followup.py:24 unused import:
之前删掉了用 ActivitySnapshot 的测试但没清 import。删掉。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(activity): 统一 utils.activity_config 的 import 风格
github-code-quality 在 tests/test_activity_tracker_followup.py:496 报
"Module is imported with 'import' and 'import from'"——文件顶部走的是
``from utils.activity_config import ...``,但 `test_tracker_picks_up_fresh_prefs_via_refresh_hook`
内部又写了 ``import utils.activity_config as ac_mod`` 来访问 `_cache`。
把 `_cache` 加进顶部的 from-import 列表,函数体里直接用就行,删掉
inner 的 `import as` 语句。混合 import 风格警告消除,行为不变。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(activity): 4 条 round-2 review 反馈(CR)
CR Major — update_window 折叠没把 intensity/genre 算进去:
当用户在游戏内热改 user_game_overrides 时,新 observation
的 category/subcategory/canonical 完全相同,只有 intensity/genre
变了。原来的 same-check 看不出区别,会把它折叠掉,propensity /
skip_probability / tone 用旧值直到玩家切换前台。same-check 加
prev.intensity / prev.genre 比较,hot reload 即时生效。
CR Major — mark_unfinished_thread_used 还在读 module 常量:
threshold 已经穿到 self._unfinished_thread_max_followups 了,
但这一处漏改,还在和 UNFINISHED_THREAD_MAX_FOLLOWUPS 比较。
结果用户把 max_followups 调到 3 时还是 2 次后被清掉,调到 1 时
也不会立刻释放。改成 self._unfinished_thread_max_followups。
CR Major — own_app 早退导致 dwell 漏算/虚增:
早退后上一条 _current_window 继续被当成前台,dwell 时钟还在跑。
用户在 N.E.K.O 待 30s 再回 VS Code 时,dwell 凭空多 30s,可能
把"短暂使用"误抬成 focused_work。加 _own_app_freeze_started_at
字段:进入 own_app 时记录 freeze 起点,下一次非 own_app
observation 时把 _current_window_started_at 前推一段(own_app
停留时长),dwell 只算非 own_app 时间。
CR Minor — canonical 缺省没真正回退到 override key:
_AppOverride 的 docstring 说"canonical 缺省时回退到 override
key",但 loader 这里实际存了 None,下游再各自 fallback——其中
user_title_overrides 的 fallback 是完整 window_title,和"override
key(substring)"不一致。loader 端直接补:canon 缺省时存 k
原 case,contract 一致化。
新增 4 条回归测试:
- test_update_window_collapses_on_canonical_but_invalidates_on_intensity_change
- test_mark_unfinished_thread_used_honors_threshold_override
- test_own_app_freezes_dwell_timer_on_previous_window
- test_loader_canonical_falls_back_to_override_key
42/42 通过,ruff clean。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(activity): 删掉未使用的 _CacheState import
CR 在 tests/test_activity_tracker_followup.py:411 报 \`_CacheState\` 没用到——
之前写测试时随手 import,后来改成只通过 \`_cache.prefs\` 直接 mutate 不需要这个
class symbol。删掉。
行为不变,42 测试全过。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(activity): own_app dwell freeze 测试加 snap_post 状态断言
github-code-quality 报 \`snap_post\` 是 unused local variable。这次没删
变量、改成补一条更强的断言:在 dwell 数值断言之外,再用 state-machine
API 层面验证一遍——brief own_app 走神 45s 后回 VS Code,state 必须**不**
是 focused_work。这条断言比"dwell < 90"语义更直白,明确把 dwell freeze
的目的("防止 brief glance 把 focused_work 提前触发")固定下来。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(activity): 2 条 round-3 review 反馈(CR)
CR Major — user_app_overrides 缺 unknown 门槛:
之前只 gate 了 user_title_overrides(result.category == 'unknown'),
app 路径没加。结果用户在 user_app_overrides 写的条目能强行改写已经
被静态 DB 稳定识别的窗口(比如把 Code.exe 改成 entertainment),
和 title 路径行为不对称、和 doc 也不一致。统一加上 unknown 门槛——
override 只填补静态 DB 漏掉的,不改写已有结果。privacy/own_app 仍
然受 static_locked 保护不变。doc 同步更新:"additive, not overriding"。
CR Minor — high_gpu reason 还在硬编码 60:
state classifier 已经走 self._gaming_gpu_threshold_percent,但
_build_propensity_reasons 里 high_gpu 还在比 60。用户把阈值调到 40
时 reason 漏报;调到 85 时 reason 误报,prompt 解释和 state 决策对
不上。改成读 self._gaming_gpu_threshold_percent。CPU 阈值(70)保持
硬编码——没有 per-instance CPU threshold(CPU 永远只是补充上下文,
不 gate state),保持现状。
新增 2 条回归测试:
- test_user_app_override_does_not_rewrite_stable_static_classification:
Code.exe(work/ide 静态命中)+ user override (entertainment) → 仍是 work
- test_high_gpu_reason_uses_threshold_override:
阈值=85 时 GPU=70 既不触发 gaming-by-GPU 也不触发 high_gpu reason;
GPU=90 时两者都触发。
44/44 通过,ruff clean。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(activity): own_app GPU fallback 测试语义改正
CR 在 tests/test_activity_tracker_followup.py:169 指出 \`test_own_app_suppresses_gpu_fallback_gaming\`
把 own_app 的语义钉反了:
原测试断言 "own_app 前台 ⇒ state != gaming",然后用空白起点(没
prev window)跑,靠 \`win is None\` 落进 idle。本质上测的是"没有任何
observation 时 state 是 idle",跟 own_app 没关系。
own_app 的真正契约是:冻结 dwell + 不替换 prev 窗口。GPU fallback
本身**不**应该被 own_app 短路——如果用户后台正跑一个未识别的高 GPU
小游戏(unknown 类别 + GPU fallback gaming),brief 切到猫娘瞄一眼
时 state 还应该是 'gaming',因为用户'真实活动'就是后台那个游戏。
把测试改名 \`test_own_app_preserves_previous_window_for_gpu_fallback\`
并改写:
Step 1:先建立 prev = unknown indie game + GPU=85%,验证 GPU fallback
触发 state='gaming'。
Step 2:再切到 N.E.K.O 前台,验证 active_window.process_name 还是
'IndieGame.exe'(不被替换)+ category 还是 'unknown' + state 还是
'gaming'(GPU fallback 在 prev 上继续生效)。
把 own_app 的真正语义从两条互补角度钉死——既不替换 prev observation,
也不抑制其分类。配合已有的 \`test_own_app_freezes_dwell_timer_on_previous_window\`
(dwell 冻结),own_app 行为契约完整。
44/44 通过。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(activity): 6 条 round-5 review 反馈(CR)
CR Major — 别名 `英雄` / `REPO` 太泛:
Apex Legends 旧 alias `'英雄'` 是纯 CJK 子串匹配,会撞任何含「英雄」二字
的标题(魔兽世界·英雄之路 / 新闻 / 博客)。`REPO` 经 _make_needle 编成
独立单词匹配,会撞 `repo - Visual Studio Code` 这种工作标题。两条都删,
各加一行注释说明为什么不能再加回来。
CR Minor x3 — own_app 注释三处写反:
ActivityCategory 枚举注释 / OWN_APP_TITLE_KEYWORDS 块顶部注释 /
_build_title_table docstring 都写成了"前一个窗口继续累计 dwell"和
"GPU fallback 被压掉",但实际契约是"进入 own_app 时记录 freeze 起点、
退出时把 _current_window_started_at 前推抵消、GPU fallback 不被短路"。
跟 _own_app_freeze_started_at 实现对齐重写三处。
CR Minor — 配置路径写错:
snapshot.py 两处注释 + 设计 doc 一处都把路径写成
`activity_preferences.json::skip_probability_overrides`,但实际加载
的是 `user_preferences.json::__global_conversation__::activity::*`。
按错路径改文件就石沉大海。三处全部改正确。
CR Minor — user_app_overrides doc 没说"只补 unknown":
设计 doc 之前只强调"不能降级 private/own_app",没写出实际更严格的
contract:"只对 unknown 补漏、不改写已有静态分类"。补充 additive
semantics 说明 + 把 user_game_overrides 的"唯一例外"特性也写清楚。
CR Minor — high_gpu reason 边界 `>` vs `>=` 不一致:
classifier 在 line 809 用 `>= self._gaming_gpu_threshold_percent`,
reason emitter 在 line 927 用 `> self._gaming_gpu_threshold_percent`。
GPU 恰好等于阈值时会判 gaming-by-GPU 但不报 high_gpu reason,prompt
解释和实际判定打架。改成 `>=` 对齐。
44/44 通过,ruff clean。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(activity): canonical 用家族名而非带年份的版本号
CR Minor — 三个游戏家族条目的 canonical 是带年份的版本号,但其它年份的
版本被并到同一别名列表里。结果是:用户想用 user_game_overrides
单独覆盖某个年份(比如「F1 25 我玩 casual」),按 result.canonical 查
键时找不到 — 因为 canonical 永远是 'F1 24' / 'NBA 2K25' / 'Battlefield
2042',用户写 'F1 25' / 'NBA 2K24' 之类的键完全 dead。
把三处都改成家族名作为 canonical:
('F1 24', ['F1 24', 'F1 25', 'F1 23'], ...)
→ ('F1', ['F1 25', 'F1 24', 'F1 23'], ...)
('NBA 2K25', ['NBA 2K25', 'NBA 2K24', 'NBA 2K23', 'NBA 2K'], ...)
→ ('NBA 2K', ['NBA 2K25', 'NBA 2K24', 'NBA 2K23', 'NBA 2K'], ...)
('Battlefield 2042', ['Battlefield 2042', 'Battlefield V', 'Battlefield 1', ...], ...)
→ ('Battlefield', ['Battlefield 2042', 'Battlefield V', 'Battlefield 1', ...], ...)
EA Sports FC 已经是家族名('EA Sports FC' canonical 配 FIFA / FC 24/25
别名),不动。
44/44 通过,ruff clean。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(activity): 删掉两条 unreachable 重复 game keyword 条目
CR Minor — \`('Genshin', ['Genshin'], 'casual', 'rpg')\` 和后面那条
\`('Rocket League', ['Rocket League'], 'competitive', 'sports')\` 都是
unreachable dead code:
- 'Genshin' 别名已经在 line 317 的 'Genshin Impact' 条目里
(\`['Genshin Impact', 'Genshin', '原神', '원신']\`)
- 'Rocket League' 已经在 line 474 的 Western multiplayer 段
(含 zh/jp/ko 别名)
\`_build_title_table\` 是首匹配(first-match wins),后面的重复条目
永远命中不到,徒增表大小。两条都删,加注释说明为什么不能再加回来。
行为不变:smoke test 验证 'Genshin' 标题仍归到 canonical 'Genshin
Impact'、'Rocket League' 标题仍归到 canonical 'Rocket League'。
44/44 通过,ruff clean。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(activity): browser-tab title hit on private 关键词降级到 unknown
CR Major — \`PRIVATE_TITLE_KEYWORDS\` 在浏览器 title fallback 路径上
误命中:用户访问 \`bitwarden.com/pricing\` / KeePass 文档 / 1Password
对比博客 / HN 上吐槽 Vaultwarden 的帖子,标题里都有 'Bitwarden'/'KeePass'/
'1Password'/'Vaultwarden' 字样,会被高优先级一刀切到 \`private\`,
直接关掉本应正常工作的 enrichment + proactive chat。
修法:\`observation_from_system\` 里浏览器路径的 title fallback 后加一道
guard——如果 fallback 命中了 \`private\`,降级回 \`unknown\`。
原生 private 应用还是走 \`PRIVATE_PROCESS_NAMES\` 的 process-name 匹配
(KeePass.exe / 1Password.exe 等),不受影响。
Tradeoff: Vaultwarden 这种自托管 web vault 通过浏览器访问就不再
触发 lockdown 了。理论上想覆盖这种场景需要扩 domain 表(vaultwarden.\* /
bitwarden.com/vault 等具体路径),现在没必要——browser-context 误判
比 niche 场景的漏报代价更高。如果将来用户反馈这个 niche,单独再开 PR。
测试同步调整:
- \`test_privacy_classification_emits_private_state\` 把 Vaultwarden
浏览器 case 移除(现在该用例属于"不该 lock"了)。
- 新增 \`test_private_title_in_browser_does_not_trigger_lockdown\`,
4 个 parametrize 用例覆盖 marketing / docs / blog / HN 四种典型
浏览器 tab 场景,全部断言 state != 'private'。
47/47 通过,ruff clean。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(activity): 3 条 round-8 review 反馈(CR)
CR Major — count 阈值 int() 静默截断:
ActivityStateMachine.__init__ 给 \`window_switch_transition_threshold\`
和 \`unfinished_thread_max_followups\` 直接 \`int(prefs.thresholds.get(...))\`,
会把 \`0.9\` 静默变成 0、\`1.7\` 变成 1。loader 那边 \`_parse_thresholds\`
只验证"正数",浮点放行。结果用户写 \`max_followups: 1.7\` 想表达"约 2 次"
会得到提前 1 次封顶。
抽 \`_int_threshold(name, default)\` helper:用 \`float(raw).is_integer()\`
判断是否真的是整数值(接受 3.0 这种),不是就回退默认值;负数 / 零也
当无效(\`>= 1\` 才接受)。同时把 bool 显式排除(虽然 loader 已经过滤,
但 instance attr 这边再守一道)。两处 instance attr 都走 helper。
CR Minor — \`test_loader_keeps_last_good_prefs_on_parse_failure\` 没真
钉契约:
原版只断言 \`_load_from_file()\` 在坏 JSON 上返回 None,但 \`_cache.prefs\`
在外层 \`get_activity_preferences()\` 失败时是否真的保留旧值,
没经过测试。如果有人未来"helpfully"在 parse 失败时把 cache 重置成
defaults,这条测试还会绿。
补 round 3:monkeypatch \`_resolve_preferences_path\` 指向 tmp 文件、
invalidate cache、写好文件 + 公开 API 装填一份 cached_good,
再写坏文件 + invalidate + 公开 API 拉一次,断言 cache 还是
cached_good 的字段(thresholds + user_app_overrides)。
CR Minor — \`test_tracker_picks_up_fresh_prefs_via_refresh_hook\` 绕过
公开入口:
原版只调 \`tracker._refresh_prefs()\`,钉不住"\`get_snapshot_sync\` /
\`get_snapshot\` 入口处必须调用 _refresh_prefs"这个公开契约。
补一段:\`tracker._sm._prefs = sentinel_prefs\` 把 instance prefs
换掉,loader cache 保持 new_prefs;调一次 \`get_snapshot_sync()\`,
断言 \`tracker._sm._prefs is new_prefs\`。如果未来有人把 hook 调用从
公开入口删掉,这条 assertion 立刻爆。
外加:补一条 \`test_count_thresholds_reject_non_integer_floats\` 直接钉
\`_int_threshold\` 行为——0.9/1.7 回退默认;7.0/3.0(whole floats)接受。
48/48 通过,ruff clean。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(activity): unify _apply_user_overrides docstring on additive rule
Round-9 CR feedback: the prior docstring led with "App / title overrides
REPLACE the keyword classification (user wins over static DB)" and then
contradicted itself with "Override priority is additive ... only fire
when static keyword DB returned unknown". The first paragraph was stale
phrasing from before the round-7 additive switch.
Drop the contradictory "REPLACE / user wins" paragraph and keep one
unambiguous description: app/title overrides are additive (only fire on
unknown), private/own_app are locked, game intensity/genre is the lone
patch-on-top exception.
No behavior change — _apply_user_overrides itself already implements the
additive rule (line 237 / 256 ``result.category == 'unknown'`` guards).
<review_comment_addressed>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(activity): pin async hot-reload + clean up stale override comment
Round-9 CR nitpicks (2):
1. tests/test_activity_tracker_followup.py:303-309
The comment in `test_user_app_override_cannot_unmask_private` was
leftover from before the round-7 additive switch — described the
behavior as "depending on implementation order this may be the
inverse" and pinned "the safer ordering". The actual contract is
now explicit: app/title overrides are additive-only (only fire on
`result.category == 'unknown'`) and `private` is `static_locked`.
Rewrote the comment to match.
2. tests/test_activity_tracker_followup.py:642-699
The hot-reload regression only covered `get_snapshot_sync()`. If a
future refactor stripped `_refresh_prefs()` from the async public
entry, live sessions would silently lose hot-reload while the test
stayed green. Added a parallel async assertion via
`asyncio.run(tracker.get_snapshot())` (with sentinel reset between
sync/async halves) — both public entries now contractually pinned
to call `_refresh_prefs`.
48 tests passing.
<review_comment_addressed>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent b7735e0 commit 06ef685
9 files changed
Lines changed: 2770 additions & 191 deletions
File tree
- config
- docs/design
- main_logic/activity
- main_routers
- tests
- utils
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
274 | 274 | | |
275 | 275 | | |
276 | 276 | | |
| 277 | + | |
277 | 278 | | |
278 | 279 | | |
279 | 280 | | |
| |||
285 | 286 | | |
286 | 287 | | |
287 | 288 | | |
| 289 | + | |
288 | 290 | | |
289 | 291 | | |
290 | 292 | | |
| |||
296 | 298 | | |
297 | 299 | | |
298 | 300 | | |
| 301 | + | |
299 | 302 | | |
300 | 303 | | |
301 | 304 | | |
| |||
307 | 310 | | |
308 | 311 | | |
309 | 312 | | |
| 313 | + | |
310 | 314 | | |
311 | 315 | | |
312 | 316 | | |
| |||
318 | 322 | | |
319 | 323 | | |
320 | 324 | | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
321 | 389 | | |
322 | 390 | | |
323 | 391 | | |
| |||
395 | 463 | | |
396 | 464 | | |
397 | 465 | | |
| 466 | + | |
398 | 467 | | |
399 | 468 | | |
400 | 469 | | |
| |||
409 | 478 | | |
410 | 479 | | |
411 | 480 | | |
| 481 | + | |
412 | 482 | | |
413 | 483 | | |
414 | 484 | | |
| |||
423 | 493 | | |
424 | 494 | | |
425 | 495 | | |
| 496 | + | |
426 | 497 | | |
427 | 498 | | |
428 | 499 | | |
| |||
437 | 508 | | |
438 | 509 | | |
439 | 510 | | |
| 511 | + | |
440 | 512 | | |
441 | 513 | | |
442 | 514 | | |
| |||
451 | 523 | | |
452 | 524 | | |
453 | 525 | | |
| 526 | + | |
454 | 527 | | |
455 | 528 | | |
456 | 529 | | |
| |||
497 | 570 | | |
498 | 571 | | |
499 | 572 | | |
| 573 | + | |
500 | 574 | | |
501 | 575 | | |
502 | 576 | | |
| |||
517 | 591 | | |
518 | 592 | | |
519 | 593 | | |
| 594 | + | |
520 | 595 | | |
521 | 596 | | |
522 | 597 | | |
| |||
537 | 612 | | |
538 | 613 | | |
539 | 614 | | |
| 615 | + | |
540 | 616 | | |
541 | 617 | | |
542 | 618 | | |
| |||
557 | 633 | | |
558 | 634 | | |
559 | 635 | | |
| 636 | + | |
560 | 637 | | |
561 | 638 | | |
562 | 639 | | |
| |||
577 | 654 | | |
578 | 655 | | |
579 | 656 | | |
| 657 | + | |
580 | 658 | | |
581 | 659 | | |
582 | 660 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
74 | | - | |
| 74 | + | |
| 75 | + | |
75 | 76 | | |
76 | 77 | | |
77 | 78 | | |
| |||
85 | 86 | | |
86 | 87 | | |
87 | 88 | | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
88 | 93 | | |
89 | 94 | | |
90 | 95 | | |
91 | 96 | | |
92 | | - | |
| 97 | + | |
93 | 98 | | |
94 | 99 | | |
95 | 100 | | |
96 | 101 | | |
97 | 102 | | |
98 | 103 | | |
99 | 104 | | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
100 | 276 | | |
101 | 277 | | |
102 | 278 | | |
| |||
0 commit comments