Commit Graph
35 Commits
Author SHA1 Message Date
wuguanganandClaude Opus 5 929f1ab06e fix: 盯梢照稿子念,不看对面说了什么
它问「要我从第 1 步开始吗?」时,盯梢照常甩出轮到的那条追问,
问题被直接埋掉 —— 它在等的决定没人回,用户看到一句驴唇不对马嘴的话。

现在发之前先看最后一句是不是在提问。是的话不发清单里的问题,
改成回应它并把决定权交回去(原问题原样带上,用户能在历史里看到它问过什么)。

判据刻意保守:只认最后一个非空正文行以问号结尾,中间的反问句不算 ——
误判会让它正常干活时也被插一句废话。同 StallDetector 的窄启发式,
不另发明一套。

455 个测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 15:38:49 +08:00
wuguanganandClaude Opus 5 5b4293b54c feat: 追问自动从验收清单生成,不再只问通用问题
手写的通用清单问几轮之后就被摸清套路了,答案越来越像模板
("落到哪些文件了" → 每次都能编出一份路径)。而清单里明明躺着
18 条待验收、7 条存疑 —— **那些才是该问的具体问题**,
而且每一条都问不出模板答案。

generatedProbes 当场从 ledger 生成:

  存疑:「你说做完了『X』,但真实 diff 里找不到对应改动(复核说:...)。
        具体是哪个文件的哪几行实现的?找不到就直说没做。」
  待验收:「『X』这条做了吗?验收条件是:Y。做了给文件路径和行号。」

存疑的排最前 —— 那一档是「自报做完但 diff 里找不到」,
整个清单里最该当面对质的一类,排后面会被 12 条的上限挤掉。

通用和清单生成**交替**:只问清单会让它一直在对答案,
只问通用又摸不到具体的漏项。

界面上两个数字分开显示("通用 8 条 + 清单 25 条 · 交替问")——
用户要能看出这是两回事,否则改了文本框却发现问出来的对不上,会以为坏了。

已定论的、已标「疑似误拆」的都不再问。超长要点截断:
注入那层有单行和长度限制,整段贴进去会被拒发。

450 个测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 12:19:50 +08:00
wuguanganandClaude Opus 5 a66f8a6ca9 fix: 盯梢记着"追问了",终端里一个字都没有
同一个 shell 误解的第二份副本,在另一个文件里。

TerminalReply.send 里有 guard !session.status.isWorking,而 isWorking
包含 shell。那个判断对它本来的用途(答终端弹出的权限对话框)是对的 ——
会话在忙时往输入框敲字会串进别的地方。

但对盯梢来说 **shell 恰恰是最该催的状态**(挂着后台 shell、人却停在提示符
上)。于是每次追问都被静默挡掉返回 .sessionBecameBusy,而我把返回值扔了
(`_ = await reply.send(...)`),历史里记着"已追问第 1/25 条",
终端 300 行历史里那句话出现 0 次。

我在 PaneActivity 里修过这个误解,却没想到 TerminalReply 有自己的一份。

改法:盯梢走自己的注入路径(它本来就已经定位到 pane 了),
并且**把结果记进心跳** —— 原来扔掉返回值才是根因,
不知道成功失败的操作等于没做。

心跳现在长这样:
  追问 xxx 第 1/25 条 —— 已送达
  追问 xxx 第 1/25 条 —— 敲字失败:can't find pane

独立验证过两步注入在真实 pane 上有效(先 send-keys -l 文字、
停 0.5 秒、再 Enter;合在一起时终端偶尔把回车吃进上一段输入)。

441 个测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:36:23 +08:00
wuguanganandClaude Opus 5 f37e39ba69 fix: 盯梢三处静默失效 —— 定位、落盘、可观测性
实机上盯梢整整 83 分钟一次都没催,用户先发现的,不是程序。三个 bug
互相掩盖,而且我用其中一个坏掉的东西去诊断另一个,得出了错误结论。

## 1. locate() 返回的是标签,不是 pane 目标

TerminalReply.locate 给的是 "tmux · hub:agentx-dev-kit"(给人看的),
我拿它去 capture-pane -t 必然失败 —— 而失败是静默的:抓到空字符串,
代码接着往下判断。改成从 pid 往上找真正的 pane(%15),
并且**抓不到画面时明确跳过**,不再拿抓取失败当结论。

## 2. history 从来没落盘

