gstack /autoplan Implementation Tasks Aggregator 原理与实战:多阶段评审任务的跨阶段汇聚管道

📅 发布时间:2026/9/7 17:31:11
gstack /autoplan Implementation Tasks Aggregator 原理与实战:多阶段评审任务的跨阶段汇聚管道 gstack /autoplan Implementation Tasks Aggregator 原理与实战多阶段评审任务的跨阶段汇聚管道【免费下载链接】gstackUse Garry Tans exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstack导读本文详解 gstack 的/autoplan自动评审流水线中Phase 4「Final Approval Gate」所使用的 Implementation Tasks Aggregator任务汇聚器它把 CEO、Design、Eng、DX 四个评审阶段各自写入磁盘的 JSONL 任务清单聚合、去重、排序后渲染成一个统一的### Implementation Tasks勾选清单。读完本文你将掌握任务 JSONL 的落盘格式与文件命名规则、聚合脚本每一段 jq 管道的语义分支/提交窗口过滤、run_id 去重、精确去重与优先级排序、运行时变量替换与构建期模板占位符的区别以及那个让聚合器永远输出零任务的 #2018 jq 上下文重绑定陷阱及其回归测试。本文对应的权威文档是 tasks-aggregator.md它在整个流水线中的触发时机由 autoplan/SKILL.md 的 Section index 与 sections/manifest.json 共同注册在呈现 Final Approval GatePhase 4时读取聚合器算出的$AGGREGATED_TASKS会被闸门消息替换进去。全文所有代码与路径均可在当前仓库中逐一验证。一、聚合器在 /autoplan 流水线中的位置/autoplan的目标是一条命令粗糙计划进、全量评审计划出它从磁盘读取四个评审 skillCEO、Design、Eng、DX并按严格顺序串行执行——CEO → Design → Eng → DX每一阶段必须完整结束后才进入下一阶段见 autoplan/SKILL.md 的 Sequential Execution — MANDATORY。中间的所有 AskUserQuestion 都基于 6 条决策原则自动裁决品味类决策close approaches、borderline scope、codex 分歧与 User Challenge 被保留到最后的 Final Approval Gate 让用户做最终判断。每个评审阶段在结束时不仅要产出评审结论还要为后续实施沉淀一份可构建的扁平任务列表。聚合器就是这些阶段产物与最终闸门之间的汇流点上游四个评审 skill 各自写入tasks-phase-*.jsonl任务工件聚合器把四份列表合并为一个全局有序、无重复的清单下游Final Approval Gate 输出模板中的### Implementation Tasks (aggregated across phases)小节直接渲染这个清单供用户在 A) 批准 / B) 覆盖 / C) 盘问 / D) 修订 / E) 拒绝之间做选择。值得强调的是聚合器不是用户手动运行的一个命令而是一段由 Agent 在运行时执行的 bash/jq 指令序列。gstack 以决策树骨架方式组织 skill主 SKILL 只负责编排读哪个 section、何时读而 tasks-aggregator.md 是 Phase 4 这一具体步骤的真源source of truth。SKILL 中的指引明确写着Read~/.claude/skills/gstack/autoplan/sections/tasks-aggregator.mdand execute it in full. Do not work from memory — that section is the source of truth for this step.也就是说任何人含 Agent 自身想理解 Phase 4 之前如何准备任务清单只需读这一个文件即可。二、数据源每个评审阶段写下的 JSONL 任务工件2.1 工件格式与写入端在 sections/manifest.json 中聚合器被声明为id: tasks-aggregator对应 Phase 4。而它的上游——每个评审阶段逐任务写 JSONL的行为——由解析器{{TASKS_SECTION_EMIT:phase}}负责渲染实现在 scripts/resolvers/tasks-section.ts。该文件头部注释直接说明了两种解析器的分工{{TASKS_SECTION_EMIT:phase}}单 skill 的任务产出 JSONL 写入CEO/Design/Eng/DX 四个评审各自使用{{TASKS_SECTION_AGGREGATE}}autoplan 跨阶段聚合即本主题。VALID_PHASES集合只接受四个值ceo-review、design-review、eng-review、devex-review传入其他值会让解析器直接抛错tasks-section.ts。四个评审 skill 的实际写入代码位于各自的review-sections.md中例如 plan-ceo-review/sections/review-sections.md、plan-eng-review/sections/review-sections.md。每条任务以一行 JSON 追加到文件末尾字段 schema 在 tasks-section.ts 中有权威定义字段含义取值示例phase来源评审阶段ceo-review/design-review/eng-review/devex-reviewrun_id本次运行的唯一标识20260814T000000Z-12345UTC 时间戳 进程 PIDbranch评审运行时的当前分支feature/xcommit评审针对的 HEAD 提交完整 SHAabc123def...id任务编号T1、T2…priority优先级P1阻塞发布/P2应落在同一分支/P3后续 TODOcomponent所属组件如browse/src/server.ts所属模块名files涉及文件JSON 数组字面量[browse/src/sanitize.ts,browse/src/server.ts]effort_human人类工作量估计~2heffort_ccCCClaude Code gstack时间估计~15mintitle任务的祈使句标题Add null check in cookie importsource_finding溯源哪个 section 的哪条 findingsection name — finding 摘要写入端的固定约束包括文件命名$TASKS_DIR/tasks-phase-$(date %Y%m%d-%H%M%S).jsonl例如tasks-ceo-review-20260814-093015.jsonlrun_id使用$(date -u %Y%m%dT%H%M%SZ)-$$$$是写入进程的 PID保证同秒内多次运行也不撞号必须用jq -nc构造每一行禁止手写echo/printf拼 JSON——因为 title 与 source_finding 可能含引号、换行、反斜杠只有交给 jq 序列化才安全tasks-section.ts零发现时仍要触碰该文件: $TASKS_FILE因为空文件 跑过但无 finding与没有文件 根本没跑在聚合端是两种语义tasks-section.tsjq缺失时跳过 JSONL 写入并向用户告警但绝不手工伪造 JSONL。这些字段名之所以重要是因为聚合脚本要按它们逐个做过滤、去重与排序。2.2 读取端目录约定TASKS_DIR派生自gstack-slug计算出的项目别名SLUGeval $(~/.claude/skills/gstack/bin/gstack-slug 2/dev/null) TASKS_DIR${HOME}/.gstack/projects/${SLUG:-unknown}SLUG无法取得时回退为字面量unknown避免目录拼接失败。四个评审阶段写入、聚合器读取的正是~/.gstack/projects/slug/下同一组文件。三、聚合主脚本逐步拆解下面这段脚本出自 tasks-aggregator.md 的完整原文也与 tasks-section.ts 中generateTasksSectionAggregate运行时产出的内容一致先整体给出再分五步解读eval $(~/.claude/skills/gstack/bin/gstack-slug 2/dev/null) TASKS_DIR${HOME}/.gstack/projects/${SLUG:-unknown} BRANCH$(git branch --show-current 2/dev/null || echo unknown) # Commit window: last 5 commits on this branch. Drops stale standalone reviews. COMMITS_RECENT$(git log --format%H -n 5 2/dev/null | tr \n | | sed s/|$//) AGGREGATED_TASKS if command -v jq /dev/null 21; then # Collect entries from all 4 phases, scoped to current branch commit window. # For each phase, keep only the latest run_id. Within the surviving set, # dedupe by (component, sorted(files), title) — exact match only. # Sort by priority (P1 P2 P3) then by phase order. ALL_JSONL$(mktemp -t autoplan-tasks.XXXXXXXX) for phase in ceo-review design-review eng-review devex-review; do # Use find instead of glob expansion — zsh nomatch errors otherwise when # a phase produced no JSONL files. Sorting by name keeps the order stable. while IFS read -r f; do [ -f $f ] || continue # Filter to current branch recent commits, then keep records for the # latest run_id only. (Single phase may have multiple files if the user # re-ran the review; aggregator takes the newest.) # .commit must be bound BEFORE piping to the split commit array: a # pipe rebinds jqs context, so a bare .commit after it indexes the # ARRAY with a string, every line errors into 2/dev/null, and the # aggregate is empty forever — the #2018 zero-tasks bug. jq -c --arg branch $BRANCH --arg commits $COMMITS_RECENT \ .commit as $c | select(.branch $branch and ($commits | split(|) | index($c) ! null)) \ $f 2/dev/null $ALL_JSONL || true done (find $TASKS_DIR -maxdepth 1 -name tasks-$phase-*.jsonl 2/dev/null | sort) # Reduce to latest run_id per phase if [ -s $ALL_JSONL ]; then jq -sc --arg phase $phase \ [.[] | select(.phase $phase)] | (max_by(.run_id) // null) as $latest_run | if $latest_run then map(select(.run_id $latest_run.run_id)) else [] end | .[] \ $ALL_JSONL $ALL_JSONL.phase 2/dev/null || true # Replace with reduced version for this phase, accumulating others jq -c --arg phase $phase select(.phase ! $phase) $ALL_JSONL $ALL_JSONL.other 2/dev/null || true cat $ALL_JSONL.other $ALL_JSONL.phase $ALL_JSONL rm -f $ALL_JSONL.phase $ALL_JSONL.other fi done # Exact-match dedup by (component, sorted(files), title). Non-matches kept # separately with a possible-duplicate marker injected by the renderer. AGGREGATED_TASKS$(jq -s \ group_by([.component, (.files | sort), .title]) | map( # Take the highest-priority entry per group; tie-break by phase order sort_by({P1:0,P2:1,P3:2}[.priority] // 99, {ceo-review:0,design-review:1,eng-review:2,devex-review:3}[.phase] // 99) | .[0] ) | sort_by({P1:0,P2:1,P3:2}[.priority] // 99, {ceo-review:0,design-review:1,eng-review:2,devex-review:3}[.phase] // 99) | if length 0 then _No actionable tasks emitted from any phase._ else map(- [ ] **\(.id) (\(.priority), human: \(.effort_human) / CC: \(.effort_cc)) — \(.component)** — \(.title)\n - Surfaced by: \(.phase) — \(.source_finding)\n - Files: \(.files | join(, ))) | join(\n) end $ALL_JSONL 2/dev/null | sed s/^//;s/$//;s/\\n/\n/g) rm -f $ALL_JSONL else AGGREGATED_TASKS_jq not installed — install jq to aggregate per-phase task lists. Skipping._ fi步骤 1建立运行上下文与提交窗口脚本开头确定四个关键变量SLUG由gstack-slug输出定位项目级工件目录~/.gstack/projects/slugTASKS_DIR上述目录所有 JSONL 的读写点BRANCH当前分支名git branch --show-current失败时回退为unknownCOMMITS_RECENT当前分支最近 5 个提交的完整 SHA用|连接成单串。它的作用是丢弃过期的独立评审——如果你在别的分支或很久之前跑过评审其任务不会混入本次闸门。步骤 2按阶段收集 分支/提交窗口过滤外层for phase in ceo-review design-review eng-review devex-review固定按 CEO → Design → Eng → DX 的顺序遍历。内层用find而非 shell 通配符展开来枚举tasks-$phase-*.jsonl——注释明确说明这是为了兼容 zsh阶段没有产出任何 JSONL 时通配符展开会触发nomatch报错中断脚本findsort则既无此问题又保证枚举顺序稳定。对每个文件的过滤是整段脚本的心智核心jq -c --arg branch $BRANCH --arg commits $COMMITS_RECENT \ .commit as $c | select(.branch $branch and ($commits | split(|) | index($c) ! null)) \ $f 2/dev/null $ALL_JSONL || true语义是仅保留branch等于当前分支、且commit命中最近 5 提交窗口的记录。2/dev/null与|| true是刻意的容错设计——单个文件损坏不应杀死整个聚合坏行被丢弃即可。步骤 3每个阶段只保留最新一次运行run_id 收敛同一阶段可能因用户重跑评审而存在多个文件每次运行一个时间戳文件名、一个run_id。聚合原则是取最新一次运行否则会重复计数[.[] | select(.phase $phase)] | (max_by(.run_id) // null) as $latest_run | if $latest_run then map(select(.run_id $latest_run.run_id)) else [] end | .[]max_by(.run_id)基于YYYYMMDDTHHMMSSZ-PID字符串做字典序比较天然等价于时间排序// null防御空输入随后用select保留该次运行的全部记录。临时文件.phase/.other的cat重组技巧实现了只缩当前阶段、保留其余阶段累积结果的原地归并。步骤 4跨阶段精确去重 双键排序所有阶段汇聚进ALL_JSONL后最终渲染前做一次全局去重与排序去重键(component, sorted(files), title)三元组的完全相等匹配。files先sort再比较使文件书写顺序不同但集合相同的任务仍被视为重复tasks-section.ts 注释还提到未命中的疑似重复会由渲染器另行标记 possible-duplicate而非静默丢弃组内择优sort_by({P1:0,P2:1,P3:2}[.priority] // 99, {...}[.phase] // 99)取每组第一条即同组冲突时高优先级P1获胜再平局则按阶段顺序 ceo → design → eng → devex 取先者缺失的优先级/阶段字段映射到99表示最低优先最终排序同上双键——先优先级P1 P2 P3再阶段顺序。步骤 5渲染 Markdown 勾选清单排序后的记录被映射为每条一行、符合gstack 风格的待办项- [ ] **T1 (P1, human: ~1h / CC: ~15min) — some-component** — Add null check - Surfaced by: ceo-review — demo finding - Files: browse/src/sanitize.ts, browse/src/server.ts渲染模板为map(- [ ] **\(.id) (\(.priority), human: \(.effort_human) / CC: \(.effort_cc)) — \(.component)** — \(.title)\n - Surfaced by: \(.phase) — \(.source_finding)\n - Files: \(.files | join(, )))。外层sed s/^//;s/$//;s/\\n/\n/g负责去掉 jq 输出字符串的外层引号并把\n还原为真实换行。没有记录时输出占位文案_No actionable tasks emitted from any phase._。四、运行时替换而非构建期模板阅读 tasks-aggregator.md 时要特别区分两个层面构建期gen-skill-docstasks-aggregator.md由tasks-aggregator.md.tmpl渲染而来而后者只有一行占位符{{TASKS_SECTION_AGGREGATE}}见 tasks-aggregator.md.tmpl由generateTasksSectionAggregate解析器填充成你看到的完整脚本文本。文档头部注释AUTO-GENERATED from tasks-aggregator.md.tmpl — do not edit directly也印证了这一点手工改.md会被下一次bun run gen:skill-docs覆盖运行时Agent 执行$AGGREGATED_TASKS不是模板占位符。文档明确写道This is NOT a template placeholder — the agent does the substitution at runtime, not gen-skill-docs at build time.即Agent 按脚本实际执行并把 bash 变量$AGGREGATED_TASKS的内容在打印闸门消息前手工替换进 Final Approval Gate 模板中的### Implementation Tasks (aggregated across phases)小节该小节模板见 autoplan/SKILL.md。如果把它当成构建期占位符处理四个阶段的任务就永远不会出现在用户的最终报告里。五、空结果与降级路径5.1 两种空必须区分无文件本轮没有任何评审 skill 运行渲染_No per-phase task lists found in $TASKS_DIR for branch $BRANCH. Each review skill writes its own; if you ran one of them but no list appears here, check that jq is installed and the tasks-phase-*.jsonl files exist._有文件但零任务所有阶段都跑了、确认无 finding聚合器输出_No actionable tasks emitted from any phase._。区分二者的关键在于 2.1 节提到的写入约定空文件意味着跑过、没发现无文件意味着压根没跑。这也解释了为什么脚本在if [ -s $ALL_JSONL ]后才做 run_id 收敛——空文件不该触发任何统计。5.2 jq 缺失时的降级脚本首先检查command -v jq。jq 不可用时AGGREGATED_TASKS被置为字符串_jq not installed — install jq to aggregate per-phase task lists. Skipping._闸门消息仍可完整呈现只是任务清单段落变成安装提示流水线不因此中断。六、#2018 零任务 Bug一个 jq 上下文陷阱的真实教训聚合脚本注释中专门内嵌了一段 bug 史值得单独展开因为它是理解 jq 语义与整个过滤链正确性的一把钥匙tasks-aggregator.md.commit must be bound BEFORE piping to the split commit array: a pipe rebinds jqs context, so a bare .commit after it indexes the ARRAY with a string, every line errors into 2/dev/null, and the aggregate is empty forever — the #2018 zero-tasks bug.错误形态曾经是select(.branch $branch and ($commits | split(|) | index(.commit) ! null))根因在 jq 中|管道会把求值上下文重绑定到左侧表达式的结果上。这里$commits | split(|)之后的上下文是字符串数组此时再写裸.commit实际是对数组做字符串索引[abc123,def456][commit]每一行都抛Cannot index array with string commit错误被2/dev/null吞掉、|| true吞掉退出码最终结果是聚合永远为空且与确实没有任务在现象上无法区分——一个死亡但外表正常的功能。正确形态是先用as把值绑定到变量再做管道.commit as $c | select(.branch $branch and ($commits | split(|) | index($c) ! null)).commit as $c在管道之前执行$c通过变量作用域穿透后续管道不再受上下文重绑定影响。回归保障落在 test/tasks-section-jq.test.ts该测试从解析器源码中正则提取真实下发的 jq 程序而非复制一份近似实现再对着 fixture JSONL 用真实jq二进制执行验证同分支且在窗口内能匹配、异分支/窗口外提交被滤除、空输入不伪造输出最后一条测试直接断言源码中不允许再次出现split(|) | ... | index(.commit)这种会重绑定上下文的写法。任何重引入该缺陷形态的改动都会让这些测试变红这正是文档注释the #2018 zero-tasks bug被逐字保留在产物脚本中的原因——它同时是给未来维护者的现场注释与给回归测试的显式契约。七、实战排查清单结合上游写入端与聚合脚本当评审跑了但闸门里没有任务清单时按下面顺序自查jq 是否安装聚合脚本输出是否为_jq not installed ...文案文件是否存在ls ~/.gstack/projects/slug/tasks-phase-*.jsonl。完全不存在 对应评审阶段本轮没跑也可能没有 UI/DX 范围而合法跳过 Phase 2 / Phase 3.5分支与提交窗口任务记录的branch/commit是否属于当前分支最近 5 个提交。文件存在但记录全部被窗口滤除时聚合结果同样为空——这正是该过滤想表达的语义只信本次评审文件是否为空wc -l为 0 表示该阶段跑过但零 finding属正常结果而非故障是否重跑过评审同一阶段多个文件时聚合只认run_id最大最新的那次内容是否含特殊字符检查 JSONL 行是否由jq -nc生成而非手写拼串title/source_finding 中的引号或换行是否被正确转义。八、小结从评审输出到可勾选实施清单的最后一步Implementation Tasks Aggregator 是/autoplan自动化闭环中承上启下的枢纽上游吞掉四个评审阶段ceo-phase、design-phase、eng-phase、dx-phase产出的 JSONL 工件下游吐给 Final Approval Gate 一份已按当前分支与最近 5 提交过滤、按 run_id 收敛到最新、按 (component, files, title) 精确去重、按 P1→P3 与 CEO→DX 排序的 markdown 勾选清单。它把多模型、多阶段的评审噪音压缩成批准、覆盖、盘问、修订、拒绝五个选项前的那一屏事实而其正确性由文档内嵌注释、运行时替换约定与 #2018 回归测试三重保障。若想深入其实现细节最直接的阅读路径是先读 tasks-aggregator.md 了解行为契约再到 tasks-section.ts 看两种解析器如何产出脚本最后在 tasks-section-jq.test.ts 里看真实 jq 程序的回归演练。【免费下载链接】gstackUse Garry Tans exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考