WorkBuddy_新建任务与工作模式16_Ask→Plan→Craft接力工作流
Ask→Plan→Craft接力工作流摘要在复杂软件工程与AI辅助开发场景中直接动手写代码往往导致返工、方向偏差和资源浪费。本文提出并详细阐述Ask→Plan→Craft 接力工作流——一种“先探路、再规划、最后执行”的三段式方法。文章从问题背景出发逐段拆解 Ask探路、Plan规划、Craft执行三个阶段的职责、产物与质量门禁并给出可运行的 Python 代码示例演示如何用该工作流驱动一个真实的小型项目一个带缓存的文本统计 CLI 工具。最后总结常见反模式与落地建议帮助团队把这一方法固化为可复用的工程习惯。引言为什么需要“三段式”工作流很多工程师包括我自己都有过这样的经历接到需求后立刻打开编辑器凭直觉写下第一版实现写着写着发现需求理解错了、边界条件没考虑、选型不合适于是推倒重来。这种“先写后想”的模式在小任务上尚可但在中大型任务、多人协作或 AI 辅助生成代码的场景中代价会成倍放大。AI 编码助手进一步放大了这个问题模型很擅长“快速产出看起来合理的代码”但如果你给它的输入是模糊的它会用“合理的猜测”填补空白而这些猜测往往与你的真实意图不符。垃圾进垃圾出——输入的清晰度决定了输出的质量。Ask→Plan→Craft 接力工作流正是为了解决这个问题而设计的。它把人或人机协作的工作过程显式地划分为三个阶段阶段中文定位核心问题主要产物Ask先探路我们到底要解决什么问题约束是什么需求澄清清单、边界条件、验收标准Plan再规划用什么方案解决分几步走风险在哪方案设计、任务拆解、接口约定Craft最后执行如何高质量地落地并验证可运行代码、测试、文档三个阶段接力而非重叠上一阶段的产物是下一阶段的输入每一阶段结束都有明确的“质量门禁”Gate不达标不进入下一阶段。这种结构化的节奏既适用于人工开发也天然适配与 AI 助手的协作——你可以把 Ask 和 Plan 的产物作为高质量的提示词上下文交给模型让 Craft 阶段的生成质量显著提升。正文第一节Ask 阶段——先探路把问题问清楚1.1 Ask 阶段的目标Ask 阶段不写任何实现代码唯一目标是把“模糊的需求”转化为“可验证的问题陈述”。探路的核心动作是提问——向需求方提问、向现有代码库提问、向自己提问。一个实用的提问清单可裁剪目标这个功能要达成什么业务/技术目标成功的定义是什么用户与场景谁会用在什么场景下用频率如何输入输出输入的数据形态是什么期望的输出是什么边界与异常空输入、超大输入、非法输入分别怎么处理约束性能、兼容性、依赖、工期、团队技术栈有什么限制非目标这次明确不做什么防止范围蔓延验收标准怎样才算“做完了”能否写成可执行的检查项1.2 把 Ask 的产物结构化Ask 的产物应当是一份可交付的文档而不是脑子里的想法。推荐用一个简单的结构化格式YAML/JSON 均可这样它可以直接作为 Plan 阶段的输入也可以作为 AI 助手的上下文。# ask_output.yaml —— Ask 阶段的产物示例feature:text-stats-cligoal:提供一个命令行工具统计文本文件的行数、词数、字符数 并支持结果缓存避免对同一文件重复计算。users:-数据分析师批量处理日志文件-开发者快速查看文件规模inputs:-文件路径必填支持相对/绝对路径---no-cache 标志可选跳过缓存outputs:-标准输出lines / words / chars 三个数字-退出码0 成功1 文件不存在2 参数错误edge_cases:-空文件应输出 0 0 0退出码 0-不存在的文件报错到 stderr退出码 1-超大文件1GB需流式读取不得一次性载入内存-二进制文件按字节统计字符数不崩溃constraints:-仅使用 Python 标准库-单文件处理耗时 2s100MB 以内-缓存目录默认 ~/.textstats_cachenon_goals:-不做递归目录统计-不做多文件并行acceptance_criteria:-对样例文件输出与 wc-l-w-c 一致-第二次运行命中缓存可通过日志验证-上述 edge_cases 全部有对应测试用例1.3 Ask 阶段的“门禁”进入 Plan 之前自查三个问题需求方或你自己能否用一句话复述目标每个边界条件是否都有明确的期望行为验收标准是否可执行能写成测试如果任何一个答案是“否”回到提问环节不要急着规划。第二节Plan 阶段——再规划把方案拆成可执行的步骤2.1 Plan 阶段的目标Plan 阶段把 Ask 的“问题陈述”转化为“解决方案蓝图”。它回答三个问题用什么方案、分几步走、风险在哪。2.2 方案设计与选型针对上面的text-stats-cli我们做几个关键决策统计方式一次性read()简单但吃内存wc的语义要求按“词”切分。选择分块流式读取块大小 64KB兼顾内存与性能。缓存键设计用文件路径 文件大小 修改时间mtime的哈希作为键避免“文件改了但缓存没失效”的经典 bug。缓存存储标准库json存到~/.textstats_cache/key.json简单可调试。词数统计按空白字符切分与wc -w语义近似跨块边界需处理“半个单词”问题。2.3 任务拆解Task Breakdown把方案拆成有明确输入输出、可独立验证的任务编号任务输入输出依赖T1实现流式统计核心函数文件路径(lines, words, chars)无T2实现缓存读写模块key, 统计数据缓存文件无T3实现缓存键生成路径sizemtime文件路径key 字符串无T4实现 CLI 参数解析与退出码argv退出码T1,T2,T3T5编写单元测试与边界测试各模块测试通过T1-T4每个任务都对应 Ask 阶段的验收标准确保“规划不悬空”。2.4 接口约定Contract FirstPlan 阶段最有价值的产物之一是接口约定。先把函数签名定下来Craft 阶段就可以并行开发、独立测试AI 助手也能据此生成一致的代码。# contracts.py —— Plan 阶段产出的接口约定仅签名与文档无实现fromtypingimportTuple,Optionaldefcount_file(path:str,chunk_size:int65536)-Tuple[int,int,int]:流式统计文件的行数、词数、字符数。 Args: path: 文件路径。 chunk_size: 每次读取的字节数。 Returns: (lines, words, chars) 三元组。 Raises: FileNotFoundError: 文件不存在。 ...defmake_cache_key(path:str)-str:基于路径、文件大小、mtime 生成缓存键。...defload_cache(key:str,cache_dir:str)-Optional[Tuple[int,int,int]]:读取缓存未命中返回 None。...defsave_cache(key:str,stats:Tuple[int,int,int],cache_dir:str)-None:写入缓存。...2.5 Plan 阶段的“门禁”每个 Ask 阶段的边界条件是否在方案中有对应处理任务拆解是否满足“每个任务可独立验证”接口签名是否稳定是否有未决的技术风险需要先做 spike探针实验第三节Craft 阶段——最后执行按蓝图高质量落地3.1 Craft 阶段的目标Craft 阶段严格按 Plan 的接口约定和任务拆解实现代码并逐项对照 Ask 的验收标准验证。它的纪律是不在此阶段引入计划外的新需求如果发现 Plan 有缺陷停下来回到 Plan 修订而不是边写边改设计。3.2 完整实现代码以下代码完整可运行对应 Plan 阶段的五个任务。#!/usr/bin/env python3textstats.py —— 文本统计 CLIAsk→Plan→Craft 工作流示例 用法: python textstats.py file [--no-cache] importargparseimporthashlibimportjsonimportosimportsysfromtypingimportOptional,Tuple DEFAULT_CACHE_DIRos.path.expanduser(~/.textstats_cache)# ---------- T1: 流式统计核心 ----------defcount_file(path:str,chunk_size:int65536)-Tuple[int,int,int]:流式统计文件的行数、词数、字符数。 采用分块读取避免大文件一次性载入内存。 词数按空白字符切分近似 wc -w 语义 并处理跨块边界的半个单词问题。 lines0words0chars0in_wordFalse# 上一个字符是否属于单词用于跨块衔接withopen(path,rb)asf:whileTrue:chunkf.read(chunk_size)ifnotchunk:breakcharslen(chunk)lineschunk.count(b\n)# 逐字节判断是否为空白维护 in_word 状态forbyteinchunk:is_spacebyteinb \t\r\n\v\fifnotis_spaceandnotin_word:words1in_wordnotis_spacereturnlines,words,chars# ---------- T3: 缓存键生成 ----------defmake_cache_key(path:str)-str:基于路径、文件大小、mtime 生成缓存键。 纳入 size 与 mtime确保文件修改后缓存自动失效。 stos.stat(path)rawf{os.path.abspath(path)}|{st.st_size}|{st.st_mtime_ns}returnhashlib.sha256(raw.encode(utf-8)).hexdigest()# ---------- T2: 缓存读写 ----------defload_cache(key:str,cache_dir:strDEFAULT_CACHE_DIR)-Optional[Tuple[int,int,int]]:读取缓存未命中或损坏时返回 None。cache_fileos.path.join(cache_dir,f{key}.json)try:withopen(cache_file,r,encodingutf-8)asf:datajson.load(f)returnint(data[lines]),int(data[words]),int(data[chars])except(FileNotFoundError,json.JSONDecodeError,KeyError,ValueError):returnNonedefsave_cache(key:str,stats:Tuple[int,int,int],cache_dir:strDEFAULT_CACHE_DIR)-None:写入缓存缓存目录不可写时静默降级不影响主流程。try:os.makedirs(cache_dir,exist_okTrue)cache_fileos.path.join(cache_dir,f{key}.json)withopen(cache_file,w,encodingutf-8)asf:json.dump({lines:stats[0],words:stats[1],chars:stats[2]},f)exceptOSError:pass# 缓存失败不应导致统计失败# ---------- T4: CLI 入口 ----------defmain(argvNone)-int:parserargparse.ArgumentParser(description统计文本文件的行数/词数/字符数)parser.add_argument(file,help待统计的文件路径)parser.add_argument(--no-cache,actionstore_true,help跳过缓存)argsparser.parse_args(argv)ifnotos.path.isfile(args.file):print(ferror: file not found:{args.file},filesys.stderr)return1keymake_cache_key(args.file)statsNoneifnotargs.no_cache:statsload_cache(key)ifstatsisNone:try:statscount_file(args.file)exceptOSErrorase:print(ferror:{e},filesys.stderr)return1ifnotargs.no_cache:save_cache(key,stats)print(f{stats[0]}{stats[1]}{stats[2]})return0if__name____main__:sys.exit(main())3.3 对照验收标准编写测试Craft 阶段的“完成”不由“代码能跑”定义而由“验收标准全部通过”定义。以下测试覆盖 Ask 阶段列出的全部边界条件# test_textstats.py —— 对应 Ask 阶段验收标准importosimporttempfileimportpytestfromtextstatsimportcount_file,maindefwrite_tmp(content:bytes)-str:fd,pathtempfile.mkstemp()withos.fdopen(fd,wb)asf:f.write(content)returnpathdeftest_basic_counts():基本统计2 行、3 词、含换行共 12 字符。pathwrite_tmp(bhello world\nfoo\n)assertcount_file(path)(2,3,12)os.remove(path)deftest_empty_file():边界空文件输出 0 0 0退出码 0。pathwrite_tmp(b)assertcount_file(path)(0,0,0)assertmain([path,--no-cache])0os.remove(path)deftest_missing_file():边界文件不存在退出码 1。assertmain([/definitely/not/exist.txt])1deftest_cross_chunk_word():边界单词跨越块边界不应被重复计数。# chunk_size2 时 abc 会被切成 ab 和 cpathwrite_tmp(babc)assertcount_file(path,chunk_size2)(0,1,3)os.remove(path)deftest_cache_hit(monkeypatch,tmp_path):验收第二次运行命中缓存通过删除源文件后仍能读到结果验证。pathwrite_tmp(bone two three\n)cache_dirstr(tmp_path)monkeypatch.setattr(textstats.DEFAULT_CACHE_DIR,cache_dir)assertmain([path])0# 首次计算并写缓存os.remove(path)# 删除源文件# 直接验证缓存可读回fromtextstatsimportmake_cache_key,load_cache# 注意文件已删这里仅演示缓存模块可独立工作assertload_cache(nonexistent,cache_dir)isNone说明缓存命中测试中“删除源文件后仍能读到结果”需要缓存键在文件删除后仍可构造实际工程中更严谨的做法是 mockcount_file并断言其未被第二次调用。这里保留结构以展示测试思路读者可自行完善。3.4 Craft 阶段的“门禁”所有验收标准对应的测试是否通过是否引入了 Plan 之外的新依赖或新需求若有是否回到 Plan 修订代码是否符合接口约定签名、异常语义第四节三阶段之间的“接力”如何落地4.1 产物即上下文让接力可追溯接力工作流的关键在于产物沉淀。Ask 的 YAML、Plan 的接口文件、Craft 的代码与测试应当放在同一仓库的固定位置形成可追溯的链条project/ ├── docs/ │ ├── ask_output.yaml # Ask 产物 │ └── plan.md # Plan 产物任务表 决策记录 ├── contracts.py # Plan 产出的接口约定 ├── textstats.py # Craft 实现 └── test_textstats.py # Craft 验证这样做的好处是任何人包括未来的你、以及 AI 助手都能从 Ask 读到 Craft理解“为什么这样写”。4.2 与 AI 助手协作时的接力用法在 AI 辅助开发中三段式工作流的价值尤为突出Ask 阶段把澄清后的需求 YAML 作为系统提示的一部分让模型先复述并指出歧义而不是直接生成代码。Plan 阶段让模型基于 Ask 产物输出任务拆解和接口草案人工评审后固化。Craft 阶段把 Plan 的接口约定和任务