我读文件看到"追问 0 次"就下了结论 —— 但那个数字本来就永远是 0。
用一个坏掉的仪表去判断另一个坏掉的东西。追问后补 persist()。

## 3. 没有心跳,"死了"和"活着但还没到时候"长得一模一样

都是没有任何输出。昨晚一个死掉的 bash 盯梢挂了快 4 小时没被发现,
就是因为分不清这两种。

心跳写文件不写日志:本机实测 log show 完全看不到这个 app 的输出
(连启动那条都没有)。**一个看不见的仪表比没有仪表更糟**——
我拿它做过诊断。

心跳内容是"这轮为什么没催",可能值全部拆开:找不到 pane / 抓不到画面 /
不打扰(原因) / 第 N/2 次确认 / 冷却期内 / 刚追问了第 N/M 条。
下次失灵时是哪一环坏的会直接写在文件里。

顺带:心跳原来只记最后一个会话的结果,一个残留的死会话会把真正在跑的
那个整个盖掉 —— 第一条心跳就撞上了。改成每个会话各记一句。

441 个测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:14:21 +08:00
wuguanganandClaude Opus 5 415f2e7caf feat: 会话观察者做进 Hub —— 停了就按清单追问
原来是个飘在后台的 bash 脚本:关机就没了,也没人知道它在不在跑。
现在是 app 的一部分,配置落盘、开关在界面上、每次追问都留痕。

## 它解决的不是「停了」,是「做浅了」

只会说「继续」的盯梢防不了它每次都继续、每次都做表面功夫,最后交一份
看起来很完整的报告。所以追问是一份轮换的清单:落到哪些文件了、
发布后还在不在、有哪些卡点没解决、勾了框但没动代码的直接说 ——
每次问一条,问完从头再来。默认清单有一条测试专门守着"不能只有继续"。

## 三条硬约束,都是实机撞出来的

1. 只有 busy 算在干活。shell 表示"挂着后台 shell",和 Claude 在不在思考
   无关 —— bash 版把它当成在干活,会话停了 20 分钟一次没催到,
   是用户先发现的,不是程序。
2. 用户在输入框里打字时绝不发。注入是往当前光标位置敲字,会拼到他没发完的
   那句后面 —— 真发生过,把用户的话和追问接成了一句。
3. 发送分两步(先文字、停一下、再回车)。合在一起时终端偶尔会把回车吃进
   上一段输入,消息卡在输入框发不出去,看起来催过了其实一个字没送。

前两条的判断抽成了 PaneActivity 的纯函数,16 条测试。这是整套里唯一
容易写错、而且写错了不报错只静默失灵的部分 —— 转圈检测刻意只匹配
"(数字s · "这个计时格式,不匹配 Wandering/Boondoggling 那些动词:
那是一份永远补不完的名单,漏一个就是一次误催。

排队提示("Press up to edit queued messages")不算草稿 ——
当成草稿的话一旦有排队盯梢就再也不敢发言,而排队恰恰说明它正忙,
忙完就会读,这时候补一条完全没问题。

截图模式下不启动:那会往演示会话里敲字。

441 个测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 02:50:15 +08:00
wuguanganandClaude Opus 5 b435a05efa fix: 盯梢误判「在干活」,还会拼到用户没发完的话后面
两个都是实机撞出来的,任何一个都会让盯梢失效或者帮倒忙。

## shell 状态不等于在干活

状态文件里的 shell 表示"挂着一个后台 shell",和 Claude 自己在不在思考无关。
实测它空在提示符上等输入时,状态照样是 shell —— 而脚本把 shell 当成在干活,
于是它停了 20 分钟一次都没催到。用户先发现的,不是脚本。

改成只有 busy 算在干活,另外看屏幕上有没有转圈计时
("✻ Boondoggling… (17s · ↓ 201 tokens)")作为必要补充。
这里用屏幕是有理由的:状态文件在这一点上信息不足,两个信号合起来才准。

## 用户正在打字时不能发

send-keys 是往当前输入位置敲字。用户在输入框里打了一半时,追问会直接拼到
他那句后面 —— 实测发生过,把用户的话和追问接成了一句。轻则语义变了,
重则改变他本来要说的意思。发之前先看输入框有没有草稿,有就跳过这轮。

## 追问清单重排

