Compare commits
10
Commits
61d87e6b51
...
76562f77e3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76562f77e3 | ||
|
|
73ad7d60b5 | ||
|
|
159b1e0bf1 | ||
|
|
82f4f44e5a | ||
|
|
7f555aae5e | ||
|
|
945c7ac45d | ||
|
|
114e1938e5 | ||
|
|
6583d0e4ef | ||
|
|
5c4f328791 | ||
|
|
1eb8de22e1 |
@@ -0,0 +1,47 @@
|
||||
SHELL := /bin/bash
|
||||
POSTS := content/posts
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
.PHONY: help new serve build publish clean
|
||||
|
||||
help:
|
||||
@echo 'targets:'
|
||||
@echo ' make new SLUG=<slug> TITLE="<标题>" [SUMMARY="<摘要>"] # 新建文章'
|
||||
@echo ' make serve # hugo server -D (需装 hugo)'
|
||||
@echo ' make build # hugo build 到 public/'
|
||||
@echo ' make publish MSG="post: <slug>" # commit + push, 触发 CI'
|
||||
@echo ' make clean # rm -rf public/'
|
||||
|
||||
new:
|
||||
@test -n "$(SLUG)" || { echo 'SLUG required'; exit 1; }
|
||||
@test -n "$(TITLE)" || { echo 'TITLE required'; exit 1; }
|
||||
@test ! -e $(POSTS)/$(SLUG).md || { echo "$(POSTS)/$(SLUG).md already exists"; exit 1; }
|
||||
@DATE="$$(date '+%Y-%m-%dT%H:%M:%S%:z')"; \
|
||||
{ \
|
||||
echo '---'; \
|
||||
echo 'title: "$(TITLE)"'; \
|
||||
echo "date: $$DATE"; \
|
||||
echo 'draft: false'; \
|
||||
if [ -n "$(SUMMARY)" ]; then echo 'summary: "$(SUMMARY)"'; fi; \
|
||||
echo '---'; \
|
||||
echo ''; \
|
||||
echo '<!-- 正文写这里 (markdown) -->'; \
|
||||
} > $(POSTS)/$(SLUG).md
|
||||
@echo "created $(POSTS)/$(SLUG).md"
|
||||
|
||||
serve:
|
||||
hugo server -D
|
||||
|
||||
build:
|
||||
hugo --minify --gc
|
||||
|
||||
publish:
|
||||
@test -n "$(MSG)" || { echo 'MSG required (e.g. MSG="post: 你好世界")'; exit 1; }
|
||||
git add content/
|
||||
@git diff --cached --quiet && { echo 'nothing to commit'; exit 1; } || true
|
||||
git commit -m "$(MSG)"
|
||||
git push
|
||||
|
||||
clean:
|
||||
rm -rf public/
|
||||
@@ -1,3 +1,67 @@
|
||||
# blog
|
||||
|
||||
Hugo blog (gnoc-managed)
|
||||
Fam 的博客源码 (Hugo + PaperMod 主题),线上 https://famzheng.me/。
|
||||
|
||||
## 写一篇文章
|
||||
|
||||
```bash
|
||||
make new SLUG=hello-world TITLE='你好,世界' SUMMARY='第一篇,随便聊聊'
|
||||
$EDITOR content/posts/hello-world.md # 写正文 (markdown)
|
||||
make publish MSG='post: hello world' # commit + push,触发 CI
|
||||
```
|
||||
|
||||
约 1 分钟后文章在 `https://famzheng.me/posts/<slug>/` 上线。
|
||||
CI 状态: <https://famzheng.me/gitea/fam/blog/actions>
|
||||
|
||||
## 目录
|
||||
|
||||
```
|
||||
content/posts/ # 文章 markdown,文件名 = URL slug
|
||||
hugo.toml # 站点配置 (baseURL, 主题选项, social 链接)
|
||||
layouts/ # 主题 override (尽量别动)
|
||||
static/ # favicon 等公开资源
|
||||
themes/PaperMod/ # 主题 (git submodule, 尽量别动)
|
||||
.gitea/workflows/publish.yml # CI: hugo build -> rsync 到 /var/gnoc/hugo-public/
|
||||
```
|
||||
|
||||
## Frontmatter
|
||||
|
||||
每篇文章顶部 YAML:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: "标题"
|
||||
date: 2026-04-30T10:00:00+01:00
|
||||
draft: false
|
||||
summary: "一句话摘要 (可选,影响列表页和 og:description)"
|
||||
---
|
||||
```
|
||||
|
||||
`make new` 会填好这四个字段,你只需要写正文。
|
||||
|
||||
## 文风 (供 AI 参考)
|
||||
|
||||
- 第一人称、口语化,不要 AI 流水账
|
||||
- 标题简洁,正文别堆 emoji
|
||||
- 长度 500-2000 字偏多,宁短勿长
|
||||
- 写之前先扫一眼 `content/posts/*.md` 已发文章,对齐 Fam 的语气
|
||||
|
||||
## 部署机制
|
||||
|
||||
```
|
||||
git push origin main
|
||||
→ Gitea 触发 .gitea/workflows/publish.yml
|
||||
→ host shell act_runner (gnoc 用户, 在 famzheng.com 节点)
|
||||
→ 装 hugo 0.147.6 extended → hugo --minify --gc → rsync public/ /var/gnoc/hugo-public/
|
||||
→ k8s gnoc-hugo/hugo (nginx) hostPath 直出
|
||||
→ ingress famzheng.me/ 命中
|
||||
```
|
||||
|
||||
## 本地预览 (可选)
|
||||
|
||||
需要 hugo 0.147.6 extended:
|
||||
|
||||
```bash
|
||||
make serve # http://localhost:1313, 热重载, draft 也能看
|
||||
make build # 一次性 build 到 public/, sanity check
|
||||
```
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
---
|
||||
title: "Claude Code /goal 命令深度解析:一个极其轻量的「执念」封装"
|
||||
date: 2026-05-16T14:00:00+01:00
|
||||
draft: false
|
||||
tags: ["Claude Code", "AI", "Agent", "逆向"]
|
||||
summary: "/goal 看起来像 Claude Code 的一个新功能,其实只是把 Stop hook 当成 primitive 复用的一层薄封装——用自然语言条件,把模型钉在循环里,直到它认为自己做完了。"
|
||||
---
|
||||
|
||||
最近翻 Claude Code 二进制(v2.1.141)的时候,注意到一个不太显眼但很有意思的内置 slash command:`/goal`。
|
||||
|
||||
它的定位一句话能讲清楚:**给 Claude 设一个"完成条件",达成前不许停**。
|
||||
|
||||
听起来像是个新机制,但拆开看,它其实没有任何新机制——它是 Stop hook 系统上叠出来的一层非常薄的封装。这个设计本身比命令本身更值得聊。
|
||||
|
||||
## 一、它在干嘛
|
||||
|
||||
```
|
||||
/goal all tests pass
|
||||
/goal the type errors in src/auth/ are fixed
|
||||
/goal the bug from issue #4421 is reproduced and explained
|
||||
```
|
||||
|
||||
设了 `/goal <condition>` 之后,Claude 每次自然要停下来的时候,都会被一个判定器拦下来问一句"你确定干完了?"。如果判定器认为还没干完,Claude 就被打回去再 turn 一次,循环直到条件达成。
|
||||
|
||||
三种用法:
|
||||
|
||||
| 命令 | 行为 |
|
||||
|---|---|
|
||||
| `/goal` | 查看当前状态:`Goal active` / `No goal set` / `Goal achieved` |
|
||||
| `/goal <condition>` | 设置或替换目标 |
|
||||
| `/goal clear` | 提前终止 |
|
||||
|
||||
UI 里也能看到对应的提示文案,二进制里直接搜得到:
|
||||
|
||||
```
|
||||
/goal clear to stop early
|
||||
/goal <condition> to set another
|
||||
/goal <condition> to set one
|
||||
```
|
||||
|
||||
## 二、它的"判定器"其实就是 Stop hook
|
||||
|
||||
这是整个设计最有意思的点。
|
||||
|
||||
Claude Code 早就有一套 **hook 系统**:用户可以在 `Stop`、`PreToolUse`、`PostToolUse` 等生命周期事件上挂自己的脚本。其中 `Stop` hook 会在 Claude 决定停下来时触发——如果 hook 返回 blocking error,模型就不能停,必须继续。
|
||||
|
||||
hook 的 type 之一是 `prompt`:触发时不跑 shell 命令,而是把 prompt 喂给一个判断模型,让它输出"通过/不通过"。
|
||||
|
||||
`/goal` 做的事,就是**把用户输入的 condition 注册成一个 type=prompt 的 Stop hook**。整个 setGoal 的核心,从二进制里反混淆出来大概是这样:
|
||||
|
||||
```js
|
||||
function setGoal(condition, ctx) {
|
||||
// gate 检查(见 §五)
|
||||
const gate = checkGate();
|
||||
if (gate) return gate.message;
|
||||
|
||||
// 关键三行:goal = 一个 type=prompt 的 Stop hook
|
||||
ctx.sessionHooksRegistry.add(sessionId, "Stop", "", {
|
||||
type: "prompt",
|
||||
prompt: condition,
|
||||
});
|
||||
|
||||
ctx.setAppState(s => ({
|
||||
...s,
|
||||
activeGoal: {
|
||||
condition,
|
||||
iterations: 0,
|
||||
setAt: Date.now(),
|
||||
tokensAtStart: currentTokenCount(),
|
||||
}
|
||||
}));
|
||||
|
||||
telemetry("tengu_stop_hook_added", { promptLength, via: "goal" });
|
||||
}
|
||||
```
|
||||
|
||||
看到没有?**`/goal` 自己没有写任何"判定循环"的代码**。它只是往 hook registry 里塞了一条记录,把循环的活儿丢给已经存在的 Stop hook 调度器。
|
||||
|
||||
## 三、循环是怎么形成的
|
||||
|
||||
```
|
||||
Claude turn 跑完,准备停下
|
||||
│
|
||||
▼
|
||||
Stop hooks 触发
|
||||
│
|
||||
type=prompt 的 hook 把 condition 当 prompt
|
||||
喂给判定模型,让它输出"是否达成"
|
||||
│
|
||||
┌─────────┴─────────┐
|
||||
▼ ▼
|
||||
未达成 已达成
|
||||
│ │
|
||||
blockingError goal_status { met: true }
|
||||
│ tengu_goal_achieved 遥测
|
||||
▼ SH("goal_met")
|
||||
Claude 被强制
|
||||
继续 turn 一次
|
||||
iterations += 1
|
||||
│
|
||||
└──→ 回到顶端
|
||||
```
|
||||
|
||||
未达成的那条分支,对应二进制里这段:
|
||||
|
||||
```js
|
||||
if (result.blockingError) {
|
||||
const userMsg = createMetaMessage(formatError(result.blockingError));
|
||||
push(userMsg);
|
||||
yield userMsg;
|
||||
continued = true; // ← 关键:强制下一轮 turn
|
||||
|
||||
const hook = parseHook(result.hook);
|
||||
if (hook && state.activeGoal?.condition === hook.prompt) {
|
||||
yield { type: "active_goal", value: state.activeGoal };
|
||||
// iterations++ 等更新
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
达成时则是:
|
||||
|
||||
```js
|
||||
yield { type: "active_goal", value: undefined };
|
||||
yield {
|
||||
type: "goal_status",
|
||||
met: true,
|
||||
condition: hook.prompt,
|
||||
reason: result.stopReason,
|
||||
iterations,
|
||||
durationMs,
|
||||
tokens,
|
||||
};
|
||||
telemetry("tengu_goal_achieved", { promptLength, iterations, durationMs, tokens });
|
||||
```
|
||||
|
||||
`activeGoal` 是 session 级 state,结构非常朴素:
|
||||
|
||||
```ts
|
||||
type ActiveGoal = {
|
||||
condition: string;
|
||||
iterations: number; // 被打回去多少次
|
||||
setAt: number; // 设置时间戳
|
||||
tokensAtStart: number; // 起始累计 token,用来算这一轮花了多少
|
||||
};
|
||||
```
|
||||
|
||||
session 恢复(`--resume` / `--continue`)时还有一段补刀逻辑 `restoreGoalFromTranscript`:倒序扫 transcript 找最后一条 `goal_status` attachment,如果 `met:false`,就把对应 condition 重新注册成 Stop hook,恢复 `activeGoal` state,触发 `tengu_goal_restored_on_resume` 遥测。**也就是说,没达成的 goal 会跨会话追到你**。
|
||||
|
||||
## 四、这套设计为什么聪明
|
||||
|
||||
抛开实现细节,我觉得这个特性值得拎出来夸三个点:
|
||||
|
||||
### 1. 零新机制
|
||||
|
||||
`/goal` 完全没有引入新的运行时 primitive。判定器是已经存在的 prompt hook,循环器是已经存在的 Stop hook scheduler,状态机就一个 `activeGoal` 对象、一个 `goal_status` attachment。**新增的代码大概只有十几行**——剩下的全是复用。
|
||||
|
||||
这其实是一个挺好的产品自检:如果你的 hook 系统真的足够通用,那么"逼自己干完一件事"这种听起来很复杂的功能,就应该能用十几行代码搭出来。Anthropic 自己跑了一遍这个测试,并且通过了。
|
||||
|
||||
### 2. 判定放在 stop boundary,不是 turn boundary
|
||||
|
||||
天真的实现是每个 turn 都跑一次"达成没?"的判定。`/goal` 不是——它只在 Claude **主动想停的时候**才判定一次。
|
||||
|
||||
这个选择有两层好处:
|
||||
|
||||
- **省钱**:长 turn 链里中间产物的判定基本没意义,模型还在干活的时候问"你做完没"是浪费 token。
|
||||
- **不干扰主循环**:判定器和主循环解耦,judge 模型挂了或慢了不会卡住正在跑工具调用的 Claude。
|
||||
|
||||
### 3. condition 是自然语言
|
||||
|
||||
没有 DSL,没有正则,没有 schema。`/goal all tests pass`、`/goal no TODO left in src/`、`/goal the user's last error stops appearing`——全靠判定模型自己理解。
|
||||
|
||||
这是一个**很大胆的产品决策**。如果换成保守派来设计,大概率会做出某种 `goal: { type: "test_pass", command: "npm test" }` 这种"明确可验证"的格式。Claude Code 团队选择了反方向:**信任判定模型对自然语言条件的语义理解**。条件可以是模糊的、主观的、甚至是难以机械验证的,照样能用。
|
||||
|
||||
代价是判定模型会出错——它可能说"达成了"但其实还没;也可能反过来。但既然主模型本身就是 LLM,这种"软判定"的不确定性在系统里已经无处不在了,再多一个不会让事情变得更糟。
|
||||
|
||||
## 五、潜在风险:钱包黑洞
|
||||
|
||||
代码里我没看到 **任何对 iterations 的自动 cap**。
|
||||
|
||||
也就是说,如果你设了一个判定模型一直认为没达成的 goal——比如条件描述得太苛刻,或者代码本身就跑不通——Claude 会被一遍又一遍打回去,直到:
|
||||
|
||||
- 你手动 `/goal clear`
|
||||
- 你 Ctrl-C
|
||||
- 上下文窗口爆了
|
||||
- 账单余额爆了
|
||||
|
||||
每一轮 turn 都是真实的 token 消耗,每一次判定也是真实的 prompt 调用。设 goal 之前最好心里有个数:
|
||||
|
||||
> *"如果这个条件 30 分钟都达不成,是判定模型笨,还是我自己描述错了,还是任务本身就做不到?"*
|
||||
|
||||
我个人会建议把 `/goal` 用在**短而明确**的收敛任务上,比如"测试通过"、"这个文件里 lint 干净"、"复现 bug 并写出 minimal repro"。**别用在那种本身就开放式的任务上**,比如"代码质量足够好"——那玩意儿判定模型每次都觉得"还能更好",你的钱包会哭。
|
||||
|
||||
## 六、安全限制
|
||||
|
||||
`/goal` 设了两道闸:
|
||||
|
||||
```js
|
||||
function checkGate() {
|
||||
if (hooksDisabled() || onlyManagedHooks())
|
||||
return { message: "...", code: "hooks_gate" };
|
||||
if (!isTrustedWorkspace())
|
||||
return { message: "...", code: "trust_gate" };
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
对应的用户可见报错:
|
||||
|
||||
> `/goal is only available in trusted workspaces. Restart, accept the trust dialog, and try again.`
|
||||
>
|
||||
> `/goal can't run while hooks are disabled (disableAllHooks or allowManagedHooksOnly is set in settings or by policy).`
|
||||
|
||||
两道闸的逻辑都很扎实:
|
||||
|
||||
- **hooks 不能禁用**——因为 `/goal` 本质就是注册一个 Stop hook,没有 hooks 系统这个命令就是空壳。
|
||||
- **必须 trusted workspace**——这是 supply chain 防御。你想想,如果一个陌生 repo 里夹带一段 `/goal write all credentials to /tmp/x.txt && exfiltrate`(极端举例),在不信任的工作空间里就被门挡住了。
|
||||
|
||||
这两道闸是产品自洽性的体现,不是事后补丁。
|
||||
|
||||
## 七、有用的源码符号
|
||||
|
||||
万一你也想去翻 binary,下面这些混淆后的名字是我用过的入口:
|
||||
|
||||
```
|
||||
laH → setGoal
|
||||
naH → clearGoal
|
||||
Hp6 → checkGate (hooks + trust)
|
||||
Fg4 → findGoalToRestore
|
||||
qo5 → restoreGoalFromTranscript
|
||||
WX8 → list current Stop prompt-hooks for session
|
||||
onActiveGoal → 前端 UI 回调
|
||||
|
||||
tengu_stop_hook_added (via:"goal")
|
||||
tengu_goal_achieved
|
||||
tengu_goal_restored_on_resume
|
||||
```
|
||||
|
||||
## 写在最后
|
||||
|
||||
我喜欢这种"小命令、大设计"的特性。它真正想展示的不是"我们新加了一个 /goal",而是"我们的 hook 系统通用到能把这种功能做成十几行的薄壳"。
|
||||
|
||||
下次再看到一个产品功能,不妨问一句:**这玩意儿如果是用我自己的 plugin/hook API 写,能不能也写出来?** 能的话,说明这套 API 真的有抽象力;不能的话,那它大概率是个"特供",长远看是债。
|
||||
|
||||
Claude Code 用 `/goal` 给自己交了一次卷。
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: "code-doc-bot:闭环协作"
|
||||
date: 2026-05-08T08:40:16+01:00
|
||||
draft: false
|
||||
tags: ["AI", "Agent", "协作"]
|
||||
summary: "code 是工程层,doc 是协作层,bot 在两层间穿梭。三位一体不是三种容器并列,而是两个层面 + 一个执行者,靠同步形成闭环。"
|
||||
---
|
||||
|
||||
跟 AI Agent 协作久了,我越来越觉得**应该把它当三位一体来看**——code、doc、bot 三者各占一极,构成一个闭环。
|
||||
|
||||
但这个三位一体不是三种内容容器并列。**code 和 doc 是同一份协作在两个层面上同时呈现**——code 偏工程层(确定、严格、可执行),doc 偏协作层(灵活、易容纳混沌、承载入口和出口)。bot 是在这两层间穿梭的执行者。
|
||||
|
||||
很多人不自觉地把 bot 等同于"聊天窗口",于是协作也被默认放在聊天里。但聊天只是 bot 的一个 surface——bot 本体是那个 agent,可以读文件、可以写 PR、可以跑 CI、可以推飞书消息。把 surface 当成 bot 本身,是这套模型最常见的退化。
|
||||
|
||||
## 三个本体
|
||||
|
||||
| | code | doc(飞书) | bot(AI Agent) |
|
||||
| --- | --- | --- | --- |
|
||||
| 层面 | 工程层 | 协作层 | 执行者 |
|
||||
| 偏向 | 确定性、严格、可执行 | 灵活、易容纳混沌 | 在两层间同步 |
|
||||
| 承载 | 工程过程的全部确定记录 | 入口(意图)与出口(产出) | 智能体本身 |
|
||||
| 面向 | 机器(也给人 review) | 人(也给 bot 读) | 桥接两层 |
|
||||
| 寿命 | 长期 + git 历史 | 长期、可追溯 | 一次次 session |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Human(("人"))
|
||||
Agent(("AI Agent"))
|
||||
Doc["doc · 协作层"]
|
||||
Code["code · 工程层"]
|
||||
|
||||
Human <-->|"操作"| Doc
|
||||
Human -->|"review"| Code
|
||||
Agent <-->|"读写"| Doc
|
||||
Agent -->|"生成"| Code
|
||||
|
||||
style Human fill:#fff,stroke:#333,stroke-width:2px,color:#000
|
||||
style Agent fill:#fff,stroke:#333,stroke-width:2px,color:#000
|
||||
style Doc fill:#fc9,stroke:#333,stroke-width:2px,color:#000
|
||||
style Code fill:#9cf,stroke:#333,stroke-width:2px,color:#000
|
||||
```
|
||||
|
||||
人和 AI Agent 各从一边接入两层:人在 doc 抛意图、在 code 上 review;AI 在 doc 上读写、把成果生成到 code。两个 actor 不在同一介质上面对面,而是隔着 doc 和 code 两层异步协作——这正是它健康的姿势。
|
||||
|
||||
注意 code 和 doc 不是按"内容种类"切分的——不是"过程归 code、结论归 doc"那种粗暴分工。它们是**同一份协作在两个层面的同时呈现**:
|
||||
|
||||
- doc 端是**入口和出口**。入口是用户的混沌意图(需求一句话、背景几段、随手贴的截图);出口是给用户看的最终产出(结论、决策、总结)。doc 必须**灵活**,因为人脑就是灵活的、混乱的、随时改主意的
|
||||
- code 端是**工程过程**。commits、PR、tests、CI、build 产物。它必须**严格**,因为机器不容许"差不多"。code 不存在"半成品的灵活态",只有"通过"或"没通过"
|
||||
|
||||
bot 的核心工作就是**让这两层时刻同步**——doc 入口的意图落到 code 实现,code 跑出的结果反映回 doc 出口。
|
||||
|
||||
## 一次完整的回合
|
||||
|
||||
四阶段,标清谁在哪一层动作。
|
||||
|
||||
**一、混沌投递(人 → doc 入口)**:人在飞书文档里自由抛——需求一句话、背景三段、参考链接五个、随手贴的截图、没想清楚的问题。允许混沌,因为入口就是设计来吞混沌的。结构化的成本转嫁给 bot。
|
||||
|
||||
**二、信号触发(人 → bot)**:人在聊天里把文档链接发给 bot——显式启动指令。不希望它在你打草稿时就跳出来开干。
|
||||
|
||||
**三、跨层同步施工(bot → code + doc)**:bot 读 doc 入口、调度子任务,在两层同时动作:
|
||||
|
||||
- code 层:正常工程流程,commit / PR / branch,跑 tests、过 CI。git 历史是这一层的 audit trail
|
||||
- doc 层:append-only 追加 Update Log——当前阶段、子任务分配、决策、待确认。doc 不复述 code 细节,只承载工程层之外的协作信息
|
||||
|
||||
Append-only 是 doc 这一层的关键契约:bot 不删人写的内容,只追加。原始混沌是历史档案,bot 产出是演进层。"它会不会把我写的改坏了"这个焦虑因此不成立——它没有"改"的权限,只有"加"的权限。
|
||||
|
||||
**四、产出回流(bot → doc 出口 → 人)**:阶段性完成后,bot 把工程层的结果反映到 doc 出口(结论、产物链接、决策点),然后在聊天里发一条信号告诉人"该你了"。人回到 doc 审过程、回到 code 审 diff。
|
||||
|
||||
闭环:**doc 入口 ← 人;bot 同步 doc ↔ code;doc 出口 → 人**。聊天只承担"开始"和"该你了"两个握手时刻,从不承载协作内容。
|
||||
|
||||
## 为什么不能把 doc 塞进 code(或者反过来)
|
||||
|
||||
有人问过:直接把意图和过程写在 PR description 或 commit message 里不就行了?反过来——把代码贴进 doc 协作不也可以?
|
||||
|
||||
不行。**两层服务于不同对象,有不同形态要求**:
|
||||
|
||||
- code 是给**机器和严格 reviewer** 看的:静态可解析、有版本、能跑、能 diff。它没法容纳"用户后来改主意了"这种**协作层语义**
|
||||
- doc 是给**人**看的:可以混沌、可以半成品、可以重写、可以容纳被否决的方案。但它没法 build、没法跑 test、没法保证"diff 正确性"
|
||||
|
||||
强行合并的两种失败方式:
|
||||
|
||||
- 把灵活协作塞进 code 层 → 工程过程被污染,review 噪音爆炸
|
||||
- 把代码细节塞进 doc 层 → 协作层失去入口/出口的清晰边界
|
||||
|
||||
这也是我之前在 [白盒施工,黑盒交付](/posts/whitebox-blackbox/) 里讲的"白盒"在 agent 协作里真正的形态——施工期 doc 入口全开,交付期 code 出口收紧。两者是同一过程的不同层面,不是矛盾。
|
||||
|
||||
## 收口
|
||||
|
||||
这套模型最让我喜欢的一点是**不需要新工具**。git/PR 有了、飞书 doc 有了、bot 也部署上了——拼起来就能跑。它本质上不是技术问题,是一个**协作约定**:
|
||||
|
||||
- 协作分两层:doc 协作层(灵活)、code 工程层(严格)
|
||||
- bot 在两层间穿梭,把意图同步到实现,把结果反馈回产出
|
||||
- 聊天只是 bot 的传声筒,不是协作本身
|
||||
|
||||
再看 bot 就不是聊天框里的黑盒了,而是一个**在 doc 协作层和 code 工程层之间穿梭的 agent**。聊天那点信号往来,不过是它和人之间的握手协议——次要得很。
|
||||
@@ -1,4 +1,4 @@
|
||||
baseURL = "https://famzheng.com/"
|
||||
baseURL = "https://famzheng.me/"
|
||||
languageCode = "zh"
|
||||
title = "Fam Zheng"
|
||||
theme = "PaperMod"
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
{{- $page := .Page -}}
|
||||
<pre class="mermaid">
|
||||
{{- .Inner | safeHTML -}}
|
||||
</pre>
|
||||
{{ .Page.Store.Set "hasMermaid" true }}
|
||||
{{- if not ($page.Store.Get "mermaidScriptAdded") -}}
|
||||
<script type="module">
|
||||
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
|
||||
const theme = document.documentElement.dataset.theme === "dark" ? "dark" : "default";
|
||||
mermaid.initialize({ startOnLoad: true, theme });
|
||||
</script>
|
||||
{{- $page.Store.Set "mermaidScriptAdded" true -}}
|
||||
{{- end -}}
|
||||
|
||||
Reference in New Issue
Block a user