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>
84 lines
2.3 KiB
Bash
Executable File
84 lines
2.3 KiB
Bash
Executable File
#!/bin/bash
|
|
# 项目入口菜单 - tmux 窗口打开时自动显示
|
|
# 用法: project-menu.sh <项目名> <项目路径>
|
|
|
|
NAME="$1"
|
|
DIR="$2"
|
|
|
|
# 颜色
|
|
CYAN='\033[0;36m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
NC='\033[0m'
|
|
|
|
cd "$DIR" 2>/dev/null || exit 1
|
|
|
|
# 自定位 hub scripts 目录(支持任意安装位置)
|
|
HUB_SCRIPTS_DIR="${HUB_SCRIPTS_DIR:-$(cd "$(dirname "$0")" && pwd)}"
|
|
|
|
# 启动前先把项目配置补齐(防污染 deny + 全套 hook)。
|
|
#
|
|
# 必须在这里而不是在选了 1/2 之后:这个脚本下面有 30 秒超时的菜单,
|
|
# 补配置放到超时之后就可能根本轮不到执行。
|
|
# 不阻断 —— 补不上也只打一行,绝不让项目起不来。
|
|
bash "$HUB_SCRIPTS_DIR/ensure-project-config.sh" "$DIR" 2>/dev/null || true
|
|
|
|
export PATH="$HUB_SCRIPTS_DIR:$PATH"
|
|
alias hub-notify="bash $HUB_SCRIPTS_DIR/hub-notify.sh"
|
|
alias hub-run="bash $HUB_SCRIPTS_DIR/hub-run.sh"
|
|
|
|
# 显示项目信息
|
|
echo ""
|
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${BOLD} $NAME${NC}"
|
|
echo -e "${DIM} $DIR${NC}"
|
|
|
|
# git 信息
|
|
if [ -d .git ]; then
|
|
BRANCH=$(git branch --show-current 2>/dev/null)
|
|
STATUS=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
echo -e "${DIM} git: ${GREEN}$BRANCH${NC} ${DIM}($STATUS 个变更)${NC}"
|
|
fi
|
|
|
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
echo -e " ${GREEN}1${NC}) Claude Code"
|
|
echo -e " ${GREEN}2${NC}) Claude Code ${YELLOW}(--dangerously-skip-permissions)${NC}"
|
|
echo -e " ${GREEN}3${NC}) 直接终端"
|
|
echo -e " ${GREEN}4${NC}) 打开 Finder"
|
|
echo -e " ${GREEN}5${NC}) 打开 VS Code"
|
|
echo ""
|
|
echo -ne "${YELLOW}选择 [1-5, 默认 3, 30秒超时]: ${NC}"
|
|
|
|
read -t 30 choice
|
|
|
|
case "${choice:-3}" in
|
|
1)
|
|
echo -e "${DIM}启动 Claude Code...${NC}"
|
|
claude
|
|
;;
|
|
2)
|
|
echo -e "${DIM}启动 Claude Code (skip permissions)...${NC}"
|
|
claude --dangerously-skip-permissions
|
|
;;
|
|
3)
|
|
echo -e "${DIM}进入终端${NC}"
|
|
exec $SHELL
|
|
;;
|
|
4)
|
|
open "$DIR"
|
|
echo -e "${DIM}Finder 已打开${NC}"
|
|
exec $SHELL
|
|
;;
|
|
5)
|
|
code "$DIR"
|
|
echo -e "${DIM}VS Code 已打开${NC}"
|
|
exec $SHELL
|
|
;;
|
|
*)
|
|
exec $SHELL
|
|
;;
|
|
esac
|