diff --git a/plugins/issue-driven-dev/README.md b/plugins/issue-driven-dev/README.md index 3e64298..a81e3ef 100644 --- a/plugins/issue-driven-dev/README.md +++ b/plugins/issue-driven-dev/README.md @@ -206,6 +206,18 @@ attachments_release: "attachments" --- ``` +### Selective auto-tag(v2.94.0, #85) + +IDD tags two milestones so you can jump back to a stable checkpoint with `git checkout` instead of hunting commit hashes: `idd-{N}-baseline` (placed by `idd-issue` on main's HEAD when the issue opens — the rollback anchor) and `idd-{N}-verified` (placed by `idd-verify` on the Aggregate-PASS snapshot — review-ready). Only these two points are tagged (no `diagnose`/`plan`/`implement` tags), so the tag namespace stays clean. + +**Default-ON**, opt-out via config. Tagging includes a `git push` (a repo-wide side effect), is idempotent (existing tag → skip), and graceful-skips on any push failure (never aborts the workflow). Turn it off with: + +```json +{ "auto_tag": { "enabled": false } } +``` + +Full schema (`enabled` / `baseline_format` / `verified_format` / `push_remote`) in [references/config-protocol.md](references/config-protocol.md) → `auto_tag` field. + ## Deep Integrations(深度整合套件總覽) IDD 遵循「**深度整合 >> hard-coded**」原則(#209/#214):生態系已有 canonical 套件時依賴它、不在內部複製等價邏輯。每個整合的綁定形狀與缺席行為: diff --git a/plugins/issue-driven-dev/references/config-protocol.md b/plugins/issue-driven-dev/references/config-protocol.md index 6717b96..c45e59a 100644 --- a/plugins/issue-driven-dev/references/config-protocol.md +++ b/plugins/issue-driven-dev/references/config-protocol.md @@ -480,6 +480,30 @@ An OPTIONAL identity registry so IDD can resolve a person's alias / email / disp **PII boundary (important).** `email` is personally-identifiable and MUST NOT live in a committed / public config. Keep the non-PII fields (`github_login` / `display_name` / `role` / `aliases`) in the normal walked-up config; put `email` only in a **private / gitignored** config layer. `idd-config validate` emits a PII reminder whenever it sees an `email` in a registry entry, so a leak into a tracked config is surfaced early. This mirrors the git-privacy boundary: a person's raw email is third-party PII, not your own derivative content. +### `auto_tag` field + +An OPTIONAL object that turns on **selective git-tag automation** at two high-value IDD milestones (#85): `idd-issue` tags the rollback baseline, `idd-verify` tags the review-ready snapshot. Deliberately *selective* — only these two points get a tag, so a long-running repo doesn't accumulate hundreds of stale lifecycle tags. + +```json +{ + "auto_tag": { + "enabled": true, + "baseline_format": "idd-{N}-baseline", + "verified_format": "idd-{N}-verified", + "push_remote": "origin" + } +} +``` + +| Field | Default | Meaning | +|-------|---------|---------| +| `enabled` | `true` | Master switch. **Default-ON**: with no config, `idd-issue` / `idd-verify` create + push these tags automatically. Set `enabled: false` to opt out completely. | +| `baseline_format` | `idd-{N}-baseline` | Tag placed by `idd-issue` on the local default-branch HEAD at issue-open time (the rollback anchor). `{N}` = issue number. | +| `verified_format` | `idd-{N}-verified` | Tag placed by `idd-verify` on the Aggregate-PASS snapshot (PR head in PR mode, current HEAD in local mode). | +| `push_remote` | `origin` | Remote the tags are pushed to. | + +**Side-effect note (important).** Tag creation includes a `git push` to `push_remote` — a **repo-wide side effect** visible to everyone with the remote. Because the default is **ON**, a fresh IDD user gets `idd-{N}-baseline` / `idd-{N}-verified` tags on their remote without asking; `enabled: false` is the one-line opt-out. Tagging is **idempotent** (a tag that already exists is skipped, so re-runs never re-tag) and **graceful-skip** (a push failure on a fork / permission-denied / rejected remote warns and continues — it never aborts the `idd-issue` / `idd-verify` workflow). Intermediate lifecycle steps (`diagnose` / `plan` / `implement`) are deliberately **not** tagged; phase-level tagging stays manual. + ## Resolution algorithm (canonical) ``` diff --git a/plugins/issue-driven-dev/scripts/tests/auto-tag/test.sh b/plugins/issue-driven-dev/scripts/tests/auto-tag/test.sh new file mode 100755 index 0000000..bbfc5c9 --- /dev/null +++ b/plugins/issue-driven-dev/scripts/tests/auto-tag/test.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# test.sh — drift-guard for selective git auto-tag (#85). +# +# #85 adds selective git-tag automation at two IDD milestones: +# - idd-issue creates `idd-{N}-baseline` (main HEAD at issue-open = rollback anchor) +# - idd-verify creates `idd-{N}-verified` on Aggregate PASS (review snapshot) +# opt-out via config `auto_tag.enabled`, idempotent, graceful-skip on push failure. +# +# WHY A CONTENT DRIFT-GUARD: the tag behaviors live in prose SKILL.md files +# (idd-issue Step 0/creation, idd-verify Aggregate-PASS branch) + a config schema +# (config-protocol.md) — AI prompts, not executable code. The falsifiable +# equivalent is asserting each file encodes the tag contract. The schema is +# documented across files that MUST agree; the failure mode is DRIFT (rename a +# format key in the schema, forget the SKILL that references it). Needles below +# are distinctive to #85 (0-occurrence before the change), so each is a genuine +# drift signal. +# +# Usage: bash test.sh (exit 0 = all pass, 1 = any fail) + +set -u + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PLUGIN_ROOT="$HERE/../../.." +PROTOCOL="$PLUGIN_ROOT/references/config-protocol.md" +ISSUE="$PLUGIN_ROOT/skills/idd-issue/SKILL.md" +VERIFY="$PLUGIN_ROOT/skills/idd-verify/SKILL.md" +README="$PLUGIN_ROOT/README.md" + +HELPERS="$HERE/../../lib/assert-helpers.sh" +[ -f "$HELPERS" ] || { echo "✗ missing $HELPERS — cannot run suite" >&2; exit 1; } +. "$HELPERS" + +assert_file_exists "config-protocol.md exists" "$PROTOCOL" +assert_file_exists "idd-issue SKILL.md exists" "$ISSUE" +assert_file_exists "idd-verify SKILL.md exists" "$VERIFY" +assert_file_exists "plugin README.md exists" "$README" + +# ── config-protocol.md: the auto_tag schema (source of truth) ── +assert_output_grep "protocol: declares the auto_tag field section" "### \`auto_tag\` field" "$PROTOCOL" +assert_output_grep "protocol: auto_tag config key" "auto_tag" "$PROTOCOL" +assert_output_grep "protocol: enabled key (default-on / opt-out)" "enabled" "$PROTOCOL" +assert_output_grep "protocol: baseline_format key" "baseline_format" "$PROTOCOL" +assert_output_grep "protocol: verified_format key" "verified_format" "$PROTOCOL" +assert_output_grep "protocol: push_remote key" "push_remote" "$PROTOCOL" +assert_output_grep "protocol: baseline tag naming default" "idd-{N}-baseline" "$PROTOCOL" +assert_output_grep "protocol: verified tag naming default" "idd-{N}-verified" "$PROTOCOL" +# default-ON must be explicit + opt-out documented (user decision 2026-07-09) +assert_output_grep "protocol: default-on documented" "default" "$PROTOCOL" +assert_output_grep "protocol: opt-out documented" "enabled: false" "$PROTOCOL" +# tag push is a repo-wide side effect — must be surfaced so default-on isn't a surprise +assert_output_grep "protocol: push side-effect surfaced" "side effect" "$PROTOCOL" + +# ── idd-issue: baseline tag after issue creation ── +assert_output_grep "idd-issue: tag_baseline bootstrap step" "tag_baseline" "$ISSUE" +assert_output_grep "idd-issue: baseline tag naming" "idd-{N}-baseline" "$ISSUE" +assert_output_grep "idd-issue: gated on auto_tag config" "auto_tag" "$ISSUE" +assert_output_grep "idd-issue: idempotent (tag exists → skip)" "idempotent" "$ISSUE" +assert_output_grep "idd-issue: graceful-skip on push failure" "graceful-skip" "$ISSUE" +# never abort the workflow on tag failure (warn-continue discipline) +assert_output_grep "idd-issue: tag failure never aborts" "never abort" "$ISSUE" + +# ── idd-verify: verified tag on Aggregate PASS ── +assert_output_grep "idd-verify: tag_verified bootstrap step" "tag_verified" "$VERIFY" +assert_output_grep "idd-verify: verified tag naming" "idd-{N}-verified" "$VERIFY" +assert_output_grep "idd-verify: gated on auto_tag config" "auto_tag" "$VERIFY" +assert_output_grep "idd-verify: fires on Aggregate PASS" "Aggregate PASS" "$VERIFY" + +# ── README: discoverability note ── +assert_output_grep "README: auto-tag convention documented" "auto_tag" "$README" +assert_output_grep "README: baseline/verified naming shown" "idd-{N}-baseline" "$README" + +print_summary "auto-tag (#85)" diff --git a/plugins/issue-driven-dev/skills/idd-issue/SKILL.md b/plugins/issue-driven-dev/skills/idd-issue/SKILL.md index b539b6d..9e8c4ed 100644 --- a/plugins/issue-driven-dev/skills/idd-issue/SKILL.md +++ b/plugins/issue-driven-dev/skills/idd-issue/SKILL.md @@ -112,6 +112,7 @@ TaskCreate(name="attach_images", description="上傳圖片到 attachments releas TaskCreate(name="create_milestone", description="來源為文件時自動建立 milestone 並指派(見 Step 4.5)") TaskCreate(name="clarity_surface", description="Step 4.6: delegate to /idd-clarify $NEW_ISSUE_NUMBER per IC clarity axis (skip in --multi-finding mode); failure → emit 'deferred' placeholder block + continue to Step 4.7 (per #135 v4 composable primitive design)") TaskCreate(name="linked_context_sister_sweep", description="Step 4.7: scan body draft + linked attachments + recent session conversation for sibling-concern markers (also / additionally / 另外 / 順便 / etc); if hits AskUserQuestion 3-option per canonical references/ic-r011-checkpoint.md; PATCH just-created issue body with `### Linked-Context Siblings Filed` audit trail (advisory, non-blocking, per IC_R011 #529)") +TaskCreate(name="tag_baseline", description="Step 3.6 (#85): if config auto_tag.enabled (default on), tag idd-{N}-baseline at the local default-branch HEAD (rollback anchor) + git push push_remote. Idempotent (tag exists → skip + note); graceful-skip on push failure (never abort). Skip when auto_tag.enabled=false.") TaskCreate(name="report_and_stop", description="回報 issue number/URL(group 模式列全部 + cross-link),停下等使用者決定下一步") ``` @@ -963,6 +964,38 @@ AskUserQuestion: - [~] **Native relationship suggestion (deferred)** — body 內 `#N` 用 GitHub 2024+ GraphQL(`addBlockedByDependency` 等)建 structured relationship,比 body text reference 多 sidebar backlink。**本 cluster 刻意不做**,理由:(1) 複雜度/風險最高(需 relationship-type picker UI);(2) #141 spec 自身將其框為「reuse v2.52.0+ `--blocked-by` code path」的後續工作,而該 path 尚未一般化成可獨立呼叫的 primitive;(3) design Q4 裁定 body text reference 本來就保留(與 native backlink 互補不重複),所以延後不損失現有 inline context。點亮條件:`--blocked-by` 的 GraphQL mutation 抽成可重用 helper 後,再接本 (d)。 +### Step 3.6: Baseline auto-tag(rollback anchor,v2.94.0+,#85) + +Issue 建立成功、拿到 `$NUMBER` 之後,`tag_baseline` step 在當前 repo 打一個 **rollback baseline** tag,標記「issue 開案當下的 main HEAD」,方便日後一秒切回開工前的乾淨狀態(`git checkout idd-{N}-baseline`),不用記 commit hash。 + +**Config gate(預設 on,可 opt-out)** — 讀 walked-up config 的 `auto_tag`(schema 見 [`references/config-protocol.md`](../../references/config-protocol.md#auto_tag-field)): + +```bash +AUTO_TAG_ENABLED=$(jq -r '.auto_tag.enabled // true' "$CONFIG_PATH" 2>/dev/null || echo true) +[ "$AUTO_TAG_ENABLED" = "false" ] && { echo "→ auto_tag disabled — skipping baseline tag"; } # opt-out: skip entirely +``` + +啟用時: + +```bash +BASELINE_FMT=$(jq -r '.auto_tag.baseline_format // "idd-{N}-baseline"' "$CONFIG_PATH" 2>/dev/null || echo "idd-{N}-baseline") +PUSH_REMOTE=$(jq -r '.auto_tag.push_remote // "origin"' "$CONFIG_PATH" 2>/dev/null || echo origin) +TAG="${BASELINE_FMT/\{N\}/$NUMBER}" # idd-{N}-baseline → idd-42-baseline +DEFAULT_BRANCH=$(gh repo view "$GITHUB_REPO" --json defaultBranchRef -q .defaultBranchRef.name) + +# idempotent: a tag that already exists is skipped (re-running idd-issue never re-tags) +if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then + echo "→ $TAG already exists — skip (idempotent)" +else + # baseline = the LOCAL default-branch HEAD (where main was when the issue opened) + git tag "$TAG" "$DEFAULT_BRANCH" && git push "$PUSH_REMOTE" "$TAG" \ + && echo "→ tagged $TAG at $DEFAULT_BRANCH HEAD + pushed to $PUSH_REMOTE" \ + || echo "⚠ auto_tag baseline: git tag/push failed (fork / no push permission / rejected) — graceful-skip, continuing" +fi +``` + +**鐵律 — graceful-skip,never abort**:tag 或 push 失敗(fork 無 push 權限、remote reject、default-branch 解析失敗等)**只 warn,絕不 abort** `idd-issue`(per IDD warn-continue pattern)。tag 是便利 checkpoint,不是 issue 建立的成敗條件。fork-aware:tag 打在當前 cwd 的 repo(Step 0.5 解析結果),push 到 `push_remote`(預設 `origin`)。intermediate step(diagnose / plan / implement)不 tag;phase-level tag 留手動。 + ### Step 4: 附加所有原始素材(鐵律:預設全保留) > 引用 Step 1 的「資料保留鐵律」:來源中**任何附件都要全部上傳**,不論張數、不論格式。 diff --git a/plugins/issue-driven-dev/skills/idd-verify/SKILL.md b/plugins/issue-driven-dev/skills/idd-verify/SKILL.md index 8e912de..32cb589 100644 --- a/plugins/issue-driven-dev/skills/idd-verify/SKILL.md +++ b/plugins/issue-driven-dev/skills/idd-verify/SKILL.md @@ -265,6 +265,7 @@ TaskCreate(name="recovery_protocol", description="Step 2.5 (NEW per #52): 缺 fi TaskCreate(name="wait_for_codex", description="等 Codex 背景任務完成,讀 /tmp/codex-verify-${NUMBER}.md") TaskCreate(name="merge_findings", description="合併 6 個來源 findings 去重,severity 取最高") TaskCreate(name="post_master_and_pointers", description="PR mode: master 貼到 PR + capture URL → 為每個 ref'd issue 貼 pointer comment;本地 mode: 貼到 issue(單 issue 直接貼/多 issue 用 SOP master+pointer)") +TaskCreate(name="tag_verified", description="Step 4.5 (#85): if config auto_tag.enabled (default on) AND Aggregate PASS, tag idd-{N}-verified at the PR head (PR mode) / current HEAD (local mode) + git push. Idempotent + graceful-skip. Cluster: tag each ref'd #N. Skip on FAIL or auto_tag.enabled=false.") TaskCreate(name="restore_working_tree", description="PR mode 結束後 git checkout 回原 branch(Step 0.5 記住的)") TaskCreate(name="decide_next_action", description="根據 findings: 通過→idd-close / 有 findings→修正 / scope creep→新 issue") TaskCreate(name="triage_followup_issues", description="Step 5b: 分類 non-blocking findings → 問使用者要不要開新 issue,確認後批次建立") @@ -911,6 +912,41 @@ Verified scope: #98, #105 | 2 | P3 | ... | agents:security | Follow-up | ``` +### Step 4.5: Verified auto-tag(review snapshot,v2.94.0+,#85) + +Master report 貼出、且 **Aggregate PASS** 後,`tag_verified` step 在 review-ready 的 snapshot 打 `idd-{N}-verified` tag —— 一個 stable handle,日後可直接 `git checkout idd-{N}-verified` 切回「verify 通過那一刻」的 code,或在 PR / issue comment 引用給 reviewer。**只在 PASS 打**(FAIL 不 tag —— snapshot 的價值是「這是驗過的版本」)。 + +**Config gate(同 idd-issue baseline,預設 on)** — 讀 `auto_tag`(schema 見 [`references/config-protocol.md`](../../references/config-protocol.md#auto_tag-field)): + +```bash +# Only when Aggregate verdict == PASS +[ "$AGGREGATE_VERDICT" != "PASS" ] && { echo "→ verify FAIL — no verified tag"; } # FAIL: no snapshot tag +# resolve the walked-up IDD config (new path first, legacy fallback — see the Configuration section) +CONFIG_PATH=$(d="$PWD"; while [ "$d" != / ]; do for f in "$d/.claude/.idd/local.json" "$d/.claude/issue-driven-dev.local.json"; do [ -f "$f" ] && { echo "$f"; break 2; }; done; d=$(dirname "$d"); done) +AUTO_TAG_ENABLED=$(jq -r '.auto_tag.enabled // true' "$CONFIG_PATH" 2>/dev/null || echo true) +[ "$AUTO_TAG_ENABLED" = "false" ] && { echo "→ auto_tag disabled — skipping verified tag"; } # opt-out + +VERIFIED_FMT=$(jq -r '.auto_tag.verified_format // "idd-{N}-verified"' "$CONFIG_PATH" 2>/dev/null || echo "idd-{N}-verified") +PUSH_REMOTE=$(jq -r '.auto_tag.push_remote // "origin"' "$CONFIG_PATH" 2>/dev/null || echo origin) +# snapshot = HEAD. tag_verified runs BEFORE restore_working_tree, so in PR mode +# HEAD is still the PR head (Step 0.5 already ran `gh pr checkout`); in local +# mode (--commits / --since) HEAD is the tree just verified. Either way = HEAD. +SNAPSHOT=HEAD + +for N in $ISSUE_NUMBERS; do # cluster: tag each ref'd issue (#N set from Step 0.5) + TAG="${VERIFIED_FMT/\{N\}/$N}" # idd-{N}-verified → idd-42-verified + if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then + echo "→ $TAG already exists — skip (idempotent)" # re-verify never re-tags + else + git tag "$TAG" "$SNAPSHOT" && git push "$PUSH_REMOTE" "$TAG" \ + && echo "→ tagged $TAG at verified snapshot + pushed" \ + || echo "⚠ auto_tag verified: git tag/push failed — graceful-skip, continuing" + fi +done +``` + +**鐵律 — graceful-skip,never abort**:同 baseline —— tag/push 失敗只 warn,絕不 abort verify(結果已 post,tag 是附加便利)。**Aggregate PASS 是唯一觸發條件**;FAIL run 不留 verified tag,避免把未過的 code 標成「已驗證」。cluster mode(多 `#N`)每個 issue 各打自己的 `idd-{N}-verified`。 + ### Step 5: 後續動作 #### Step 5a: 分類 findings