Files
octopus-workflow/core/skills/analyze-dag/SKILL.md
T

28 KiB
Raw Blame History

name, description, triggers, role
name description triggers role
analyze-dag Use ONLY when an Epic is routed to the DAG ticket pipeline (Kind/Epic or Kind/Feature — both resolve to analyze-dag via the routes table directly, [org-internal #3072] phase 3 / [org-internal #3061] Phase 2). The Analyst decomposes the Epic into a single task-DAG artifact — merging roadmap+requirements+design+plan into one graph (nodes = acceptance criteria, edges = contracts, topology = plan).
analyze-dag
dag pipeline
DAG 管线
任务 DAG
task DAG
DAG decomposition
DAG 分解
Producer

Analyze DAG — Task-DAG Decomposition (DAG route entry skill)

Purpose

analyze-dag is the entry skill of the DAG ticket pipeline (dag.route.entry.skill, spec-07 §2). For a DAG-routed Epic (Kind/Epic, or Kind/Feature — both resolve here via the routes table directly, [org-internal #3072] phase 3 / [org-internal #3061] Phase 2 / TD-388 — including BACKFILL mode for MVP graduates and big-bug relabels), it replaces roadmap + requirements-elicitation + design + plan-iterations and produces ONE artifact: the task DAG, published as wiki page {epic-slug}/dag (Tier 2 — human-visible, cross-worktree stable).

The task DAG merges three views into a single graph:

View Where it lives in the DAG
requirements node acceptance_criteria (each node is its own spec)
design edge contract_ref (inter-node interface contract)
plan topology (edge direction = dependency / execution order)

{epic-slug} = the slug of the routed Epic — not this design's own slug dag-pipeline.

Agent Role

The Analyst (Producer) owns and produces the task DAG. The Analyst decomposes the Epic into nodes (acceptance criteria), edges (contracts), and a topological order (the plan), then publishes the single graph.

Route Disqualifiers — 适用性预检(分解前必查)

分解前,Analyst 必须对照下表预检。任一命中 → 不分解,将具体未决问题 列回 Epic 并 bounce(在不稳定需求上分解会产出一个即刻腐烂的冻结图; "先出个小 DAG" 不是降级选项——不进管线,先稳定需求):

Disqualifier 判定 Bounce 动作
需求未稳 scope item 含糊、互相矛盾、或只能以 "视情况" 回答;关键干系人问题仍开放 在 Epic 上列出澄清问题清单;问题解决后重入 analyze-dag
设计主导 主要风险是一个有 ≥2 个可行选项的架构/平台决策,而非任务分解;需要先做一次探索 将开放问题转 prototypeTHROWAWAY spike)或设计探索;决策记录后重入
外部契约未决 scope 依赖未决的外部契约(第三方 schema、上游 API 未定) 按 External-System Rule(见下文)解析或围栏;不可拆的残余隔离后再重入

反向拦截(Kind/MVP 降级建议,[org-internal #3061]:一个 Kind/Feature 工单在分解预检中 暴露"概念未稳"(scope 仍在探索、成功标准未定、决策日志会推翻节点划分)时, 正确出口不是硬分解,而是建议改标签 Kind/MVP 先行交互式验证——概念稳定后 经 backfill 模式(见下文)带着决策日志重入。

Phase 0 需求澄清(disqualifier 的结构化扩展,[org-internal #3061]

预检不止于"能否分解",还须证明"需求已澄清到可分解"。两项强制检查:

1. Scope item 可证伪性预检:逐条检查 Epic 的 scope item。每条必须能改写为 一个可观察的通过条件(给定输入/操作 → 可观测的结果);写不出来的条目就是 澄清问题,列入 bounce 清单。禁止把不可证伪条目直接塞进 node AC。

2. 横向完备性三态清单:对下表每个维度做三态判定——已覆盖scope 有 对应条目)/ 不适用(一句话理由)/ 待澄清(列具体问题)。任一维度停在 "待澄清" → bounce(澄清问题清单随 bounce 一起给出,不让用户猜缺什么):

维度 检查
外部系统 沿用 External-System Rule(见下文):每个外部交互有明确协议与失败语义
错误路径 关键流程的失败分支有定义(不是只写 happy path)
数据生命周期 数据的创建/保留/删除策略已明确
权限/安全 访问边界与敏感数据处理已明确(或显式声明"无安全面")
NFR 性能/容量/兼容性约束已枚举(见下)
边界条件 值域/并发/时序等边界有约定(或声明"无特殊边界")

NFR 输入来源明确化NFR 不允许"分解时脑补"。Epic 必须自带 NFR 输入—— scope item 中带 NFR: 前缀的条目,或一行显式声明"无 NFR 约束"。两者皆缺 → 作为澄清问题 bounce,而不是默认无约束。node schema 的 NFR: 条目只能来自 该枚举,不得新增臆造。

Node Schema (spec-03 §1)

node:
  id: "N-01"                    # task node N-{nn}; milestone node M-{nn}
  title: "契约骨架"              # one line
  type: task                    # task | milestone
  acceptance_criteria:          # "requirements" view — one falsifiable AC per line
    - "AC-1: <可证伪的验收条件, 映射 test_id>"
    - "NFR: <产品 NFR 条目>"      # NFR:-prefixed entry — enumerated storage, no REQ-F number
  req_refs:                     # functional-requirement references
    - "REQ-F-001"
  status: pending               # pending | ready | in_progress | done | blocked | green(仅里程碑)
  owner_session: null           # assigned session id, null when unassigned
  size_attrs:                   # decomposition-time measured attributes (spec-06)
    cross_session_in: 1
    cross_session_out: 1
    contract_change: additive   # none | additive | breaking (out-edge aggregation)
    estimated_hours: 8          # task nodes only; milestone nodes omitted
    estimated_sessions: 1       # task nodes only; milestone nodes omitted

Field semantics:

  • acceptance_criteria — the node is its spec. Each AC is falsifiable and maps a test_id{ file-path::test-name | MANUAL | BENCH:<script> } (the plan-iterations §6 SDD→TDD handshake; no other values). Entries MAY be NFR:-prefixed (product NFRs — written into node AC as enumerated storage; they carry no REQ-F number and are not functional ACs).
  • req_refs — functional-requirement ids this node covers, derived from the requirement registry (see below). Drives the REQMAP "every requirement ≥1 node / every node ≥1 requirement" mechanical check.
  • status — drives rolling release (spec-04 RELEASE). ready = all cross-session in-edge source nodes reached their terminal state (task done, milestone green; same-session edges do not block). green is milestone only (a task node's terminal state is done).
  • size_attrs — decomposition-time measured inputs; the DAG-level dag_metrics aggregates them. estimated_hours / estimated_sessions are required only for type: task nodes (milestone nodes have no implementation work and omit them). The two fields are two units of the same estimate; the consistency invariant is |estimated_hours 8 × estimated_sessions| ≤ 2, and granularity is always judged by estimated_sessions (1 session ≈ 8h).

Edge Schema (spec-03 §2)

edge:
  from: "N-01"                  # source node id
  to: "N-02"                    # target node id
  contract_ref: "shared/types-v1"   # contract pointer (wiki {epic-slug}/shared/ or schema $id)
  cross_session: true           # from/to owned by different sessions?
  contract_state: draft         # draft | frozen (only meaningful for task-to-task cross-session edges)
  change_type: additive         # none | additive | breaking (only cross-session edges)

Field semantics:

  • contract_ref — the "design" view: where the contract lives. Cross-session task edges point at wiki {epic-slug}/shared/ or a schema $id; same-session contracts may be inlined.
  • cross_session — contract freezing applies only to true edges (spec-02 §1 D-05). Same-session edges are cross_session: false (zero freeze cost).
  • contract_statedraft (initial, freely revisable) → frozen (after the single-gate PASS). Only meaningful for task-to-task cross-session edges.
  • change_type — the contract change surface, feeding contract_change_surface.

Milestone edge exception (frozen, spec-03 §2 / spec-02 §2.1): a milestone is a session-less integration point, so every edge connected to a milestone (in + out) is cross_session: true but carries NO contract_ref, NO change_type, and contract_state is not applicable (never frozen). Milestone edges do not participate in contract_change_surface aggregation (treated as none).

Topology Constraints (spec-03 §3 — must satisfy, or TOPO fails)

  1. Acyclicity — the directed graph must have no cycle. A cycle = BLOCKER.
  2. Milestone weld rule — any node with cross_session_in ≥ 2 (≥2 cross-session in-edges converging on it) MUST have a milestone M welded at the convergence point, such that:
    • (a) all cross-session in-edges terminate at M (M is the convergence point — fan-in edges end at M, no separate original convergence task node);
    • (b) M has no implementation work (type: milestone, no acceptance_criteria, only a DoD);
    • (c) M's DoD = integration verification of the converged scope (spec-05);
    • (d) M is the 必经 (must-pass) node for all downstream consumers — downstream nodes may not become ready before M is green. A convergence point with no downstream consumer is a sink milestone (in-edges only).
  3. Granularity floor — every type: task node's implementation estimate is carried by size_attrs.estimated_hours / estimated_sessions (milestones omit both). Judged by estimated_sessions (1 session ≈ 8h). The SINGLE SOURCE of the judging criteria — floor/ceiling bounds, the severity mapping (field-inconsistency, below-floor, above-ceiling, giant node), and the folded checklist rows — is core/checklists/dag-single-gate.md §TOPO: recalibrate there, not here. (One-line summary: sessions far below the floor → merge candidates; far above → split/giant; hours-sessions mismatch → field inconsistency.)

Requirement Registry (需求登记表, spec-02 §2.9)

Maintained at the DAG root. Each row:

- id: "REQ-F-001"               # derived from the Epic scope-item number
  title: "<需求标题>"
  source: "<来源 scope item>"
  refs_by: ["N-01", "N-02"]     # node ids that reference this requirement

Rules:

  • Only functional requirements (Epic scope items) get REQ-F-{NNN} numbers and rows in the registry.
  • Product NFRs do NOT get REQ-F numbers and are NOT registered as rows — they are written into node acceptance_criteria as NFR:-prefixed entries (enumerated storage), so "every NFR ≥1 node" is mechanically checkable via node-AC enumeration.
  • refs_by is derived from the REQMAP "every requirement ≥1 node" criterion (missing coverage = a requirement with no node).

Breadth Coverage — 类目覆盖矩阵 + 排除账本 ([org-internal #2905] 方案 1)

The four-stage fold that produced the single DAG artifact kept requirement depth (REQMAP three-path coverage + falsifiable AC) but dropped legacy elicitation's breadth scanning ("还有什么别的需求?"-style category sweep). The registry above only registers what the proposer wrote — a requirement nobody wrote does not exist in the system, and REQMAP's bidirectional mapping checks in-table consistency, never 表 ↔ 现实 completeness. The coverage matrix is the mechanical, enumerable part of the compensation (three layers: artifact = matrix, graph = probe nodes, data = dag.route.preflight learning loop; this section is the artifact layer).

analyze-dag MUST emit both sections in {epic-slug}/dag (template core/templates/dag.md §2.1/§2.2):

  1. 类目覆盖矩阵 (coverage matrix) — the fixed category set (≤10 categories, extracted from legacy <instance-root>/archive/checklists/requirements-analysis.md CMP/SAF dimensions) × state ∈ {已覆盖, 明确排除, 待确认}. The category list is FIXED in the template — do not invent or drop categories per Epic; state is per-Epic.
    • 已覆盖 — the category has registered demand (registry REQ-F-{NNN} row or a node NFR: entry); evidence cell cites the id(s).
    • 明确排除 — out of scope, with a ledger row (below). An exclusion without a ledger row is NOT valid.
    • 待确认 — intermediate state; MUST carry (向{确认人}确认{什么}) so it is distinguishable from 漏了. 待确认 rows SHOULD be driven to a terminal state (已覆盖/明确排除) before the draft is submitted to review-dag.
  2. 排除账本 (exclusion ledger) — one E-n row per exclusion: 条目 — out of scope, 理由, 确认人. Exclusions are reversible (delete the row + flip the matrix state in the same revision).

明确排除 ≠ 漏了 (distinguishability): excluded = matrix row 明确排除 + ledger E-n row with reason and confirmer; 漏了 = blank state, or an "N/A" without a ledger row. The single gate does NOT gain criteria for this — REQMAP's three tables are frozen (spec-04 §1) and 表 ↔ 现实 completeness is a production-side obligation of analyze-dag; reviewers may still raise findings on the matrix as ordinary artifact content.

Relation to the Product-Epic External-System Rule: that rule is the external-integration category applied to product Epics — the matrix generalizes its shape from 1 category to N. For a product-type Epic the 外部系统集成 category remains mandatory-covered (task node or cross-session contract edge, see the rule below). Process-infrastructure Epics will typically 明确排除 most product-facing categories — that is legitimate; the ledger records why.

Page-size budget: the matrix + ledger participate in the ≤800-line budget (spec-02 §2.6). If the page exceeds, sink category DETAIL to sub-page {epic-slug}/dag-coverage using the same mechanics as AC sinking (in-page pointer, return link above the status line, version stamp rides the frozen copy) — purely organizational, NOT an oversize signal, no re-derivation.

Breadth probe nodes (P2, [org-internal #2905] 方案 2)

When a 待确认 category is cheapest to resolve empirically, a probe node is a legal task node at the granularity floor:

- id: "N-07"
  title: "探查:{类目} 存量/可行性核对"
  type: task
  acceptance_criteria:
    - "AC-1: 产出 {类目} 结论(进/不进登记表)— MANUAL"
  req_refs: ["REQ-F-00n"]     # 指向被其澄清的需求;探查本身不派生新 REQ-F
  status: pending
  size_attrs:
    cross_session_in: 0
    cross_session_out: 0
    contract_change: none
    estimated_hours: 2
    estimated_sessions: 0.25  # 粒度下限(Topology Constraints 3)——合法,不得更低
  • MANUAL is a legal test_id value — a probe's deliverable is a human verdict (matrix flip), not an automated test.
  • A probe node anchors to an existing requirement it clarifies (req_refs non-empty — REQMAP 节点溯源 requires it). A 待确认 category with NO anchor requirement is resolved by confirmation (向确认人问询), not by a probe node.
  • The probe's conclusion flips the category: → 已覆盖 (new registry row + covering nodes) or → 明确排除 (new ledger row). A conclusion landing BEFORE freeze is ordinary draft revision (no signal).
  • 登记表单独加行 ≡ node_split(显式化):post-freeze 新增登记表行(新需求) 按 node_split oversize 信号处理——触发重派生 + 无条件重跑 review-dag、修订 冻结副本(新增需求必然改变节点集合或 req_refs;需要新节点时全额按 node_split 建单传播)。

Data layer — read dag.route.preflight BEFORE decomposition ([org-internal #2905] 方案 3)

Before starting decomposition, analyze-dag MUST read <instance-root>/workflow-routing.yaml dag.route.preflight and, when the list is non-empty, verify each item as a producer self-check (the same injection contract implement applies for routes.{Kind}.preflight, [org-internal #2599] — landed items carry their retro evidence in evidence):

  1. Read the dag: block's route.preflight array (live yaml, repo root).
  2. For each item: run its item self-check against the Epic's scope / breadth matrix before writing the DAG (items are typically recurring late-discovery categories, e.g. the retro-derived REQ × late-discovery class — requirements that surfaced only after previous DAGs froze).
  3. An item that fires becomes a 待确认 matrix row (or a probe node, above) — never silently dropped.
  4. Absent/empty array → no injection, proceed (current state: the list is empty until a retro cycle lands evidence).

dag_metrics (spec-03 §4 / spec-06)

Stored at the DAG root / Epic aggregator. Keys are frozen (spec-02 §2.6):

dag_metrics:
  node_count: 6                  # task + milestone nodes total
  cross_session_edge_count: 7    # total cross_session:true edges (milestone edges counted too)
  contract_change_surface: additive   # none | additive | breaking
  review_depth: D4               # derived tier D1D4
  • contract_change_surfaceworst-value aggregation of change_type across cross-session task edges only (breaking > additive > none). Milestone edges are excluded (treated as none — no contract, no change_type).
  • review_depth — derived (decomposition is the sizing, 分解即定档):
review_depth = max(
    depth_by(node_count),
    depth_by(cross_session_edge_count),
    depth_by(contract_change_surface)
)

Thresholds (spec-06 §2 — authoritative):

Measured attribute D1 D2 D3 D4
node_count ≤3 ≤8 ≤15 >15
cross_session_edge_count 0 13 ≥4
contract_change_surface none additive breaking
  • cross_session_edge_count: 0 → D1; 13 → D3; ≥4 → D4. D2 is not derivable from this dimension.
  • contract_change_surface: none → D1; additive → D3; breaking → D4 (D2 not derivable from this dimension).
  • review_depth is the review-dag review-resource parameter (reviewers / round cap, spec-06 §3) — analyze-dag computes it; it does not assign Size/* labels (DAG-routed tickets ignore Size/*).

Page-Size Budget (spec-02 §2.6)

Threshold single source: the page-size budget and the > 页尺寸自检: 超限 signal are judged by core/checklists/dag-single-gate.md (TOPO 页尺寸自检信号 row) — recalibrate there, not here. (One-line summary: the {epic-slug}/dag body has a line budget (~800 lines at calibration time); on breach, sink node detail to sub-pages and emit the self-check signal per the checklist row.)

The {epic-slug}/dag page body — node table + edge table + requirement registry

  • breadth coverage matrix (§2.1) + exclusion ledger (§2.2) + dag_metrics — must fit the checklist's page-size budget (see the single-source note above; ~800 lines at calibration time). On breach, analyze-dag MUST:
  1. Sink node AC detail into sub-pages {epic-slug}/dag-nodes/{node-id} (sub-page naming {epic-slug}/dag-nodes/{node-id} is frozen);
  2. Keep an in-page pointer {node-id} → {epic-slug}/dag-nodes/{node-id} for each sunk node;
  3. Put a return-link line on each sub-page, above its status line: > 返回 [{epic-slug}/dag](…);
  4. Write a page-size self-check signal into the DAG page header, parallel to the > DAG 工件状态 line: > 页尺寸自检: 超限({node-id} 已下沉子页).

Category detail (matrix/ledger 细目) may likewise sink to {epic-slug}/dag-coverage (same mechanics — see Breadth Coverage). This is NOT an oversize_signal and does NOT trigger re-derivation — page overflow does not change the three derivation inputs (node_count / cross_session_edge_count / contract_change_surface); sub-page sinking is purely organizational. (Re-derivation is triggered only by the four spec-06 §5 oversize signals.)

Product-Epic External-System Rule (spec-04 §2 ARCH 1.1 alternative)

For a product-type Epic, external systems and integration protocols MUST explicitly appear in the DAG — either as task nodes (with acceptance criteria) or as cross-session contract edges (contract_ref pointing at an external contract). This is what lets the REQMAP "需求覆盖" (requirement coverage) check mechanically verify that external-integration requirements map to nodes. (The ARCH 1.1 system-context diagram is discarded only for process-infrastructure-type Epics; product Epics get this alternative rule instead.) [org-internal #2905]: this rule is the 外部系统集成 category of the breadth coverage matrix held at its original strength — see Breadth Coverage above.

Exec-Resource Configuration (spec-07 §4 — mechanical destination of folded checklist rows)

This clause is the mechanically checkable destination of the folded plan checklist rows RISK 3.73.10 / EST 6.36.12 / EXE 8.78.11 (single-gate does not re-audit these — they live here, in the skill). A analyze-dag run MUST observe:

Resource Configuration
Model tier assignment reasoning-tier for decomposition; fast-tier for mechanical transcription (node/edge/registry/dag_metrics table assembly). reviewer-tier — i.e. the Reviewer Explorer role (_shared/roles/reviewer.yaml; the vocabulary the review skills use for reviewer dispatch is agent type Explorer) — is used only via the review-dag gate (see review-artifact target review-dag "Reviewer Constants": Model tier) — never inside analyze-dag.
Token budget a single analyze-dag agent call's estimated token consumption must stay within the model's context-window capacity, and the {epic-slug}/dag page body ≤ 800 lines (page-size budget) bounds the artifact's token footprint.
Context-window ceiling keep single-call consumption ≤ 70% of the model's context window.
RAG supplement if the Epic's scope touches knowledge newer than the model's training cutoff, configure RAG supplementation before decomposition.
API-unavailable fallback define a fallback path (retry / degrade to a lower tier / pause and report) when the model API is unavailable.
Deterministic tasks deterministic tasks (mechanical transcription) run at temperature ≤ 0.1.
AI-artifact origin annotation the DAG artifact is annotated with its origin — model + temperature + timestamp.

Output & Lifecycle

  1. analyze-dag writes wiki page {epic-slug}/dag with first line: > DAG 工件状态: draft.
  2. After the single-gate (review-dag) PASS, the aggregator agent (main session / Orchestrator — not analyze-dag) asks the freeze confirmation question ([org-internal #3379]), then on confirmation flips the page to > DAG 工件状态: frozen and stamps version v1. The confirmation is an OPTIONIZED question call (stage review-dag, never destructive — freeze is a process gate, not a business decision): options Freeze as v1 (Recommended) / Hold — back to review, plus a one-line summary of the gate verdict (rounds, worst severity, open waivers) in the question text. Choosing hold = do NOT flip — leave the page draft, record the hold and the reason as a comment on the source Epic, and route back to review-artifact. An unconfirmed flip is a spec violation: the flip MUST be preceded by the question call in the same session, and the confirmation leaves a trace — post a comment on the source Epic (DAG frozen: {epic-slug}/dag v1 — confirmed via question, {date}) before flipping. In autonomous runs the question auto-approves via its stage (choosing the recommended freeze) — the gate guarantees the decision SURFACES with a defined shape at a defined moment, not that a human always attends it.
  3. On each post-freeze re-review PASS (an oversize-signal re-run, spec-06 §5), the aggregator agent updates the frozen copy in place and increments the version stamp vN → vN+1 (no re-confirmation — only the initial freeze gates on a decision).

Backfill Input Mode (Kind/MVP graduation & big-bug relabels, [org-internal #3061])

When a Kind/MVP ticket graduates (relabel → Kind/Feature → Step 0 → this skill), the DAG is derived from what was actually built, not from greenfield decomposition:

  • Inputs: the ticket body's ## 决策日志 (one line per decision: what / why / expiry) + ## Debt Register section + codegraph reverse call graph of the implemented surface.
  • Nodes follow the code's actual module/contract structure; each node's AC traces to a decision-log line or an existing test. The decision log is the REQMAP baseline for the single gate — decisions still marked deferred/expires become explicit open questions on the Epic, not silent assumptions.
  • Debt register items map to node ACs when they describe unfinished behavior; remaining shortcuts stay debt (prototype/EVOLUTIONARY path → verify Phase 5.5 registration).
  • Big-bug relabel inputs (a Kind/Bug relabeled Kind/Feature per the big-bug rule, rules/workflow-routing.md §"Runtime growth signals (post-sizing)" — big-bug relabel rule): the repro
    • root-cause notes (wiki page {slug}/repro-notes) carry over as node input — node ACs trace to the reproduction steps and root-cause findings, and the root-cause analysis (not a decision log) serves as the REQMAP baseline for the single gate.
  • Disqualifiers STILL apply in backfill mode: an MVP whose decision log shows unresolved conflicting deferred decisions bounces (stabilize first) instead of freezing a contradictory graph.

What analyze-dag does NOT do

  • No synthesis. analyze-dag does not produce a synthesis — the review-artifact skill Phase E produces the single-gate synthesis after review-dag converges.
  • No ## 工件索引 issue comment. analyze-dag is a sub-agent; it does NOT create the Epic's ## 工件索引 comment. The aggregator agent / main session creates and maintains that comment (issue-cross-linking Pattern 10), and the review / verify stages report their rows to it — analyze-dag only publishes the {epic-slug}/dag artifact.

References

  • core/templates/dag.md — the DAG artifact structure contract (this skill writes {epic-slug}/dag against it; node/edge/milestone/dag_metrics shape + §6 topology constraints)
  • dag-pipeline/spec-02 — frozen contracts (§2.1 node/edge, §2.6 dag_metrics + page-size budget, §2.9 requirement registry + test_id)
  • dag-pipeline/spec-03 — node/edge schema + topology constraints
  • dag-pipeline/spec-04 — single-gate TOPO/REQMAP/RELEASE dimensions (folded-row destinations)
  • dag-pipeline/spec-06 — dag_metrics derivation + thresholds
  • dag-pipeline/spec-07 — dag.route landing row (entry skill, skip set, single-gate) + §4 landing list
  • dag-pipeline/spec-08 — DAG artifact lifecycle / version stamp
  • <instance-root>/archive/skills/roadmap/SKILL.md — legacy route (archived 2026-08-21, [org-internal #3072] phase 3; this skill replaces it for all Epics)