SDD + Harness 四周学习计划 · Week 2 · Day 10(全计划第 10 课)
今日主题:实操:安装 harness——clone sdd-harness 到测试仓库,装 pre-commit hook,体验「改了代码没写 spec → 提交被拦」
建议用时:60–90 分钟 | 前置:Day 9 的
.ai/结构精读;本地已 clone sdd-harness;熟悉 Git 基础命令输出物:一个已装好 harness 的测试仓库 + 一次「被拦截」的提交尝试记录(git log / 终端输出留痕)
0. 一句话剧透
前两天的纸面功夫今天落地。你把 sdd-harness 装进一个专门用来破坏的测试仓库,然后做一件很反直觉的事:故意在 feat 分支上只改代码、不写 spec,然后提交——等着被 pre-commit hook 拦下来。
被拦不是失败,被拦是今天的目标。只有亲眼看见「改了代码没写 spec → 提交被拒」,你才算真正理解了护栏三「事后验证」不是概念,是机制。
1. 今日目标(学完你能做到)
- 能独立在测试仓库里安装 sdd-harness(两种方式至少跑通一种)
- 能确认
.ai/目录已生成、pre-commit hook 已生效 - 能复述 pre-commit hook 的判定规则(哪些分支拦、哪些豁免、显式例外是什么)
- 亲手制造一次「被拦截」的提交,并读懂终端输出
- 能说出 post-commit hook 与 pre-commit hook 的分工(拦 vs 记)
- 完成实操记录:把被拦截的输出贴进学习笔记
对应计划 Day 10 主题:实操:安装 harness。今天起你有一个「自己的 harness」——Week 3、Week 4 都会用到它。
2. 学习动线(时间盒)
| 步骤 | 内容 | 用时 |
|---|---|---|
| 1 | 准备工作:建测试仓库 + 确认 clone | 10 分钟 |
| 2 | 安装方式 A(bootstrap.sh 一键)或方式 B(clone + install.sh) | 15–20 分钟 |
| 3 | 验证安装:.ai/ 结构 + hook 生效 | 10 分钟 |
| 4 | 精读「6」:pre-commit hook 判定规则 | 10 分钟 |
| 5 | 实操:体验「改了代码没写 spec → 被拦」 | 15 分钟 |
| 6 | 观察 post-commit trace + 记录 + 打卡 | 10 分钟 |
本日命令全部在测试仓库里执行。不要拿真实项目试——今天的目标就是故意制造「非法提交」。
3. 准备工作:建测试仓库
开一个终端,在安全的位置建测试仓库(不要建在你的真实项目里):
1
2
3
4
5
6
7
8
9
10
11
# ① 建目录并初始化
mkdir harness-test
cd harness-test
git init
# ② 放一个最简「代码」文件(generic 栈,够用)
echo "print('hello from harness-test')" > app.py
# ③ 初始提交(注意:此时默认分支通常是 main/master,见下节 hook 规则)
git add .
git commit -m "chore: init test repo"
确认你本地已有 Day 7 预习时 clone 的 sdd-harness;没有的话现在补:
1
git clone https://github.com/iMark21/sdd-harness
小知识:
git init后的默认分支名取决于你的 Git 配置(main或master)。sdd-harness 的日常分支是develop,后面切分支时留意。
4. 安装方式 A:bootstrap.sh 一键脚本
README 提供的一键安装(在测试仓库根目录执行):
1
curl -sSL https://raw.githubusercontent.com/iMark21/sdd-harness/main/bootstrap.sh | bash -s -- --stack generic
--stack指定技术栈:python/swift/android/node/go/rust/generic- 测试仓库没有具体技术栈,用
generic即可 - 脚本从 GitHub 拉取并执行,如果你所在网络访问 GitHub 受限,见方式 B 或自行配置代理
5. 安装方式 B:clone + install.sh + init
如果你已经 clone 了 sdd-harness(Day 7 预习动作),可以走本地安装:
1
2
3
4
# 在 sdd-harness 仓库目录里运行安装脚本
cd sdd-harness
./install.sh # 安装脚本,具体参数以仓库 README 为准
sdd-harness init # 初始化命令(由安装脚本提供到 PATH)
init 之后回到测试仓库目录,确认 .ai/ 已生成:
1
2
cd ../harness-test
ls -a .ai
两种方式跑通一种即可。若安装脚本行为与上述描述有出入,以仓库 README 与脚本实际输出为准(本讲义未逐行核验安装脚本源码,细节标「待核验」)。
6. pre-commit hook 的判定规则(先看懂再动手)
hook 是 hooks/pre-commit-spec-check.sh。它不是一个「无脑拦所有提交」的脚本,判定规则如下(已核验):
| 场景 | 结果 |
|---|---|
在 feat/* 或 feature/* 分支上,改了代码,但没有改动 .ai/specs/ 或 .ai/adrs/ |
提交被拒 |
在 chore/*、docs/*、fix/*、hotfix/* 分支上 |
豁免(不检查) |
设了环境变量 SH_SDD_SKIP=1 |
显式例外,放行(且 shell 历史会留下痕迹) |
两个关键点:
- 它检查的是「你有没有碰 spec/ADR」,不是「spec 写得好不好」。 守门守的是「spec-first 这个动作有没有发生」,内容质量靠 review(Day 11 的
review命令)把关。 - 被拦在哪个分支、被豁免在哪个分支,是写死的规则——这也是为什么 Day 9 说「规则是机制,不是自觉」。
术语回顾:这就是 spec-first 的强制形态——spec 先于/伴随代码进入提交,hook 保证「只改代码」过不了门。
7. 实操:体验「改了代码没写 spec → 提交被拦」
7.1 先按规范起分支
1
2
3
# 从 develop 起一个 feat 分支(若仓库没有 develop,先创建)
git checkout -b develop 2>/dev/null || git checkout develop
git checkout -b feat/notify
7.2 只改代码,不碰 .ai/specs/ 与 .ai/adrs/
1
2
3
echo "print('v2 - notify feature')" >> app.py
git add app.py
git commit -m "feat: 增加通知功能"
7.3 预期与记录
- 预期:提交被拒。hook 判定你处于
feat/*分支、改了代码、没动.ai/specs/或.ai/adrs/→ 拦截。 - 把终端输出原样复制进学习笔记(这是今天的核心产出)。
- 被拒后检查:
git log --oneline——刚才那条提交不存在,你的代码改动还在工作区/暂存区,没有被丢。
提示:拒绝信息的具体文案以你实际安装版本为准(本讲义未核验逐字文本,标「待核验」)。你只需要确认两件事:①提交被拒;②被拒原因是「改了代码但没写 spec/ADR」。
7.4 顺便验证豁免分支
1
2
git checkout -b fix/typo # 从当前分支切出 fix 分支
git commit -m "fix: 修正文案" # 先 add 好改动
- 预期:放行。
fix/*分支在豁免名单里,hook 不拦。 - 这条提交成功,恰好反衬出 7.3 的拦截是「规则使然」而不是「hook 坏了」。
8. 提交成功后的 post-commit 观察
当你做出一条合法提交(比如 7.4 的 fix 提交,或后续补了 spec 的提交),观察终端里 post-commit hook 的输出:
- post-edit-trace.sh:被动打印本次提交涉及了哪些 spec / ADR / 代码文件。
- 它不拦截——只做「痕迹记录」,让每次提交和 spec/ADR/代码的对应关系可追溯。
对比记忆:pre-commit 是「守门员」(拦),post-commit 是「记录员」(记)。 一个管「能不能过」,一个管「过了要留痕」。
9. 今日实操记录模板(提交进学习笔记)
1
2
3
4
5
# Day 10 实操记录:安装 harness + 体验被拦
日期:______ 测试仓库路径:______
安装方式:□ bootstrap.sh(--stack: ___) □ clone + install.sh + init
验证 .ai/ 目录:□ 已生成(列出你看到的子目录:______)
被拦截输出(原样粘贴):