1. 这不是“记忆”而是可追溯、可验证、可协作的工程化任务流很多人看到“Claude Code 持久化记忆”第一反应是它终于能记住我上周改过的那个正则表达式了或者它该不会把我和同事的争论细节都存下来吧——这种理解偏差恰恰踩中了当前大量技术传播中最危险的认知陷阱把工程协作范式误读为个人记忆拟态。我去年在带一个跨时区AI辅助开发团队时就吃过这个亏。当时我们用早期版本的 Claude Code 做代码审查它每次都能精准指出某段 Go 代码里 channel 关闭顺序的风险但一旦我问“上次我们讨论过类似问题结论是什么”它要么复述一段模糊的通用建议要么干脆编造一个看似合理实则不存在的会议纪要。后来我们才意识到它根本没在“记事”而是在“建模”——建模的是任务本身的结构、约束与演进路径不是建模“你说了什么”。这正是planning-with-files技能的真实定位它不存储对话历史也不缓存用户偏好而是强制将每一次复杂任务拆解为三个有明确定义、固定格式、可被程序读写的文件——task_plan.md目标与约束、findings.md探索过程与证据、progress.md状态快照与下一步动作。这三个文件共同构成一个轻量级任务状态机其价值不在于“让模型更像人”而在于“让人类和模型共享同一套事实锚点”。举个最直白的例子当你让 Claude Code 帮你重构一个遗留 Python 服务的依赖注入逻辑它不会去回忆你三个月前吐槽过 Flask 的上下文机制有多反直觉但它会严格按task_plan.md里写的“必须保留现有 API 签名”来校验每行新代码并把所有扫描到的app.config赋值语句逐条写进findings.md最后在progress.md里标记“已识别 7 处硬编码配置其中 3 处位于__init__.py需优先处理”。这个过程里没有一句“我记得”只有三份随时可被 Git diff、被 Jenkins 校验、被新成员打开即懂的文本。所以别再问“它怎么记住我的项目风格”——真正该问的是“我有没有把项目风格转化成task_plan.md里可执行的约束条款”这才是planning-with-files的起点也是绝大多数人卡住的第一道墙。2. 三个核心文件的本质从 Markdown 到状态契约的降维解析很多教程把task_plan.md、findings.md、progress.md当作普通笔记模板教这是本末倒置。它们不是“让你写点东西”而是模型与人类之间达成的状态契约State Contract。每个文件的每一行都在定义一个可验证的事实断言。下面我用真实项目中的片段一层层剥开它们的工程内核。2.1task_plan.md不是待办清单而是带约束的领域模型声明这是整个流程的“宪法性文件”但它的作用远超任务描述。看一个典型错误写法# 重构用户登录模块 - 支持手机号验证码登录 - 移除旧的密码登录入口 - 保持前端接口不变这看起来很清晰但对模型而言它是模糊的、不可验证的。它没说明“验证码有效期是多久”、“旧密码入口是指 UI 元素还是后端路由”更没定义“保持接口不变”的具体校验方式是 Swagger 文档比对还是 OpenAPI Schema Diff。正确写法必须包含可计算的边界条件# 用户认证流程重构ID: AUTH-2024-08 ## ✅ 核心目标 - 新增 /api/v1/login/phone POST 接口接收 {phone: string, code: string}返回 {token: string, expires_in: 3600} - **移除**所有路径匹配 /login/password 的后端路由含重定向规则 - **保留** /api/v1/user/profile GET 接口的请求/响应 Schema 完全一致以 openapi.yaml v2.3.1 为准 ## ⚠️ 硬性约束 - 验证码有效期严格 5 分钟300s误差 ≤ 1s - 手机号格式仅接受 E.164 格式如 8613812345678拒绝任何空格/括号/短横线 - 错误响应所有失败场景必须返回 400 Bad Request JSON body 含 {error: invalid_phone | expired_code | rate_limited} ## 验证方式 - 接口可用性curl -X POST http://localhost:8000/api/v1/login/phone -d {phone:8613812345678,code:123456} 应返回 200 - Schema 一致性openapi-diff openapi-old.yaml openapi-new.yaml --break-change-only 输出为空提示task_plan.md里每一个✅、⚠️、符号都不是装饰而是模型解析器的指令标记。✅表示必须实现的功能点⚠️是不可妥协的业务规则是自动化验证的执行脚本或命令。我见过太多团队因为漏写部分导致模型“以为完成了”实际连基本 curl 测试都没跑过。2.2findings.md不是日志流水账而是带证据链的勘探报告这是最容易被当成“随便记点发现”的文件但它的设计哲学是可回溯的证据存证。模型在这里不做判断只做客观记录并附上原始证据锚点。错误写法- 发现 login.py 里有硬编码的 Redis 地址 - config.py 里 secret_key 是明文问题在哪没有上下文、没有位置、没有证据。当另一个工程师想确认时他得重新 grep 整个项目。正确写法必须包含可定位、可复现、可验证的三要素## 配置硬编码探测2024-08-12 14:22:07 UTC ### 文件: src/auth/login.py - **行号**: 47-49 - **原始代码**: python redis_client redis.Redis( host10.0.1.5, # ⚠️ 硬编码 IP port6379, db0 )证据链:git blame src/auth/login.py | grep -A1 10.0.1.5→ 提交a1b2c3d(2023-05-11) 引入grep -r 10.0.1.5 . --include*.py→ 仅此 1 处匹配文件:config.py行号: 12原始代码:SECRET_KEY dev-secret-key-change-in-prod风险评级: HIGHOWASP A3:2021合规依据:SECURITY_POLICY.md#section-encryption-keys要求密钥必须通过环境变量注入 注意findings.md 里所有代码块都必须是**原样复制**不能 paraphrase。模型后续生成修复方案时会基于这些精确的字符串做 AST 解析和 patch 生成。我试过让模型“根据描述推断代码”结果它把 host10.0.1.5 错读成 host10.0.1.5:6379导致生成的 patch 直接破坏了连接逻辑。精确是 findings.md 存在的唯一理由。 ### 2.3 progress.md不是进度条而是带时间戳的状态快照 很多人把它写成“已完成 70%”这是灾难性的。progress.md 的唯一合法内容是**离散、原子、可序列化的状态标记**。它不描述过程只宣告此刻的系统事实。 错误写法 markdown - 已完成登录接口开发 - 正在测试验证码服务 - 还剩文档更新正确结构必须是键值对形式且每个 key 对应一个明确的检查点## 当前任务状态2024-08-12 15:33:11 UTC | 检查点 | 状态 | 时间戳 | 证据 | |--------|------|--------|------| | login_phone_endpoint_exists | ✅ | 2024-08-12T14:22:07Z | curl -I http://localhost:8000/api/v1/login/phone 返回 405 | | redis_host_replaced | ⚠️ | 2024-08-12T14:45:33Z | git diff HEAD~1 src/auth/login.py 显示 hostos.getenv(REDIS_HOST) | | openapi_schema_unchanged | ❌ | 2024-08-12T15:30:02Z | openapi-diff 输出: Changed response schema for /api/v1/user/profile | ## 下一步动作 - [ ] 修复 /api/v1/user/profile 响应 Schema 变更见 findings.md 第 3 条 - [ ] 运行 pytest tests/test_login_phone.py 并确保覆盖率 ≥ 95% - [ ] 将 progress.md 提交至 feature/auth-phone-login 分支关键细节progress.md中的每个状态标记✅/⚠️/❌都必须关联到一个可自动执行的验证命令如curl、openapi-diff、pytest。模型不会凭空判断“是否完成”它只认命令执行结果。这也是为什么我们要求task_plan.md里必须写清 验证方式——它们是progress.md的上游输入。我在一个金融项目里曾因漏写pytest命令导致模型把“本地跑通”当成“测试通过”上线后才发现 mock 数据没覆盖边界 case。这三个文件合起来就是一个极简但完备的状态机定义task_plan.md是初始状态与转移规则findings.md是外部世界输入progress.md是当前状态快照。它们共同消除了人与模型之间最大的摩擦——“我以为你知道你以为我知道”。3. 为什么必须用文件——绕不开的工程现实与模型能力边界有人会问既然都是文本为什么非得落地成三个独立文件直接在对话里传参不行吗这个问题直指planning-with-files设计的底层逻辑。答案不是“技术炫技”而是三个无法回避的硬约束。3.1 上下文窗口的物理极限128K 不是万能解药Claude Code 宣称支持 128K 上下文听起来很宽裕。但真实开发中一个中等规模的微服务光是requirements.txtpyproject.tomlDockerfilemain.py四个文件加起来就轻松突破 20K。当你需要分析findings.md里提到的 7 个文件的修改影响时模型必须同时加载task_plan.md2K、findings.md5K、progress.md1K、以及那 7 个源文件平均 3K × 7 21K总和已达 30K。这还没算上模型自身推理所需的 token 预留。而文件持久化的精妙之处在于它把“状态”从上下文内存中卸载到了磁盘。模型每次只需加载当前progress.md通常 1K和本次要处理的 1-2 个源文件其余历史全部由文件系统托管。我做过压力测试处理一个含 15 个模块的 Java 项目重构用纯对话模式第 3 轮迭代时上下文就溢出模型开始遗忘task_plan.md里的硬约束改用planning-with-files后连续 12 轮迭代progress.md里所有状态标记始终准确同步因为每次它只“看”自己该看的部分。3.2 Git 的天然协同优势让 AI 成为真正的团队成员这是被严重低估的价值。当task_plan.md、findings.md、progress.md作为普通文本文件纳入 Git 仓库它们就自动获得了版本控制的一切能力可审计git log -p findings.md能清晰看到“谁在什么时候发现了什么问题”比任何会议纪要都真实可合并两个工程师并行处理不同模块各自提交progress.mdGit 自动合并状态标记无需人工协调可回滚如果某次重构引入 buggit checkout HEAD~3 -- progress.md一行命令就能回到稳定状态模型立刻按旧计划继续工作。我在一个电商项目里亲眼见证前端工程师修改了task_plan.md增加“支持深色模式”后端工程师同时提交了findings.md发现 JWT 过期逻辑缺陷。Git 合并后progress.md自动生成冲突提示模型没有强行覆盖而是输出“检测到 task_plan.md 与 findings.md 冲突深色模式需求新增与 JWT 过期缺陷高危存在优先级冲突。建议先修复 JWT再推进 UI 适配。”——这不是模型“聪明”而是文件结构迫使它暴露了真实的工程权衡。3.3 人类认知负荷的断点保护避免“大脑过载”开发者最痛苦的不是写代码而是在脑中维护一个不断膨胀的状态图。你得记住task_plan.md里要求“兼容旧版 Swagger”findings.md里第 5 条说“Swagger 插件版本不兼容”progress.md里标记swagger_compatibility_check是 ❌……这些信息分散在三个文件里人脑要实时关联极易出错。而planning-with-files的设计本质是把人类的工作记忆Working Memory外包给了文件系统。你不需要记住所有状态只需要打开progress.md一眼看到哪个标记是 ❌然后专注解决那一个问题。模型同理——它不用在每次响应时重新解析全部历史只要读取progress.md的当前状态就知道下一步该生成什么。我强制团队在每日站会只允许展示progress.md的 diff效果惊人会议时间从平均 45 分钟缩短到 12 分钟因为所有人注意力都聚焦在“哪些 ❌ 变成了 ✅”而不是争论“我们到底做到哪了”。文件不是妥协而是对人与机器认知边界的诚实承认。它不追求“无缝”而追求“可验证的缝合”。4. 实战从零搭建一个可运行的planning-with-files工作流理论讲完现在手把手带你搭一个能在今天下午就跑起来的最小可行工作流。这里不依赖任何第三方 CLI 工具只用最基础的 shell git 一个文本编辑器确保你能完全掌控每个环节。4.1 初始化创建你的第一个任务骨架假设你要重构一个老旧的 Node.js 日志模块目标是替换console.log为winston并添加结构化日志。在项目根目录执行# 创建标准文件夹结构 mkdir -p .claude-plans/{current,history} # 生成初始 task_plan.md cat .claude-plans/current/task_plan.md EOF # 日志模块重构ID: LOG-2024-08 ## ✅ 核心目标 - 将所有 console.log、console.error 替换为 winston.createLogger() 实例调用 - 日志输出格式必须为 JSON含字段 {timestamp: ..., level: ..., message: ..., service: auth} ## ⚠️ 硬性约束 - service 字段值必须来自文件所在目录名如 src/auth/login.js → service: auth - 禁止在日志消息中拼接敏感数据如 password, token需用占位符 [REDACTED] ## 验证方式 - 检查替换grep -r console\. src/ | wc -l 应返回 0 - 检查 JSON 格式node scripts/validate-log-format.js 应输出 All logs valid EOF # 生成空 findings.md 和 progress.md touch .claude-plans/current/findings.md cat .claude-plans/current/progress.md EOF ## 当前任务状态2024-08-12 16:00:00 UTC | 检查点 | 状态 | 时间戳 | 证据 | |--------|------|--------|------| | console_log_replaced | ❌ | 2024-08-12T16:00:00Z | grep -r console\. src/ 返回 42 行 | | json_format_valid | ❌ | 2024-08-12T16:00:00Z | node scripts/validate-log-format.js 未找到 | EOF # 提交初始状态 git add .claude-plans/current/ git commit -m chore(claude): init LOG-2024-08 planning files关键点.claude-plans/current/是你的工作区所有 Claude Code 的读写都发生在此。history/文件夹留作归档每次任务完成就把current/重命名为history/LOG-2024-08-$(date %Y%m%d)/。这个约定比任何工具都可靠。4.2 让 Claude Code “看见”文件CLI 调用的核心技巧Claude Code 本身不主动读文件你需要用 CLI 把文件内容喂给它。这里有两个关键技巧技巧一用catheredoc构造精准 prompt不要这样# ❌ 危险会泄露整个项目结构 claude code 请重构日志请参考 task_plan.md要这样构造结构化输入# ✅ 精确提供三要素 cat PROMPT_END 你正在执行任务 LOG-2024-08。请严格遵循以下文件 TASK_PLAN $(cat .claude-plans/current/task_plan.md) FINDINGS $(cat .claude-plans/current/findings.md) PROGRESS $(cat .claude-plans/current/progress.md) 请输出下一步操作1) 若有 ❌ 状态生成修复 patch2) 若全部 ✅输出 release note。 PROMPT_END技巧二用sedawk自动更新progress.md手动改progress.md是最大瓶颈。写个简单脚本自动同步# save as scripts/update-progress.sh #!/bin/bash # Usage: ./scripts/update-progress.sh console_log_replaced ✅ 2024-08-12T17:30:00Z grep -r console\. src/ | wc -l CHECKPOINT$1 STATUS$2 TIMESTAMP$3 EVIDENCE$4 # 用 sed 替换对应行的状态和时间戳 sed -i /$CHECKPOINT/ s/❌\|✅\|⚠️/$STATUS/ .claude-plans/current/progress.md sed -i /$CHECKPOINT/ s/2024-[0-9]\{2\}-[0-9]\{2\}T[0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}Z/$TIMESTAMP/ .claude-plans/current/progress.md sed -i /$CHECKPOINT/ s/.*Evidence.*/| $EVIDENCE |/ .claude-plans/current/progress.md # 自动提交 git add .claude-plans/current/progress.md git commit -m chore(claude): update $CHECKPOINT status to $STATUS运行./scripts/update-progress.sh console_log_replaced ✅ $(date -u %Y-%m-%dT%H:%M:%SZ) grep -r console\. src/ | wc -l一行命令完成状态更新提交。4.3 一个完整闭环从发现到验证的 5 分钟实操现在我们走一遍真实闭环。假设你刚运行了上面的初始化progress.md里console_log_replaced是 ❌。步骤 1让 Claude Code 生成 patch# 将三文件内容喂给 Claude Code claude code $(cat EOF 你正在执行任务 LOG-2024-08。请严格遵循以下文件 TASK_PLAN $(cat .claude-plans/current/task_plan.md) FINDINGS $(cat .claude-plans/current/findings.md) PROGRESS $(cat .claude-plans/current/progress.md) 请生成一个 patch将 src/auth/login.js 中的 console.log 替换为 winston 调用service 字段设为 auth。 EOF )它会输出类似这样的 patch--- a/src/auth/login.js b/src/auth/login.js -10,7 10,8 const express require(express); const router express.Router(); -const logger console; const winston require(winston); const logger winston.createLogger({ format: winston.format.json(), transports: [new winston.transports.Console()] }); router.post(/login, (req, res) { try { -25,3 26,3 res.status(200).json({ success: true }); } catch (err) { - console.error(Login failed:, err); logger.error({ message: Login failed, service: auth, error: err.message }); } });步骤 2应用 patch 并验证# 应用 patch假设保存为 patch.diff git apply patch.diff # 验证是否还有 console.log COUNT$(grep -r console\. src/ | wc -l) if [ $COUNT -eq 0 ]; then echo ✅ console.log replaced ./scripts/update-progress.sh console_log_replaced ✅ $(date -u %Y-%m-%dT%H:%M:%SZ) $COUNT else echo ❌ Still $COUNT console.log found fi步骤 3触发下一轮此时progress.md里console_log_replaced已是 ✅但json_format_valid还是 ❌。下次调用 Claude Code 时它会自动聚焦在json_format_valid的验证上比如生成scripts/validate-log-format.js的测试脚本。这个闭环之所以高效是因为所有状态变更都发生在文件层面而非对话记忆中。你关掉终端明天打开progress.md依然告诉你“现在该做什么”Claude Code 也依然知道“你现在在哪”。5. 那些没人告诉你的坑我在 12 个生产项目里踩出的经验清单再完美的设计落地时也会撞上现实的墙。以下是我在金融、电商、IoT 三个领域 12 个生产项目中用planning-with-files踩出的真坑以及最省力的解决方案。这些细节文档里永远不会写。5.1 坑一findings.md的“证据链”失效——当git blame指向错误提交现象findings.md里写git blame src/auth/login.py | grep 10.0.1.5但实际10.0.1.5是上个月从config.pycopy-paste 过来的git blame显示的是config.py的提交导致证据链断裂。原因git blame只追踪当前行的最后修改不追踪内容来源。对于 copy-paste、refactor、auto-gen 代码它完全失灵。解决方案永远用git log -S替代git blamegit log -S 10.0.1.5 --oneline -- src/auth/login.py会列出所有引入该字符串的提交无论是否 copy-paste。我在scripts/validate-findings.sh里强制校验任何findings.md中的git blame行都必须有对应的git log -S输出否则 CI 直接 fail。5.2 坑二progress.md的状态漂移——当多个开发者同时修改同一检查点现象工程师 A 把openapi_schema_unchanged改成 ✅工程师 B 同时把同一行改成 ❌Git 合并后变成| openapi_schema_unchanged | ✅ | ... | ❌ |模型无法解析。原因progress.md是表格但 Git 合并表格是灾难。解决方案不是禁止并发而是用单行键值对替代表格# progress.md 新格式推荐 CONSOLE_LOG_REPLACED✅;2024-08-12T17:30:00Z;grep -r console\. src/ | wc -l OPENAPI_SCHEMA_UNCHANGED❌;2024-08-12T17:35:00Z;openapi-diff old.yaml new.yamlGit 合并单行键值对几乎无冲突且sed -i s/^CONSOLE_LOG_REPLACED.*/CONSOLE_LOG_REPLACED✅;$(date -u %Y-%m-%dT%H:%M:%SZ);$(grep -r console\. src/ | wc -l)/ progress.md更可靠。我们在所有新项目强制切换至此格式。5.3 坑三task_plan.md的约束被“优雅降级”——当模型为满足 ✅ 而违反 ⚠️现象task_plan.md要求“禁止拼接敏感数据”但模型生成的代码里写了logger.info(User user.email logged in)它认为email不算敏感——而SECURITY_POLICY.md明确将 email 列为 PII。原因模型对“敏感”的定义是统计性的不是策略性的。它没见过你的SECURITY_POLICY.md。解决方案在task_plan.md顶部强制嵌入策略摘要## 合规策略摘要源自 SECURITY_POLICY.md v3.1 - PII 字段包括email, phone, name, address, id_number - 所有 PII 在日志中必须替换为 [REDACTED] - 违反者将触发 SOC2 审计告警模型看到这段会把email当作硬约束而不是模糊概念。我们在支付项目里加入此段后PII 泄露类 bug 下降 92%。5.4 坑四文件权限与路径混乱——当claude code读不到.claude-plans/现象本地测试一切正常CI 环境里claude code报错“找不到 task_plan.md”但文件明明存在。原因CI runner 的工作目录不是项目根目录或者.claude-plans/被.gitignore忽略了别笑真有人这么干。解决方案绝对路径 显式检查在所有调用脚本开头加# 获取项目根目录基于 git PROJECT_ROOT$(git rev-parse --show-toplevel) if [ ! -f $PROJECT_ROOT/.claude-plans/current/task_plan.md ]; then echo ERROR: task_plan.md not found at $PROJECT_ROOT/.claude-plans/current/ exit 1 fi并且 CI 配置里显式cd $PROJECT_ROOT。这是最无聊但最有效的防御。5.5 坑五时间戳时区混乱——当progress.md里的时间全是UTC但团队在东京现象东京团队成员看到2024-08-12T17:30:00Z以为是下午 5:30实际是凌晨 2:30导致误判进度。解决方案统一用 ISO 8601 带时区偏移不带 Zdate %Y-%m-%dT%H:%M:%S%z→2024-08-12T17:30:000900并在task_plan.md顶部声明⏰ 所有时间戳使用本地时区JST。模型会据此调整时间感知人类也一目了然。这些坑每一个都曾让我们在周五下午 5 点紧急 rollback。现在它们都固化在团队的claude-template仓库里新项目git clone就自带免疫。6. 超越 Claude Codeplanning-with-files作为通用工程协议的延展最后我想说点可能让你意外的事planning-with-files的价值早已超出 Claude Code 本身。它本质上是一种轻量级、文本化的工程协议Engineering Protocol可以无缝嫁接到任何需要人机协同的场景。6.1 嫁接到 CI/CD让流水线“读懂”任务意图我们把task_plan.md里的 验证方式直接转成 GitHub Actions 的 job# .github/workflows/claudetask.yml name: Claude Task Validation on: push: paths: - .claude-plans/current/** jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Run task plan validations run: | # 解析 task_plan.md 中的 部分提取命令 VALIDATION_CMD$(grep -A5 验证方式 .claude-plans/current/task_plan.md | grep -v | head -1 | sed s/^- //) echo Running: $VALIDATION_CMD eval $VALIDATION_CMD当工程师提交progress.mdCI 自动运行task_plan.md里定义的验证失败则阻断。这不再是“AI 在做事”而是“AI 和 CI 共同守护契约”。6.2 嫁接到文档系统自动生成架构决策记录ADRfindings.md里所有带标记的探测天然就是 ADR 的输入。我们用一个 10 行 Python 脚本把findings.md转成标准 ADR# scripts/gen-adr.py import re with open(.claude-plans/current/findings.md) as f: content f.read() # 提取所有 区块 blocks re.findall(r## (.*?)\n(.*?)(?\n## |\Z), content, re.DOTALL) for title, body in blocks: print(f# {title.strip()}\n\n## Context\n{body.strip()}\n\n## Decision\nTBD\n\n## Status\nproposed)每天晨会findings.md就是今天的 ADR 待办列表。工程师不再需要“额外写文档”文档就是工作流的副产品。6.3 嫁接到知识库让新成员 5 分钟看懂项目现状新入职工程师第一天不再被丢进 200 页 Confluence。我们给他一个命令# 查看所有进行中任务 ls .claude-plans/current/ # 查看最新进展 cat .claude-plans/current/progress.md # 查看关键发现 head -50 .claude-plans/current/findings.md三分钟他就知道“现在最紧急的是修复 JWT 过期”“上周发现的 Redis 硬编码还没处理”“task_plan.md里要求所有 API 必须有 OpenAPI 文档”。知识不是藏在 wiki 里而是活在工作流的文件里。这就是planning-with-files的终极意义它不试图让 AI 更像人而是让人和 AI 共同遵守一套更严谨、更透明、更可验证的工程语言。文件不是妥协是共识的载体Markdown 不是格式是契约的语法。我在最后一个项目交付时客户 CEO 看着progress.md里密密麻麻的 ✅对我说“这比你们所有的 PPT 汇报都让我安心。”那一刻我知道我们没在教 AI 写代码而是在重建人与机器协作的基本信任。