前四条全部改成关于「落地」。用户实测下来最大的问题不是它偷懒,是**它在写报告**:
训练跑了、报告漂亮,但产出没进代码、没进发布包,下一次发布就冲没了。
先问落到哪些文件、发布后还在不在、有没有落到 agents/ 下、
是在训练还是在写关于训练的报告;再问深度。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 02:41:44 +08:00
wuguanganandClaude Opus 5 501334cb65 fix: 老实回答「没做」的要点会被永远重复问;盯梢升级成追问
## 诚实反而被惩罚

答 done:false 的项保持 .open,而 .open 正是注入的筛选条件 ——
于是每一轮问的都是同一批 6 条,后面的要点一次都轮不到。
实测在我自己身上撞到:连着两轮被拦,问的是一模一样的 6 条。

加 lastAskedAt / askCount,按「最久没问过」轮换。问过 3 次还说没做的
标成「疑似误拆」并停止再问 —— 多半不是它偷懒,是这条拆偏了。
真实例子:用户说「加个人工确认吧 不然都清理不掉了」(意思是要一个手动
确认的入口),被拆成了「清理操作前增加确认步骤」和「取消确认时中止清理」
两条根本不存在的需求。

「疑似误拆」必须在界面上标出来:它已经被排除出注入了,不标的话就是
安静地消失,用户永远不知道有一条要点再也不会被问。

## 盯梢:从「说继续」升级成「追问」

「继续」只能防它停,防不了它做浅。它完全可以每次都继续、每次都做表面功夫,
最后给一份看起来很完整的报告。

scripts/keep-going.sh 改成读追问清单轮着问;
scripts/probes/agentx-training.txt 是 agentx 训练的 11 条:
训练包逐条对照、AI 浏览器实际验证、定时任务、handoff 覆盖、
测了几轮调了什么参数、卡点落点、有没有落成 skills、有没有新增子员工、
上下文被压缩后有没有遗漏、拿给真实用户最可能在哪翻车。

413 个测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 02:36:31 +08:00
wuguanganandClaude Opus 5 b8520c3b14 fix: Claude 勾掉的框不算证据;一条 todo 里的多件事要拆开标注
两条都来自用户的观察,两条我都犯了自己一直在防的错误。

## 勾掉 ≠ 做完

先前把 Claude 勾了 completed 的 todo 直接记成 .confirmed(已验收),
理由是"勾状态是它边干边写进文件的,比嘴上说可信"。这个理由站不住 ——
它勾自己的框和它嘴上说"做完了"是同一性质的东西:都是声明,都没有外部证据。
拿它自己勾的框当证据,正是这整个功能存在的理由所要防的事。

改成落到 .claimed(待复核),证据记成 .claimed(...)(isProof = false),
由旁路复核拿真实 git diff 判 confirmed / disputed。
用户的原话:「他勾掉的到底有没有做」—— 只有 diff 能回答。

顺带补上 baselineCommit:缺了它复核只能拿未提交的改动去比,结论全是噪音。

## 一条 todo 里常常不止一件事

「批次2:设备自动连接(Android 地基修复→状态机→UI→iOS)」是四件。
整条勾掉时里面做了三件还是四件,从条目本身看不出来 ——
而漏掉的往往正是最后那件。

拆分是机械的(按 →、;+/ 等明确分隔符),不调模型:这一步要对每条 todo
都跑,而它要解决的问题很窄。切不出来的("深色模式双端"其实是 iOS+Android)
就不切 —— 切错比不切更糟,用户会对着一堆碎片然后不再相信这个标注。

界面上:行内直接显示「2/4 项」,展开逐项勾。藏在展开区里等于没标,
用户不会为了看有没有分项去逐条展开。

全部分项勾完**不自动**把整条标成已验收 —— 那等于绕过"拿 diff 核对"这一步。

修 bug:标题前缀门槛原来是 12 字,把「把整个权限体系重构一遍:改A、改B」
的前半句当标题剥掉了。中文里 11 个字是实打实的内容,压到 6 字。

411 个测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 22:51:03 +08:00
wuguanganandClaude Opus 5 920ad7505f feat: 追踪 AI 自己答应的事,并能推回对应 session
用户一眼看出的缺口:清单里全是他说过的话,**AI 自己列的 todo 一条都没有**。

我先前的判断是"Claude 的 todo 不能当基线"——这没错,翻译那一步就已经丢
东西了。但我顺手把它整个排除了,而「它自己列了 6 项做完 3 项就说完事了」
恰恰是遗漏最直接的证据,且这类承诺不在用户原话里,别处根本抓不到。

