From 8be146cdef6fb00b2747a220dc36b808943de043 Mon Sep 17 00:00:00 2001 From: che cheng Date: Sat, 18 Jul 2026 22:42:30 +0800 Subject: [PATCH 1/3] =?UTF-8?q?test:=20SKILL-helper=20integration=20contra?= =?UTF-8?q?ct=20layer=20for=20/idd-edit=20=E2=80=94=20shape=20(c)=20static?= =?UTF-8?q?=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 23 idd-edit fixtures all invoke the helper directly, so 'SKILL references a variable the helper never emits' bugs shipped with green tests three times (R1 B1 APPEND_BODY, R1 B2 GITHUB_REPO, independently flagged by 3 reviewers). New suite idd-edit-contract embeds a python3 checker: EMITTED derives mechanically from the helper source (emit_assignment calls + TARGETS array — single source, no third variable list), CONSUMED/DEFINED parse the SKILL's fenced bash blocks, and the invariant CONSUMED - DEFINED - EMITTED - ALLOWLIST = empty catches the unsourced-reference class. Allowlist is env only (CLAUDE_PLUGIN_ROOT, IDD_CALLER per the #161 cross-skill contract, ...; GITHUB_REPO deliberately excluded — that WAS bug B2). Checker efficacy is self-tested against a seeded three-violation fixture (detection failing = suite fails), and the 11-name emit surface is frozen (deleting an emit goes red here before it breaks consumers). First live run caught IDD_CALLER and forced its provenance classification — working as intended. SKILL gains a Contract section documenting mechanism + scan boundary (H7 structural class stays with fixture 14). Shape (a) escalation stays trigger-based per the approved plan. GREEN 17/0; sweep 39 suites 0 fail. Refs #163 --- .../scripts/tests/idd-edit-contract/test.sh | 118 ++++++++++++++++++ .../issue-driven-dev/skills/idd-edit/SKILL.md | 6 + 2 files changed, 124 insertions(+) create mode 100644 plugins/issue-driven-dev/scripts/tests/idd-edit-contract/test.sh diff --git a/plugins/issue-driven-dev/scripts/tests/idd-edit-contract/test.sh b/plugins/issue-driven-dev/scripts/tests/idd-edit-contract/test.sh new file mode 100644 index 0000000..c65d123 --- /dev/null +++ b/plugins/issue-driven-dev/scripts/tests/idd-edit-contract/test.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# test.sh — SKILL.md↔helper integration contract layer for /idd-edit (#163). +# +# THE GAP THIS CLOSES: /idd-edit's 23 fixtures all invoke the helper directly, +# bypassing the SKILL.md orchestration layer — so "SKILL references a variable +# the helper never emits" bugs (R1 B1 $APPEND_BODY, R1 B2 $GITHUB_REPO) shipped +# with green tests. Shape (c) per the #163 ruling: a STATIC contract check — +# every uppercase $VAR consumed in a SKILL fenced-bash block must have a +# provenance (assigned in some SKILL block, emitted by the helper, or on the +# documented env allowlist). The helper source is the single source of truth +# for the emit surface (no third variable list to drift). +# +# Scan boundary (documented in the SKILL's Contract section): fenced ```bash +# blocks only, heredocs included (B1 lived in one); single-quote non-expansion +# false positives are absorbed by the allowlist. The structural bug class +# (R2 H7 loop closure) is NOT this layer — fixture 14 prose contracts cover it. +# +# Usage: bash test.sh (exit 0 = all pass, 1 = any fail) + +set -u + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PLUGIN_ROOT="$HERE/../../.." +HELPER="$PLUGIN_ROOT/scripts/idd-edit-helper.py" +SKILL="$PLUGIN_ROOT/skills/idd-edit/SKILL.md" + +HELPERS="$HERE/../../lib/assert-helpers.sh" +[ -f "$HELPERS" ] || { echo "✗ missing $HELPERS — cannot run suite" >&2; exit 1; } +. "$HELPERS" + +assert_file_exists "idd-edit-helper.py exists" "$HELPER" +assert_file_exists "idd-edit SKILL.md exists" "$SKILL" + +CHECKER="$HERE/.contract-checker.py" +cat > "$CHECKER" <<'PYEOF' +import re, sys + +def emitted_names(helper_src): + names = set(re.findall(r'emit_assignment\(\s*"([A-Z_]+)"', helper_src)) + if re.search(r'print\(f?"TARGETS=\(', helper_src): + names.add("TARGETS") + return names + +FENCE_RE = re.compile(r'```bash\n(.*?)```', re.DOTALL) +# provenance-granting forms inside SKILL blocks +ASSIGN_RE = re.compile(r'^\s*(?:local\s+|export\s+)?([A-Z_][A-Z0-9_]*)\+?=', re.MULTILINE) +FOR_RE = re.compile(r'\bfor\s+([A-Z_][A-Z0-9_]*)\s+in\b') +READ_RE = re.compile(r'\bread\s+(?:-r\s+)?([A-Z_][A-Z0-9_]*)\b') +USE_RE = re.compile(r'\$\{?([A-Z_][A-Z0-9_]*)\b') + +# environment / harness-provided (NOT helper contract — a name here is +# deliberately exempt; GITHUB_REPO must never be added, that was bug B2) +# IDD_CALLER: cross-skill invocation env contract (#161), consumed with a +# ${VAR:-} safe default — provided by the calling skill's environment. +ALLOWLIST = {"CLAUDE_PLUGIN_ROOT", "HOME", "PWD", "PATH", "ARGUMENTS", "EOF", "IDD_CALLER"} + +def violations(skill_md, helper_src): + blocks = FENCE_RE.findall(skill_md) + defined, consumed = set(), set() + for b in blocks: + defined |= set(ASSIGN_RE.findall(b)) + defined |= set(FOR_RE.findall(b)) + defined |= set(READ_RE.findall(b)) + consumed |= set(USE_RE.findall(b)) + return sorted(consumed - defined - emitted_names(helper_src) - ALLOWLIST) + +if __name__ == "__main__": + mode = sys.argv[1] + if mode == "check": + skill = open(sys.argv[2]).read() + helper = open(sys.argv[3]).read() + v = violations(skill, helper) + if v: + print("VIOLATIONS: " + " ".join(v)) + sys.exit(1) + print("OK") + elif mode == "selftest": + # seeded violation: SKILL block consumes $UNDEFINED_VAR (and the two + # historical bug names) with a helper that emits only BODY_INPUT/REPO. + bad_skill = "```bash\necho \"$UNDEFINED_VAR\"\ncat <&1) +if [ "$OUT" = "SELFTEST-OK" ]; then + pass "checker self-test: seeded B1/B2-class violations are detected" +else + fail "checker self-test: seeded B1/B2-class violations are detected" "$OUT" +fi + +# ── 2) the real contract: every SKILL-consumed var has a provenance ── +OUT=$(python3 "$CHECKER" check "$SKILL" "$HELPER" 2>&1); RC=$? +if [ "$RC" -eq 0 ]; then + pass "SKILL↔helper contract: no unsourced variable references" +else + fail "SKILL↔helper contract: no unsourced variable references" "$OUT" +fi + +# ── 3) emit-surface freeze: deleting an emit breaks SKILL consumers → red here first ── +for NAME in MODE SCOPE_FLAG SECTION_FLAG REASON BODY_INPUT BODY_FILE REPO CWD LAST OVERRIDE_USER_CONTENT; do + assert_output_grep "helper emits $NAME" "emit_assignment(\"$NAME\"" "$HELPER" +done +assert_output_grep "helper emits TARGETS array" 'TARGETS=(' "$HELPER" + +# ── 4) SKILL documents the contract mechanism ── +assert_output_grep "SKILL has the Contract section" "## SKILL↔helper Contract" "$SKILL" +assert_output_grep "SKILL documents the scan boundary" "fenced bash" "$SKILL" + +rm -f "$CHECKER" +print_summary "idd-edit-contract" diff --git a/plugins/issue-driven-dev/skills/idd-edit/SKILL.md b/plugins/issue-driven-dev/skills/idd-edit/SKILL.md index 2bc8882..ee1624f 100644 --- a/plugins/issue-driven-dev/skills/idd-edit/SKILL.md +++ b/plugins/issue-driven-dev/skills/idd-edit/SKILL.md @@ -477,3 +477,9 @@ backup 檔案命名:`comment--.md`,方便 time-series Edit 後通常不需要後續 skill。如果是修 diagnosis comment,可能要重跑 `/idd-verify`。 如果是 errata flow,由 `/idd-comment` 統一 orchestrate。 + +## SKILL↔helper Contract(#163) + +本 SKILL 的 bash 與 `scripts/idd-edit-helper.py` 之間的變數契約由 **`scripts/tests/idd-edit-contract/`** 靜態鎖定:SKILL fenced bash 內消費的每個大寫 `$VAR` 必有出處 —(a)SKILL block 內賦值、(b)helper `emit_assignment` / `TARGETS` emit(**helper 源碼即 source of truth**,本節不重列變數清單)、(c)環境 allowlist(`CLAUDE_PLUGIN_ROOT`、`IDD_CALLER` #161 等,明文列於 checker)。此層封殺 #154 R1 B1(`$APPEND_BODY`)/ B2(`$GITHUB_REPO`)的「fixtures 全綠、production 壞掉」class。 + +**掃描邊界**:只掃 fenced bash blocks(heredoc 在內 — B1 就住在 heredoc);single-quote 不展開造成的 false positive 由 allowlist 消化;loop 結構 class(R2 H7)不在此層 — 由 `scripts/tests/idd-edit/` fixture 14 的 prose 契約覆蓋。編輯本 SKILL 引入新變數時:在 block 內賦值、或經 helper emit、或(僅環境變數)進 checker allowlist — 三者皆非會被 suite 擋下。 From 0481da8e38a2b063cbf682d94d265ff9d137c486 Mon Sep 17 00:00:00 2001 From: che cheng Date: Sat, 18 Jul 2026 22:50:30 +0800 Subject: [PATCH 2/3] feat(idd-comment): add --type=reply human-facing point-by-point reply (v2.100.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seventh comment type for correspondence to a human collaborator (review author, advisor), the recipient-facing counterpart to the six audit-facing types. Per-point structure: verbatim blockquote of the counterpart's point -> what changed and where -> commit/PR/merge-SHA anchor -> honest per-point status. Required --points-from resolves through a three-layer chain (comment URL -> issue-body Original-text blockquote -> user-pasted); verbatim, no paraphrase. verify-before-claim gate (same family as idd-close Step 1.6) keeps unevidenced points open/pending. perspective-writer soft integration with graceful degrade: presence-check present -> calibrate voice/recipient; absent -> print the two install commands and post the anchored draft anyway. No install-time dependency (deliberate opposite of superpowers hard-dependency — calibration is an enhancement, not a canonical-process substitute). Anchoring precedes calibration; calibration must not alter anchored facts. New drift-guard suite idd-comment-reply (19 assertions). run-all-tests: 40 suites, 0 fail. Docs (commands/workflows/README) + CHANGELOG + both manifests bumped to 2.100.0. Spectra change add-idd-comment-reply-type (validate + analyze clean). Refs #269 Related PsychQuant/psychquant-claude-plugins#116, PsychQuant/perspective-writer#1 --- .claude-plugin/marketplace.json | 2 +- docs/commands.md | 6 +- docs/workflows.md | 4 +- .../add-idd-comment-reply-type/.openspec.yaml | 4 + .../add-idd-comment-reply-type/design.md | 46 +++++++++++ .../add-idd-comment-reply-type/proposal.md | 38 +++++++++ .../specs/idd-comment-reply/spec.md | 75 ++++++++++++++++++ .../add-idd-comment-reply-type/tasks.md | 25 ++++++ .../.claude-plugin/plugin.json | 2 +- plugins/issue-driven-dev/CHANGELOG.md | 14 ++++ plugins/issue-driven-dev/README.md | 2 +- .../scripts/tests/idd-comment-reply/test.sh | 63 +++++++++++++++ .../skills/idd-comment/SKILL.md | 79 ++++++++++++++++++- 13 files changed, 350 insertions(+), 10 deletions(-) create mode 100644 openspec/changes/add-idd-comment-reply-type/.openspec.yaml create mode 100644 openspec/changes/add-idd-comment-reply-type/design.md create mode 100644 openspec/changes/add-idd-comment-reply-type/proposal.md create mode 100644 openspec/changes/add-idd-comment-reply-type/specs/idd-comment-reply/spec.md create mode 100644 openspec/changes/add-idd-comment-reply-type/tasks.md create mode 100644 plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index e201bbf..38d66f0 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -15,7 +15,7 @@ "plugins": [ { "name": "issue-driven-dev", - "version": "2.99.1", + "version": "2.100.0", "description": "v2.99.1: staleness sweep + guard-net expansion (#267). README carried three stale gpt-5.5 pins and a stale vendored-codex-call claim — all outside the drift-guard scan net; fixed and the net widened: model-generation-sync now refutes pins in README + both catalog docs (31 assertions), and a new docs-catalog-sync suite requires every skills/* directory to appear in the catalog docs (the #122 no-forcing-function root cause is now test-detectable; it caught idd-ask and idd-config on its first RED). docs/workflows.md + skill-dimensions.md backfilled to v2.99 reality (P-find-lookup / P-ask-history / P-report-rollup / P-config-maintain / P-verify-file-profile paths, matrix rows, D12 4th member). 38 suites 0 fail. v2.99.0: /idd-ask — grounded QA over the issue corpus (#72), the surfacing family's 4th member mirroring /spectra-ask. Natural-language question -> decide-to-search gate (greetings/meta skip; bug-shaped questions never trigger diagnose) -> retrieval delegating idd-find's search backend (family rule: never rebuild a read-only query) -> full-text read of top-N hits (default 5, capped 10) -> grounded synthesis: first line blockquotes the question, every claim carries an issue/comment citation, source priority closed-with-PR > open > orphaned comment with conflicts surfaced, ending with Referenced Issues; corpus silence reported honestly, never filled from training memory. Read-only allowed-tools locked. First live run of the #140 fourth-member procedure (Q3 weak-hit judgment recorded in the family canonical). New capability spec idd-ask (+2 requirements); new drift-guard suite; 37 suites 0 fail. v2.98.0: codex channel goes full-dependency (#264, user ruling 'like superpowers'). The vendored bin/codex-call is DELETED — it trailed pai 2.18.0 by four security/correctness fixes (token-exp NSNumber parse, OAuth-file umask 0o077, form-encoding escape, post-flock re-read). Executable now resolves from the parallel-ai-agents plugin cache (MIN_PAI 2.19.0 — the codexModel/codexEffort contract floor, pai issue 22); model/effort/max-time governance resolves from codex-pro's EXTERNAL-CONSUMER CONTRACT (MIN_CODEX_PRO 0.7.0: machine-readable references/defaults.json base + global/project profile.yaml overlay, codex-pro issue 7) and is passed explicitly on all three call paths (canonical Workflow args + manual fan-out + legacy direct). IDD's tree contains ZERO model pins — generation bumps touch codex-pro's defaults.json only. Dependency wiring mirrors the superpowers shape: install-time dependencies entry (codex-pro@codex-pro), allowCrossMarketplaceDependenciesOn, check-plugin-presence pre-flight, fail-fast with a one-step install instruction, no soft fallback. model-generation-sync drift-guard reshaped to the v2 contract (a re-vendored codex-call fails the suite). 36 suites 0 fail. v2.97.0: 9-issue drain via 5 cluster PRs (#259-#263). Composable verification profiles (#258): idd-verify --profile code|prose|academic (+ config-registered custom via verify_profiles) switches the (lens set, DA focus, input source, freshness) four-tuple; new --file/--dir input sources make the git worktree optional; file-mode SHA-256 freshness gate mirrors the #228 diff gate (never silently exempted); code default byte-identical. New /idd-find skill (#139): surfacing-only semantic lookup over the open+closed corpus with GitHub relevance + phase/PR overlay; read-only, filter flags redirect to idd-list, embedding honestly deferred. Dashboard comment contract (#133) + idd-report --rollup (#134): one human-facing narrative snapshot per issue (marker-located, updates bound to phase transitions only, anti-#116) and a pull-only four-group attention view (need-attention / in-progress / stalled>14d / recently-closed). sdd_bias config switch (#252): hard-gate hits escalate to Spectra when high; default routing byte-identical. Layer V unattended deferred-record (#120): registry literal + structured catch-up record aggregated by idd-all Phase 6. Surfacing-primitives family doc, D12 axis (#140). Model-generation sync (#251): codex-call default gpt-5.6-sol is the tree's single generation pin (live-probed); prose generation-neutral; idd-route candidate renamed codex-xhigh. Docs path catalog completed (#122). 5 new drift-guard suites; 36 suites 0 fail. v2.96.0: gh-egress hardening cluster + idd-edit batch semantics. Exit-code band >=10 (#227: 10=privacy/11=mention/12=unscannable/13=attestation/14=usage; wrapper never exits <10 on its own — rc<10 is always gh's, so unattended callers can split gate-refusal from gh-failure on $? alone). Unified python3 content-net scan (#225: kills the jq/no-jq divergence; taxonomy = projects keys + path-shaped values under sensitive key names; fail-closed wide net when python3 absent). Phase 2 rollout (#226: all 6 skills' comment/edit egress now dispatch through gh-egress with attestation — the #117 mention net is mechanically enforced on the comment channel). idd-edit batch x R5 (#158: per-comment refuse + continue, batch outcome report, exit 4 iff any refused). v2.95.0: Discussions intake bridge (#221) — opt-in `idd-list --discussions` (GraphQL surface: Q&A/Ideas + unanswered + deduped vs issue refs; graceful no-op) + `idd-issue --from-discussion` (Provenance seed + draft-and-confirm reply, unattended never posts); cardinal rule: never auto-file. Plus idd-verify diff-freshness gate (#228: FROZEN_SHA vs HEAD before aggregate — refuse stale-snapshot verdicts) and the IDD_CALLER registry (#161: dynamic tree-sweep drift-guard). v2.94.0: selective git auto-tag (#85) — idd-issue tags idd-{N}-baseline at main HEAD (rollback anchor); idd-verify tags idd-{N}-verified on Aggregate PASS (review snapshot). 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). v2.93.1: collaborator identity registry in idd-config (#86) — optional `collaborators[]` config field mapping a person's alias / email / display-name → GitHub @login WITHOUT guessing (github_login required; email is PII, private/gitignored only). tagging-collaborators.md Step 2.5 consults the registry first as an accelerator (a hit is still existence-verified via `gh api users/`; a miss falls through to the API fuzzy-match); idd-config validate checks login charset + globally-unique aliases + PII reminder. v2.93.0: reshape Plan / pre-implementation tier (Cluster C, #129/#57/#111, via reshape-plan-preimpl-tier Spectra change) — first-class `meeting` issue type (meeting-first routing + Phase A/B/C deliberation + self-contained close gate), complexity hard gate (>=5-file interdependent-concept OR shared-abstraction MUST-trigger Plan, escalate-only), and superpowers pre-implementation hand-off (README stage-mapping table + non-binding brainstorming pointer, no self-built staging skill). v2.92.1: hotfix — parallel-ai-agents install-time dependency pointed at the wrong marketplace (psychquant-claude-plugins), making v2.92.0 fail to load and silently dropping all /idd-* skills; corrected to the parallel-ai-agents marketplace. v2.92.0: /idd-all batch-drain release — 23 issues verified+closed via 16 PRs (#223, #229-#243), the plugin's largest self-dogfood. Added: unattended-contract (state-file signal + TTL, TTY heuristic removed, idd-all/chain dependency early gates #123/#222/#211); gh-egress unconditional @-mention net with --mention-attested escape-or-attest contract (#117) atop 6-item mechanical-net precision hardening (#203); idd-close Step 6.3 doc-sync sweep (#220); test aggregator + GitHub Actions CI, 21 suites (#217); idd-list blocked-state grouping + all-blocked banner (#84); config Mechanism 3.5 submodule routing (#162); check-plugin-presence enabled-state detection exit 3 (#212); monorepo host plugin disambiguation (#68); assert-helpers eval-content ban + safe output-grep pair (#188); diagnosis-detection contract fixtures (#61). Changed: parallel-ai-agents promoted to install-time dependency, vendored ensemble fork DELETED, idd-verify two-tier chain (#219); DA sequenced-spawn eliminates the #119 socket-crash polling window (#130); spectra-archive-post-ic --force-linked-issue vs --linked-issue intent separation (#172); worktree conventions unified on the managed helper (#169); bridge state migrated to .claude/.idd/state/bridge.json (#199); .gitattributes LF policy (#216); merge-completeness fixtures default-branch self-sufficiency (#224). Audits: dependency bindings vs deep-integration rule (#210), rules layering 12/12 (#215). Follow-ups filed: #225-#228.", "author": { "name": "Che Cheng" diff --git a/docs/commands.md b/docs/commands.md index 289b562..b087711 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -13,7 +13,7 @@ | [`/idd-config`](#idd-config) | Inspect / init / validate `.claude/issue-driven-dev.local.json` | `[show \| init \| validate \| which]` | | [`/idd-list`](#idd-list) | List issues + IDD phase + suggested next action | `[--state ...] [--label ...] [--limit N] [--target ...]` | | [`/idd-issue`](#idd-issue) | Create a well-documented GitHub issue | `[description \| path/to/.docx] [--target ...] [--parent N] [--blocked-by M,...] [--bundle-mode ordered\|unordered] [--mention login]` | -| [`/idd-comment`](#idd-comment) | Add template-guided comment (decision / note / question / correction / link / errata) | `#N [#N ...] --type= [options]` | +| [`/idd-comment`](#idd-comment) | Add template-guided comment (decision / note / question / correction / link / errata / reply) | `#N [#N ...] --type= [options]` | | [`/idd-edit`](#idd-edit) | Edit existing comment (append / replace / prepend-note) | `comment:[ ...] \| #N --last [--append \| --replace \| --prepend-note] [--body=...]` | | [`/idd-diagnose`](#idd-diagnose) | RCA / requirements analysis + complexity verdict | `#N [#N ...] [--cwd ...]` | | [`/idd-plan`](#idd-plan) | Plan-tier: EnterPlanMode approval gate before TDD | `#N [--pr \| --no-pr] [--cwd ...]` | @@ -176,7 +176,7 @@ **Syntax**: `/idd-comment #N [#N ...] --type= [options]` -**Options — `--type`** (mandatory, 6 types): +**Options — `--type`** (mandatory, 7 types): | Type | Use | |---|---| @@ -186,6 +186,7 @@ | `correction` | Mark a previous claim wrong + correction | | `link` | External context (URL, related issue, gist) | | `errata` | Errata about a prior comment in the same issue | +| `reply` | Human-facing point-by-point reply to a comment/issue (v2.100.0+, #269) | **Other options**: @@ -194,6 +195,7 @@ | `--body '...'` | Free-text body; appended under the type-specific template heading | | `--quote 'text'` | Source quote — forced into blockquote, marked as quoted text | | `--quote-source 'where'` | Attribution for the quote | +| `--points-from=` | reply type 必填,逐點來源(human-facing 逐點回覆:verbatim blockquote 對方原文 → 各點怎麼改 + SHA 錨定) | | `#N #N #N` (batch mode, v2.34.0+) | Same comment goes to each issue (3 `gh issue comment` calls, one master report) | **Workflow position**: Side-quest, any phase. Use when a real-time decision / context fact deserves the audit trail but doesn't merit a new issue. diff --git a/docs/workflows.md b/docs/workflows.md index 30af9ae..9ad1556 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -336,9 +336,9 @@ idd-route recommend --complexity Plan --signals ... --candidates ... idd-comment #N --type decision --body "..." ``` -- **Use case**:加 decision / note / question / correction / link / errata 到既存 issue,不走 phase +- **Use case**:加 decision / note / question / correction / link / errata / reply 到既存 issue,不走 phase - **Mode**:Attended/Unattended hybrid -- **6 types**:decision / note / question / correction / link / errata +- **7 types**:decision / note / question / correction / link / errata / reply(human-facing 逐點回覆,`--points-from`,v2.100.0+ #269) #### P-edit-only diff --git a/openspec/changes/add-idd-comment-reply-type/.openspec.yaml b/openspec/changes/add-idd-comment-reply-type/.openspec.yaml new file mode 100644 index 0000000..58b4f85 --- /dev/null +++ b/openspec/changes/add-idd-comment-reply-type/.openspec.yaml @@ -0,0 +1,4 @@ +schema: spec-driven +created: 2026-07-18 +created_by: che cheng +created_with: claude diff --git a/openspec/changes/add-idd-comment-reply-type/design.md b/openspec/changes/add-idd-comment-reply-type/design.md new file mode 100644 index 0000000..d758408 --- /dev/null +++ b/openspec/changes/add-idd-comment-reply-type/design.md @@ -0,0 +1,46 @@ +## Context + +idd-comment 現有六型(decision / note / question / correction / link / errata)全部 audit-facing:讀者是未來回來考古的 maintainer 或 AI。#269 的觸發案例顯示第七種讀者真實存在——review 提出者(人類 collaborator)要的是「我提的第 3 點你怎麼處理了」,而非聚合式 closing summary。 + +三個既有資產構成設計素材: + +1. **suggestion-report skill**(ai_martech_principles,repo 外 prior art):逐點「客戶反映(blockquote 原文)→ 問題原因 → 解決方式 → 目前狀態」模板,加上「寫報告前強制 verify completeness」紀律(per-issue commits check、實質性 spot-check)——與本案 R1 / R2 同構且已實戰驗證。 +2. **perspective-writer plugin**:已抽成 standalone marketplace(`PsychQuant/perspective-writer`,v2.10.0,cache 座標 `perspective-writer/perspective-writer/`)。提供 voice / recipient calibration、T-Schema referent 紀律、per-recipient rules 學習迴圈(draft-learner / save-feedback → target repo `.claude/rules/correspondence-*.md`)。consumer-facing 契約另案(PsychQuant/perspective-writer#1)。 +3. **superpowers-integration spec**(本 repo):跨 plugin delegation 的 hard-dependency 前例——fail-fast、無 fallback,理由是「canonical process 替代、built-in 等價敘述已刪除、無物可退」。 + +## Goals / Non-Goals + +**Goals:** + +- `--type=reply`:recipient-facing 逐點回覆型,結構正確性(逐點 verbatim + 錨定 + 誠實狀態)由 IDD 自有紀律保證 +- perspective-writer 缺席時功能完整可用(graceful degrade),命中時自動 calibration +- 契約元素被 drift-guard 測試鎖住(與 repo 既有 38 suites 同慣例) + +**Non-Goals:** + +- 不做 `--audience` 正交 flag、不開新 skill、不動既有六型 +- 不定義 perspective-writer 端 EXTERNAL-CONSUMER CONTRACT(PsychQuant/perspective-writer#1) +- 不涵蓋 PR description / Discussions 報告等其他 human-facing 面 + +## Decisions + +**D1 — 第 7 型,不是 audience flag、不是新 skill。** idd-comment 的架構是「per-type 模板 + per-type 必填欄位驗證」(decision 要 `--quote`、errata 要 `--target-comment`);`reply` 帶必填 `--points-from` 完美嵌入。替代案 `--audience human` 需要定義 6 型 × audience 的組合矩陣(每型的 human 變體語意),spec 面積不成比例;新 skill 違反 70%-重疊反模式(target resolution、mention 協定、egress、body 同步全部要複製)。 + +**D2 — soft integration + graceful degrade,刻意走 superpowers 的另一側。** superpowers-integration 的 fail-fast 前提是「SHALL NOT fall back to built-in equivalent」——IDD 已刪除自有 process 敘述,缺 plugin 即無物可退。`reply` 相反:逐點結構與 verify-before-claim gate 是 IDD 自有紀律,缺 calibration 時輸出仍完整正確。故 presence check(`check-plugin-presence.sh perspective-writer perspective-writer`)命中 → calibration;缺席 → 印一行含兩步安裝指令的 notice 照 post。**不**新增 install-time `dependencies` 條目(soft = runtime-only,manifest 零改動)。替代案 hard fail-fast 會把 enhancement 綁架成 hard dependency,讓沒裝該 marketplace 的公共使用者連逐點 reply 都不能用。 + +**D3 — `--points-from` 三層解析 + verbatim 鐵律。** (1) 顯式 comment URL 或字面值 `issue-body`;(2) 未指定時預設抓 issue body 的 Original text blockquote(idd-issue 建案紀律保證其存在於多數 issue);(3) 兩者皆無 → 要求使用者貼上原文(AskUserQuestion / 對話)。逐點內容一律 verbatim blockquote,禁止 paraphrase——收件人看到自己的話被改寫即失去信任(IC_R007 同源紀律)。 + +**D4 — recipient context 留在 target repo,IDD 只傳 pointer。** `--mention ` 走既有 tagging-collaborators 5-step 協定 resolve;calibration 階段把 resolved login 與 target repo `.claude/rules/correspondence-.md`(若存在)的路徑交給 perspective-writer。關係語境不進 plugin、不跨專案共享。rules 檔缺席時由 perspective-writer 自行 fallback(其 6-phase 流程含 recipient interview),IDD 不複製該邏輯。 + +**D5 — 執行序不變量:錨定在前、calibration 在後。** draft 流程固定為:逐點 verbatim 引文組裝 → verify-before-claim(每點以 `git log --grep "#N"` / PR merge 狀態驗證證據;無證據的點寫 open / pending)→ referent 錨定完成(SHA、file / theorem 引用)→ 才進 calibration → egress。calibration 不得改動錨定事實(SHA、引用、verbatim 引文原文);違反即為 bug。理由:先潤飾再錨定會讓句子找不到對應 diff,T-Schema 破功。 + +**D6 — verify-before-claim 是 prose 紀律 + drift-guard,不是新 script。** 檢查邏輯與 idd-close Step 1.6 semantic gate 同族(keyword → artifact 存在性),在 SKILL.md 以步驟陳述並由測試 suite 鎖關鍵字,不另寫 helper script——避免與既有 gate 形成兩份漂移的實作。 + +## Implementation Contract + +- **Command shape**:`/idd-comment #N --type=reply --points-from= [--mention ] [--body ""]`。`--points-from` 缺席 → Step 2 validation 拒絕(與 decision 缺 `--quote` 同機制)。 +- **模板產出**(SKILL.md 新增 `#### Template: reply`):每點一段 `> {verbatim 原文}` → 處理說明(哪裡改、怎麼改、cross-link commit / PR / label)→ 狀態(✅ 已解決(``)/ ⏳ open);結尾整體狀態行(merged SHA、同步狀態、下一步);metadata marker ``。 +- **Degrade notice literal**(缺 perspective-writer 時印出,內容進 SKILL.md 固定文字):`claude plugin marketplace add PsychQuant/perspective-writer` + `claude plugin install perspective-writer@perspective-writer`。 +- **Egress**:沿用 `gh-egress.sh comment` + `--scrub-attested` + (有 mention 時)`--mention-attested`——與既有六型同 choke-point,零新 egress 面。 +- **驗證目標**:新 suite `plugins/issue-driven-dev/scripts/tests/idd-comment-reply/run.sh` 斷言 SKILL.md 含:(a) reply 型必填 `--points-from` 驗證列於 Step 2 表格;(b) degrade 安裝指令兩行 literal;(c) 執行序不變量字句(錨定在前 / calibration 不得改動錨定事實);(d) presence-check 座標 `perspective-writer perspective-writer`。`run-all-tests.sh` 全綠(38 → 39 suites)。 +- **文件同步**:docs/commands.md 型別清單、docs/workflows.md matrix 行、plugins README 型別摘要、CHANGELOG 版本段。 diff --git a/openspec/changes/add-idd-comment-reply-type/proposal.md b/openspec/changes/add-idd-comment-reply-type/proposal.md new file mode 100644 index 0000000..5a53fb8 --- /dev/null +++ b/openspec/changes/add-idd-comment-reply-type/proposal.md @@ -0,0 +1,38 @@ +## Why + +idd-* 產出的 comment 全部是 audit-facing(closing 五段式、verify master report、idd-comment 六型),沒有一型是 recipient-facing。當 comment 的讀者是人類 collaborator(指導教授、review 提出者),maintainer 只能每次口頭指揮 ad-hoc 重排(PsychQuant/issue-driven-development#269 的觸發案例:6 點 review 的逐點回覆分兩輪口頭指令才成形),且沒有「每句已修正都指到真實 diff」的機械保證,也沒有語氣/對象 calibration。 + +## What Changes + +- `idd-comment` 新增第 7 型 `--type=reply`(human-facing 逐點回覆):對方原文逐點 verbatim blockquote → 該點怎麼改、改在哪(file / theorem / section)→ 錨定 commit / PR / merge SHA → 該點狀態;結尾整體狀態(merged SHA、同步狀態、下一步) +- 新增必填 flag `--points-from`,三層解析:顯式 comment URL → 預設 issue body 的 Original text blockquote → fallback 使用者貼上原文;逐點內容一律 verbatim,禁止 paraphrase +- **verify-before-claim gate**:每一點在 draft 內宣稱「已修正」之前,先以 `git log --grep` / PR merge 狀態驗證證據存在;無證據的點必須誠實表述為 open / pending,不得含混帶過 +- **perspective-writer soft integration(graceful degrade)**:`check-plugin-presence.sh perspective-writer perspective-writer` 命中 → draft 經 `Skill(perspective-writer:perspective-writer)` 做 voice / recipient calibration;缺席 → 印一行安裝指令(`claude plugin marketplace add PsychQuant/perspective-writer` + `claude plugin install perspective-writer@perspective-writer`)並照 post 未 calibrate 的 draft。不新增 install-time `dependencies` 條目 +- **執行序不變量**:referent 錨定(SHA / 檔案 / 定理引用 / verbatim 引文)在 calibration 之前完成;calibration 不得改動任何錨定事實 +- reply comment 是 closing summary 的加項,不取代任何 audit-facing 產出;egress 沿用 gh-egress choke-point(scrub + mention attestation 既有契約組合不變) +- 新增 drift-guard 測試 suite 鎖住上述 SKILL.md 契約元素(必填 flag、degrade 安裝指令 literal、執行序不變量) + +## Non-Goals + +- 不做 `--audience` 正交 flag(6 型 × audience 組合矩陣的 spec 面積不成比例);不開新 skill(與 idd-comment 既有機制 70% 重疊) +- 不改動既有六型模板與其必填欄位驗證 +- 不定義 perspective-writer 端的 EXTERNAL-CONSUMER CONTRACT(另案 PsychQuant/perspective-writer#1;本案只依賴 presence check + graceful degrade,契約收斂後可後續升級整合深度) +- 不涵蓋 PR description / GitHub Discussions 報告等其他 human-facing 輸出面(同 horizon、另案處理) + +## Capabilities + +### New Capabilities + +- `idd-comment-reply`: idd-comment 的 human-facing `--type=reply` 逐點回覆型 — 逐點 verbatim 引文結構、verify-before-claim gate、perspective-writer soft integration 與 graceful degrade、referent 錨定先於 calibration 的執行序不變量 + +### Modified Capabilities + +(none) + +## Impact + +- Affected specs: 新增 `idd-comment-reply`(既有 `superpowers-integration` 不修改 — 本案刻意走 soft 側,設計對比記於 design.md) +- Affected code: + - New: plugins/issue-driven-dev/scripts/tests/idd-comment-reply/run.sh + - Modified: plugins/issue-driven-dev/skills/idd-comment/SKILL.md, plugins/issue-driven-dev/README.md, docs/commands.md, docs/workflows.md, plugins/issue-driven-dev/CHANGELOG.md, plugins/issue-driven-dev/.claude-plugin/plugin.json, .claude-plugin/marketplace.json + - Removed: (none) diff --git a/openspec/changes/add-idd-comment-reply-type/specs/idd-comment-reply/spec.md b/openspec/changes/add-idd-comment-reply-type/specs/idd-comment-reply/spec.md new file mode 100644 index 0000000..6f8f567 --- /dev/null +++ b/openspec/changes/add-idd-comment-reply-type/specs/idd-comment-reply/spec.md @@ -0,0 +1,75 @@ +## ADDED Requirements + +### Requirement: Reply comment type with per-point structure + +`idd-comment` SHALL support a seventh type `--type=reply` producing a recipient-facing point-by-point reply comment. The flag `--points-from` SHALL be required for this type; invocation without it SHALL be refused by the same Step 2 validation mechanism that enforces per-type required fields for the existing six types. The rendered comment SHALL contain, for each point: (1) the original point quoted verbatim as a blockquote, (2) a description of what was changed and where (file / section / theorem or symbol reference), (3) an anchor to the evidencing commit, PR, or merge SHA, and (4) a per-point status. The comment SHALL end with an overall state line (merged SHA, sync status, next step) and a metadata marker `` recording the points source and whether calibration ran. + +#### Scenario: Missing required flag is refused + +- **WHEN** `/idd-comment #N --type=reply` is invoked without `--points-from` +- **THEN** validation refuses the invocation before any egress, naming `--points-from` as the missing required field + +#### Scenario: Per-point rendering + +- **WHEN** a reply is drafted for an issue whose review contains 3 points and 2 of them were addressed by merged commits +- **THEN** the drafted comment contains 3 verbatim blockquotes in the original order, each followed by its handling description and anchor, with the 2 evidenced points marked resolved and the third stated as open + +### Requirement: Points-source resolution + +The `--points-from` value SHALL resolve through a three-layer chain: (1) an explicit comment URL or the literal `issue-body`; (2) when unspecified, the default SHALL be the verbatim "Original text" blockquote(s) of the issue body; (3) when neither yields points, the skill SHALL ask the user to paste the original text. Resolved points SHALL be reproduced verbatim in the reply; paraphrasing the counterpart's original wording SHALL NOT occur. + +#### Scenario: Default source from issue body + +- **WHEN** `--points-from` is not given a URL and the issue body contains an Original text blockquote +- **THEN** the points are extracted from that blockquote and quoted verbatim in the reply draft + +#### Scenario: No resolvable source falls back to user input + +- **WHEN** neither an explicit source nor an issue-body blockquote yields points +- **THEN** the skill asks the user to provide the original text and does not fabricate or summarize points on its own + +### Requirement: Verify-before-claim gate + +Before the draft claims any point as resolved, the skill SHALL verify supporting evidence exists — a commit found via `git log --grep "#N"`, a merged PR, or an equivalent artifact reference. Points without verified evidence SHALL be stated as open or pending in the reply; the draft SHALL NOT assert completion for a point whose evidence was not found. + +#### Scenario: Unevidenced point stays honest + +- **WHEN** a point has no matching commit or merged PR at draft time +- **THEN** the reply renders that point with an open / pending status instead of claiming it resolved + +### Requirement: Perspective-writer soft integration with graceful degrade + +The reply drafting flow SHALL probe for the `perspective-writer` plugin via `check-plugin-presence.sh perspective-writer perspective-writer`. When present, the anchored draft SHALL be passed through the `perspective-writer:perspective-writer` skill for voice and recipient calibration, forwarding the resolved `--mention` login and the path of the target repo's `.claude/rules/correspondence-.md` when that file exists. When absent, the skill SHALL print a single notice containing the two install commands (`claude plugin marketplace add PsychQuant/perspective-writer` and `claude plugin install perspective-writer@perspective-writer`) and SHALL proceed to post the uncalibrated draft. The plugin manifest SHALL NOT declare an install-time dependency on perspective-writer. + +#### Scenario: Plugin absent degrades gracefully + +- **WHEN** the reply flow runs on a machine without the perspective-writer plugin +- **THEN** the notice with both install commands is printed and the structurally complete draft is still posted + +#### Scenario: Plugin present triggers calibration + +- **WHEN** the perspective-writer plugin is present in the plugin cache +- **THEN** the draft is calibrated before egress and the metadata marker records that calibration ran + +### Requirement: Anchoring precedes calibration + +The drafting flow SHALL complete referent anchoring — verbatim quotes assembled, verify-before-claim executed, SHAs and file / symbol references fixed — before any calibration step runs. Calibration SHALL NOT alter anchored facts: commit SHAs, file / theorem references, and the verbatim quoted text of the counterpart's points. + +#### Scenario: Calibration preserves anchors + +- **WHEN** calibration rewrites the tone of a per-point handling description +- **THEN** the point's verbatim blockquote, its anchor SHA, and its file / symbol references remain byte-identical to the pre-calibration draft + +### Requirement: Additive audit posture and egress discipline + +A reply comment SHALL be additive to existing audit-facing outputs — it SHALL NOT replace closing summaries, verify reports, or any of the six existing comment types. Reply egress SHALL dispatch through the `gh-egress.sh` choke-point with scrub attestation, and with mention attestation whenever the reply carries `@` mentions. + +#### Scenario: Reply does not displace the closing summary + +- **WHEN** an issue is closed after a reply comment was posted +- **THEN** the closing flow still requires its own closing summary; the reply is not accepted as a substitute + +#### Scenario: Egress goes through the choke-point + +- **WHEN** a reply draft is dispatched to GitHub +- **THEN** the dispatch command is `gh-egress.sh comment` with `--scrub-attested`, never a raw `gh issue comment` diff --git a/openspec/changes/add-idd-comment-reply-type/tasks.md b/openspec/changes/add-idd-comment-reply-type/tasks.md new file mode 100644 index 0000000..3131085 --- /dev/null +++ b/openspec/changes/add-idd-comment-reply-type/tasks.md @@ -0,0 +1,25 @@ +## 1. SKILL.md — reply 型契約落地 + +- [x] 1.1 在 plugins/issue-driven-dev/skills/idd-comment/SKILL.md 的型別總表加入第 7 型 `reply`(用途「human-facing 逐點回覆」、必填 `--points-from`、emoji header 🧑‍🏫 或等價),並在 Step 2 validation 表加入「reply → `--points-from` 必存在」列(spec requirement: Reply comment type with per-point structure)。完成判準:缺 `--points-from` 的 reply invocation 依表被拒;新 suite 斷言 (a) 命中。 +- [x] 1.2 新增 `#### Template: reply` 模板段:每點「verbatim blockquote → 處理說明(哪裡改、怎麼改、cross-link)→ 錨定(commit / PR / merge SHA)→ 狀態(✅ / ⏳ open)」、結尾整體狀態行、metadata marker ``(spec requirement: Reply comment type with per-point structure)。完成判準:模板含全部四個 per-point 元素與 marker 欄位,內容審查對照 design.md Implementation Contract。 +- [x] 1.3 新增「Points-source 三層解析」步驟:顯式 comment URL / `issue-body` → 預設 issue body Original text blockquote → fallback 要求使用者貼原文;明文 verbatim 鐵律(禁止 paraphrase 對方原文)。完成判準:三層順序與 verbatim 禁令逐字可 grep;spec `Points-source resolution` 兩個 scenario 對得上。 +- [x] 1.4 新增「verify-before-claim gate」步驟:每點宣稱已解決前以 `git log --grep "#N"` / PR merge 狀態驗證證據,無證據的點必須寫 open / pending;註明與 idd-close Step 1.6 semantic gate 同族、prose 紀律不另立 helper script(design D6)。完成判準:gate 字句可 grep,含「無證據不得宣稱完成」的禁令。 +- [x] 1.5 新增「perspective-writer soft integration」步驟:`check-plugin-presence.sh perspective-writer perspective-writer` 命中 → `Skill(perspective-writer:perspective-writer)` calibration(轉交 resolved `--mention` login 與 target repo `.claude/rules/correspondence-.md` 路徑,若存在);缺席 → 印兩行安裝指令 literal(`claude plugin marketplace add PsychQuant/perspective-writer`、`claude plugin install perspective-writer@perspective-writer`)並照 post。明文「不新增 install-time dependencies 條目」(spec requirement: Perspective-writer soft integration with graceful degrade)。完成判準:座標、兩行 literal、degrade 行為三者可 grep;新 suite 斷言 (b)(d) 命中。 +- [x] 1.6 新增執行序不變量段:referent 錨定(verbatim 引文、verify-before-claim、SHA / 引用固定)完成後才進 calibration;calibration 不得改動錨定事實(SHA、file / theorem 引用、verbatim 引文),違反即 bug(spec requirement: Anchoring precedes calibration)。完成判準:不變量字句可 grep;新 suite 斷言 (c) 命中。 +- [x] 1.7 在 SKILL.md Step 0 bootstrap TaskCreate 清單補 reply 型專屬 stage tasks(points 解析 / verify-before-claim / calibration or degrade),並於「與其他 idd-* skill 的關係」表與鐵律段補 reply 是 closing summary 加項、egress 沿用 gh-egress `--scrub-attested`(有 mention 時 `--mention-attested`)(spec requirement: Additive audit posture and egress discipline)。完成判準:bootstrap 清單含新 stage tasks;additive 語意與 egress 紀律可 grep。 + +## 2. Drift-guard 測試 + +- [x] 2.1 [P] 新增 plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh(跟隨既有 suite 慣例與 assert-helpers):斷言 SKILL.md 含 (a) Step 2 表的 reply 必填 `--points-from` 列、(b) 兩行 degrade 安裝指令 literal、(c) 執行序不變量字句、(d) presence-check 座標 `perspective-writer perspective-writer`。完成判準:單跑 `bash plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh` 綠燈;故意刪 SKILL.md 任一元素時紅燈(RED 驗證後還原)。 +- [x] 2.2 跑 `bash plugins/issue-driven-dev/scripts/run-all-tests.sh` 全綠(38 → 39 suites),特別確認 docs-catalog-sync 與 idd-caller-registry 未因文件改動翻紅。完成判準:aggregator 輸出 0 fail。 + +## 3. 文件同步 + +- [x] 3.1 [P] docs/commands.md:`/idd-comment` 摘要行的型別列舉補 `reply`,並在 `### /idd-comment` 段補 `--points-from` 語法與一句 reply 用途。完成判準:兩處可 grep;型別列舉與 SKILL.md 總表一致。 +- [x] 3.2 [P] docs/workflows.md 與 plugins/issue-driven-dev/README.md:matrix 行與型別摘要(decision / note / question …)補 reply。完成判準:兩檔的型別列舉與 SKILL.md 一致(docs-catalog-sync 慣例的人工對照)。 +- [x] 3.3 plugins/issue-driven-dev/CHANGELOG.md 加版本段(feature:`--type=reply`、soft perspective-writer integration、graceful degrade、drift-guard suite),版本號依 repo 慣例 minor bump;.claude-plugin 兩個 manifest(plugin.json 與 root marketplace.json entry)同步同一版本字串。完成判準:三處版本字串一致;changelog 段涵蓋四個要點。 + +## 4. 驗證與收尾 + +- [x] 4.1 Dogfood:在測試 issue(或 #269 本身)以 `--points-from=issue-body` 走一次 reply 流程之乾跑(draft 產出、不實際 post 到無關 issue),核對 per-point 四元素、degrade / calibration 分支各一次(透過暫時 rename cache 目錄模擬缺席,事後還原)。完成判準:兩分支的 draft 皆符合模板、缺席分支印出兩行安裝指令。 +- [x] 4.2 spectra validate 通過 + `spectra analyze` 無 Critical/Warning,PR 依 repo 慣例 Refs #269。完成判準:validate exit 0;PR body 含 spec delta 摘要。 diff --git a/plugins/issue-driven-dev/.claude-plugin/plugin.json b/plugins/issue-driven-dev/.claude-plugin/plugin.json index adc0501..cc5da55 100644 --- a/plugins/issue-driven-dev/.claude-plugin/plugin.json +++ b/plugins/issue-driven-dev/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "issue-driven-dev", "description": "v2.99.1: staleness sweep + guard-net expansion (#267). README carried three stale gpt-5.5 pins and a stale vendored-codex-call claim — all outside the drift-guard scan net; fixed and the net widened: model-generation-sync now refutes pins in README + both catalog docs (31 assertions), and a new docs-catalog-sync suite requires every skills/* directory to appear in the catalog docs (the #122 no-forcing-function root cause is now test-detectable; it caught idd-ask and idd-config on its first RED). docs/workflows.md + skill-dimensions.md backfilled to v2.99 reality (P-find-lookup / P-ask-history / P-report-rollup / P-config-maintain / P-verify-file-profile paths, matrix rows, D12 4th member). 38 suites 0 fail.", - "version": "2.99.1", + "version": "2.100.0", "author": { "name": "Che Cheng" }, diff --git a/plugins/issue-driven-dev/CHANGELOG.md b/plugins/issue-driven-dev/CHANGELOG.md index 2bcc6b0..11cbc8e 100644 --- a/plugins/issue-driven-dev/CHANGELOG.md +++ b/plugins/issue-driven-dev/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.100.0] - 2026-07-18 + +### Added + +- **`/idd-comment --type=reply` — human-facing point-by-point reply (#269, Spectra `add-idd-comment-reply-type`)** — a seventh comment type for correspondence written *to a human collaborator* (a review author, an advisor), not for the audit trail. Every prior type is audit-facing; `reply` is the recipient-facing one. Structure: each of the counterpart's points quoted **verbatim** as a blockquote → what changed and where (file / section / theorem) → an anchor to the evidencing commit / PR / merge SHA → an honest per-point status, closing with an overall state line. Required flag `--points-from` resolves through a three-layer chain (explicit comment URL → default issue-body Original-text blockquote → user-pasted fallback); paraphrasing the counterpart's own words is forbidden. A **verify-before-claim** gate (same family as `idd-close`'s Step 1.6 semantic gate) refuses to mark a point resolved without evidence found via `git log --grep "#N"` / merged-PR state — unevidenced points stay `open`/`pending`. **perspective-writer soft integration**: `check-plugin-presence.sh perspective-writer perspective-writer` present → the anchored draft is calibrated through `perspective-writer:perspective-writer` for voice / recipient tone; absent → a one-line notice with the two install commands is printed and the structurally-complete draft is posted anyway (graceful degrade, NO install-time dependency — the deliberate opposite of the superpowers hard-dependency, because calibration is an enhancement, not a canonical-process substitute). Ordering invariant: referent anchoring (verbatim quotes, verify-before-claim, SHAs/refs) completes **before** calibration, and calibration must not alter anchored facts. `reply` is additive to closing summaries — it never substitutes for one. Egress unchanged (gh-egress choke-point + scrub/mention attestation). Motivated by the #141 6-point review-reply that took two rounds of verbal steering to shape. + +### Tests + +- New drift-guard suite `idd-comment-reply` (19 assertions locking the SKILL contract: type-table + Step-2 required-field row, reply template + metadata marker, three-layer points source + verbatim ban, verify-before-claim gate, presence-check coordinates + both degrade install literals + no-install-dependency clause, anchoring-precedes-calibration invariant, additive-audit posture, and the docs-catalog `reply` entry). Aggregator 40 suites, 0 fail. + +### Related + +- Depends on the standalone perspective-writer marketplace extraction (PsychQuant/psychquant-claude-plugins#116 — `PsychQuant/perspective-writer` v2.10.0) so the degrade notice's install command is meaningful to public IDD users. The perspective-writer-side EXTERNAL-CONSUMER CONTRACT for a programmatic calibrate-draft entry is tracked separately (PsychQuant/perspective-writer#1); this change depends only on presence-check + graceful degrade. + ## [2.99.1] - 2026-07-18 ### Fixed diff --git a/plugins/issue-driven-dev/README.md b/plugins/issue-driven-dev/README.md index b8e5445..f3761f0 100644 --- a/plugins/issue-driven-dev/README.md +++ b/plugins/issue-driven-dev/README.md @@ -62,7 +62,7 @@ idd-issue → idd-diagnose → idd-implement → idd-verify → idd-close | `idd-verify` | Independent verification — 5 Claude reviewer agents + cross-model Codex leg (gpt-5.x; executable from pai, governance from codex-pro per #264). `--profile code/prose/academic` + `--file`/`--dir` for non-code deliverables (#258, v2.97+) | | `idd-close` | Closing comment documenting problem, root cause, solution, verification | | `idd-clarify` | **Terminology / Semantic accuracy axis** (v2.72.0+, [#135](https://github.com/PsychQuant/issue-driven-development/issues/135)) — surfacing-only primitive that scans issue body for terminology mismatches / ambiguity / missing-context gaps, annotates via `### Clarity Surface` block. Standalone runnable for retroactive audit; delegated by `idd-issue` Step 4.6; gated by `idd-diagnose` Step 0.5. Third axis alongside IC_R010 Confidence + IC_R007 Verbatim. | -| `idd-comment` / `idd-edit` | Add or amend issue comments with template guidance (decision / note / question) | +| `idd-comment` / `idd-edit` | Add or amend issue comments with template guidance (decision / note / question / … / reply — reply is the human-facing point-by-point type, v2.100.0+) | | `idd-list` / `idd-update` / `idd-report` | List open issues by phase, sync issue body, generate progress reports (`idd-report --rollup` = cross-issue human triage view over dashboard comments, #134 v2.97+) | | `idd-find` | **Semantic lookup** (v2.97+, [#139](https://github.com/PsychQuant/issue-driven-development/issues/139)) — surfacing-only search over the open+closed corpus (GitHub relevance + phase/PR overlay); answers "have we solved something like X, and where" | | `idd-ask` | **Grounded QA** (v2.99+, [#72](https://github.com/PsychQuant/issue-driven-development/issues/72)) — mirrors `/spectra-ask` for the issue corpus: full-text top-N read, every claim cited, source priority closed-with-PR > open, honest silence on corpus miss; answers "why did we decide that" | diff --git a/plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh b/plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh new file mode 100644 index 0000000..acea921 --- /dev/null +++ b/plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# test.sh — drift-guard for #269 idd-comment --type=reply (change +# add-idd-comment-reply-type, spec idd-comment-reply ADDED requirements). +# +# WHY A CONTENT DRIFT-GUARD (not an execution harness): idd-comment is a prose +# SKILL.md. "Invoke --type=reply without --points-from → assert refusal" cannot +# be executed here. The falsifiable equivalent is asserting the reply-type +# contract exists verbatim at its SKILL sites: the type table + Step 2 +# required-field row (validation), the reply template (per-point structure + +# metadata marker), the points-source three-layer chain + verbatim ban, the +# verify-before-claim gate, the perspective-writer soft-integration step +# (presence-check coordinates + the two degrade install literals + no +# install-time dependency), and the anchoring-precedes-calibration invariant. +# +# Usage: bash test.sh (exit 0 = all pass, 1 = any fail) + +set -u + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PLUGIN_ROOT="$HERE/../../.." +SKILL="$PLUGIN_ROOT/skills/idd-comment/SKILL.md" +COMMANDS_DOC="$PLUGIN_ROOT/../../docs/commands.md" + +HELPERS="$HERE/../../lib/assert-helpers.sh" +[ -f "$HELPERS" ] || { echo "✗ missing $HELPERS — cannot run suite" >&2; exit 1; } +. "$HELPERS" + +# ── type registration + required-field validation (spec: Reply comment type) ── +assert_output_grep "skill: reply listed in type table" "| \`reply\` |" "$SKILL" +assert_output_grep "skill: Step 2 requires --points-from" "reply | \`--points-from\` 必存在" "$SKILL" + +# ── reply template: per-point structure + metadata marker ── +assert_output_grep "skill: reply template section" "#### Template: \`reply\`" "$SKILL" +assert_output_grep "skill: metadata marker records type" "idd:comment type=reply" "$SKILL" +assert_output_grep "skill: marker records calibration outcome" "calibrated={yes|no}" "$SKILL" + +# ── points-source three-layer chain + verbatim ban (spec: Points-source resolution) ── +assert_output_grep "skill: issue-body source literal" "\`issue-body\`" "$SKILL" +assert_output_grep "skill: default = Original text blockquote" "Original text" "$SKILL" +assert_output_grep "skill: verbatim ban on counterpart's words" "禁止 paraphrase 對方原文" "$SKILL" + +# ── verify-before-claim gate (spec: Verify-before-claim gate) ── +assert_output_grep "skill: evidence check before claiming" "git log --grep" "$SKILL" +assert_output_grep "skill: unevidenced points stay honest" "無證據的點必須寫 open / pending" "$SKILL" + +# ── perspective-writer soft integration (spec: soft integration with graceful degrade) ── +assert_output_grep "skill: presence-check coordinates" "check-plugin-presence.sh perspective-writer perspective-writer" "$SKILL" +assert_output_grep "skill: degrade install literal (marketplace)" "claude plugin marketplace add PsychQuant/perspective-writer" "$SKILL" +assert_output_grep "skill: degrade install literal (install)" "claude plugin install perspective-writer@perspective-writer" "$SKILL" +assert_output_grep "skill: calibration via skill invocation" "perspective-writer:perspective-writer" "$SKILL" +assert_output_grep "skill: no install-time dependency" "不新增 install-time \`dependencies\` 條目" "$SKILL" + +# ── ordering invariant (spec: Anchoring precedes calibration) ── +assert_output_grep "skill: anchoring precedes calibration" "錨定完成後才進 calibration" "$SKILL" +assert_output_grep "skill: calibration must not alter anchors" "calibration 不得改動錨定事實" "$SKILL" + +# ── additive audit posture (spec: Additive audit posture and egress discipline) ── +assert_output_grep "skill: reply is additive to closing summary" "closing summary 的加項" "$SKILL" + +# ── docs catalog: commands.md lists the new type ── +assert_output_grep "docs: commands.md type enumeration has reply" "reply" "$COMMANDS_DOC" + +print_summary "idd-comment-reply" diff --git a/plugins/issue-driven-dev/skills/idd-comment/SKILL.md b/plugins/issue-driven-dev/skills/idd-comment/SKILL.md index a021030..7a97f31 100644 --- a/plugins/issue-driven-dev/skills/idd-comment/SKILL.md +++ b/plugins/issue-driven-dev/skills/idd-comment/SKILL.md @@ -2,8 +2,9 @@ name: idd-comment description: | 加 template-guided comment 到 GitHub issue。 - 支援 decision/note/question/correction/link/errata 6 個 types,強制 blockquote 原文、加 timestamp 與 metadata marker。 - 用於記錄使用者決定、外部 context、開放問題,不走完整 diagnose/implement phase 時。 + 支援 decision/note/question/correction/link/errata/reply 7 個 types,強制 blockquote 原文、加 timestamp 與 metadata marker。 + 用於記錄使用者決定、外部 context、開放問題,不走完整 diagnose/implement phase 時; + reply type(#269)是 human-facing 逐點回覆——寫給 review 提出者等人類 collaborator 的 correspondence。 支援 batch mode(v2.34.0+):多個 #N 同步加同一段 comment(如 `#34 #36 --type note --body 'blocked by upstream'`)。 防止的失敗:非流程性 decision 散落在 chat,未來無法追溯。 argument-hint: "#issue [#issue ...] --type= [options] e.g. '#42 --type note --body ...' or '#34 #36 --type note --body ...' (batch)" @@ -45,7 +46,7 @@ idd-issue source.docx # auto-trigger when source contains ≥2 findings 完整 multi-finding mode 契約見 `idd-issue` SKILL.md `## Multi-finding source mode` 段落。 -## 6 個 Types +## 7 個 Types | Type | 用途 | 必填 | 產出格式 emoji | |------|------|------|--------| @@ -55,6 +56,7 @@ idd-issue source.docx # auto-trigger when source contains ≥2 findings | `correction` | 修正外部 data / interpretation | `--body` | 🔧 | | `link` | 交叉引用其他 issue | `--target`, `--body` | 🔗 | | `errata` | 標記既有 comment 內容錯了(配 idd-edit 用) | `--target-comment`, `--body` | ⚠️ | +| `reply` | Human-facing 逐點回覆(給 review 提出者等人類 collaborator;v2.100.0+,#269) | `--points-from`, `--body` | 🧑‍🏫 | ## Configuration @@ -78,6 +80,9 @@ TaskCreate(name="detect_spectra_context", description="Step 0.7: 偵測是否從 TaskCreate(name="validate_type_requirements", description="依 type 檢查必填欄位(decision 要 quote、note 要 source、link 要 target、errata 要 target-comment)") TaskCreate(name="resolve_mentions", description="Step 1.5: 若有 --mention 或 body 含 @xxx,強制走 rules/tagging-collaborators.md 協定(gh api 取 collaborators → fuzzy match → AskUserQuestion fallback → 用 @login 不用 display name)") TaskCreate(name="build_comment_body", description="按 type 對應 template 組 markdown(emoji header + blockquote + body + metadata marker),插入已驗證的 @login mentions") +TaskCreate(name="resolve_points_source", description="(reply 專屬)--points-from 三層鏈解析:comment URL / `issue-body` → 預設 issue body Original text blockquote → fallback 要求使用者貼原文;verbatim,禁止 paraphrase(Reply drafting pipeline R1)") +TaskCreate(name="verify_before_claim", description="(reply 專屬)每點宣稱已解決前以 git log --grep / PR merge 狀態驗證證據;無證據寫 open / pending(R2)") +TaskCreate(name="calibrate_or_degrade", description="(reply 專屬)check-plugin-presence perspective-writer 命中 → calibration(不得改動錨定事實);缺席 → 印安裝指令照 post(R4/R5 graceful degrade)") TaskCreate(name="verify_mentions", description="post 前 grep body 的 @\\w+ 全部 cross-check 已驗證的 collaborator set;未驗證 token 直接 abort") TaskCreate(name="post_comment", description="經 gh-egress.sh comment 派送(#226;--scrub-attested 依 rules/privacy-scrubbing.md 解析,mention 帶 --mention-attested),--body-file 避免 escape 問題;errata type 額外 auto-call idd-edit") TaskCreate(name="report_result", description="輸出 ✓ Comment posted + URL;errata type 加報 idd-edit 結果") @@ -149,6 +154,7 @@ Options (視 type 而定): - `--target=#XX` — 目標 issue(link type 必填) - `--target-comment=` — 目標 comment ID(errata type 必填) - `--deadline=YYYY-MM-DD` — 未決問題的期限(question type 可選) +- `--points-from=` — 逐點來源(reply type 必填;值解析走 Reply drafting pipeline 的三層鏈) - `--mention [,...]` — tag 人到 comment(**v2.32.0+**,強制走 [`rules/tagging-collaborators.md`](../../rules/tagging-collaborators.md)) - `--mention-prompt` — 強制走 AskUserQuestion 從 collaborator menu 選(不嘗試 auto-resolve) - `--resume-spectra=""` — 顯式宣告從 spectra-discuss 中斷進來(**v2.32.0+**,觸發 Step 7 resume prompt) @@ -163,6 +169,7 @@ Options (視 type 而定): | correction | `--body` 必存在 | | link | `--target` + `--body` 必存在 | | errata | `--target-comment` + `--body` 必存在 | +| reply | `--points-from` 必存在(`--body` 選填:per-point 處理說明素材) | 缺必填 → 用 AskUserQuestion 詢問。 @@ -316,6 +323,59 @@ fi 完整 R4/R5 規範 + override pathway 見 [`/idd-edit` SKILL.md](../idd-edit/SKILL.md) `## Runtime gates (#154, v2.81.0+)`。 +#### Template: `reply`(v2.100.0+,#269 — human-facing 逐點回覆) + +```markdown +## 🧑‍🏫 Reply + +> **Point {i}**({points 來源標示,如 review 日期}): +> 「{對方原文,逐字}」 + +{該點的處理說明:改了哪裡(file / section / theorem / symbol)、怎麼改、cross-link commit / PR / label} + +**狀態**:✅ 已解決(`{merge SHA / PR}`)|⏳ open / pending({原因與下一步}) + +(…每點重複以上三段,依對方原始順序…) + +--- + +**整體狀態**:{merged SHA、同步狀態(如 Overleaf / deploy)、下一步} + + +``` + +#### Reply drafting pipeline(reply 專屬,強制順序) + +reply 是**寫給人看的 correspondence**,不是 audit log。draft 依以下順序執行,順序本身是契約: + +**R1 — Points-source 解析(`--points-from`,三層鏈)**:flag 必填(Step 2 validation 擋缺席)。值域:comment URL(逐點取自該 comment 的 blockquote / 列點)或字面值 `issue-body`。`issue-body`(或 URL 解析不到列點)→ 預設抓 issue body 的 Original text blockquote(idd-issue 建案紀律寫入的逐字原文)→ 仍解析不到 → 要求使用者貼上原文,**不得**自行歸納。逐點內容一律 verbatim blockquote,**禁止 paraphrase 對方原文**——收件人看到自己的話被改寫即失去信任(IC_R007 同源紀律)。 + +**R2 — verify-before-claim gate**:每一點在 draft 宣稱「已解決」之前,先驗證證據存在——`git log --grep "#N"` 找 commit、PR merge 狀態、或等價 artifact。無證據的點必須寫 open / pending,不得宣稱完成。與 idd-close Step 1.6 semantic gate 同族;prose 紀律,不另立 helper script。 + +**R3 — referent 錨定**:verbatim 引文組裝 + R2 通過 + SHA / file / theorem 引用固定。錨定完成後才進 calibration。 + +**R4 — perspective-writer soft integration(graceful degrade)**:probe 指令 `check-plugin-presence.sh perspective-writer perspective-writer`(座標 = marketplace name + plugin name,standalone repo `PsychQuant/perspective-writer`): + +```bash +if "$CLAUDE_PLUGIN_ROOT/scripts/check-plugin-presence.sh" perspective-writer perspective-writer 2>/dev/null; then + # 命中 → Skill(skill="perspective-writer:perspective-writer") 做 voice / recipient calibration + # 轉交:resolved --mention login + target repo .claude/rules/correspondence-.md 路徑(若存在) + CALIBRATED=yes +else + cat <<'EOF' +ℹ perspective-writer 未安裝 — reply 以未 calibrate 的 draft 照常 post(結構完整可用)。 + 要啟用 voice / recipient calibration: + claude plugin marketplace add PsychQuant/perspective-writer + claude plugin install perspective-writer@perspective-writer +EOF + CALIBRATED=no +fi +``` + +**不新增 install-time `dependencies` 條目**——soft integration 是 runtime-only presence check。與 superpowers 的 hard-dependency 刻意相反:那是 canonical process 替代(無物可退、fail-fast);calibration 是 enhancement,缺席時 reply 的結構正確性由 R1–R3 自有紀律保證。consumer-facing 契約收斂後(PsychQuant/perspective-writer#1)可升級整合深度。 + +**R5 — 不變量**:calibration 不得改動錨定事實——commit SHA、file / theorem 引用、對方原文的 verbatim 引文。違反即 bug。calibration 之後才走 Step 3.5 verify mentions → Step 4 egress(同六型的 gh-egress choke-point,`--scrub-attested` + 有 mention 時 `--mention-attested`)。 + ### Step 3.5: Verify mentions (v2.32.0+) Post 前最後一道防線: @@ -468,6 +528,17 @@ HTML comment 在 GitHub 不渲染,但可 parse: → 觸發 [`rules/tagging-collaborators.md`](../../rules/tagging-collaborators.md):抓 `gh api repos/PsychQuant/contact-book/collaborators` → 找到 `@Hardy1Yang` → 插入 body → verify pass → post。 +### Reply(human-facing 逐點回覆,v2.100.0+) + +``` +/idd-comment #141 --type=reply \ + --points-from=issue-body \ + --mention= \ + --body="per-point 處理說明素材:各點改在哪個 lemma / section、merge SHA" +``` + +→ 逐點 verbatim blockquote 對方 review 原文 → 各點處理說明 + SHA 錨定(無證據的點誠實標 open)→ perspective-writer 存在則 calibrate 後 post、缺席則印安裝指令照 post。 + ### Spectra-bridge resume (v2.32.0+) ``` @@ -489,6 +560,8 @@ HTML comment 在 GitHub 不渲染,但可 parse: - **不走 phase detection**:idd-comment 是 ad-hoc,不觸發 phase 推斷(那是 idd-update 的事) - **Comment body 要自我解釋**:三個月後回來看,光看 comment 就知道 context。這是「第一段 prose」的動機。 - **任何 @xxx 必走 collaborator API**(v2.32.0+):禁止從訓練記憶 / 聊天歷史 / git log 推測 handle。違反 = 通知錯人,不可逆。詳見 [`rules/tagging-collaborators.md`](../../rules/tagging-collaborators.md)。 +- **reply 是 closing summary 的加項**(v2.100.0+):不取代 closing summary / verify report / 任何 audit-facing 產出;close gate 不接受 reply 作為 closing summary 替代。 +- **reply 的錨定先於 calibration**(v2.100.0+):R1–R3 錨定完成後才進 R4 calibration;calibration 不得改動錨定事實(見 Reply drafting pipeline R5)。 - **Spectra context 不可靜默忽略**(v2.32.0+):偵測到從 spectra-discuss 進來必須印 resume prompt。使用者要不要回去 spectra 是他的事,但 skill 不能讓 context 默默掉地。 ## 與其他 idd-* skill 的關係 From ef61572257e81de9328c0f47d23eedc0344171c9 Mon Sep 17 00:00:00 2001 From: che cheng Date: Sun, 19 Jul 2026 06:03:50 +0800 Subject: [PATCH 3/3] fix(idd-comment): reconcile reply-type contract per 6-AI verify round 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verify round 1 (4 lens + Codex + Devil's Advocate) findings, all folded: - DA-1 (HIGH): verify-before-claim was issue-level (git log --grep "#N") but the obligation is per-point. Rewrote R2: the grep is an entry point; the found commit/PR must actually contain the change addressing THAT point, and finding any #N commit must not mark all points resolved. Locked by 2 new drift-guard assertions. - Codex + Regression F1: --points-from "required" vs spec "when unspecified default to issue-body" was a genuine contradiction. Reworded spec so the three layers describe how the REQUIRED value resolves (issue-body is an explicit sentinel), not a default for an absent flag. Dropped --body from reply's 必填 column (Step 2 + spec make it optional). - DA-2 (MEDIUM): bootstrap TaskCreate order (build_comment_body before resolve/verify) contradicted the pipeline "順序是契約". Reordered so resolve_points_source + verify_before_claim precede build_comment_body (= R3 anchoring) and annotated the dependency. - Security F4 + DA-3 (MEDIUM): reply is the only type reproducing third-party verbatim. Added scrub-wins-over-verbatim clause + noted SCRUB_LEVEL is repo-visibility-keyed (own-repo → warn/light, never enforce) so layer-3 pasted external content needs heightened self-review, not tier default. - Test strengthening (Logic LOW-1 / Requirements F3 / Codex gaps): anchored the weak "reply" docs needle, added assertions for layer-1 URL / layer-3 paste / per-point template elements / degrade post-anyway / scrub-wins. Suite 19 -> 27 assertions. - Doc drift: design/proposal run.sh -> test.sh, suite count 39->40. run-all-tests: 40 suites 0 fail. reply suite 27/27. spectra valid. Refs #269 --- .../add-idd-comment-reply-type/design.md | 2 +- .../add-idd-comment-reply-type/proposal.md | 2 +- .../specs/idd-comment-reply/spec.md | 6 +++--- .../scripts/tests/idd-comment-reply/test.sh | 18 +++++++++++++----- .../skills/idd-comment/SKILL.md | 14 +++++++------- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/openspec/changes/add-idd-comment-reply-type/design.md b/openspec/changes/add-idd-comment-reply-type/design.md index d758408..a43a609 100644 --- a/openspec/changes/add-idd-comment-reply-type/design.md +++ b/openspec/changes/add-idd-comment-reply-type/design.md @@ -42,5 +42,5 @@ idd-comment 現有六型(decision / note / question / correction / link / erra - **模板產出**(SKILL.md 新增 `#### Template: reply`):每點一段 `> {verbatim 原文}` → 處理說明(哪裡改、怎麼改、cross-link commit / PR / label)→ 狀態(✅ 已解決(``)/ ⏳ open);結尾整體狀態行(merged SHA、同步狀態、下一步);metadata marker ``。 - **Degrade notice literal**(缺 perspective-writer 時印出,內容進 SKILL.md 固定文字):`claude plugin marketplace add PsychQuant/perspective-writer` + `claude plugin install perspective-writer@perspective-writer`。 - **Egress**:沿用 `gh-egress.sh comment` + `--scrub-attested` + (有 mention 時)`--mention-attested`——與既有六型同 choke-point,零新 egress 面。 -- **驗證目標**:新 suite `plugins/issue-driven-dev/scripts/tests/idd-comment-reply/run.sh` 斷言 SKILL.md 含:(a) reply 型必填 `--points-from` 驗證列於 Step 2 表格;(b) degrade 安裝指令兩行 literal;(c) 執行序不變量字句(錨定在前 / calibration 不得改動錨定事實);(d) presence-check 座標 `perspective-writer perspective-writer`。`run-all-tests.sh` 全綠(38 → 39 suites)。 +- **驗證目標**:新 suite `plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh` 斷言 SKILL.md 含:(a) reply 型必填 `--points-from` 驗證列於 Step 2 表格;(b) degrade 安裝指令兩行 literal;(c) 執行序不變量字句(錨定在前 / calibration 不得改動錨定事實);(d) presence-check 座標 `perspective-writer perspective-writer`。`run-all-tests.sh` 全綠(39 → 40 suites;baseline 已含 #163 的 idd-edit-contract suite)。 - **文件同步**:docs/commands.md 型別清單、docs/workflows.md matrix 行、plugins README 型別摘要、CHANGELOG 版本段。 diff --git a/openspec/changes/add-idd-comment-reply-type/proposal.md b/openspec/changes/add-idd-comment-reply-type/proposal.md index 5a53fb8..829deb8 100644 --- a/openspec/changes/add-idd-comment-reply-type/proposal.md +++ b/openspec/changes/add-idd-comment-reply-type/proposal.md @@ -33,6 +33,6 @@ idd-* 產出的 comment 全部是 audit-facing(closing 五段式、verify mast - Affected specs: 新增 `idd-comment-reply`(既有 `superpowers-integration` 不修改 — 本案刻意走 soft 側,設計對比記於 design.md) - Affected code: - - New: plugins/issue-driven-dev/scripts/tests/idd-comment-reply/run.sh + - New: plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh - Modified: plugins/issue-driven-dev/skills/idd-comment/SKILL.md, plugins/issue-driven-dev/README.md, docs/commands.md, docs/workflows.md, plugins/issue-driven-dev/CHANGELOG.md, plugins/issue-driven-dev/.claude-plugin/plugin.json, .claude-plugin/marketplace.json - Removed: (none) diff --git a/openspec/changes/add-idd-comment-reply-type/specs/idd-comment-reply/spec.md b/openspec/changes/add-idd-comment-reply-type/specs/idd-comment-reply/spec.md index 6f8f567..69a71e5 100644 --- a/openspec/changes/add-idd-comment-reply-type/specs/idd-comment-reply/spec.md +++ b/openspec/changes/add-idd-comment-reply-type/specs/idd-comment-reply/spec.md @@ -16,11 +16,11 @@ ### Requirement: Points-source resolution -The `--points-from` value SHALL resolve through a three-layer chain: (1) an explicit comment URL or the literal `issue-body`; (2) when unspecified, the default SHALL be the verbatim "Original text" blockquote(s) of the issue body; (3) when neither yields points, the skill SHALL ask the user to paste the original text. Resolved points SHALL be reproduced verbatim in the reply; paraphrasing the counterpart's original wording SHALL NOT occur. +`--points-from` is required (per the Reply comment type requirement); its *value* SHALL resolve through a three-layer chain: (1) an explicit comment URL → points are taken from that comment's blockquote / enumerated list; (2) the literal `issue-body` — or an explicit URL that yields no enumerable point list — → the points SHALL be taken from the verbatim "Original text" blockquote(s) of the issue body; (3) when neither yields points, the skill SHALL ask the user to paste the original text. The three layers describe how the required value resolves — they are NOT a default for an absent flag (an absent flag is refused at Step 2). Resolved points SHALL be reproduced verbatim in the reply; paraphrasing the counterpart's original wording SHALL NOT occur. Verbatim reproduction is nonetheless subject to the privacy-scrub gate: when a quoted point carries private / PII content, the scrub gate takes precedence over verbatim (redaction wins on conflict). -#### Scenario: Default source from issue body +#### Scenario: Value issue-body resolves to the issue-body blockquote -- **WHEN** `--points-from` is not given a URL and the issue body contains an Original text blockquote +- **WHEN** `--points-from=issue-body` is given and the issue body contains an Original text blockquote - **THEN** the points are extracted from that blockquote and quoted verbatim in the reply draft #### Scenario: No resolvable source falls back to user input diff --git a/plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh b/plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh index acea921..5a3f353 100644 --- a/plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh +++ b/plugins/issue-driven-dev/scripts/tests/idd-comment-reply/test.sh @@ -31,23 +31,31 @@ assert_output_grep "skill: Step 2 requires --points-from" "reply | \`--poi # ── reply template: per-point structure + metadata marker ── assert_output_grep "skill: reply template section" "#### Template: \`reply\`" "$SKILL" +assert_output_grep "skill: template per-point verbatim blockquote" "「{對方原文,逐字}」" "$SKILL" +assert_output_grep "skill: template per-point anchor + status" "已解決(\`{merge SHA / PR}\`)" "$SKILL" assert_output_grep "skill: metadata marker records type" "idd:comment type=reply" "$SKILL" assert_output_grep "skill: marker records calibration outcome" "calibrated={yes|no}" "$SKILL" # ── points-source three-layer chain + verbatim ban (spec: Points-source resolution) ── -assert_output_grep "skill: issue-body source literal" "\`issue-body\`" "$SKILL" -assert_output_grep "skill: default = Original text blockquote" "Original text" "$SKILL" +assert_output_grep "skill: layer-1 explicit comment URL" "comment URL(逐點取自該 comment" "$SKILL" +assert_output_grep "skill: layer-2 issue-body source literal" "\`issue-body\`" "$SKILL" +assert_output_grep "skill: layer-2 = Original text blockquote" "Original text" "$SKILL" +assert_output_grep "skill: layer-3 user-paste fallback" "要求使用者貼上原文" "$SKILL" assert_output_grep "skill: verbatim ban on counterpart's words" "禁止 paraphrase 對方原文" "$SKILL" +assert_output_grep "skill: scrub wins over verbatim on PII" "scrub 優先於 verbatim" "$SKILL" # ── verify-before-claim gate (spec: Verify-before-claim gate) ── assert_output_grep "skill: evidence check before claiming" "git log --grep" "$SKILL" -assert_output_grep "skill: unevidenced points stay honest" "無證據的點必須寫 open / pending" "$SKILL" +assert_output_grep "skill: evidence is per-point not per-issue (DA-1)" "證據是 per-point 的" "$SKILL" +assert_output_grep "skill: commit must actually address that point" "必須實際包含處理『該點』的改動" "$SKILL" +assert_output_grep "skill: unevidenced points stay honest" "寫 open / pending" "$SKILL" # ── perspective-writer soft integration (spec: soft integration with graceful degrade) ── assert_output_grep "skill: presence-check coordinates" "check-plugin-presence.sh perspective-writer perspective-writer" "$SKILL" assert_output_grep "skill: degrade install literal (marketplace)" "claude plugin marketplace add PsychQuant/perspective-writer" "$SKILL" assert_output_grep "skill: degrade install literal (install)" "claude plugin install perspective-writer@perspective-writer" "$SKILL" assert_output_grep "skill: calibration via skill invocation" "perspective-writer:perspective-writer" "$SKILL" +assert_output_grep "skill: absent → post uncalibrated draft anyway" "reply 以未 calibrate 的 draft 照常 post" "$SKILL" assert_output_grep "skill: no install-time dependency" "不新增 install-time \`dependencies\` 條目" "$SKILL" # ── ordering invariant (spec: Anchoring precedes calibration) ── @@ -57,7 +65,7 @@ assert_output_grep "skill: calibration must not alter anchors" "calibration 不 # ── additive audit posture (spec: Additive audit posture and egress discipline) ── assert_output_grep "skill: reply is additive to closing summary" "closing summary 的加項" "$SKILL" -# ── docs catalog: commands.md lists the new type ── -assert_output_grep "docs: commands.md type enumeration has reply" "reply" "$COMMANDS_DOC" +# ── docs catalog: commands.md lists the new type (anchored, not the bare `reply` substring) ── +assert_output_grep "docs: commands.md quick-ref enumerates reply" "link / errata / reply" "$COMMANDS_DOC" print_summary "idd-comment-reply" diff --git a/plugins/issue-driven-dev/skills/idd-comment/SKILL.md b/plugins/issue-driven-dev/skills/idd-comment/SKILL.md index 7a97f31..a6afc18 100644 --- a/plugins/issue-driven-dev/skills/idd-comment/SKILL.md +++ b/plugins/issue-driven-dev/skills/idd-comment/SKILL.md @@ -56,7 +56,7 @@ idd-issue source.docx # auto-trigger when source contains ≥2 findings | `correction` | 修正外部 data / interpretation | `--body` | 🔧 | | `link` | 交叉引用其他 issue | `--target`, `--body` | 🔗 | | `errata` | 標記既有 comment 內容錯了(配 idd-edit 用) | `--target-comment`, `--body` | ⚠️ | -| `reply` | Human-facing 逐點回覆(給 review 提出者等人類 collaborator;v2.100.0+,#269) | `--points-from`, `--body` | 🧑‍🏫 | +| `reply` | Human-facing 逐點回覆(給 review 提出者等人類 collaborator;v2.100.0+,#269) | `--points-from` | 🧑‍🏫 | ## Configuration @@ -79,10 +79,10 @@ TaskCreate(name="parse_args", description="Parse #NNN + --type + --body / --quot TaskCreate(name="detect_spectra_context", description="Step 0.7: 偵測是否從 spectra-discuss 中斷進來(--resume-spectra flag / --source 含 spectra / spectra list 有 in-flight / .claude/.idd/state/bridge.json(legacy fallback: .claude/state/idd-bridge.json)— 詳見 rules/spectra-bridge.md") TaskCreate(name="validate_type_requirements", description="依 type 檢查必填欄位(decision 要 quote、note 要 source、link 要 target、errata 要 target-comment)") TaskCreate(name="resolve_mentions", description="Step 1.5: 若有 --mention 或 body 含 @xxx,強制走 rules/tagging-collaborators.md 協定(gh api 取 collaborators → fuzzy match → AskUserQuestion fallback → 用 @login 不用 display name)") -TaskCreate(name="build_comment_body", description="按 type 對應 template 組 markdown(emoji header + blockquote + body + metadata marker),插入已驗證的 @login mentions") -TaskCreate(name="resolve_points_source", description="(reply 專屬)--points-from 三層鏈解析:comment URL / `issue-body` → 預設 issue body Original text blockquote → fallback 要求使用者貼原文;verbatim,禁止 paraphrase(Reply drafting pipeline R1)") -TaskCreate(name="verify_before_claim", description="(reply 專屬)每點宣稱已解決前以 git log --grep / PR merge 狀態驗證證據;無證據寫 open / pending(R2)") -TaskCreate(name="calibrate_or_degrade", description="(reply 專屬)check-plugin-presence perspective-writer 命中 → calibration(不得改動錨定事實);缺席 → 印安裝指令照 post(R4/R5 graceful degrade)") +TaskCreate(name="resolve_points_source", description="(reply 專屬,排在 build_comment_body 之前 — 順序是契約)--points-from 三層鏈解析:comment URL / `issue-body` → issue body Original text blockquote → fallback 要求使用者貼原文;verbatim,禁止 paraphrase(Reply drafting pipeline R1)") +TaskCreate(name="verify_before_claim", description="(reply 專屬,排在 build_comment_body 之前)每點宣稱已解決前 per-point 驗證證據:git log --grep 只是入口,找到的 commit / PR 須實際含處理『該點』的改動;無對應改動寫 open / pending(R2)") +TaskCreate(name="build_comment_body", description="按 type 對應 template 組 markdown(emoji header + blockquote + body + metadata marker),插入已驗證的 @login mentions。**reply 時本步即 pipeline R3(referent 錨定)**,consume 前兩步 resolve_points_source + verify_before_claim 的產物,故排在其後") +TaskCreate(name="calibrate_or_degrade", description="(reply 專屬,排在 build_comment_body/R3 之後)check-plugin-presence perspective-writer 命中 → calibration(不得改動錨定事實);缺席 → 印安裝指令照 post(R4/R5 graceful degrade)") TaskCreate(name="verify_mentions", description="post 前 grep body 的 @\\w+ 全部 cross-check 已驗證的 collaborator set;未驗證 token 直接 abort") TaskCreate(name="post_comment", description="經 gh-egress.sh comment 派送(#226;--scrub-attested 依 rules/privacy-scrubbing.md 解析,mention 帶 --mention-attested),--body-file 避免 escape 問題;errata type 額外 auto-call idd-edit") TaskCreate(name="report_result", description="輸出 ✓ Comment posted + URL;errata type 加報 idd-edit 結果") @@ -348,9 +348,9 @@ fi reply 是**寫給人看的 correspondence**,不是 audit log。draft 依以下順序執行,順序本身是契約: -**R1 — Points-source 解析(`--points-from`,三層鏈)**:flag 必填(Step 2 validation 擋缺席)。值域:comment URL(逐點取自該 comment 的 blockquote / 列點)或字面值 `issue-body`。`issue-body`(或 URL 解析不到列點)→ 預設抓 issue body 的 Original text blockquote(idd-issue 建案紀律寫入的逐字原文)→ 仍解析不到 → 要求使用者貼上原文,**不得**自行歸納。逐點內容一律 verbatim blockquote,**禁止 paraphrase 對方原文**——收件人看到自己的話被改寫即失去信任(IC_R007 同源紀律)。 +**R1 — Points-source 解析(`--points-from`,三層鏈)**:flag 必填(Step 2 validation 擋缺席)。值域:comment URL(逐點取自該 comment 的 blockquote / 列點)或字面值 `issue-body`。`issue-body`(或 URL 解析不到列點)→ 預設抓 issue body 的 Original text blockquote(idd-issue 建案紀律寫入的逐字原文)→ 仍解析不到 → 要求使用者貼上原文,**不得**自行歸納。逐點內容一律 verbatim blockquote,**禁止 paraphrase 對方原文**——收件人看到自己的話被改寫即失去信任(IC_R007 同源紀律)。**但 verbatim 服從 privacy-scrub gate**:reply 是唯一逐字重製第三方原文的型別,當某點引文含私人/PII 內容(尤其 layer 3 使用者貼上的外部原文),**scrub 優先於 verbatim(衝突時 redaction 勝)**——egress 的 `--scrub-attested` 本就掃整個 body 含 blockquote,此處明文化該優先序,避免執行者過度字面化 verbatim 而把未遮蔽的第三方 PII 推上 remote。**注意 SCRUB_LEVEL 是 repo-visibility-keyed**(third-party=enforce / own-public=warn / own-private=light):reply 的典型情境是「第三方逐字內容貼到使用者自己的 repo」→ 落在 warn / light(預設 proceed),**不會**觸發 enforce 的 block-with-diff。因此對 **layer 3 使用者貼上的外部原文**(機械網抓不到的人名/未發表結果等 human-prose PII),executor **必須主動施加 heightened 隱私自審**、不得僅倚賴 repo-tier 預設放行;比照 CLAUDE.md「raw 第三方逐字內容不進 remote」鐵律。(把 reply 第三方 payload 強制拉到 enforce tier 屬 privacy-scrubbing 跨切面決策,另案 follow-up。) -**R2 — verify-before-claim gate**:每一點在 draft 宣稱「已解決」之前,先驗證證據存在——`git log --grep "#N"` 找 commit、PR merge 狀態、或等價 artifact。無證據的點必須寫 open / pending,不得宣稱完成。與 idd-close Step 1.6 semantic gate 同族;prose 紀律,不另立 helper script。 +**R2 — verify-before-claim gate(per-point,非 issue-level)**:每一點在 draft 宣稱「已解決」之前,先驗證證據存在。**證據是 per-point 的,不是 per-issue**:`git log --grep "#N"` 只是**入口**——找到的 commit / merged PR **必須實際包含處理『該點』的改動**(讀 diff 確認觸及該點所指的 file / function / theorem / behavior),才算該點的證據。**嚴禁**「找到任一提及 `#N` 的 commit 就把所有點都標已解決」——那正是本 gate 要防的 over-claim(本 type 的 raison d'être:每句『已修正』都指到真實 diff)。某點找不到對應該點的具體改動 → 該點寫 open / pending,不得宣稱完成。與 idd-close Step 1.6 semantic gate 同族;prose 紀律,不另立 helper script。 **R3 — referent 錨定**:verbatim 引文組裝 + R2 通過 + SHA / file / theorem 引用固定。錨定完成後才進 calibration。