Skip to content

Commit 0aa17a5

Browse files
committed
feat: selective git auto-tag at idd-issue baseline + idd-verify PASS (#85)
idd-issue tags idd-{N}-baseline at main HEAD (rollback anchor); idd-verify tags idd-{N}-verified at the Aggregate-PASS snapshot (review-ready). Only these two milestones (no diagnose/plan/implement tags) so the tag namespace stays clean. Config auto_tag (default-on, opt-out via enabled:false); idempotent (existing tag skipped) + graceful-skip on push failure (never aborts the workflow). New auto-tag drift-guard, 27 assertions. Refs #85
1 parent 4397f2b commit 0aa17a5

5 files changed

Lines changed: 173 additions & 0 deletions

File tree

plugins/issue-driven-dev/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,18 @@ attachments_release: "attachments"
206206
---
207207
```
208208

209+
### Selective auto-tag(v2.94.0, #85
210+
211+
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.
212+
213+
**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:
214+
215+
```json
216+
{ "auto_tag": { "enabled": false } }
217+
```
218+
219+
Full schema (`enabled` / `baseline_format` / `verified_format` / `push_remote`) in [references/config-protocol.md](references/config-protocol.md)`auto_tag` field.
220+
209221
## Deep Integrations(深度整合套件總覽)
210222

211223
IDD 遵循「**深度整合 >> hard-coded**」原則(#209/#214):生態系已有 canonical 套件時依賴它、不在內部複製等價邏輯。每個整合的綁定形狀與缺席行為:

plugins/issue-driven-dev/references/config-protocol.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,30 @@ An OPTIONAL identity registry so IDD can resolve a person's alias / email / disp
480480

481481
**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.
482482

483+
### `auto_tag` field
484+
485+
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.
486+
487+
```json
488+
{
489+
"auto_tag": {
490+
"enabled": true,
491+
"baseline_format": "idd-{N}-baseline",
492+
"verified_format": "idd-{N}-verified",
493+
"push_remote": "origin"
494+
}
495+
}
496+
```
497+
498+
| Field | Default | Meaning |
499+
|-------|---------|---------|
500+
| `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. |
501+
| `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. |
502+
| `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). |
503+
| `push_remote` | `origin` | Remote the tags are pushed to. |
504+
505+
**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.
506+
483507
## Resolution algorithm (canonical)
484508

