project-plan

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

/project-plan

/project-plan

Turn a one-line idea into a Linear project sized for hands-off parallel execution.
将一句简短的想法转化为适合无人值守并行执行的Linear项目。

Conventions

约定

yaml
linear:
  team: thesignup            # Linear team name to file tickets under
  ticket_prefix: THE         # Used in display; Linear assigns the actual id.
If unset, ask which Linear team to use (
mcp__claude_ai_Linear__list_teams
).
yaml
linear:
  team: thesignup            # 工单所属的Linear团队名称
  ticket_prefix: THE         # 用于显示;Linear会分配实际ID。
如果未设置,询问应使用哪个Linear团队(调用
mcp__claude_ai_Linear__list_teams
)。

When to use

使用场景

  • Starting any non-trivial body of work (roughly 5+ tickets).
  • Scaffolding a follow-up to a project that just shipped.
  • Batch-creating several projects in planning state — run
    /project-plan
    multiple times; projects sit in Backlog until
    /project-start
    fires them.
For one-off tickets, skip this and use
/start
directly.
  • 启动任何非琐碎的工作任务(约5个及以上工单)。
  • 为刚上线的项目搭建后续规划。
  • 批量创建多个处于规划状态的项目——多次运行
    /project-plan
    ;项目将处于Backlog状态,直到执行
    /project-start
    才会启动。
如果是单个工单,直接使用
/start
即可,无需此技能。

Plan against
origin/main
, not your local checkout — fetch first

基于
origin/main
规划,而非本地分支——先执行fetch

