Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions openspec/changes/discussions-intake-bridge/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
schema: spec-driven
created: 2026-07-11
created_by: che cheng <kiki830621@gmail.com>
created_with: claude
41 changes: 41 additions & 0 deletions openspec/changes/discussions-intake-bridge/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Context

Two prose SKILLs (idd-list, idd-issue) gain Discussions awareness through GraphQL (Discussions have no REST API). The direction was fully settled by the issue author's own design constraints in #221's body plus the 2026-07-06 diagnosis; this design records the operational decisions the implementer needs.

## Goals / Non-Goals

**Goals**: opt-in surfacing in idd-list; Discussion-seeded filing in idd-issue; one shared normative contract + GraphQL templates in a reference doc; drift-guard test.

**Non-Goals**: sentiment-based resolution detection; auto-filing; always-on fetch; any Discussions write beyond the single back-reference reply. (See proposal Non-Goals.)

## Decisions

### D1 — One shared reference doc owns the contract and the GraphQL

Both skills cite `references/discussions-intake.md` instead of each embedding its own query text. The doc carries: (a) the three normative constraints — **no-auto-file** (surface + human judge), **dedup** (a Discussion referenced by any existing issue, open or closed, is not re-flagged), **resolution-detection** (`answerChosenAt != null` → not actionable; sentiment reading is out of scope); (b) the `hasDiscussionsEnabled` probe query; (c) the discussions list query (first 50, fields: number, title, url, category name, answerChosenAt, updatedAt, author login); (d) the single-discussion fetch query (title, body, url, author, category, answerChosenAt) for idd-issue seeding. Rationale: two copies of a GraphQL string is the drift the plugin's own deep-integration rule forbids.

### D2 — idd-list surfacing is a new Step 2.7 (opt-in, after PR fetch, before phase extraction)

`--discussions` flag only (no config key in v1). Flow: probe `hasDiscussionsEnabled` → false prints one line `(discussions disabled on this repo — skip)` and continues; true → fetch → filter category ∈ {Q&A, Ideas} AND `answerChosenAt == null` → dedup by searching issue bodies for the discussion URL (`gh search issues` / `gh issue list --search` with the URL, both states) → render a dedicated `Discussions (actionable)` block after the issues table with per-row `→ /idd-issue --from-discussion <url>` as the suggested next, and a footer count. Zero actionable → one-line note, not silence (surfacing tools must not silently swallow an empty channel).

### D3 — idd-issue seeding records provenance verbatim; the reply is draft-and-confirm with a hard unattended boundary