现在收,但单独标成「AI 计划」,Origin.isFromUser 把"用户的权威基线"和
"待核对的 AI 自述"在类型层面分开 —— 混成一档就等于把自述当成了基线。

- TaskStateReader.readTasks 逐条读 ~/.claude/tasks/(零成本,不调模型)。
  原来只给计数,而验收要的是"没做的那几项分别是什么"。
- Claude 自己勾掉的跟着同步成已确认(否则永远挂在未验收,用户手动清不动,
  清不动的清单最后没人看)。只动 .assistantTask 那一档:它勾掉自己的 todo
  不代表用户提的需求做完了,同名时尤其危险("发布新版本")。
- 按**会话**统计而不是按项目:用户问的是"这一轮它答应的做全了没"。
- 「告知这个会话」走 TerminalReply.send 把未做项注入回那个终端 ——
  不是发通知让用户自己切过去,是直接把话说到那个 Claude 面前。

实测本机某会话读出 6 项完成 + 1 项 pending「真机验证」——
正是典型的遗漏形态:活干完了,验证挂着,而它已经说"完成了"。

401 个测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 22:31:44 +08:00
wuguanganandClaude Opus 5 f1cdcd2547 feat: 验收清单行内直接确认/划掉
原来只有展开单条才有确认按钮。一份清单几十条,逐条展开再确认根本清不动 ——
清不掉的清单会一直堆着,堆到没人看,那时这个功能就等于不存在了。

按钮放在可点击区域**外面**:套在同一个 onTapGesture 里的话,
点「确认」会顺手把行也展开。

展开区里原来的接受/划掉去掉了,只留删除 —— 同一个动作在两处出现,
用户会以为它们不是一回事。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 21:03:01 +08:00
wuguanganandClaude Opus 5 78d24c934f fix: 加字段会把用户已有的验收清单整个抹掉
给 AcceptanceLedger 加了非可选的 authorizedCommands 之后,所有已存在的
清单文件当场解码失败 —— Swift 合成的 Codable 要求非可选字段必须存在。
而读取是 try? 静默吞错误的,于是下一次写入从空清单开始,把内容整个覆盖掉,
全程无声无息。

真丢了数据:本机 9 个项目里,claude-hub 的 13 条和 storefront-a 的 1 条没了,
另有 demo-release 的 7 条正等着被同样地清空(赶在它之前修好了,
实测那 7 条经历一次真实写入后完整存活)。

我在 HookEvent 那边特意把新字段都设成可选并写了兼容性测试,却没对持久化的
ledger 做同样的事。而 ledger 的后果严重得多:hook 事件丢一条无所谓,
用户攒几周的清单没了是真损失。

三层修复:

1. 手写 init(from:),每个字段 decodeIfPresent —— 加字段不再让老数据全废。
2. 逐条解码要点,坏的跳过。原来一条坏的会让整个数组抛错,整份清单报废。
   同 TaskStateReader 的处理:边跑边写的数据撞上半条是正常情况。
3. **解不出来的文件先改名保住,绝不静默覆盖。** 丢数据和读不出来是两个
   量级的事故 —— 后者还能捞回来。

391 个测试,三条都有对应的回归测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 20:33:55 +08:00
wuguanganandClaude Opus 5 a5ba3f7573 feat: Hub 自己跑验收命令 —— 实际功能证明
阶段 4 的教训直接催生了这一步:diff 复核把 `case ran(command:exitCode:)`
这个**枚举声明**当成了功能已实现,而没有任何代码产生它。读代码判断不了
"功能是否真的能用",只有跑一遍能。

执行者必须是 Hub 而不是 Claude —— 让它跑完再转述,就又回到了「说说而已」,
转述那一步正是失真发生的地方。产出 Evidence.ran(isProof = true)。

三道闸,全部有测试,元字符那道做过变异验证:

1. 白名单:只放行构建/测试类。刻意排除 npx / pnpm dlx(会下载执行任意包,
   放行它等于白名单形同虚设)、npm install(跑 postinstall 等于执行任意
   代码)、git 的写操作。bash 限定在项目的 scripts/ 下。
2. 元字符:16 个字符逐个测。**这道检查必须在白名单之前** ——
   变异验证时把它挪到后面,`swift test | tee /tmp/x` 立刻因为前缀匹配上
   `swift test` 而被放行。配合 Shell.run 走 execve 数组参数(不是 sh -c)。
3. 总开关默认 off + 逐条授权。授权按命令原文精确匹配,改一个字要重新授权;
   而且手工往 authorized 里塞危险命令也绕不过前两道(单独一条测试守)。