Before any probing or ticketing, run
git fetch origin
and plan against
origin/main
.
A local
HEAD
can be hours stale, and every premise you verify against a stale tree is a coin-flip.
On the participant-flow-repair project the planner ran
git log
without fetching; local
main
was 21 commits behind
origin/main
. In that gap, other work had already fixed bugs the plan was about to file, shipped a data-model the plan was about to duplicate, and turned one "bug" into deliberate security design. Four of eighteen tickets were cancelled at execution time for this single omission — including two where building the ticket as written would have reintroduced a security hole the codebase had just closed.
Concretely, at plan start:
  • git fetch origin
    and note how far behind you are:
    git rev-list --count HEAD..origin/main
    . If it's non-zero, every file:line you cite must be verified against
    origin/main
    (
    git show origin/main:<path>
    ), never the working tree.
  • When you quote code in a ticket ("
    foo.ts:88
    is a ternary"), you are asserting it's true now. Read the body on
    origin/main
    , not from memory or a skimmed grep — a signature is not an implementation (one ticket wired a checkbox to a procedure that turned out to be a
    console.log
    stub returning
    success:true
    ).
  • Re-verify at execution time too: every
    /start
    prompt should tell the agent to
    git fetch
    and branch from the fetched
    origin/main
    , and to stop and report if a premise is already fixed — treat "this is stale" as a success, not a failure. That instruction is what let subagents catch the planner's stale-tree errors before they shipped.
**在进行任何探查或工单创建前,先运行
git fetch origin
并基于
origin/main
进行规划。**本地
HEAD
可能已过时数小时,基于过时代码树验证的所有前提都存在不确定性。
在participant-flow-repair项目中,规划者未执行fetch就运行了
git log
;本地
main
分支比
origin/main
落后21个提交。在这段时间里,其他工作已经修复了规划中要创建的bug,上线了规划中要重复开发的数据模型,还将一个“bug”转化为了有意的安全设计。最终18个工单中有4个在执行阶段被取消——其中两个工单如果按原计划开发,会重新引入代码库刚修复的安全漏洞
具体来说,在规划开始时:
  • 执行
    git fetch origin
    并记录落后的提交数:
    git rev-list --count HEAD..origin/main
    。如果数值非零,所有引用的文件行都必须基于
    origin/main
    验证
    (使用
    git show origin/main:<path>
    ),绝不能使用工作树中的文件。
  • 当在工单中引用代码时(如“
    foo.ts:88
    是一个三元表达式”),你需要确认这在当前
    origin/main
    上是真实存在的。要读取
    origin/main
    上的代码内容,而非凭记忆或粗略搜索——签名不等于实现(曾有工单将复选框连接到一个
    console.log
    存根函数,该函数仅返回
    success:true
    )。
  • 在执行阶段也要重新验证:每个
    /start
    指令都应告知agent执行
    git fetch
    并基于拉取的
    origin/main
    创建分支,并且如果前提条件已被修复,要停止执行并报告——将“内容已过时”视为一种成功,而非失败。正是这一指令让子agent在上线前发现了规划者基于过时代码树的错误。

Cross-check other in-flight
.handoffs/
bundles before writing tickets

在创建工单前,交叉检查其他正在执行的
.handoffs/

/project-plan
is often run while other planned projects are executing in parallel. Enumerate them —
ls .handoffs/*/tickets.yaml
— and skim each for overlap with your scope before writing tickets. On participant-flow-repair, one ticket (a plus-one data model) turned out to duplicate the entire first half of a separate
p4-rsvp-first-class
project whose foundational ticket had merged two hours earlier; two projects building the same primitive is the worst outcome. Flag overlapping scope, shared files (two projects editing one file race at merge), and migration-number collisions (both projects claiming
0060_*.sql
) in the project description, and re-scope or cancel the overlap at plan time.
/project-plan
通常会在其他规划好的项目并行执行时运行。在创建工单,先枚举这些项目——运行
ls .handoffs/*/tickets.yaml
——并浏览每个文件以检查与当前范围的重叠部分。在participant-flow-repair项目中,一个工单(添加一个数据模型)意外重复了另一个
p4-rsvp-first-class
项目的前半部分内容,而该项目的基础工单在两小时前已合并;两个项目开发相同的基础功能是最糟糕的结果。在项目描述中标记重叠范围、共享文件(两个项目编辑同一文件会导致合并冲突)以及迁移编号冲突(两个项目都使用
0060_*.sql
),并在规划阶段重新调整范围或取消重叠部分。

Probe the codebase before declaring file surfaces

在确定文件范围前,先探查代码库

Before any tickets are written, probe the repo for its actual conventions — declared paths that don't match reality cause silent drift at execution time. The probe is cheap; do it once at plan start and refer to it while filling
files:
for each ticket.
Run these globs (adapt to language/framework as needed):
  • Server services:
    src/server/services/**
    (or equivalent). Note the actual structure — is it
    src/server/services/<group>/<thing>.ts
    or flat
    src/server/services/<thing>.service.ts
    ? Across projects #2–#4, server tickets drifted on declared paths every time because the planner assumed dir-grouping that didn't exist. Match the existing convention exactly.
  • Server routers:
    src/server/routers/**
    — is there a
    viewer
    router, an
    organizations
    router? Procedures land on existing routers when possible.
  • Migrations system: check for
    drizzle/
    ,
    migrations/
    ,
    prisma/migrations/
    , or
    db/migrate/
    . Use the project's actual directory and filename convention (e.g. Drizzle is
    drizzle/<NNNN>_<snake>.sql
    , not
    migrations/<ts>_<snake>.sql
    ). Migration-path drift is one of the most common errors.
  • UI components:
    src/components/**
    or
    src/app/**
    — observe component-file naming (PascalCase vs kebab), test-file colocation pattern (
    Foo.test.tsx
    next to
    Foo.tsx
    vs separate
    __tests__/
    ), and any feature-folder shape.
  • Test-file naming — probe the RUNNER's globs, not just where tests live. Read the test scripts in
    package.json
    and the runner configs (
    vitest.config.*
    ,
    playwright.config.*
    ,
    bunfig.toml
    ) and note which glob picks up which directory. A repo can have two runners splitting the same tree by filename suffix. On the multi-calendar project two tickets drifted because
    test:bun
    only globs
    *.bun.test.ts
    in
    src/lib/calendar/
    and
    src/components/**
    — a plainly-named
    foo.test.ts
    there would have silently fallen through to vitest and run under the wrong environment. The plan declared
    googleEvents.test.ts
    ; the real file had to be
    googleEvents.bun.test.ts
    . Looking at where tests live told us nothing; only the runner's glob did. Declare the filename the runner will actually pick up.
  • Hooks / contexts:
    src/hooks/**
    ,
    src/contexts/**
    — does the project even have these dirs? If not, where do shared hooks land?
  • Route gating (layouts / middleware): for any route you're adding — and especially any route meant to be public — enumerate what already wraps it:
    layout.tsx
    files up the segment tree,
    middleware.ts
    matchers, parent-level session/membership checks. A file surface that lists only what you'll write misses what will wrap it. On the Public org events catalog project, SIGN-341's "page is NOT member-gated" AC was silently unsatisfiable because
    /o/[slug]/layout.tsx
    enforced auth + org membership across the whole segment — and that file appeared in no ticket's declared file surface. The agent only found it by tracing at build time; a planning-time probe would have caught it.
When the probe reveals a convention that differs from the typical pattern, write the actual path in tickets, not the typical one. Note unusual conventions in the project description's "Background" section so reviewers understand the choices.
If the project doesn't have CLAUDE.md or a clear conventions file, surface the probe results as a brief preamble in
project.md
("The repo uses flat
*.service.ts
server files; Drizzle migrations at
drizzle/<NNNN>_*.sql
; tests colocated; ...") so the executing subagents can match.
在创建任何工单前,探查代码库的实际约定——声明的路径与实际不符会导致执行阶段出现隐性偏差。探查成本很低,在规划开始时执行一次,然后在填写每个工单的
files:
字段时参考结果。
运行以下通配符查询(根据语言/框架调整):
  • 服务器服务:
    src/server/services/**
    (或等效路径)。注意实际结构——是
    src/server/services/<group>/<thing>.ts
    还是扁平结构
    src/server/services/<thing>.service.ts
    ?在项目#2到#4中,服务器工单每次都会在声明路径上出现偏差,因为规划者假设了不存在的目录分组。要完全匹配现有约定。
  • 服务器路由:
    src/server/routers/**
    ——是否有
    viewer
    路由、
    organizations
    路由?尽可能将流程添加到现有路由中。
  • 迁移系统: 检查是否存在
    drizzle/
    migrations/
    prisma/migrations/
    db/migrate/
    。使用项目实际的目录和文件名约定(例如Drizzle的格式是
    drizzle/<NNNN>_<snake>.sql
    ,而非
    migrations/<ts>_<snake>.sql
    )。迁移路径偏差是最常见的错误之一。
  • UI组件:
    src/components/**
    src/app/**
    ——观察组件文件命名(大驼峰式 vs 短横线式)、测试文件的共存模式(
    Foo.test.tsx
    Foo.tsx
    相邻 vs 单独的
    __tests__/
    目录),以及任何功能文件夹的结构。
  • 测试文件命名——探查RUNNER的通配符,而非仅查看测试文件位置。读取
    package.json
    中的测试脚本和运行器配置文件(
    vitest.config.*
    playwright.config.*
    bunfig.toml
    ),并记录
    哪些通配符匹配哪些目录
    。一个代码库可能有两个运行器通过文件名后缀拆分同一目录。在multi-calendar项目中,两个工单出现偏差,因为
    test:bun
    仅匹配
    src/lib/calendar/
    src/components/**
    中的
    *.bun.test.ts
    ——普通命名的
    foo.test.ts
    会被忽略并由vitest在错误的环境下运行。规划中声明的是
    googleEvents.test.ts
    ;实际文件必须命名为
    googleEvents.bun.test.ts
    。仅查看测试文件位置无法得知这一点,只有运行器的通配符规则能提供准确信息。要声明运行器实际会识别的文件名。
  • 钩子/上下文:
    src/hooks/**
    src/contexts/**
    ——项目是否有这些目录?如果没有,共享钩子放在哪里?
  • 路由限制(布局/中间件): 对于任何要添加的路由——尤其是公开路由——枚举已有的包装内容:路由段树中的
    layout.tsx
    文件、
    middleware.ts
    匹配器、父级会话/成员资格检查。仅列出要编写的文件会忽略那些会包装它的文件。在Public org events catalog项目中,SIGN-341的“页面无需成员权限”验收标准无法满足,因为
    /o/[slug]/layout.tsx
    对整个路由段强制要求认证和组织成员资格——而该文件未出现在任何工单的文件范围声明中。agent仅在构建时通过追踪才发现它;如果在规划阶段进行探查就能提前发现。
当探查发现约定与典型模式不同时,在工单中写入实际路径,而非典型路径。在项目描述的“背景”部分记录不寻常的约定,以便评审者理解选择的原因。
如果项目没有CLAUDE.md或明确的约定文件,将探查结果作为简短的前言写入
project.md
(例如“代码库使用扁平的
*.service.ts
服务器文件;Drizzle迁移文件位于
drizzle/<NNNN>_*.sql
;测试文件与源码共存;...”),以便执行子agent能够匹配约定。

Up front: assume parallel execution

前置要求:假设并行执行

Everything this skill outputs is in service of
/project-start
running tickets in parallel via background subagents, with no per-ticket babysitting. That means:
  • Acceptance criteria must be testable and unambiguous. Vague criteria → divergent parallel agents.
  • Each ticket must declare its expected file surface so the orchestrator can auto-sequence tickets that would conflict.
  • Each ticket must declare dependencies on other tickets in the same project.
  • Plumbing files belong on every sibling ticket that modifies them. When a scaffolding ticket creates a shared file (wizard shell with a
    renderStep()
    dispatch, a tabbed layout, a router that sibling tickets each add a route to), every sibling that will touch that file must list it in their
    files:
    . Otherwise the auto-sequencer doesn't know to serialize them and the orchestrator has to do it by hand mid-run. See "Identify shared plumbing" below.
  • Wiring tickets must reference real surfaces. If a ticket says "wire X into existing Y," grep for Y at planning time. If it doesn't exist, drop or rescope the ticket. See "Validate wiring tickets" below.
本技能输出的所有内容都是为了让
/project-start
通过后台子agent并行运行工单,无需逐个工单监控。这意味着:
  • 验收标准必须可测试明确。模糊的标准会导致并行agent执行结果不一致。
  • 每个工单必须声明其预期文件范围,以便编排器自动对可能冲突的工单进行排序。
  • 每个工单必须声明与同一项目中其他工单的依赖关系
  • **共享基础文件必须添加到所有会修改它的同级工单中。**当一个搭建工单创建了共享文件(带有
    renderStep()
    调度的向导外壳、标签页布局、同级工单各自添加路由的路由器),所有会修改该文件的同级工单都必须在其
    files:
    中列出它。否则自动排序器无法知道需要序列化这些工单,编排器必须在运行过程中手动处理。请参阅下文“识别共享基础文件”。
  • **连接类工单必须引用真实的文件范围。**如果工单说“将X连接到现有Y”,在规划阶段要搜索Y的存在。如果Y不存在,删除或重新调整工单范围。请参阅下文“验证连接类工单”。

Conversation flow

对话流程

Run the clarify step as a
/grilling
interview, not a flat questionnaire — the 7 questions below are its agenda, not its ceiling. Grilling pushes on vague answers, surfaces the assumption behind each one, and won't let a fuzzy boundary survive; that rigor is exactly what parallel execution needs (vague criteria → divergent agents). Use the questions to make sure grilling covers the planning-specific ground (cardinality, phasing, DAG), then fold what it extracts into the artifacts below.
Ask these one or two at a time, not all at once. After each answer, write the answer back in your own words and ask the next.
  1. What are we building and why now? (frames the Why / Background)
  2. What does "done" look like? (frames the Goal — one crisp sentence)
  3. What's explicitly out of scope? (forces the boundary — vague boundaries kill parallel execution)
  4. Depends on / unblocks any other projects? (project-level DAG)
  5. Rough cardinality? small (~5 tickets), medium (~12), large (20+).
  6. Phased or flat? — phased means a milestone gate matters (e.g., "Phase 0 — Foundations" must complete before Phase 1). Flat means tickets can fire in any order subject to per-ticket deps. Default to flat unless you hear a clear sequencing reason.
  7. Follow-up to a prior project? — if yes, fetch the parent via
    mcp__claude_ai_Linear__get_project
    and thread its context (PRs merged, deferred work) into the new Why.
将澄清步骤作为
/grilling
访谈进行,而非扁平的问卷——以下7个问题是访谈的议程,而非全部内容。
/grilling
会针对模糊的答案进一步追问,揭示每个答案背后的假设,不会让模糊的边界存在;这种严谨性正是并行执行所需要的(模糊标准会导致agent执行结果不一致)。使用这些问题确保访谈覆盖规划特定的要点(数量、阶段、依赖图),然后将提取的内容整合到以下输出物中。
一次只问1-2个问题,而非全部。每个问题得到答案后,用自己的话复述答案,再问下一个。
  1. 我们要构建什么,为什么现在启动?(用于构建项目的背景/动因)
  2. “完成”的定义是什么?(用于明确目标——用一句简洁的话描述)
  3. 明确排除在范围外的内容是什么?(明确边界——模糊的边界会破坏并行执行)
  4. 依赖于/会解锁哪些其他项目?(项目级依赖图)
  5. 大致的工单数量? 小型(约5个)、中型(约12个)、大型(20个以上)。
  6. 分阶段还是扁平式?——分阶段意味着里程碑至关重要(例如“阶段0——基础架构”必须在阶段1前完成)。扁平式意味着工单可在满足工单间依赖的前提下按任意顺序执行。除非有明确的排序理由,否则默认使用扁平式。
  7. 是否是之前项目的后续?——如果是,通过
    mcp__claude_ai_Linear__get_project
    获取父项目,并将其上下文(已合并的PR、延迟的工作)整合到新项目的动因中。

Pin the vocabulary before writing tickets

在创建工单前统一术语

The interview will surface domain terms — some fuzzy, some overloaded (one word doing three jobs). Before writing acceptance criteria, run
/domain-modeling
to sharpen those terms and record them in
CONTEXT.md
(create it if absent). Then write ticket titles and acceptance criteria in that pinned vocabulary, and note in
project.md
that executing subagents should read
CONTEXT.md
for the glossary. Consistent language across tickets is what keeps N parallel agents from each naming the same concept differently — the vocabulary equivalent of the shared-plumbing rule below.
访谈会涉及领域术语——有些模糊,有些存在重载(一个词承担三个职责)。在编写验收标准前,运行
/domain-modeling
来明确这些术语,并记录到
CONTEXT.md
中(如果不存在则创建)。然后使用统一的术语编写工单标题和验收标准,并在
project.md
中注明执行子agent应阅读
CONTEXT.md
中的术语表。工单间使用一致的术语能避免N个并行agent对同一概念使用不同的命名——这相当于共享基础文件规则在术语层面的应用。

Output: three artifacts

输出:三个产物

1. Linear project (Backlog state)

1. Linear项目(Backlog状态)

Create via
mcp__claude_ai_Linear__save_project
. Use this section structure in the description:
markdown
undefined
通过
mcp__claude_ai_Linear__save_project
创建。描述使用以下结构:
markdown
undefined

Why / Background

动因/背景

<narrative — what existed before, what changed, why now. Thread prior PRs/projects if relevant.>
<叙述内容——之前的状态、发生的变化、为什么现在启动。如果相关,整合之前的PR/项目内容。>

Goal

目标

<one sentence>
<一句话描述>

Scope

范围

  1. <item>
  2. <item>
...
  1. <项>
  2. <项> ...

Out of scope

排除范围

  • <item>
  • <item>
  • <项>
  • <项>

Depends on / unblocks

依赖关系/解锁项目

  • Depends on: <project link>
  • Unblocks: <project link>
  • 依赖于:<项目链接>
  • 解锁:<项目链接>

Plan doc

规划文档

See
docs/plans/<slug>.md
(large projects only — skip for small/medium)
请查看
docs/plans/<slug>.md
(仅大型项目需要——中小型项目可跳过)

Handoff bundle

交接包

.handoffs/<slug>/

Set milestones only for **phased** projects. Name them `Phase 0 — <name>`, `Phase 1 — <name>`, etc. Each milestone gets a one-line description.
.handoffs/<slug>/

仅对**分阶段**项目设置里程碑。命名格式为`Phase 0 — <名称>`、`Phase 1 — <名称>`等。每个里程碑配一句简短描述。

2. Per-scope-item Linear tickets

2. 按范围项划分的Linear工单

Create one ticket per scope item via
mcp__claude_ai_Linear__save_issue
. Use the configured
linear.team
. Each ticket gets:
markdown
undefined
通过
mcp__claude_ai_Linear__save_issue
为每个范围项创建一个工单。使用配置的
linear.team
。每个工单包含:
markdown
undefined

Acceptance criteria

验收标准

  • <testable statement — something a test or a human can falsify>
  • <testable statement>
  • <testable statement>
  • <可测试的陈述——可通过测试或人工验证是否符合要求>
  • <可测试的陈述>
  • <可测试的陈述>

Expected file surface

预期文件范围

  • src/foo/bar.ts
  • src/foo/bar.test.ts
  • e2e/specs/foo.spec.ts
  • src/foo/bar.ts
  • src/foo/bar.test.ts
  • e2e/specs/foo.spec.ts

Depends on

依赖于

  • <TICKET-ID> (sibling ticket in this project), if any

Set priority and assign to the project. Initial status: `Backlog` (will move to `Todo` when `/project-start` fires).
  • <TICKET-ID>(同一项目中的同级工单)(如有)

设置优先级并分配到项目。初始状态:`Backlog`(执行`/project-start`时会变为`Todo`)。

3.
.handoffs/<project-slug>/
bundle

3.
.handoffs/<project-slug>/

This is the machine-readable contract
/project-start
consumes. Layout:
.handoffs/<project-slug>/
├── project.md       # full plan, same content as Linear project description
└── tickets.yaml     # the contract
tickets.yaml
format — this is load-bearing, don't deviate:
yaml
project:
  id: <Linear project id or slug>
  name: <project name>
  slug: <kebab-case-slug>
  phased: false                      # true if milestones matter
  concurrency: 3                     # default; override per-project if conflict-prone

tickets:
  - id: <TICKET-ID>                  # filled in after Linear save
    title: <ticket title>
    milestone: null                  # or "Phase 0 — Foundations" for phased projects
    files:                           # expected file surface (paths or globs)
      - src/auth/middleware.ts
      - src/auth/middleware.test.ts
    depends_on: []                   # other ticket ids in this project
    external_depends_on: []          # ticket ids in OTHER projects (cross-project dep)
                                     # /project-start checks these are Done before
                                     # releasing this ticket into ready set
    acceptance:
      - <testable statement>
      - <testable statement>
external_depends_on
captures cross-project deps. Example: project #4's UI tickets needed project #3's
THE-256
(admin shell scaffolding) before they could render in the right slot. List the foreign ticket id;
/project-start
will check its Linear status and refuse to release this ticket into the ready set until the external dep is Done. Avoids the situation where an
external_depends_on:
ticket starts but finds half-built foundations.
If a project needs a long-form plan doc (large projects, complex data shapes, tier matrices), also write
docs/plans/<slug>.md
and reference it from both the Linear project description and
project.md
.
这是
/project-start
技能所依赖的机器可读契约。结构如下:
.handoffs/<project-slug>/
├── project.md       # 完整规划,内容与Linear项目描述一致
└── tickets.yaml     # 契约文件
tickets.yaml
格式——至关重要,请勿偏离:
yaml
project:
  id: <Linear项目ID或slug>
  name: <项目名称>
  slug: <短横线命名的slug>
  phased: false                      # 如果里程碑重要则设为true
  concurrency: 3                     # 默认值;如果容易冲突可按项目覆盖

tickets:
  - id: <TICKET-ID>                  # 在Linear保存后填写
    title: <工单标题>
    milestone: null                  # 分阶段项目可设为“Phase 0 — Foundations”
    files:                           # 预期文件范围(路径或通配符)
      - src/auth/middleware.ts
      - src/auth/middleware.test.ts
    depends_on: []                   # 同一项目中的其他工单ID
    external_depends_on: []          # 其他项目中的工单ID(跨项目依赖)
                                     # /project-start会检查这些工单是否已完成,
                                     # 才会将当前工单放入就绪队列
    acceptance:
      - <可测试的陈述>
      - <可测试的陈述>
**
external_depends_on
**用于记录跨项目依赖。示例:项目#4的UI工单需要项目#3的
THE-256
(管理外壳搭建)完成后才能在正确的位置渲染。列出外部工单ID;
/project-start
会检查其Linear状态,直到外部依赖完成才会将当前工单放入就绪队列。避免出现
external_depends_on:
工单已启动但基础架构尚未完成的情况。
如果项目需要长篇规划文档(大型项目、复杂数据结构、层级矩阵),还需编写
docs/plans/<slug>.md
并在Linear项目描述和
project.md
中引用它。

Integration ticket for composition-heavy projects

针对组合型项目的集成工单

When a project produces multiple component blocks that all mount into one parent (sidebar with sections, dashboard with cards, tab bar with tabs, layout with slot children), the per-block tickets are not enough on their own — each block can ship to disk without ever being composed into the parent.
This is the "components shipped but never composed" failure mode. Post-Org-UX-initiative, the admin shell sidebar shipped THE-258 (org switcher) and THE-259 (role-gated bottom band) as separate components with correct internal logic, but
SettingsSidebar.tsx
never imported or rendered them. Tests passed (each component had its own unit tests), types passed (props matched), individual PR reviews looked fine. The gap only surfaced when a user opened the page and saw nothing.
Add an explicit integration ticket that:
  • Sequences AFTER all the component-block tickets it integrates (declared
    depends_on
    ).
  • Lists the parent file in its
    files:
    (e.g.
    SettingsSidebar.tsx
    ).
  • Acceptance criteria assert composition concretely:
    • "
      <ChildA />
      is imported in
      Parent.tsx
      and rendered between
      <Foo>
      and
      <Bar>
      ."
    • "
      <ChildB />
      is rendered after
      <ChildA>
      with the right props threaded through."
    • "Structural test in
      Parent.test.tsx
      asserts every expected child renders under the conditions documented by each child's own gating logic."
    • "Manual smoke check (or e2e): visit the live route and confirm each block appears in the right state."
The integration ticket is small but essential. It's also a natural place for a
verify
skill invocation since these bugs are visible only at the rendered-page level, not the code level.
当项目生成多个组件块且都要挂载到同一个父组件中(带多个章节的侧边栏、带多个卡片的仪表盘、带多个标签的标签栏、带插槽子组件的布局),仅靠单个组件块工单是不够的——每个组件块都可单独交付,但可能从未被组合到父组件中。
这就是“组件已交付但未组合”的失败模式。在Post-Org-UX-initiative项目中,管理外壳侧边栏交付了THE-258(组织切换器)和THE-259(角色限制的底部栏)两个独立组件,内部逻辑正确,但
SettingsSidebar.tsx
从未导入或渲染它们。测试通过(每个组件都有自己的单元测试),类型检查通过(props匹配),单个PR评审也没问题。直到用户打开页面发现内容缺失才暴露问题。
添加一个明确的集成工单
  • 依赖于所有要集成的组件块工单(在
    depends_on
    中声明)。
  • 在其
    files:
    中列出父文件(例如
    SettingsSidebar.tsx
    )。
  • 验收标准明确断言组合逻辑:
    • <ChildA />
      已在
      Parent.tsx
      中导入,并渲染在
      <Foo>
      <Bar>
      之间。”
    • <ChildB />
      <ChildA />
      之后渲染,并传递正确的props。”
    • Parent.test.tsx
      中的结构测试断言每个预期子组件都能根据各自的限制逻辑在对应条件下渲染。”
    • “手动冒烟测试(或端到端测试):访问实际路由并确认每个组件块都在正确的状态下显示。”
集成工单规模小但至关重要。它也是调用
verify
技能的理想场景,因为这类bug仅在渲染页面层面可见,而非代码层面。

Identify shared plumbing

识别共享基础文件

Before finalizing tickets, look at the scope and ask: does any ticket create a file that siblings will modify? Typical patterns:
  • Wizards / multi-step flows — the shell (
    OrgWizardShell.tsx
    ) routes between steps; each step ticket extends the
    renderStep()
    switch.
  • Tabbed layouts — the parent component composes tabs; each tab ticket adds itself.
  • Routers / dispatchers — the parent maps keys to handlers; each handler ticket registers itself.
  • Shared hooks / contexts — the provider defines the API surface; consumer tickets extend it.
When you spot this: the shared file goes on every sibling's
files:
list
, not just the scaffolding ticket's. The auto-sequencer reads file overlap as "serialize these" — that's the correct behavior because two siblings can't safely edit the same file in parallel even with merge auto-rebase.
在最终确定工单前,查看范围并思考:**是否有工单创建的文件会被同级工单修改?**典型模式包括:
  • 向导/多步骤流程——外壳文件(
    OrgWizardShell.tsx
    )在步骤间路由;每个步骤工单扩展
    renderStep()
    分支。
  • 标签页布局——父组件组合标签页;每个标签页工单添加自己的标签。
  • 路由器/调度器——父组件将键映射到处理程序;每个处理程序工单注册自己。
  • 共享钩子/上下文——提供者定义API范围;消费者工单扩展它。
当发现这种情况时:共享文件要添加到所有同级工单的
files:
列表中
,而非仅添加到搭建工单的列表中。自动排序器会将文件重叠视为“需要序列化这些工单”——这是正确的行为,因为即使有自动合并和变基,两个同级工单也无法安全地并行修改同一文件。

Pre-declare shared utilities, not just plumbing

预先声明共享工具,而非仅基础文件

A separate but related case: multiple sibling tickets each need the same low-level chrome — card styles, list-row layouts, status-pill tokens, empty-state shells. These don't fit "scaffolding + step" because no single ticket creates them; each sibling independently reaches for the same shape and (without coordination) re-invents it with slightly different APIs.
When the scope contains several visually-cousin tickets (e.g. multiple "Section X re-skin" tickets, multiple "Card Y" tickets), name the shared utility files explicitly in the project description and add them to one ticket's
files:
as the canonical owner. Siblings reference the utility by path in their acceptance criteria ("uses
src/components/settings/org/cards/style.ts
for card chrome") so subagents reach for the existing file instead of independently creating
cards/style.ts
in their own subtree.
Project #5: THE-278 (Billing) and THE-281 (Dev Hub Overview) each independently invented a
cards/style.ts
in their respective subtrees — different parent paths so no collision, but the divergent shapes are technical debt waiting to be reconciled. A planning-time call-out would have prevented it.
另一个相关场景:多个同级工单都需要相同的底层样式组件——卡片样式、列表行布局、状态标记、空状态外壳。这些不属于“搭建+步骤”模式,因为没有单个工单创建它们;每个同级工单会独立使用相同的结构,且(在无协调的情况下)会重新实现略有不同的API。
当范围包含多个视觉相关的工单(例如多个“Section X重新设计”工单、多个“Card Y”工单),在项目描述中明确命名共享工具文件,并将其添加到一个工单的
files:
中作为规范所有者。同级工单在验收标准中通过路径引用该工具(例如“使用
src/components/settings/org/cards/style.ts
作为卡片样式”),以便子agent使用现有文件而非在各自的子目录中独立创建
cards/style.ts
项目#5:THE-278(计费)和THE-281(Dev Hub概览)各自在对应的子目录中独立创建了
cards/style.ts
——父路径不同所以没有冲突,但不同的结构会导致后续需要调和的技术债务。如果在规划阶段明确指出就能避免这种情况。

Declare the sibling TEST files a shared component drags in

声明共享组件引入的同级测试文件

The single biggest source of file-surface drift is invisible in the source tree: when a ticket adds a tRPC query, hook, or prop to a shared component, every existing test file that mocks that component must also change — because their hand-written mocks now lack the new procedure and the component throws. These test files are nowhere in the ticket's conceptual scope, so they get omitted from
files:
, and the drift only surfaces at build time.
On "Monetize the AI creation flow", 7 of 10 tickets touched undeclared files, and the majority of that was this one pattern: SIGN-609 and SIGN-612 each had to edit four existing
CreateSignupWizard.*.test.tsx
files to add a
draftAllowance
mock, none of which they'd declared. Two tickets, eight undeclared test files, one cause.
So at plan time, for any ticket whose deliverable adds a query / hook / context-value / required prop to an existing shared component: grep for who already mocks it —
grep -rl "<ComponentName>\|trpc\.<router>\.<newProcedure>" src/**/__tests__ src/**/*.test.*
— and add every hit to that ticket's
files:
. This both makes the drift honest and lets the auto-sequencer serialize correctly against any sibling touching the same test files. If you can't enumerate them at plan time, say so in the ticket ("expect to update existing
CreateSignupWizard
test mocks — enumerate at build") so the executing agent treats it as expected work rather than unplanned scope.
Example from project #2's first run (THE-247 + step tickets):
yaml
undefined
文件范围偏差的最大来源在源码树中不可见:当工单向共享组件添加tRPC查询、钩子或prop时,所有模拟该组件的现有测试文件都必须修改——因为它们手写的模拟现在缺少新的流程,组件会抛出错误。这些测试文件不在工单的概念范围内,所以会被遗漏在
files:
之外,偏差仅在构建时才会暴露。
在“Monetize the AI creation flow”项目中,10个工单中有7个涉及未声明的文件,其中大部分是这种模式:SIGN-609和SIGN-612各自需要修改4个现有的
CreateSignupWizard.*.test.tsx
文件以添加
draftAllowance
模拟,而这些文件都未在工单中声明。两个工单,8个未声明的测试文件,都是同一原因导致的。
因此,在规划阶段,对于任何向现有共享组件添加查询/钩子/上下文值/必填prop的工单:搜索哪些测试文件已经在模拟该组件——
grep -rl "<ComponentName>\\|trpc\\.<router>\\.<newProcedure>" src/**/__tests__ src/**/*.test.*
——并将所有匹配结果添加到该工单的
files:
中。这既明确了可能的偏差,又让自动排序器能针对修改同一测试文件的同级工单进行正确的序列化。如果在规划阶段无法枚举这些文件,在工单中注明(“预计需要更新现有
CreateSignupWizard
测试模拟——构建时枚举”),以便执行agent将其视为预期工作而非意外范围。
项目#2首次运行的示例(THE-247 + 步骤工单):
yaml
undefined

Scaffolding ticket creates the shell

搭建工单创建外壳

  • id: THE-247 files:
    • src/components/orgs/wizard/OrgWizardShell.tsx # creates
    • src/components/orgs/wizard/StepBasics.tsx # placeholder

    ...

  • id: THE-247 files:
    • src/components/orgs/wizard/OrgWizardShell.tsx # 创建
    • src/components/orgs/wizard/StepBasics.tsx # 占位符

    ...

Step tickets each ALSO list the shell

步骤工单也列出外壳文件

  • id: THE-248 files:
    • src/components/orgs/wizard/StepBasics.tsx # real impl
    • src/components/orgs/wizard/OrgWizardShell.tsx # extends renderStep + props depends_on: [THE-247]

This way THE-248/249/250/251 auto-serialize against each other (all touch `OrgWizardShell.tsx`) instead of racing.
  • id: THE-248 files:
    • src/components/orgs/wizard/StepBasics.tsx # 实际实现
    • src/components/orgs/wizard/OrgWizardShell.tsx # 扩展renderStep + props depends_on: [THE-247]

这样THE-248/249/250/251会自动按顺序执行(都修改`OrgWizardShell.tsx`),而非并行执行导致冲突。

A "model on existing X" instruction inherits X's bugs — spot-check the precedent first

“基于现有X建模”的指令会继承X的bug——先检查先例

When a ticket tells an agent to clone or mirror an existing surface ("model on the org-logo route", "same pattern as the avatar upload"), the agent will faithfully reproduce it — including any latent defect in the precedent. A clone is only as safe as what it copies, and a security-relevant flaw propagates silently because the ticket told the agent to match.
On the Event-cover-images project, SIGN-624 was scoped "model on
src/app/api/orgs/[id]/logo/route.ts
" and did exactly that — inheriting a cross-tenant blob-delete IDOR (a substring
includes()
tenant check bypassable via query string) that ships in the avatar and org-logo routes today. Independent review caught it in the new route, but the same bug lives on unreviewed in the precedent it was copied from (filed as a follow-up).
So, before writing "model on X" into a ticket: read X's security-relevant lines — authz checks, tenant/ownership containment, input validation, path/URL parsing. If X has a flaw, either (a) point the ticket at a corrected pattern and note the fix in its ACs, or (b) file a follow-up ticket for X and tell the new ticket not to copy the flaw. Don't hand an agent a precedent you haven't vetted.
当工单告诉agent克隆或镜像现有文件范围(“基于org-logo路由建模”、“与头像上传使用相同模式”),agent会忠实地复制——包括先例中存在的任何潜在缺陷。克隆的安全性取决于被复制的内容,而安全相关的缺陷会被静默传播,因为工单明确要求agent匹配先例。
在Event-cover-images项目中,SIGN-624的范围是“基于
src/app/api/orgs/[id]/logo/route.ts
建模”,并完全照做——继承了跨租户blob删除的IDOR漏洞(使用
includes()
检查租户,可通过查询字符串绕过),该漏洞目前仍存在于头像和org-logo路由中。独立评审在新路由中发现了这个漏洞,但被复制的先例中的同一bug仍未被评审(已作为后续工单提交)。
因此,在工单中写入“基于X建模”前:阅读X中与安全相关的代码行——授权检查、租户/所有权限制、输入验证、路径/URL解析。如果X存在缺陷,要么(a)让工单基于修正后的模式,并在验收标准中注明修复内容,要么(b)为X提交一个后续工单,并告知新工单不要复制该缺陷。不要将未经过审核的先例交给agent。