485509
```
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env bash
2+
# test.sh — drift-guard for selective git auto-tag (#85).
3+
#
4+
# #85 adds selective git-tag automation at two IDD milestones:
5+
# - idd-issue creates `idd-{N}-baseline` (main HEAD at issue-open = rollback anchor)
6+
# - idd-verify creates `idd-{N}-verified` on Aggregate PASS (review snapshot)
7+
# opt-out via config `auto_tag.enabled`, idempotent, graceful-skip on push failure.
8+
#
9+
# WHY A CONTENT DRIFT-GUARD: the tag behaviors live in prose SKILL.md files
10+
# (idd-issue Step 0/creation, idd-verify Aggregate-PASS branch) + a config schema
11+
# (config-protocol.md) — AI prompts, not executable code. The falsifiable
12+
# equivalent is asserting each file encodes the tag contract. The schema is
13+
# documented across files that MUST agree; the failure mode is DRIFT (rename a
14+
# format key in the schema, forget the SKILL that references it). Needles below
15+
# are distinctive to #85 (0-occurrence before the change), so each is a genuine
16+
# drift signal.
17+
#
18+
# Usage: bash test.sh (exit 0 = all pass, 1 = any fail)
19+
20+
set -u
21+
22+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23+
PLUGIN_ROOT="$HERE/../../.."
24+
PROTOCOL="$PLUGIN_ROOT/references/config-protocol.md"
25+
ISSUE="$PLUGIN_ROOT/skills/idd-issue/SKILL.md"
26+
VERIFY="$PLUGIN_ROOT/skills/idd-verify/SKILL.md"
27+
README="$PLUGIN_ROOT/README.md"
28+
29+
HELPERS="$HERE/../../lib/assert-helpers.sh"
30+
[ -f "$HELPERS" ] || { echo "✗ missing $HELPERS — cannot run suite" >&2; exit 1; }
31+
. "$HELPERS"
32+
33+
assert_file_exists "config-protocol.md exists" "$PROTOCOL"
34+
assert_file_exists "idd-issue SKILL.md exists" "$ISSUE"
35+
assert_file_exists "idd-verify SKILL.md exists" "$VERIFY"
36+
assert_file_exists "plugin README.md exists" "$README"
37+
38+
# ── config-protocol.md: the auto_tag schema (source of truth) ──
39+
assert_output_grep "protocol: declares the auto_tag field section" "### \`auto_tag\` field" "$PROTOCOL"
40+
assert_output_grep "protocol: auto_tag config key" "auto_tag" "$PROTOCOL"
41+
assert_output_grep "protocol: enabled key (default-on / opt-out)" "enabled" "$PROTOCOL"
42+
assert_output_grep "protocol: baseline_format key" "baseline_format" "$PROTOCOL"
43+
assert_output_grep "protocol: verified_format key" "verified_format" "$PROTOCOL"
44+
assert_output_grep "protocol: push_remote key" "push_remote" "$PROTOCOL"
45+
assert_output_grep "protocol: baseline tag naming default" "idd-{N}-baseline" "$PROTOCOL"
46+
assert_output_grep "protocol: verified tag naming default" "idd-{N}-verified" "$PROTOCOL"
47+
# default-ON must be explicit + opt-out documented (user decision 2026-07-09)
48+
assert_output_grep "protocol: default-on documented" "default" "$PROTOCOL"
49+
assert_output_grep "protocol: opt-out documented" "enabled: false" "$PROTOCOL"
50+
# tag push is a repo-wide side effect — must be surfaced so default-on isn't a surprise
51+
assert_output_grep "protocol: push side-effect surfaced" "side effect" "$PROTOCOL"
52+
53+
# ── idd-issue: baseline tag after issue creation ──
54+
assert_output_grep "idd-issue: tag_baseline bootstrap step" "tag_baseline" "$ISSUE"
55+
assert_output_grep "idd-issue: baseline tag naming" "idd-{N}-baseline" "$ISSUE"
56+
assert_output_grep "idd-issue: gated on auto_tag config" "auto_tag" "$ISSUE"
57+
assert_output_grep "idd-issue: idempotent (tag exists → skip)" "idempotent" "$ISSUE"
58+
assert_output_grep "idd-issue: graceful-skip on push failure" "graceful-skip" "$ISSUE"
59+
# never abort the workflow on tag failure (warn-continue discipline)
60+
assert_output_grep "idd-issue: tag failure never aborts" "never abort" "$ISSUE"
61+
62+
# ── idd-verify: verified tag on Aggregate PASS ──
63+
assert_output_grep "idd-verify: tag_verified bootstrap step" "tag_verified" "$VERIFY"
64+
assert_output_grep "idd-verify: verified tag naming" "idd-{N}-verified" "$VERIFY"
65+
assert_output_grep "idd-verify: gated on auto_tag config" "auto_tag" "$VERIFY"
66+
assert_output_grep "idd-verify: fires on Aggregate PASS" "Aggregate PASS" "$VERIFY"
67+
68+
# ── README: discoverability note ──
69+
assert_output_grep "README: auto-tag convention documented" "auto_tag" "$README"
70+
assert_output_grep "README: baseline/verified naming shown" "idd-{N}-baseline" "$README"
71+
72+
print_summary "auto-tag (#85)"

plugins/issue-driven-dev/skills/idd-issue/SKILL.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ TaskCreate(name="attach_images", description="上傳圖片到 attachments releas
112112
TaskCreate(name="create_milestone", description="來源為文件時自動建立 milestone 並指派(見 Step 4.5)")
113113
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)")
114114
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)")
115+
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.")
115116
TaskCreate(name="report_and_stop", description="回報 issue number/URL(group 模式列全部 + cross-link),停下等使用者決定下一步")
116117
```
117118

@@ -963,6 +964,38 @@ AskUserQuestion:
963964

964965
- [~] **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)。
965966