`--from-discussion <url|number>`: fetch the Discussion, seed the issue body with a `## Provenance` section (Discussion URL + author + a verbatim blockquote of the opening post — blockquote per the plugin's 原文引用 discipline), then continue the normal idd-issue flow (type/priority gathering, privacy gate, egress). After the issue exists: draft the back-reference reply (`Filed as <issue-url> — follow-up there`) and (attended) AskUserQuestion confirm before `gh api graphql` addDiscussionComment; (unattended) **never post** — print the draft in the Step 5 report as "suggested reply (not posted)". This mirrors the #141 surface-only contract for outward-facing writes.

### D4 — Drift-guard tests mock nothing live

`scripts/tests/discussions-intake/test.sh` asserts the prose contract (flags documented, constraints named, reference doc sections present, unattended draft-only boundary stated, no-auto-file literal) — the falsifiable form for prose SKILLs, consistent with every sibling suite. No live GraphQL in tests.

## Risks / Trade-offs

- **GraphQL schema drift** (Discussions API is younger than REST): the reference doc records the schema assumption (field names + query date); a query failure in the wild degrades to the same one-line skip as discussions-disabled, never a hard abort of idd-list.
- **Dedup search cost**: one search per actionable discussion, bounded by the ≤50 fetch window and the category/answered filters that run first.
- **Opt-in invisibility**: users who never pass `--discussions` keep the blind spot. Accepted for v1 (latency + noise trade-off, per issue body); revisit a config key if the flag proves sticky.

## Migration Plan

Additive flags only; no existing invocation changes behavior. No migration.

## Open Questions

(none — direction settled in #221 body + diagnosis; Spectra opt-out conditions were met)
35 changes: 35 additions & 0 deletions openspec/changes/discussions-intake-bridge/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## Why

GitHub Discussions are a real intake channel that IDD is completely blind to. On 2026-07-04, `/idd-list` on `PsychQuant/che-ical-mcp` reported a clean backlog (0 open issues) while a real permission-bug report — with the maintainer's diagnosis and the reporter's "it's fixed" confirmation — had lived its entire lifecycle inside Discussion 105. Discussions have no REST API, so `gh issue list` never sees them; every IDD intake surface (`/idd-list` triage, `/idd-issue` filing) is bound to the Issues tab only. (#221)

## What Changes

- `/idd-list` gains an **opt-in** `--discussions` flag: fetch open Discussions via `gh api graphql`, no-op gracefully when the repo has Discussions disabled, filter to actionable ones (Q&A / Ideas category, unanswered via `answerChosenAt: null`, not already referenced by an existing issue), and render them in a dedicated block with a suggested next action. Opt-in keeps the default invocation latency- and noise-free.
- `/idd-issue` gains `--from-discussion <url|number>`: seed the issue body from the Discussion (provenance section with URL + verbatim blockquote of the opening post), and after the issue exists, post a back-reference reply in the Discussion — **draft-and-confirm only**; under unattended mode the reply is drafted but never posted (surfaced in the report instead).
- New reference `plugins/issue-driven-dev/references/discussions-intake.md` holds the normative contract (no-auto-file / dedup / resolution-detection) plus the GraphQL query templates both skills share.
- **Hard constraint carried from the motivating case: the tool never auto-files.** Discussion 105 was already resolved when it was discovered — mechanical filing would have created a noise issue. The bridge surfaces; the human judges.

## Non-Goals

- **Latest-comment sentiment analysis** for resolution detection — `answerChosenAt` is the honest mechanical boundary; reading "everything is fixed, thank you" is human judgment (diagnosis Residue, #221).
- **Auto-filing issues from Discussions** — rejected by design (see the che-ical-mcp 105 case).
- **Always-on Discussions fetch in idd-list** — opt-in flag only; no config key in v1 (a config key can be added later if the flag proves sticky).
- **Discussions write-path beyond the single back-reference reply** (no answering, no labeling, no closing Discussions).

## Capabilities

### New Capabilities

- `discussions-intake`: the Discussions→IDD intake bridge contract — idd-list surfacing (opt-in, filtered, deduped), idd-issue seeding with provenance, the no-auto-file / dedup / resolution-detection constraints, and the shared GraphQL templates.

### Modified Capabilities

(none — idd-list and idd-issue behavior extensions are additive flags; no existing spec's normative clauses change)

## Impact

- Affected specs: new `discussions-intake`
- Affected code:
- New: plugins/issue-driven-dev/references/discussions-intake.md, plugins/issue-driven-dev/scripts/tests/discussions-intake/test.sh
- Modified: plugins/issue-driven-dev/skills/idd-list/SKILL.md, plugins/issue-driven-dev/skills/idd-issue/SKILL.md, plugins/issue-driven-dev/references/usecase-routing.md, plugins/issue-driven-dev/README.md
- Removed: (none)
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
## ADDED Requirements

### Requirement: idd-list surfaces actionable Discussions on opt-in

`/idd-list` SHALL fetch open GitHub Discussions via `gh api graphql` when — and only when — the `--discussions` flag is passed. When the repository has Discussions disabled (`hasDiscussionsEnabled: false`), the step SHALL no-op with a single visible note and continue. A Discussion SHALL be surfaced as actionable only when its category is Q&A or Ideas, `answerChosenAt` is null, and no existing issue (open or closed) references its URL. Actionable Discussions SHALL render in a dedicated block with a suggested next action pointing at `/idd-issue --from-discussion <url>`; zero actionable SHALL print a one-line note rather than silence.

#### Scenario: repo with Discussions disabled

- **GIVEN** a target repo where `hasDiscussionsEnabled` is false
- **WHEN** the user runs `/idd-list --discussions`
- **THEN** the run prints one skip note for the Discussions step and the issues table renders unaffected

#### Scenario: answered discussion is not flagged

- **GIVEN** a Q&A Discussion whose `answerChosenAt` is non-null
- **WHEN** `/idd-list --discussions` runs
- **THEN** that Discussion does not appear in the actionable block

#### Scenario: discussion already referenced by an issue is deduped

- **GIVEN** an unanswered Q&A Discussion whose URL appears in the body of an existing closed issue
- **WHEN** `/idd-list --discussions` runs
- **THEN** that Discussion does not appear in the actionable block

### Requirement: idd-issue seeds an issue from a Discussion with verbatim provenance

`/idd-issue` SHALL accept `--from-discussion <url|number>`, fetch the Discussion via GraphQL, and seed the drafted issue body with a Provenance section containing the Discussion URL, its author, and a verbatim blockquote of the opening post. The seeded draft SHALL then flow through the unchanged idd-issue pipeline (type/priority gathering, privacy gate, egress wrapper).

#### Scenario: seeding from a Discussion URL

- **GIVEN** an unanswered Q&A Discussion reporting a bug
- **WHEN** the user runs `/idd-issue --from-discussion <its url>`
- **THEN** the drafted body contains a Provenance section with the URL and a blockquote of the opening post, and issue creation proceeds through the normal gates

### Requirement: the intake bridge never auto-files and never auto-posts

The bridge SHALL NOT create an issue from a Discussion without an explicit human invocation of `--from-discussion`, and SHALL NOT post the back-reference reply to the Discussion without explicit confirmation. Under unattended mode the reply SHALL be drafted and surfaced in the report but never posted.

#### Scenario: unattended run drafts but does not post the reply

- **GIVEN** `/idd-issue --from-discussion <url>` running under an orchestrator's UNATTENDED MODE directive
- **WHEN** the issue is created successfully
- **THEN** the back-reference reply text appears in the final report marked as not posted, and no `addDiscussionComment` mutation is executed

### Requirement: the contract and GraphQL templates live in one shared reference

The normative constraints (no-auto-file, dedup, resolution-detection) and every GraphQL query the bridge uses (hasDiscussionsEnabled probe, discussions list, single-discussion fetch) SHALL be defined once in the discussions-intake reference document; both skills SHALL cite it rather than embedding divergent copies.

#### Scenario: adding a field to the list query

- **GIVEN** a maintainer extending the discussions list query with a new field
- **WHEN** they edit the reference document's query template
- **THEN** both idd-list and idd-issue consume the change without any skill-local query text needing a parallel edit
17 changes: 17 additions & 0 deletions openspec/changes/discussions-intake-bridge/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## 1. Contract + tests first (RED)

- [x] 1.1 (Design D1; Req: the contract and GraphQL templates live in one shared reference) Write plugins/issue-driven-dev/references/discussions-intake.md — the three normative constraints (no-auto-file / dedup / resolution-detection with `answerChosenAt` as the mechanical boundary), the `hasDiscussionsEnabled` probe query, the discussions list query (first 50: number, title, url, category name, answerChosenAt, updatedAt, author login), the single-discussion fetch query, and the schema-assumption note (field names + date). Verify: file exists and contains all three constraint names + three query blocks.
- [x] 1.2 (Design D4) Write plugins/issue-driven-dev/scripts/tests/discussions-intake/test.sh (assert-helpers): asserts idd-list documents `--discussions` + disabled-repo no-op + category/answered/dedup filter + dedicated block; idd-issue documents `--from-discussion` + Provenance blockquote + draft-and-confirm reply + unattended draft-only; reference doc carries constraints + queries; both skills cite the reference. Run it: RED (SKILL prose absent), reference assertions GREEN.

## 2. idd-list Step 2.7 (GREEN part 1)

- [x] 2.1 (Design D2; Req: idd-list surfaces actionable Discussions on opt-in) Add Step 2.7 to plugins/issue-driven-dev/skills/idd-list/SKILL.md: opt-in `--discussions` flag parse (Step 1 table row), probe → skip-note on disabled, fetch → filter (Q&A/Ideas AND answerChosenAt null) → dedup (search issue bodies for the discussion URL, both states) → `Discussions (actionable)` block after the issues table with per-row `→ /idd-issue --from-discussion <url>` + footer count; zero-actionable one-line note; GraphQL failure degrades to the same skip-note (never aborts idd-list). Bootstrap TaskCreate list gains a `fetch_discussions` entry. Verify: discussions-intake suite idd-list assertions GREEN.

## 3. idd-issue --from-discussion (GREEN part 2)

- [x] 3.1 (Design D3; Req: idd-issue seeds an issue from a Discussion with verbatim provenance; Req: the intake bridge never auto-files and never auto-posts) Add `--from-discussion <url|number>` to plugins/issue-driven-dev/skills/idd-issue/SKILL.md: fetch via the reference's single-discussion query, seed draft body with `## Provenance` (URL + author + verbatim blockquote of opening post), continue unchanged pipeline; after creation, draft back-reference reply — attended: AskUserQuestion confirm then `addDiscussionComment`; unattended: print draft in Step 5 report marked not-posted, never mutate. Bootstrap TaskCreate list gains a `seed_from_discussion` entry. Verify: discussions-intake suite idd-issue assertions GREEN.

## 4. Docs + full-suite regression

- [x] 4.1 Add the two flags to plugins/issue-driven-dev/references/usecase-routing.md (one new scenario row: "user report arrived as a Discussion") and a short README section under the skills table noting the opt-in Discussions bridge (#221). Verify: discussions-intake suite doc assertions GREEN.
- [x] 4.2 Run the full plugin test set (all suites under plugins/issue-driven-dev/scripts/tests/) and `spectra validate discussions-intake-bridge`. Verify: no suite regresses; validate clean.
4 changes: 4 additions & 0 deletions plugins/issue-driven-dev/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ attachments_release: "attachments"
---
```

### Discussions intake bridge(v2.95.0, #221)

GitHub Discussions(Q&A / Ideas)是 Issues 之外的真實 intake channel — 本 plugin 現在能看到它:`idd-list --discussions`(**opt-in**)surface actionable 的 Discussions(未答 `answerChosenAt: null`、未被任何 issue 引用、Discussions 停用時優雅 no-op),`idd-issue --from-discussion <url|number>` 把一則 Discussion seed 成 issue(`## Provenance` + 原文 verbatim blockquote),建案後的 back-reference 回文 **draft-and-confirm**(unattended 絕不 auto-post)。**鐵律:絕不 auto-file** — bridge 只 surface,人來判斷。契約 + GraphQL 樣板:[references/discussions-intake.md](references/discussions-intake.md)。

### Selective auto-tag(v2.94.0, #85)

IDD tags two milestones so you can jump back to a stable checkpoint with `git checkout` instead of hunting commit hashes: `idd-{N}-baseline` (placed by `idd-issue` on main's HEAD when the issue opens — the rollback anchor) and `idd-{N}-verified` (placed by `idd-verify` on the Aggregate-PASS snapshot — review-ready). Only these two points are tagged (no `diagnose`/`plan`/`implement` tags), so the tag namespace stays clean.
Expand Down
Loading
Loading