Validate wiring tickets

验证连接类工单

A "wiring" ticket is one that says "wire X into existing Y" or "integrate X with current Z" — its scope is to connect new code to surfaces that should already exist.
Before finalizing such a ticket, grep for the named surface. If it doesn't exist:
  • Either drop the ticket (no work to do) and document it in the project description's "Out of scope" section.
  • Or rescope the ticket to create the surface as well, and adjust acceptance criteria accordingly.
Don't ship a wiring ticket on faith. The agent will grep at execution time, find nothing, and silently downgrade the ticket to "shipped the building block" — the wiring never happens. Surface this at plan time, not retro time.
Example: THE-254 was scoped as "wire useInviteAttempt into existing Invite surfaces" but those surfaces don't exist in the codebase. The agent shipped only the hook; the actual wiring is now deferred indefinitely. A planning-time grep would have caught this.
“连接类”工单是指那些要求“将X连接到现有Y”或“将X与当前Z集成”的工单——其范围是将新代码连接到应已存在的文件范围。
**在最终确定此类工单前,搜索指定的文件范围。**如果不存在:
  • 要么删除该工单(无需执行任何工作)并在项目描述的“排除范围”部分记录。
  • 要么重新调整工单范围以同时创建该文件范围,并相应调整验收标准。
不要凭信任提交连接类工单。agent会在执行阶段搜索,如果找不到,会静默将工单降级为“仅交付构建块”——连接工作从未执行。要在规划阶段就暴露这个问题,而非事后补救。
示例:THE-254的范围是“将useInviteAttempt连接到现有Invite文件范围”,但这些文件范围在代码库中不存在。agent仅交付了钩子;实际的连接工作被无限期延迟。如果在规划阶段进行搜索就能提前发现。