顺带修了注入正文太长:真实项目上拦了 12 条带完整验收条件,糊了满屏。
要点一多就没人看,反而比不拦更容易被忽略。上限 6 条,存疑的排前面,
省略了多少明确说出来。

Shell.run 加 currentDirectory —— 验收命令必须在项目目录里跑,
否则 swift test 找不到 Package.swift。

388 个测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 19:37:45 +08:00
wuguanganandClaude Opus 5 d93a624b4e feat: 旁路 diff 复核 —— 拿真实改动核对它的自报
自查治「忘」,但自查仍然是它在说自己,治不了「骗」,也治不了
「它真心以为自己做了」。这一层读 git diff 里到底有没有对应改动。
结论只有 confirmed / disputed 两档:中间档会变成模型拿不准时的垃圾桶,
而用户看到"部分完成"也不知道该做什么。

新增 GitDiff(numstat/patch/balancedPatch)和 AcceptanceAuditor。
UI 上点 .diff 证据能看那个文件从基线到现在的真实 diff。

三次真机实验(一条真做了的 + 一条我故意谎报的),前两次都判错,
两个 bug 都只有在真机上才暴露得出来:

1. 整份 diff 截断 → git diff 按字母序输出,前几个文件吃光 24KB 预算,
   MainWindow.swift(真改了 79 行)一行都没进模型眼里,于是判"存疑,
   实际 diff 未展示"。任何真实规模的改动都会这样,不是调大上限能解决的。
   改成按文件均分预算(balancedPatch)。

2. 均分之后每文件只有 1200 字符,只露出第一个 hunk,对应那条要点的改动
   在后面 —— 同一类问题降了一层。提到 120k 总预算 / 6k 每文件。
   省这点 token 换来的是不可信的结论,而不可信的结论比没有结论更糟:
   用户会照着它去改本来没问题的东西。

3. 谎报的那条被判成 confirmed,理由是"Evidence.ran 真实执行命令"——
   模型把**类型声明**当成了功能存在。而 .ran 只是个 enum case,
   没有任何代码产生它。声明读起来非常像功能已经实现,这是 diff 复核
   最容易翻的一类车。提示词里单独加了一条。

