Files
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

116 lines
4.0 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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: /'