Validate acceptance criteria against the DAG

针对依赖图验证验收标准

Two failure modes, both hit on the Public org events catalog project. Each cost a full pause and a human round-trip mid-run, and both were plan-authoring bugs, not agent failures — the agents correctly refused to stub or guess.
1. An AC that asserts code a later ticket produces.
SIGN-340's acceptance said "with the flag OFF,
listPublicForOrg
throws NOT_FOUND."
But
listPublicForOrg
was SIGN-339's deliverable, and SIGN-339
depends_on
SIGN-340 — so the endpoint could not exist at SIGN-340's build time. The AC was unsatisfiable by construction.
Check every acceptance criterion: does the code it asserts either (a) already exist on the base branch, or (b) get produced by a ticket this one
depends_on
? If neither, the AC is filed on the wrong ticket — move it to the ticket that owns the thing being asserted. (Here: the gate assertion belonged on SIGN-339, which already carried an equivalent criterion.)
2. A UI affordance with no producing ticket.
SIGN-342's card had to render "N of M spots left" — but the query feeding the page returned
maxParticipants
(M) and no signup count (N), and no ticket in the plan ever produced N. Building the card anyway would have shipped a decorative affordance pointing at data that doesn't exist — the same "UI shipped against data that isn't there" class as the composition failure above.
Check every UI ticket: enumerate the concrete fields its ACs require, and confirm some dependency's AC actually returns them. "Shows X" is only testable if a ticket produces X. If nothing does, either add it to the producing ticket's AC or widen this ticket's scope explicitly at plan time — don't discover it at build time.
3. A row nobody writes.
The two checks above ask "who produces this field?" Neither asks "who creates this row?" — and a plan can pass both while shipping a table nothing ever inserts into.
The multi-calendar project planned a
calendar_connections
table, a ticket to create the schema, and four tickets to read from it. No ticket owned writing a row. The gap was invisible to every existing check: the schema ticket produced the table, the service ticket produced the queries, the UI tickets consumed real fields from real endpoints. Everything was "produced" by something. But the OAuth flow returns via a redirect, so there was no obvious moment where a row got written, and nobody asked. SIGN-347's agent discovered it mid-build and invented a lazy reconcile-on-read (
syncConnections
) to close it — a load-bearing design decision, made under time pressure, by an agent, because the planner never asked the question. It happened to be a good decision. That was luck.
For every table or persisted entity the plan introduces, walk its full lifecycle and name the owning ticket for each step: who CREATES a row, who UPDATES it, who DELETES it. If any step has no owner, you have found a hole — fill it at plan time. Pay special attention to rows created as a side-effect of a flow you don't control (an OAuth redirect, a webhook, a third-party callback): those are exactly the ones with no natural home, which is why they end up with none.
两种失败模式,都在Public org events catalog项目中出现过。每种模式都导致执行过程中完全暂停并需要人工介入,且都是规划阶段的错误,而非agent的问题——agent正确地拒绝了存根或猜测。
1. 验收标准断言后续工单才会生成的代码。
SIGN-340的验收标准称*“当标志关闭时,
listPublicForOrg
抛出NOT_FOUND。”*但
listPublicForOrg
是SIGN-339的交付物,且SIGN-339依赖于SIGN-340——因此在SIGN-340构建时该端点还不存在。该验收标准从根本上无法满足。
**检查每个验收标准:**它断言的代码要么(a)已存在于基础分支上,要么(b)由当前工单依赖的某个工单生成。如果都不是,该验收标准属于错误的工单——将其移到负责生成对应代码的工单中。(此处:权限检查断言应属于SIGN-339,该工单已有等效的验收标准。)
2. UI功能没有对应的生成工单。
SIGN-342的卡片需要显示*“剩余M个名额中的N个”*——但为页面提供数据的查询返回了
maxParticipants
(M)但没有报名人数(N),且规划中没有任何工单生成N。如果仍构建该卡片,会交付一个指向不存在数据的装饰性功能——与之前的组合失败属于同一类“UI已交付但数据不存在”问题。
**检查每个UI工单:**枚举其验收标准所需的具体字段,并确认某个依赖工单的验收标准实际会返回这些字段。“显示X”只有在有工单生成X时才是可测试的。如果没有,要么将其添加到生成工单的验收标准中,要么在规划阶段明确扩大当前工单的范围——不要在构建阶段才发现。
3. 无人写入的数据库行。
上述两个检查问的是*“谁生成这个字段?”但都没有问“谁创建这个数据库行?”*——规划可能通过了前两个检查,但交付的表中从未插入任何数据。
multi-calendar项目规划了
calendar_connections
表、创建 schema 的工单,以及四个读取该表的工单。**没有任何工单负责写入数据行。这个漏洞无法被现有检查发现:schema工单生成了表,服务工单生成了查询,UI工单从真实端点读取真实字段。所有内容都由某个工单“生成”。但OAuth流程通过重定向返回,所以没有明显的写入数据行的时机,也没有人提出这个问题。SIGN-347的agent在构建过程中发现了这个问题,并发明了一个延迟的读取时同步机制(
syncConnections
)来填补漏洞——这是一个
至关重要的设计决策,由agent在时间压力下做出,因为规划者从未提出这个问题。**结果碰巧是个好决策,但这只是运气。
**对于规划引入的每个表或持久化实体,梳理其完整生命周期并为每个步骤指定负责的工单:谁创建行、谁更新行、谁删除行。**如果任何步骤没有负责人,说明存在漏洞——在规划阶段填补它。特别注意那些由你无法控制的流程(OAuth重定向、webhook、第三方回调)作为副作用创建的数据行:这些正是最容易没有归属的,所以最终会无人负责。