第三次两条都判对,理由精确到代码位置("SessionsPane 会话行新增
Button(\"清单\") 调 onOpenLedger" / "仅见 Evidence.ran 声明,
未见实际执行验收命令并填充 exitCode/tail 的调用点")。

**但这仍然是一个弱信号,不该被当成结论。** 第 3 个 bug 恰好证明了
读 diff 判断不了"功能是否真的能用"——这正是阶段 5(Hub 自己跑验收命令)
存在的理由。

369 个测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 19:10:05 +08:00
wuguanganandClaude Opus 5 fe3febbfd2 feat: Stop 拦截注入 —— 收工时拿清单逼它逐条报证据
Stop 从「非阻塞、输出被忽略」改成阻塞式,有待验收项时输出
{"decision":"block","reason":<清单>} 让 Claude 不停下来,先逐条核对。
下一轮的 lastAssistantMessage 里的自查 JSON 被收回来,把 open 推到 claimed。

Stop 的超时阶梯和审批那条**完全分开**,方向也相反:

  3s HookCoordinator / 5s hubctl / 10s settings.json,超时一律 = 不拦。

审批超时 = 拒绝(安全刹车);这里超时 = 放行。写反的话 Hub 一出问题
所有会话就再也收不了工,比"少提醒一次验收"严重得多。实测 hook 本身
6-11ms,Hub 在不在都一样。

查出来三个真问题,都不是靠读代码发现的:

1. 服务端从不给 stop 回写应答。我改了 hubctl 让它等,却没动
   HubSocketServer 里写死的 guard kind == .preToolUse —— 拦截一次都没生效
   过,而且不报错、不超时、10ms 就返回,表现得和"清单里没有待办"
   一模一样。端到端测了三轮才找到。修法不是补一个 if,是加
   Kind.expectsDecision 让两边共用同一个判断。

   这和上次「grep activeScreen() 漏了一个调用点」是同一类错误:
   改了协议的一端,假设另一端会跟上。

2. SIGPIPE 会杀掉 Hub。服务端开始给 stop 回写之后,遇到不等应答就关连接
   的客户端(旧版 hubctl、被 Ctrl-C 的 hook),那次 write 直接终止进程 ——
   不是报错,是进程没了。测试崩在 signal 13 上才暴露。
   先加了 per-socket 的 SO_NOSIGPIPE(看起来更讲究),结果没生效,
   照样崩;改成进程级 signal(SIGPIPE, SIG_IGN) 才好。
   更讲究的写法如果不生效,就只是更讲究的 bug。

3. stop 事件不能去重。缓存决策只有在决策是事件的纯函数时才成立,
   而 stop 的结论取决于会变的上膛状态:同样内容的两条 stop,
   一条在用户说话前、一条在之后,结论截然相反。实测被这个坑到过。

4. {"done":1} 被判成做完了。JSONSerialization 把数字 1 桥接成 NSNumber,
   as? Bool 对它成立 —— 模型只要把 done 写成数字就能绕过"含糊按没做算"。
   改用 CFBooleanGetTypeID 严格判定。

端到端实测(真实 hubctl 二进制):未上膛不拦、上膛后拦下 13 条要点、
连着两次收工都放行、Hub 存活、自查回答(带散文和围栏)正确回收成
claimed 且 hasProof 仍为 false。

346 个测试。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 18:10:35 +08:00
wuguanganandClaude Opus 5 f0f6cd7454 feat: 验收守望 —— 独立于 Claude 自述的需求清单
Claude 会把用户需求翻译成自己的 TodoWrite,翻译那一步就已经丢东西了:
「修复 apple-header sticky」是施工步骤,不是原始需求里的功能要点。
再拿它自己的 todo 去验收,等于让它自己判自己的卷子。

清单挂在项目上跨会话累积,基线取自用户原话和用户批准过的计划。

几个刻意的设计:

- Evidence 在类型层面就分开 claimed / diff / ran。全塞进 [String] 等于在
  数据结构上承认「它说改了」和「它真改了」是一回事,而这正是要解决的问题。
  汇报条的「已证」只数 isProof 的,想把自述渲染成已验证得先改类型。

- 拦截的「上膛」只有 UserPromptSubmit 能做,Stop 拦过立即卸膛。Claude
  没有任何路径能给自己上膛,所以「拦一次 → 它回一轮 → 又拦」这个死循环
  是构造不出来的,不是被 if 挡住的。

- applyAudit 跳过用户终裁过的项。勾了「接受」一转身被机器打回 disputed,
  那个勾就白点了。

- extract 区分「失败」(nil) 和「没有新要点」([])。混在一起的话模型抽风
  一次就会清掉原话缓冲,而那是这个功能唯一的基线来源。

hook 从 5 类补到 9 类。PostToolUse 是最大的收获:Write/Edit 之后 Hub
第一手知道哪个文件被改了,不用回头问 Claude——问这个问题本身就给了
转述失真的机会。

实测确认(都不是推断):
- hubctl hook userprompt 的 stdout 是 0 字节。这条必须验,因为
  UserPromptSubmit 的 stdout 会被当成附加 context 注入进对话。
- 项目级和全局 hook:命令字符串相同时 Claude Code 自己去重,不同才叠加。
  第一次实验用了两条不同命令,据此普遍化成「一定会重复」是错的。
  HookDedup 保留为深度防御(路径不一致时才真用得上),不是必需品。
- 真实拆解链路 20 秒出结果,正确忽略了缓冲里的闲聊、正确去重了已有条目。

ensure-project-config.sh 接了三个调用点。其中 TerminalDispatch.open() 是
关键:app 从岛/主窗口启动项目完全不经过 project-menu.sh,漏了那里等于
最常用的入口一个都没检查。

326 个测试。上膛、自述不算证明、外部编辑不被覆盖三条做过变异验证。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 17:50:25 +08:00
wuguanganandClaude Opus 5 61aaea6062 fix: 关掉「跟随光标」后灵动岛仍然跟着光标跑
加这个设置时只改了三处调用 activeScreen() 的地方,漏了控制器里的
followCursorAcrossScreens() —— 它直接调 screenUnderCursor(),名字对不上
所以 grep 没找到。用户在界面上关掉跟随,岛照样跑到外接屏压住浏览器标签栏。

除了补 guard,把 NotchGeometry.screenUnderCursor() 收成 private:
绕过设置去取屏现在在编译期就不成立,而不是靠下一个人记得加判断。

新增的那条测试特意标注了它的局限——检出能力取决于跑测试时光标在哪块屏,
单屏机器上永远绿。真正的护栏是 private 那条,别把它当保证。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 17:49:57 +08:00
wuguangan aa9330afed chore: 版本号 2.3.0 2026-08-06 17:20:16 +08:00
wuguangan 0b4cec785b fix: 灵动岛默认固定在刘海屏,跟随外接屏改成可选
岛以前写死跟着光标跑。动机没错(用户在外接屏工作时看不见岛),但代价没算清:
外接屏没有刘海,岛走「悬浮胶囊」形态,正好压在浏览器标签栏那一带 —— 命中测试
虽然是按形状精确做的,但那块形状底下本来就有东西,于是外接屏上点标签页点不动。

这不是 hit-test 的 bug,是岛出现在了一个它不该占位的地方。所以默认改回固定在
刘海屏,想跟随的人去设置里开,并把代价写在开关下面。

- 新增 IslandPlacementStore,落 island-placement.json,URL 可注入(测试不碰真实设置)
- NotchGeometry.activeScreen() 改成显式的 screen(followsCursor:),不再藏全局行为
- 设置面板加「灵动岛位置」卡片
- relocate() 不复用 realign():后者有 geo != geometry 的去重 guard,两块同型号
  外接屏时会让手动切设置看起来没生效

7 条新测试,其中默认值那条做过变异验证(改成 true 立刻红)。全量 264 绿。
2026-08-06 16:34:38 +08:00
wuguanganandClaude Fable 5 288e4844f4 fix: 防污染模板去掉无效的 Write deny 规则
Edit(path) 已经覆盖所有文件编辑工具(含 Write),而单独的 Write(path)
规则不被权限检查识别 —— 加了不起作用,还会在每次调用 claude 时刷警告:
「Write(...) is not matched by file permission checks — only Edit(path) rules are」

防护本身一直是生效的(靠 Edit 那组),这次只是去掉噪音并在注释里写明原因,
防止以后有人看着「只禁了 Edit 没禁 Write」又给加回去。

已同步修正三个已生成项目的 settings.json。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 22:31:26 +08:00
wuguanganandClaude Opus 4.6 e029bbc1ce feat: setup.sh 自动配置 iTerm2 tmux 集成,无需手动 GUI 操作
- 通过 defaults write 直接修改 iTerm2 plist 配置
- OpenTmuxWindowsIn=2(标签页模式)+ AutoHideTmuxClientSession=true
- README 更新:说明 iTerm2 配置已自动化

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:10:38 +08:00
wuguanganandClaude Opus 4.6 a3f707d629 docs: 重写 README 为开箱即用级别完整文档
- iTerm2 配置:精确到选项名 + 解释为什么需要每一项
- macOS 权限:说明 osascript 首次运行需授权 + 补救步骤
- 通知机制:完整链路图(Claude → hook → 通知/语音/面板/信号)
- 故障排查:通知不工作、标签页异常、权限报错、编译失败、浏览器连接
- 补充:cc.sh 路径限制、Chrome >= 144 版本要求、磁盘空间提示
- 补充:macOS 内置工具说明、sounds 目录、Rust 源码目录结构

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:53:50 +08:00
wuguanganandClaude Opus 4.6 669c58f0e6 docs: 完善新电脑安装文档,补充 iTerm2 配置和依赖说明
- 新增 7 步完整安装流程(从 Homebrew 到启动)
- 补充 iTerm2 tmux -CC 模式关键配置说明
- 补充 terminal-notifier、Node.js 等依赖
- 新增前置条件汇总表
- 功能列表补充用量统计

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:45:24 +08:00
wuguanganandClaude Opus 4.6 b008172ace docs: 更新 README,补充新电脑安装说明和用量统计功能
- 前置条件:说明 Rust 自动安装、Apple Silicon/Intel 均支持
- 通知面板:补充用量统计、编译机制、cc 别名说明
- 目录结构:补充 build-app.sh 和 cc.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:19:23 +08:00
wuguanganandClaude Opus 4.6 1649ed9070 fix: 完善新电脑初始化流程,build-app 自动检查环境
- build-app.sh: 自动检查/安装 Rust、检查 Xcode CLT、显示架构信息
- setup.sh: 首次运行自动触发编译托盘 app

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:18:07 +08:00
wuguanganandClaude Opus 4.6 ffa18e2575 feat: 添加用量统计面板 + 优化托盘 app 启动方式
- 新增用量统计 tab,按项目展示 token 消耗、会话数、模型分布
- 新增 usage.rs 模块解析 Claude 日志计算用量
- 托盘 app 改用 release 编译产物直接启动,移除 claude-hub-bin
- 项目窗口管理:修复新建 session 时 _init 窗口清理逻辑
- 新增 build-app.sh 构建脚本

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:06:21 +08:00
wuguanganandClaude Opus 4.6 0bca945994 docs: 补充通知面板和浏览器自动化文档到 README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 18:51:13 +08:00
wuguanganandClaude Opus 4.6 af1f9307e1 feat: 修复托盘图标交互 + 项目搜索 + 浏览器配置优化
- 修复托盘图标无响应:显式设置 icon(include_bytes)、移除 config 重复 trayIcon
- 托盘交互:左键点击显示窗口、右键退出菜单、关闭窗口隐藏到托盘
- 新增项目搜索框:支持按名称/描述/路径模糊匹配
- 启动序列自动拉起通知面板
- browser-auto skill 改用 --autoConnect 连接用户日常 Chrome
- 新增 setup-browser.sh 一键配置浏览器环境
- CLAUDE.md 补充 skill 管理和浏览器配置文档

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 13:47:51 +08:00
wuguanganandClaude Opus 4.6 fe98293154 fix: browser-auto skill 自动配置 .mcp.json
AI 首次使用浏览器时,直接检查并写入 chrome-devtools 配置,不问用户

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:39:28 +08:00
wuguanganandClaude Opus 4.6 92650e8843 fix: browser-auto skill 强制连接用户日常 Chrome
核心问题:chrome-devtools-mcp 默认启动独立 Chrome,和 Claude in Chrome 不在同一个浏览器
- 顶部加铁律:永远不开新浏览器
- 说明 --connect-url=http://localhost:9222 的正确配置方式
- 新项目配置检查清单
- Chrome 调试端口检测作为启动流程第一步

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:40:37 +08:00
wuguanganandClaude Opus 4.6 ab268b3825 feat: cc 一键启动命令
- scripts/cc.sh: 启动托盘 app + claude 调度中心
- alias cc 加入 ~/.zshrc,任何终端输入 cc 即可启动
- release 二进制编译完成

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 12:29:10 +08:00
wuguanganandClaude Opus 4.6 5db1bc1822 feat: Dashboard 项目列表 + 交互
- 启动时从 projects.yaml 加载所有项目
- 运行中的项目(有 tmux 窗口)绿色高亮,点击跳转 iTerm2
- 未运行项目灰色,点击弹出菜单(Claude Code / 终端 / Finder / VS Code)
- 每 5 秒自动刷新项目状态
- 新增 projects.rs:读取 yaml + 检查 tmux + 打开项目
- 添加 Tauri capabilities 配置

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 12:26:01 +08:00
wuguanganandClaude Opus 4.6 2344ecf4cf feat: terminal-notifier 替代 osascript,点击通知跳转 iTerm2
- hook 脚本优先使用 terminal-notifier(点击跳转 iTerm2)
- osascript 作为 fallback
- 修复 Tauri app 编译错误(去掉 dirs_next,修复 notification 配置)
- 窗口默认可见
- setup.sh 增加 terminal-notifier 检查

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 11:39:21 +08:00
wuguanganandClaude Opus 4.6 5403bdad1f feat: Tauri 桌面通知 app + 状态面板
- app/ 目录:Tauri v2 项目(Rust + HTML/JS)
- 托盘图标 + 点击显示状态面板
- 监听 /tmp/hub-signals/events/ 的 JSON 事件
- 系统原生通知,点击跳转到对应 tmux 窗口
- hooks 改为写 JSON 事件文件,Tauri 没运行时 fallback 到 osascript

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 09:59:04 +08:00
wuguanganandClaude Opus 4.6 f92763f8fb feat: scan_dirs 配置 + 智能扫描
- projects.yaml 支持 scan_dirs 字段,配置一次后续扫描无需传参
- scan-projects.sh 自动跳过已存在的项目
- 支持多目录扫描

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 09:41:23 +08:00
wuguanganandClaude Opus 4.6 5519188cd5 initial commit: claude-hub dispatch center
tmux 多项目窗口管理 + 全局通知 hooks(Stop/Notification)
- scripts/setup.sh 一键初始化
- scan-projects.sh 自动发现项目
- hub-hook-stop.sh / hub-hook-permission.sh 全局通知
- skills: tmux-hub, browser-auto, lanhu-design-viewer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 09:38:42 +08:00