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>
116 lines
4.0 KiB
Bash
Executable File
116 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# new-project.sh —— 一键创建新项目:目录 + git + GitHub 远程 + 防污染配置 + 注册进 hub
|
||
#
|
||
# 用法:
|
||
# new-project.sh <name> [选项]
|
||
#
|
||
# 选项:
|
||
# --dir <parent> 父目录(默认 ~/Documents/code)
|
||
# --private GitHub 建私有仓(默认)
|
||
# --public GitHub 建公开仓
|
||
# --no-remote 只建本地仓,不创建 GitHub 远程
|
||
# --no-deny 不写防污染 deny 配置
|
||
# --desc "<text>" 项目描述(进 README 和 projects.yaml)
|
||
#
|
||
# 防污染 deny 说明:
|
||
# 禁止 Write/Edit 宿主机 ~/.claude/ 的 agents/skills/commands/memory/CLAUDE.md,
|
||
# 把「项目会话把知识写进全局面」的污染路径堵死。
|
||
# **刻意不禁 ~/.claude/plans/**:plan mode 的计划文件就写在那里,
|
||
# 禁了它所有 plan 会话都会报 "Error writing file"(在 agentx-dev-kit 真踩过)。
|
||
# deny 与 autoMemoryDirectory 必须成对存在——只禁不给去处等于把记忆扔了。
|
||
set -euo pipefail
|
||
|
||
HUB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
CYAN=$'\033[36m'; GREEN=$'\033[32m'; RED=$'\033[31m'; RESET=$'\033[0m'
|
||
say() { echo "${CYAN}▸${RESET} $*"; }
|
||
ok() { echo "${GREEN}✓${RESET} $*"; }
|
||
die() { echo "${RED}✗${RESET} $*" >&2; exit 1; }
|
||
|
||
NAME="${1:-}"
|
||
[ -n "$NAME" ] || die "用法:new-project.sh <name> [--dir <parent>] [--public|--private] [--no-remote] [--no-deny] [--desc <text>]"
|
||
shift
|
||
|
||
PARENT="$HOME/Documents/code"
|
||
VISIBILITY="--private"
|
||
REMOTE=1
|
||
DENY=1
|
||
DESC=""
|
||
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--dir) PARENT="${2:?--dir 需要参数}"; shift 2 ;;
|
||
--public) VISIBILITY="--public"; shift ;;
|
||
--private) VISIBILITY="--private"; shift ;;
|
||
--no-remote) REMOTE=0; shift ;;
|
||
--no-deny) DENY=0; shift ;;
|
||
--desc) DESC="${2:?--desc 需要参数}"; shift 2 ;;
|
||
*) die "未知选项:$1" ;;
|
||
esac
|
||
done
|
||
|
||
PARENT="${PARENT/#\~/$HOME}"
|
||
TARGET="$PARENT/$NAME"
|
||
[ -e "$TARGET" ] && die "目标已存在:$TARGET"
|
||
command -v git >/dev/null || die "找不到 git"
|
||
if [ "$REMOTE" = 1 ]; then
|
||
command -v gh >/dev/null || die "找不到 gh(GitHub CLI);用 --no-remote 可只建本地仓"
|
||
gh auth status >/dev/null 2>&1 || die "gh 未登录:先跑 gh auth login"
|
||
fi
|
||
|
||
say "创建 $TARGET"
|
||
mkdir -p "$TARGET"
|
||
cd "$TARGET"
|
||
git init -q -b main
|
||
|
||
# README
|
||
{
|
||
echo "# $NAME"
|
||
[ -n "$DESC" ] && { echo; echo "$DESC"; }
|
||
} > README.md
|
||
|
||
# 基础 .gitignore
|
||
cat > .gitignore <<'EOF'
|
||
.DS_Store
|
||
node_modules/
|
||
.env
|
||
.env.*
|
||
*.log
|
||
EOF
|
||
|
||
# 防污染配置 + 项目内记忆目录
|
||
if [ "$DENY" = 1 ]; then
|
||
# 模板只留一份 —— 由 ensure-project-config.sh 维护(启动路径也调它,
|
||
# 两边各写一份必然漂移)。它自己会建 .claude/memory 和补 deny + 全套 hook。
|
||
bash "$(cd "$(dirname "$0")" && pwd)/ensure-project-config.sh" "$TARGET"
|
||
ok "已写入 .claude/settings.json(防污染 deny + 全套 hook + 项目内记忆目录)"
|
||
fi
|
||
|
||
|
||
git add -A
|
||
git commit -qm "init: $NAME"
|
||
ok "本地仓已建好(分支 main)"
|
||
|
||
# GitHub 远程
|
||
if [ "$REMOTE" = 1 ]; then
|
||
# 注意:macOS 自带 bash 3.2 会把紧跟在变量后的全角字符吞进变量名,
|
||
# 变量与中文标点相邻时必须用 ${} 括起来。
|
||
say "创建 GitHub 远程仓(${VISIBILITY})"
|
||
if gh repo create "$NAME" "$VISIBILITY" --source . --remote origin --push; then
|
||
ok "已推送:$(git remote get-url origin)"
|
||
else
|
||
echo "${RED}✗${RESET} GitHub 建仓失败(同名仓已存在?),本地仓保留在 $TARGET" >&2
|
||
echo " 手动补救:gh repo create $NAME $VISIBILITY --source \"$TARGET\" --remote origin --push" >&2
|
||
fi
|
||
fi
|
||
|
||
# 注册进 hub 的 projects.yaml
|
||
if [ -x "$HUB_DIR/scripts/add-project.sh" ]; then
|
||
bash "$HUB_DIR/scripts/add-project.sh" "$NAME" "$TARGET" >/dev/null 2>&1 \
|
||
&& ok "已注册进 projects.yaml" \
|
||
|| echo "(注册 projects.yaml 失败,可手动:bash ${HUB_DIR}/scripts/add-project.sh ${NAME} ${TARGET})"
|
||
fi
|
||
|
||
echo
|
||
ok "完成:$TARGET"
|
||
[ "$REMOTE" = 1 ] && git remote get-url origin 2>/dev/null | sed 's/^/ remote: /'
|