Be careful when an acceptance criterion mandates literal copy

当验收标准要求字面复制时要谨慎

Quoting exact user-facing strings in ACs is good — it makes them testable, and it stops N parallel agents each inventing their own wording. But a quoted string is not just copy: it silently imports a format, a convention, and a set of assumptions into the ticket, and those can contradict another criterion in the same plan without anyone noticing.
Multi-calendar shipped an unintended visual regression this way. SIGN-349's AC specified a degraded count row reading
11:15a–12p · 2 events
— a casual 12-hour format. The grid's existing time format was
09:00–10:00
. The same plan also demanded that a single-connection user see "zero visual change." Both criteria were reasonable in isolation and jointly unsatisfiable: once the count row is mandated, the agent must either run two time formats side-by-side in one rail (worse) or convert every block — which changes what existing users see. It chose correctly and flagged it, but the contradiction was authored into the plan, not introduced by the build.
When you quote a literal string in an AC, grep the codebase for how that thing is currently rendered. Dates, times, currency, pluralization, capitalization, truncation. If your string implies a different convention than what ships today, you have either (a) accidentally mandated a migration, or (b) written a criterion that contradicts a "no visual change" / "preserve existing behavior" criterion elsewhere in the plan. Decide which, deliberately, at plan time.
在验收标准中引用确切的用户可见字符串是好事——这让标准可测试,且能避免N个并行agent各自发明不同的措辞。但引用的字符串不仅仅是文本:它会将格式、约定和一组假设静默导入工单,而这些可能与同一规划中的其他标准矛盾,却无人察觉。
multi-calendar项目因此出现了意外的视觉回归。SIGN-349的验收标准指定了一个降级的计数行,内容为
11:15a–12p · 2 events
——采用非正式的12小时制格式。而网格现有的时间格式是
09:00–10:00
。**同一规划还要求单连接用户“看不到任何视觉变化”。**两个标准单独看都合理,但合在一起无法满足:一旦计数行被强制要求,agent要么在同一区域同时使用两种时间格式(更糟),要么转换所有模块——这会改变现有用户看到的内容。agent做出了正确选择并标记了问题,但矛盾是被写入规划的,而非构建阶段引入的。
**当你在验收标准中引用字面字符串时,搜索代码库中当前如何渲染同类内容。**日期、时间、货币、复数形式、大小写、截断方式。如果你的字符串暗示的约定与当前交付的内容不同,你要么(a)意外强制要求了一次迁移,要么(b)编写的标准与规划中其他“无视觉变化”/“保留现有行为”的标准矛盾。要在规划阶段故意做出选择。

