diff --git a/plugins/issue-driven-dev/references/chain-flow.md b/plugins/issue-driven-dev/references/chain-flow.md index fb23041..6c6de04 100644 --- a/plugins/issue-driven-dev/references/chain-flow.md +++ b/plugins/issue-driven-dev/references/chain-flow.md @@ -10,13 +10,29 @@ This document defines the **algorithmic contract** the chain shell honors. Imple | Phase | Action | Side effects | |-------|--------|--------------| -| 0 | Pre-flight + cluster branch setup + manifest init | Creates `idd/chain--` branch; writes `.claude/.idd/state/chain-spawned-issues.json` schema_version=1 | +| 0 | Pre-flight (5 gates: git repo / gh auth / issue OPEN / **diagnosis-readiness** / clean tree) + cluster branch setup + manifest init | May PATCH issue body (audit trail) when diagnosis bypassed; creates `idd/chain--` branch; writes `.claude/.idd/state/chain-spawned-issues.json` schema_version=1 | | 1 | Initialize chain state (queue, depth_map, processed set) | In-memory only | | 2 | Main chain loop — pop, invoke `/idd-all #N --in-chain`, read manifest delta, enqueue eligible spawns | Commits on cluster branch (via sub-`/idd-all`); manifest grows append-only | | 3 | Open cluster PR with collapsed per-issue sections | `git push` cluster branch; `gh pr create` | | 4 | Final report; STOP at verified | Print summary; no auto-close, no auto-merge | -A chain run is fully determined by `(root_issue, working_tree_state, manifest_writes_during_run)`. No external state. +A chain run is fully determined by `(root_issue, working_tree_state, manifest_writes_during_run, user_choice_at_diagnosis_gate)`. No external state. + +## Diagnosis-readiness gate (Phase 0 Step 0.4, v2.55+ #47) + +Phase 0 includes a diagnosis-readiness gate **before** any branch / manifest creation. Detects whether the root issue has a `## Diagnosis` comment via `comments[*].body` filter (precise — does NOT inspect issue body, which may discuss "diagnosis" conceptually). + +When detection finds no diagnosis comment, the chain shell fires `AskUserQuestion` with three options reflecting different mental models: + +- **`run /idd-diagnose first`** — diagnosis-first discipline (default safest). Halts cleanly with zero side effects (no branch / manifest yet); user runs `/idd-diagnose #N` then re-invokes `/idd-all-chain`. +- **`proceed anyway`** — escape hatch for fresh-issue / quick-iter scenarios. PATCHes issue body with `### Chain pre-flight: diagnosis bypassed` audit section so future readers know this chain ran without prior diagnose. +- **`cancel`** — clean recovery. Same as `run /idd-diagnose first` mechanically (zero side effect, just exits) but communicates "user changed mind" rather than "user will diagnose later". Print explicit `(no cleanup needed — Phase 0.4 ran before any branch/manifest creation)` to remove confusion. + +**No unattended fallback by design**: `/idd-all-chain` is a user-invoked deliberation moment. There is no automated caller path (`/loop`, cron, etc.) that should reach Step 0.4. If the call is made non-interactively for some reason, the AskUserQuestion will block — that is the correct behavior, not a bug. (Earlier draft included an `IN_CHAIN_CONTEXT` env detection for unattended fallback, but `/idd-verify #47` proved it was dead code with no producer in the repo, so the fallback was removed.) + +**Why placement before branch/manifest creation matters**: cancel-path side-effect minimization. If user picks `cancel`, no work is undone — there is no work yet. Diagnosis-readiness gate placed AFTER branch creation would leave dangling cluster branches user must manually delete, breaking IDD's halt+preserve discipline (preserve assumes there's something worth preserving). + +**Future #46 multi-root extension**: helper function design preserves N-arg shape (`check_diagnosis_readiness(issue_numbers...) → [ready_list, not_ready_list]`) so #46 can reuse for per-root readiness aggregation. v1 ships single-root only. ## Cluster branch naming diff --git a/plugins/issue-driven-dev/skills/idd-all-chain/SKILL.md b/plugins/issue-driven-dev/skills/idd-all-chain/SKILL.md index c358f18..c4e9e5b 100644 --- a/plugins/issue-driven-dev/skills/idd-all-chain/SKILL.md +++ b/plugins/issue-driven-dev/skills/idd-all-chain/SKILL.md @@ -62,6 +62,7 @@ Inherits `/idd-all` config protocol (walked-up `.claude/issue-driven-dev.local.j ``` TaskCreate(name="preflight", description="Phase 0: 解析 args、gh auth、確認 root issue OPEN") +TaskCreate(name="check_diagnosis_readiness", description="Phase 0.4 (NEW, v2.55+ #47): gh issue view --json comments + jq filter '## Diagnosis'; found → silent pass; not found → AskUserQuestion 3-option (run /idd-diagnose first / proceed anyway / cancel). Placed before cluster branch / manifest creation so cancel has zero side effect.") TaskCreate(name="setup_cluster_branch", description="Phase 0: 建 cluster branch idd/chain-N- from default branch + 初始化 spawn manifest") TaskCreate(name="init_queue", description="Phase 1: queue = [root], depth_map = {root: 0}, closed_set = {}") TaskCreate(name="chain_loop", description="Phase 2: 主 loop — pop queue, invoke /idd-all #current --in-chain, read manifest, enqueue eligible spawns until queue empty / depth limit / max-issues cap reached / verify FAIL halt") @@ -113,7 +114,101 @@ STATE=$(gh issue view "$ROOT_ISSUE" -R "$GITHUB_REPO" --json state -q .state 2>/ [ "$STATE" = "OPEN" ] || abort "Issue #$ROOT_ISSUE state=$STATE (must be OPEN)" ``` -#### Step 0.4: Cluster branch setup +#### Step 0.4 (NEW, v2.55+ #47): Diagnosis-readiness check + +Chain 預期 root issue 已 spec 收斂(有 `## Diagnosis` comment)。沒收斂就跑 chain → unattended idd-diagnose Layer V 自動 `proceed anyway` → idd-implement 基於 vague spec 做 design 猜測 → 6-AI verify 抓不到根本問題(因為 reviewers 也只看到 partial spec)。 + +**Why ASK not BLOCK**:fresh-issue + quick-iter scenarios 有時 explicit 想跳過 prior diagnose;hard block 太嚴。AskUserQuestion + audit trail 是 IC_R011 canonical pattern 的 balance point。 + +**Why placed here (before cluster branch / manifest creation)**:user 選 `cancel` 時 zero side effect to clean — 不會留 dangling branch / manifest。 + +##### Detection (bash) + +```bash +# Detect via comments[*].body — NOT issue body +# (precise — avoids false-positive on issue body discussing "diagnosis" concept) +# Enable strict mode: gh / jq failure should NOT silently set HAS_DIAGNOSIS="" +# (which would then equal "0" in `=` test below and silently skip the gate). +set -e + +HAS_DIAGNOSIS=$(gh issue view "$ROOT_ISSUE" -R "$GITHUB_REPO" --json comments \ + | jq -r '[.comments[] | select(.body | contains("## Diagnosis"))] | length') + +# Defensive: if upstream returned empty/non-numeric, treat as infrastructure failure not silent pass +if ! [[ "$HAS_DIAGNOSIS" =~ ^[0-9]+$ ]]; then + abort "Diagnosis-readiness check failed: gh/jq returned non-numeric output ('$HAS_DIAGNOSIS'). Investigate gh auth / network and retry." +fi +``` + +If `HAS_DIAGNOSIS != 0` → diagnosis comment exists → silent pass, fall through to Step 0.5。 + +If `HAS_DIAGNOSIS == 0` → no diagnosis comment → enter the AskUserQuestion deliberation moment described below。 + +##### AskUserQuestion deliberation (prose — NOT a bash function call) + +> **Why prose instead of bash**: `AskUserQuestion` is a Claude Code tool invoked at the agent level, **not** a binary on `$PATH` or a shell function. Embedding `AskUserQuestion(...)` inside a fenced bash block was a category error caught in /idd-verify #47 (P1 finding 2). The agent reads the bash detection logic, branches at the agent level on `HAS_DIAGNOSIS == 0`, then handles the deliberation as described in prose here. Same pattern as `idd-all/SKILL.md` Phase 0.5 ask-policy interaction. + +When `HAS_DIAGNOSIS == 0`, the agent invokes the **AskUserQuestion** tool with this question structure (per IC_R011 canonical 3-option pattern): + +> "Issue #${ROOT_ISSUE} 沒有 diagnosis comment。沒 diagnose 跑 chain 風險:unattended idd-diagnose Layer V 自動 proceed 可能基於 vague spec 做出 design 猜測。怎麼處理?" +> +> Options (default = first): +> - **`run /idd-diagnose first`** — halt chain + preserve nothing (本 step 前無 branch/manifest) + 提示跑 `/idd-diagnose #N`,完成後重 invoke `/idd-all-chain` +> - **`proceed anyway`** — 繼續 chain;PATCH issue body 加 `### Chain pre-flight: diagnosis bypassed` audit section +> - **`cancel`** — abort + 印 cleanup commands (本 step 前無 side effect,只 exit) + +Based on the user's selection: + +- **`run /idd-diagnose first`** → echo `"→ Halt: please run /idd-diagnose ${ROOT_ISSUE} first, then re-invoke /idd-all-chain"`, then `exit 0` (clean halt — user's deliberate choice, not an error) +- **`proceed anyway`** → invoke the proceed-anyway audit trail PATCH bash below, then continue to Step 0.5 +- **`cancel`** → echo `"→ Aborted by user. No state changes made (Phase 0.4 ran before any branch/manifest creation)."`, then `exit 0` + +##### proceed-anyway audit trail PATCH (bash) + +```bash +# Build audit block (defined as inline expansion, not a function — avoids +# forward-reference issues caught in /idd-verify #47 P1 finding 3). +AUDIT_BLOCK=$(cat < **Future #46 multi-root extension hook**:#46 multi-root chain 落地時,本 step 的 detection logic 應 refactor 成 named helper function `check_diagnosis_readiness(issue_numbers...)` return `[ready_list, not_ready_list]` struct,讓 multi-root 可批次 check + 聚合 AskUserQuestion。**Currently inline only**(待 #46 + #51 follow-up issue 處理)— 本 step v1 single-root sufficient。 + +> **Removed pseudo-fallback for unattended caller**: 早期 design 含 `IN_CHAIN_CONTEXT` env var 偵測作 unattended fallback,但實際 repo 中**無任何 producer** sets this var(/idd-verify #47 P1 finding 1)。`/idd-all-chain` 是 user-invoked deliberation moment,沒 unattended caller path,該 env detection 是 dead code,移除。若未來真有 unattended caller,需明確設計 producer + 文件化 detection convention。 + +#### Step 0.5: Cluster branch setup ```bash # Working tree must be clean @@ -140,7 +235,7 @@ git -C "$CWD" checkout -b "$CLUSTER_BRANCH" echo "→ Cluster branch created: $CLUSTER_BRANCH" ``` -#### Step 0.5: Initialize spawn manifest +#### Step 0.6: Initialize spawn manifest Per `references/spawn-manifest.md` schema v1: @@ -393,9 +488,11 @@ EOF | 情況 | 行為 | |------|------| | Root issue not OPEN | Phase 0.3 abort | -| Working tree dirty | Phase 0.4 abort, 提示 stash/commit | -| Not on default branch | Phase 0.4 abort, 提示 checkout default | -| Cluster branch already exists | Phase 0.4 abort, 提示手動清理 | +| Diagnosis comment missing on root issue + user picks `cancel` | Phase 0.4 clean halt (no branch/manifest yet) | +| Diagnosis comment missing on root issue + user picks `run /idd-diagnose first` | Phase 0.4 clean halt + 提示 `/idd-diagnose #N` | +| Working tree dirty | Phase 0.5 abort, 提示 stash/commit | +| Not on default branch | Phase 0.5 abort, 提示 checkout default | +| Cluster branch already exists | Phase 0.5 abort, 提示手動清理 | | `/idd-all #M --in-chain` 沒在 cluster branch | sub-skill 自己 abort (Step 0.5 sanity check) | | Chained verify FAIL | Phase 2 halt, partial commits 保留, abort report 印 recovery options | | Chain depth > 2 | Spawn filed (sub-skill audit trail) but NOT enqueued, chain continues |