967+
### Step 3.6: Baseline auto-tag(rollback anchor,v2.94.0+,#85
968+
969+
Issue 建立成功、拿到 `$NUMBER` 之後,`tag_baseline` step 在當前 repo 打一個 **rollback baseline** tag,標記「issue 開案當下的 main HEAD」,方便日後一秒切回開工前的乾淨狀態(`git checkout idd-{N}-baseline`),不用記 commit hash。
970+
971+
**Config gate(預設 on,可 opt-out)** — 讀 walked-up config 的 `auto_tag`(schema 見 [`references/config-protocol.md`](../../references/config-protocol.md#auto_tag-field)):
972+
973+
```bash
974+
AUTO_TAG_ENABLED=$(jq -r '.auto_tag.enabled // true' "$CONFIG_PATH" 2>/dev/null || echo true)
975+
[ "$AUTO_TAG_ENABLED" = "false" ] && { echo "→ auto_tag disabled — skipping baseline tag"; } # opt-out: skip entirely
976+
```
977+
978+
啟用時:
979+
980+
```bash
981+
BASELINE_FMT=$(jq -r '.auto_tag.baseline_format // "idd-{N}-baseline"' "$CONFIG_PATH" 2>/dev/null || echo "idd-{N}-baseline")
982+
PUSH_REMOTE=$(jq -r '.auto_tag.push_remote // "origin"' "$CONFIG_PATH" 2>/dev/null || echo origin)
983+
TAG="${BASELINE_FMT/\{N\}/$NUMBER}" # idd-{N}-baseline → idd-42-baseline
984+
DEFAULT_BRANCH=$(gh repo view "$GITHUB_REPO" --json defaultBranchRef -q .defaultBranchRef.name)
985+
986+
# idempotent: a tag that already exists is skipped (re-running idd-issue never re-tags)
987+
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
988+
echo "$TAG already exists — skip (idempotent)"
989+
else
990+
# baseline = the LOCAL default-branch HEAD (where main was when the issue opened)
991+
git tag "$TAG" "$DEFAULT_BRANCH" && git push "$PUSH_REMOTE" "$TAG" \
992+
&& echo "→ tagged $TAG at $DEFAULT_BRANCH HEAD + pushed to $PUSH_REMOTE" \
993+
|| echo "⚠ auto_tag baseline: git tag/push failed (fork / no push permission / rejected) — graceful-skip, continuing"
994+
fi
995+
```
996+
997+
**鐵律 — 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 留手動。
998+
966999
### Step 4: 附加所有原始素材(鐵律:預設全保留)
9671000

9681001
> 引用 Step 1 的「資料保留鐵律」:來源中**任何附件都要全部上傳**,不論張數、不論格式。

plugins/issue-driven-dev/skills/idd-verify/SKILL.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ TaskCreate(name="recovery_protocol", description="Step 2.5 (NEW per #52): 缺 fi
265265
TaskCreate(name="wait_for_codex", description="等 Codex 背景任務完成,讀 /tmp/codex-verify-${NUMBER}.md")
266266
TaskCreate(name="merge_findings", description="合併 6 個來源 findings 去重,severity 取最高")
267267
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)")
268+
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.")
268269
TaskCreate(name="restore_working_tree", description="PR mode 結束後 git checkout 回原 branch(Step 0.5 記住的)")
269270
TaskCreate(name="decide_next_action", description="根據 findings: 通過→idd-close / 有 findings→修正 / scope creep→新 issue")
270271
TaskCreate(name="triage_followup_issues", description="Step 5b: 分類 non-blocking findings → 問使用者要不要開新 issue,確認後批次建立")
@@ -911,6 +912,37 @@ Verified scope: #98, #105
911912
| 2 | P3 | ... | agents:security | Follow-up |
912913
```
913914
915+
### Step 4.5: Verified auto-tag(review snapshot,v2.94.0+,#85)
916+
917+
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 的價值是「這是驗過的版本」)。
918+
919+
**Config gate(同 idd-issue baseline,預設 on)** — 讀 `auto_tag`(schema 見 [`references/config-protocol.md`](../../references/config-protocol.md#auto_tag-field)):
920+
921+
```bash
922+
# Only when Aggregate verdict == PASS
923+
[ "$AGGREGATE_VERDICT" != "PASS" ] && { echo "→ verify FAIL — no verified tag"; } # FAIL: no snapshot tag
924+
AUTO_TAG_ENABLED=$(jq -r '.auto_tag.enabled // true' "$CONFIG_PATH" 2>/dev/null || echo true)
925+
[ "$AUTO_TAG_ENABLED" = "false" ] && { echo "→ auto_tag disabled — skipping verified tag"; } # opt-out
926+
927+
VERIFIED_FMT=$(jq -r '.auto_tag.verified_format // "idd-{N}-verified"' "$CONFIG_PATH" 2>/dev/null || echo "idd-{N}-verified")
928+
PUSH_REMOTE=$(jq -r '.auto_tag.push_remote // "origin"' "$CONFIG_PATH" 2>/dev/null || echo origin)
929+
# snapshot target: PR head (PR mode) / current HEAD (local mode)
930+
SNAPSHOT=$([ "$INPUT_SOURCE" = "pr" ] && echo "$PR_HEAD_SHA" || echo HEAD)
931+
932+
for N in $ISSUE_NUMBERS; do # cluster: tag each ref'd #N
933+
TAG="${VERIFIED_FMT/\{N\}/$N}" # idd-{N}-verified → idd-42-verified
934+
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
935+
echo "→ $TAG already exists — skip (idempotent)" # re-verify never re-tags
936+
else
937+
git tag "$TAG" "$SNAPSHOT" && git push "$PUSH_REMOTE" "$TAG" \
938+
&& echo "→ tagged $TAG at verified snapshot + pushed" \
939+
|| echo "⚠ auto_tag verified: git tag/push failed — graceful-skip, continuing"
940+
fi
941+
done
942+
```
943+
944+
**鐵律 — 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`。
945+
914946
### Step 5: 後續動作
915947
916948
#### Step 5a: 分類 findings

0 commit comments

Comments
 (0)