A foundation ticket's ACs must enumerate every rule, not summarize

基础工单的验收标准必须枚举每个规则,而非总结

When a ticket's deliverable is a data-driven registry / config / rule-table that N sibling tickets consume (an action registry, a capability map, a permission set), its acceptance criteria must specify the rule for each entry explicitly, and its tests must assert each — because a summary reads as complete while shipping a hole that only surfaces three tickets later.
On P1, SIGN-405's registry AC said "Move/Remove don't apply to a headcount row." The agent implemented the move gating (via
slotCount
) and never hid remove or resend — "Move/Remove" read as one rule but was two, and the gap stayed invisible until SIGN-416 tried to converge onto the registry and hit a merged sibling's test. It forced a mid-run
needs input:
pause and a scope expansion. "Move/remove" is not one rule. If the deliverable is a table, the AC is a table: one falsifiable row per entry, per surface it feeds.
当工单的交付物是一个由N个同级工单使用的数据驱动注册表/配置/规则表(动作注册表、能力映射、权限集),其验收标准必须明确指定每个条目的规则,且测试必须断言每个规则——因为总结性描述看起来完整,但可能存在漏洞,直到三个工单之后才会暴露。
在P1项目中,SIGN-405的注册表验收标准称“Move/Remove不适用于人数统计行”。agent实现了移动限制(通过
slotCount
),但从未隐藏删除重新发送——“Move/Remove”被视为一个规则,但实际是两个,漏洞直到SIGN-416尝试使用注册表并遇到已合并同级工单的测试时才被发现。这导致执行过程中暂停并需要输入,还扩大了范围。“Move/remove”不是一个规则。如果交付物是一个表,验收标准也应是一个表:每个条目、每个关联文件范围对应一个可证伪的规则。

An example in an AC must actually reproduce the bug

验收标准中的示例必须能实际复现bug

A bug ticket usually quotes a concrete input — the email that overflows, the title that wraps, the payload that 500s. That example is not illustration. It is the thing the agent will write its test against, so a plausible-looking example that doesn't actually reproduce the bug is worse than no example: it manufactures a false green. The agent writes the test the AC asked for, watches it pass, and ships a fix it never proved.
This applies to geometry and threshold assertions too, not just string/value inputs. On P1, SIGN-428's e2e was authored to prove the participant detail was "a centered modal, not a right-hugging drawer" via
box.x + box.width < viewportWidth - 1
. That assertion passed for the drawer it was meant to reject — a ~460px drawer's right edge lands ~15px short of a 1280px viewport, so only the (also-present) height check would have caught it. The agent found it, replaced it with a real centered-check, and verified red against the drawer before shipping. When an AC quotes a pixel bound, a timeout, a percentage, or any numeric threshold as the discriminator, compute it against the actual reject-target at plan time — the same "run it before you quote it" rule the value-input case demands.
Mobile-responsive-fixes authored this twice in fifteen tickets:
  • SIGN-363's AC quoted
    christopher.wolfeschlegelstein@example-domain.org
    as an email that overflows its box. It contains a hyphen, and Chrome breaks lines at hyphens — it wraps unaided. The AC's
    scrollWidth <= clientWidth
    assertion was green without the fix.
  • SIGN-368's AC quoted a 51-character location as one that forces sideways scroll. It overflows the header row but not the document — it spills into the page's own 16px padding. Also green without the fix. It took 72 characters to genuinely scroll the page.
Both were caught only because the executing agents reverted their fix and confirmed the test went red. Neither would have been caught by review, by CI, or by the criterion itself.
Before you write a concrete example into an acceptance criterion, run it. Reproduce the bug with that exact value — in a browser, a REPL, a scratch test, whatever's cheap. If it doesn't reproduce, find a value that does and quote that. If you can't reproduce it at plan time, say so in the ticket ("repro value not verified — confirm before writing the test") rather than presenting an unverified example as if it were the spec.
This is the single highest-leverage check in this skill. Hands-off parallel execution cannot self-detect a test that is green for the wrong reason.
bug工单通常会引用具体的输入——溢出的邮箱、换行的标题、导致500错误的负载。这个示例不是说明性的。它是agent编写测试的依据,所以一个看似合理但无法实际复现bug的示例比没有示例更糟:它会制造虚假的测试通过。agent编写验收标准要求的测试,看到测试通过,就交付了一个从未被验证过的修复。
这不仅适用于字符串/值输入,也适用于几何和阈值断言。在P1项目中,SIGN-428的端到端测试被设计为通过
box.x + box.width < viewportWidth - 1
来证明参与者详情是“居中模态框,而非靠右的抽屉”。这个断言对于它本应拒绝的抽屉也能通过——一个约460px的抽屉右边缘距离1280px视口约15px,所以只有(同时存在的)高度检查能发现问题。agent发现了这个问题,将其替换为真正的居中检查,并在抽屉上验证测试失败后才交付。当验收标准引用像素边界、超时、百分比或任何数值阈值作为判别标准时,在规划阶段针对实际要拒绝的目标计算该值——这与值输入案例中的“引用前先运行”规则相同。
Mobile-responsive-fixes项目在15个工单中出现过两次这种情况:
  • SIGN-363的验收标准引用
    christopher.wolfeschlegelstein@example-domain.org
    作为溢出邮箱。该邮箱包含连字符,Chrome会在连字符处换行——无需修复就能自动换行。验收标准中的
    scrollWidth <= clientWidth
    断言无需修复就能通过。
  • SIGN-368的验收标准引用一个51字符的位置作为强制横向滚动的案例。它会溢出标题但不会溢出文档——它会延伸到页面自身的16px内边距中。同样无需修复就能通过测试。需要72字符才能真正导致页面滚动。
这两个问题都只有在执行agent撤销修复并确认测试失败时才被发现。评审、CI或标准本身都无法发现它们。
**在将具体示例写入验收标准前,先运行它。**用该精确值复现bug——在浏览器、REPL、临时测试中,任何低成本的方式都可以。如果无法复现,找到能复现的值并引用那个值。如果在规划阶段无法复现,在工单中注明(“未验证复现值——编写测试前确认”),而非将未验证的示例作为规范呈现。
这是本技能中杠杆率最高的检查。无人值守的并行执行无法自行检测到因错误原因通过的测试。

Never prescribe an assertion that cannot go red

绝不要规定无法失败的断言

The example is one way to manufacture a false green; the assertion itself is the other. When an AC dictates how to test — and layout/CSS ACs almost always do — it is easy to prescribe an assertion that passes against the broken code, so the agent writes exactly what you asked and ships an unproven fix. participant-flow-repair authored three of these, one of them in an AC written specifically to prevent false greens:
  • Class-name assertions. ACs demanded the fix be verified by
    expect(el.className).toContain("break-all")
    (and three existing tests did the same). The class is the bug; asserting its presence passes while the page is visibly broken. A Tailwind arbitrary-value class is worse still — it can be typed in source but never emitted by the build, so a class-name assertion is green whether or not the style exists.
  • getClientRects().length === 1
    on a block element.
    An AC prescribed this to prove a heading no longer wraps.
    getClientRects()
    on a block returns one border-box rect no matter how many text lines it holds — the assertion is
    === 1
    before and after the fix. It can never go red. (The honest version counts line boxes with a
    Range
    over the heading text:
    range.getClientRects()
    .)
Rules for any AC that mandates a test:
  • Assert a rendered/computed property, never a source token: line boxes via a
    Range
    ,
    getComputedStyle().overflowWrap
    ,
    elementFromPoint
    , measured geometry. Never a
    className
    , and never a single block-level rect count.
  • Require the ticket to prove red-before-green by reverting only the fix — the agent must watch the test fail against the unfixed tree, and for a class/CSS fix, strip only the changed class from a real build and confirm it goes red (a subagent caught an inert Tailwind fix exactly this way).
  • If you cannot construct a falsifiable assertion at plan time, say so in the ticket rather than prescribing one that can't fail. A wrong prescribed assertion is worse than none — the agent trusts it.
示例是制造虚假测试通过的一种方式;断言本身是另一种方式。当验收标准规定如何测试时——尤其是布局/CSS验收标准——很容易规定一个能在代码有问题时仍通过的断言,所以agent会完全按照要求编写测试并交付未经验证的修复。participant-flow-repair项目出现过三次这种情况,其中一次是在专门为防止虚假测试通过而编写的验收标准中:
  • **类名断言。**验收标准要求通过
    expect(el.className).toContain("break-all")
    验证修复(已有三个测试采用相同方式)。类名本身就是bug;断言其存在会在页面明显有问题时仍通过。Tailwind任意值类更糟——它可能在源码中存在但从未被构建工具生成,所以类名断言无论样式是否存在都会通过。
  • 块级元素的
    getClientRects().length === 1
    断言。验收标准规定用这个断言证明标题不再换行。块级元素的
    getClientRects()
    会返回
    一个边框盒矩形,无论文本有多少行
    ——修复前后断言都是
    === 1
    。它永远不会失败。(正确的方式是用
    Range
    覆盖标题文本来计算行盒:
    range.getClientRects()
    。)
对于任何规定测试方式的验收标准,遵循以下规则:
  • 断言渲染/计算属性,而非源码标记:通过
    Range
    计算行盒、
    getComputedStyle().overflowWrap
    elementFromPoint
    、测量几何尺寸。绝不要断言
    className
    ,也不要断言单个块级矩形计数。
  • 要求工单通过仅撤销修复来证明测试先失败后通过——agent必须观察测试在未修复的代码树上失败,对于类/CSS修复,要从实际构建中仅移除修改的类并确认测试失败(子agent正是通过这种方式发现了一个无效的Tailwind修复)。
  • 如果在规划阶段无法构建可证伪的断言,在工单中注明,而非规定一个无法失败的断言。错误的断言比没有断言更糟——agent会信任它。

Sequence global-chrome tickets ahead of layout-measuring ones

将全局样式工单排在布局测量工单之前

When one ticket changes something global — a CSS rule on
html
/
body
, a shared layout wrapper, a root provider, a base font size — and another ticket measures what the global thing affects, they are not independent, even when their file surfaces don't overlap. The file-surface auto-sequencer will not catch this: the conflict is in the rendered result, not the source tree.
Two instances in one project, both discovered in CI rather than at plan time:
  • SIGN-373 added
    html { overflow-x: clip }
    . That makes
    document.documentElement.clientWidth
    report the clip box rather than the content width — so SIGN-374's viewport-width assertions started measuring the wrong ruler and its CI went red. The code was correct; the yardstick wasn't.
  • SIGN-366 added a mobile affordance to a page whose above-the-fold contract SIGN-364 had asserted in e2e. The new row pushed the first slot below the fold and correctly failed SIGN-364's test.
Both are good outcomes — the tests did their job — but each cost a red CI and a debugging round-trip that an explicit edge would have prevented.
At plan time, ask: does any ticket change a global that another ticket measures? Global CSS, root layout, shared providers, viewport meta, base typography. If yes, add an explicit
depends_on
so the global lands first and the measuring ticket is written against the world as it will actually exist. Note it in the dependent's description too, so the agent knows why it's sequenced.
当一个工单修改全局内容——
html
/
body
上的CSS规则、共享布局包装器、根提供者、基础字体大小——而另一个工单测量受全局内容影响的元素时,即使它们的文件范围不重叠,它们也不是独立的。文件范围自动排序器无法发现这一点:冲突存在于渲染结果中,而非源码树中。
同一个项目中出现过两次这种情况,都是在CI中发现而非规划阶段:
  • SIGN-373添加了
    html { overflow-x: clip }
    。这会让
    document.documentElement.clientWidth
    报告裁剪框而非内容宽度——所以SIGN-374的视口宽度断言开始使用错误的标尺,导致CI失败。代码是正确的;测量标准错了。
  • SIGN-366向一个页面添加了移动端功能,而SIGN-364已在端到端测试中断言了该页面的首屏内容契约。新添加的行将第一个内容块推到了首屏下方,导致SIGN-364的测试正确失败。
这两种都是结果——测试发挥了作用——但每种都导致了CI失败和调试往返,如果在规划阶段明确依赖关系就能避免。
**在规划阶段思考:是否有工单修改全局内容,而另一个工单测量受其影响的元素?**全局CSS、根布局、共享提供者、视口元标签、基础排版。如果是,添加明确的
depends_on
,让全局修改先完成,测量工单基于实际存在的环境编写。在依赖工单的描述中也注明,以便agent知道排序的原因。

Manual setup is not a ticket — file it, don't schedule it

手动设置不是工单——提交任务,不要排入计划

Planning routinely surfaces prerequisites that no agent can execute: an API key that has to be generated in someone's dashboard, a DNS record, an OAuth app that needs a consent screen, a third-party account, an env var that must exist in Vercel before the feature does anything.
Do not turn these into tickets in
tickets.yaml
. A ticket in the bundle is a promise that
/start
can pick it up in a worktree and merge a PR — and
/start
cannot log into a dashboard. Scheduling one guarantees a spawned agent, a burned worktree, and a pause.
Instead, file each as a manual task per
/manual-tasks
, at plan time, before the project starts. Then:
  • If a ticket can't work until the manual task is done, say so in that ticket's description ("requires <TASK-ID>:
    RESEND_API_KEY
    set on Production") so the
    /start
    agent knows why its feature won't come alive locally, and doesn't go looking for a bug that isn't there.
  • Report the count in your final line so the user can clear them while the project runs, rather than discovering them at merge time.
The
external_depends_on
field is for foreign tickets, not for these — a manual task has no PR and no Done-via-merge, so don't wire it into the DAG. It runs alongside the project, not inside it.
规划通常会发现agent无法执行的先决条件:必须在某人的仪表盘中生成的API密钥、DNS记录、需要同意屏幕的OAuth应用、第三方账户、必须在Vercel中存在的环境变量,否则功能无法正常工作。
不要将这些转化为
tickets.yaml
中的工单。包中的工单意味着
/start
可以在工作树中获取它并合并PR——而
/start
无法登录到仪表盘中。排入计划会导致生成agent、占用工作树并暂停执行。
相反,在规划阶段,按照
/manual-tasks
的要求将每个手动操作提交为任务
,在项目启动前完成。然后:
  • 如果某个工单必须等手动任务完成才能工作,在该工单的描述中注明(“需要<TASK-ID>
    RESEND_API_KEY
    已在生产环境设置”),以便
    /start
    agent知道功能在本地无法正常工作的原因,不会去寻找不存在的bug。
  • 在最终报告中统计手动任务数量,以便用户在项目运行时处理它们,而非在合并时才发现。
external_depends_on
字段用于外部工单,而非手动任务——手动任务没有PR,也不会通过合并完成,所以不要将其连接到依赖图中。它与项目并行执行,而非在项目内部执行。

Asking-questions discipline

提问原则

  • Don't ask all 7 questions at once. Ask 1–2, listen, restate, ask the next.
  • If the user gives a tight one-liner upfront ("scaffold a follow-up to <Project> covering the deferred polish") you can skip directly to confirming and proceed.
  • If the user pushes back on a scope item ("nah, drop #3"), update the plan and proceed — don't relitigate.
  • Push back when you see a vague acceptance criterion. "Make it better" or "polish the UI" are not testable — ask for a concrete check.
  • 不要一次性问完7个问题。一次问1-2个,倾听,复述,再问下一个。
  • 如果用户一开始给出简洁的一句话(“为<项目>搭建后续规划,覆盖延迟的优化工作”),你可以直接确认并继续。
  • 如果用户拒绝某个范围项(“不用,去掉第3项”),更新规划并继续——不要争论。
  • 当看到模糊的验收标准时要追问。“让它更好”或“优化UI”是不可测试的——要求具体的检查标准。

What this skill does NOT do

本技能不做的事情

  • Does not implement anything. No code is written.
  • Does not start execution. The project sits in Backlog until you run
    /project-start <project>
    .
  • Does not create a branch or worktree.
  • Does not invent dependencies between tickets you didn't mention. If the file surface implies a conflict but you didn't declare a dependency, ask — don't silently add one.
  • 不实现任何内容。不编写代码。
  • 不启动执行。项目会处于Backlog状态,直到你运行
    /project-start <project>
  • 不创建分支或工作树。
  • 不会在你未提及的工单间添加依赖关系。如果文件范围暗示冲突但你未声明依赖,要询问——不要静默添加。

Report

报告

Final line:
result: planned <project-name> — N tickets, M depends_on edges, handoff at .handoffs/<slug>/
.
If you filed any manual tasks, add:
manual: K task(s) filed — <ids>
.
最后一行:
result: planned <project-name> — N tickets, M depends_on edges, handoff at .handoffs/<slug>/
如果提交了任何手动任务,添加:
manual: K task(s) filed — <ids>
。",