Skip to content
Open
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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"plugins": [
{
"name": "issue-driven-dev",
"version": "3.0.0",
"version": "3.0.1",
"description": "v3.0.0 (BREAKING): the closing-summary helper may VETO and may never PERMIT. After twelve verify rounds failing in one direction — a real summary the recogniser could not follow classified `missing`, and `missing` being the sole authorisation for `/idd-close --retroactive` to post a duplicate — the power was split along the direction that is sound. \"A marker IS here\" is an observation; \"a marker is NOT here\" is an inference from a failure to recognise, and no matcher over source bytes can answer a question about rendered output in the negative. Gate exit codes are now 1 (recognised) / 2 (undeterminable) / 10 (nothing recognised — NOT permission); there is no exit 0 in gate mode, deliberately, so a caller still reading `rc == 0 means go` breaks loudly. Gate class `missing` → `unrecognised`, every reply carries authorises:false, and a fifth class `mentioned` names the state the tool can actually observe. `--retroactive` loses its unattended path: the skill must read the comment set itself and obtain human confirmation that cannot be disabled. Classification now asks who wrote the comment, so a commenter can no longer move an issue between classes. Also: three more exit-0 parser paths, markup counted as content three layers deep, a quotation reaching `compliant`, the mention gate passing on zero iterations by three routes, untrusted prose reaching a shell command line, and #317 criterion (c) answered correctly for the first time in five attempts. Ten guards were mutation-proven vacuous and rebuilt.",
"author": {
"name": "Che Cheng"
Expand Down
6 changes: 6 additions & 0 deletions .claude/.idd/attachments/issue-353/_manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"issue": 353,
"fetched_at": "2026-10-02T07:00:33Z",
"fetched_by": "idd-diagnose",
"files": []
}
1 change: 1 addition & 0 deletions .claude/.idd/routing-stats.jsonl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{"agent":"claude-fable-5","complexity":"Spectra","followups_spawned":2,"issue_number":209,"issue_repo":"PsychQuant\/issue-driven-development","outcome":"in_review","recorded_by":"idd-verify-2.89.0","round_trips":2,"scope_files":15,"scope_loc":359,"scope_signals":["design_negotiation","public_api","breaking_change","requires_changelog"],"ts":"2026-07-02T14:38:05Z","verify_blocking":1,"verify_low":11,"verify_medium":9}
{"agent":"claude-fable-5","complexity":"Spectra","followups_spawned":2,"issue_number":209,"issue_repo":"PsychQuant\/issue-driven-development","outcome":"merged","outcome_ts":"2026-07-02T20:11:52Z","recorded_by":"update-outcome-cli","round_trips":2,"scope_files":15,"scope_loc":359,"scope_signals":["design_negotiation","public_api","breaking_change","requires_changelog"],"ts":"2026-07-02T20:11:52Z","verify_blocking":1,"verify_low":11,"verify_medium":9}
{"agent":"claude-fable-5","complexity":"Spectra","followups_spawned":2,"issue_number":214,"issue_repo":"PsychQuant\/issue-driven-development","outcome":"merged","outcome_ts":"2026-07-03T02:38:11Z","recorded_by":"idd-close-2.89.0","round_trips":2,"scope_files":19,"scope_loc":1100,"scope_signals":["design_negotiation","public_api","requires_changelog"],"ts":"2026-07-03T02:38:11Z","verify_blocking":1,"verify_low":16,"verify_medium":12}
{"agent":"claude-opus-5.5","complexity":"Simple","followups_spawned":4,"issue_number":353,"issue_repo":"PsychQuant\/issue-driven-development","outcome":"in_review","recorded_by":"idd-verify-3.0.0","round_trips":2,"scope_files":9,"scope_loc":484,"scope_signals":["explicit_acceptance","single_handler","multi_file","new_test_infrastructure"],"ts":"2026-10-02T08:10:43Z","verify_blocking":0,"verify_low":16,"verify_medium":2}
30 changes: 30 additions & 0 deletions .github/workflows/live-schema.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# live-schema.yml — check GitHub's real GraphQL schema for the names idd-issue uses (#353).
#
# `idd-issue --blocked-by` calls the `addBlockedBy` mutation. If GitHub renames it, nothing
# in this repo changes, so the PR suite (tests.yml, offline by design) cannot notice. The
# wrong name used from v2.52.0 to 3.0.0 could only fail against today's schema, and the
# failure was hidden. This job runs the blocked-by-mutation suite with IDD_LIVE_GH=1, which
# introspects the live schema, on a schedule and on demand.
#
# GitHub disables scheduled workflows in a public repository after 60 days without
# repository activity. If that happens, re-enable this workflow from the Actions tab.
name: live-schema
on:
schedule:
- cron: '0 1 * * 1' # Mondays 01:00 UTC = Mondays 09:00 Taipei (UTC+8)
workflow_dispatch:
permissions:
contents: read
jobs:
blocked-by-mutation:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false # the suite only reads; gh gets GH_TOKEN below
- name: Introspect GitHub's schema for addBlockedBy
env:
IDD_LIVE_GH: '1'
GH_TOKEN: ${{ github.token }}
run: bash plugins/issue-driven-dev/scripts/tests/blocked-by-mutation/test.sh
2 changes: 1 addition & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
| _positional_ description / path | Raw text, file path (docx/pdf/md/txt), or chat reference; skill auto-detects type and routes to the matching reader (`che-word-mcp`, `che-pdf-mcp`, Telegram MCP, Apple Mail/Notes MCP). Missing MCP plugin → **fail-fast** with install instructions (per #27 / #32). |
| `--target <owner/repo \| group:label>` | Per-invocation target override; does **not** write to config |
| `--parent N` | Link the new issue under issue #N (idempotent PATCH of #N's body task-list, see [`bundle-flags.md`](../plugins/issue-driven-dev/references/bundle-flags.md)) |
| `--blocked-by M[,M2,...]` | Three-layer chain: body blockquote (unconditional) + `addBlockedByDependency` GraphQL (best-effort) + parent annotation (if `--parent` co-used) |
| `--blocked-by M[,M2,...]` | Three-layer chain: body blockquote (unconditional) + `addBlockedBy` GraphQL (best-effort; GitHub's error is printed verbatim, an existing dependency counts as success) + parent annotation (if `--parent` co-used) |
| `--bundle-mode ordered \| unordered` | Create an epic + N children; `ordered` also wires a Blocked-by chain. Mutually exclusive with group mode. |
| `--mention login[,login2,...]` | Force the 5-step collaborator-tagging protocol ([`rules/tagging-collaborators.md`](../plugins/issue-driven-dev/rules/tagging-collaborators.md)); cannot fail open. |

Expand Down
18 changes: 12 additions & 6 deletions openspec/specs/idd-issue-bundle/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,27 @@ The PATCH operation SHALL preserve existing parent body content: it SHALL NOT re
The `idd-issue` skill SHALL accept a `--blocked-by <M>[,<M2>...]` flag where each value is a positive integer issue number. After creating the child issue, the skill SHALL apply the dependency annotation through three layers:

1. The skill SHALL prepend a blockquote `> Blocked by #M` (one line per `M`) to the child issue body, regardless of subsequent layer outcomes.
2. The skill SHALL attempt the GitHub GraphQL `addBlockedByDependency` mutation for each `M`. Failure SHALL NOT abort the operation;the skill SHALL emit a warning naming the failed `M` and continue.
2. The skill SHALL attempt the GitHub GraphQL `addBlockedBy` mutation (input fields `issueId` and `blockingIssueId`) for each `M`. Failure SHALL NOT abort the operation;the skill SHALL emit a warning naming the failed `M` that includes the error text GitHub returned, SHALL NOT attribute the failure to a cause GitHub did not report, and continue. A failure whose GitHub error text contains `Target issue has already been taken` means the dependency already exists and SHALL be treated as success; any other error, including other "has already been taken" validation failures, SHALL produce the warning. The warning and any other message from this layer SHALL be written to stderr, because bundle mode captures the child number from stdout.
3. When `--parent <N>` is also provided, the skill SHALL annotate the corresponding parent task list entry as `- [ ] #child (blocked by #M)` to surface dependency at parent view level.

#### Scenario: Native dependency mutation succeeds

- **WHEN** `idd-issue --blocked-by 50` is invoked and the GraphQL `addBlockedByDependency` mutation returns success
- **WHEN** `idd-issue --blocked-by 50` is invoked and the GraphQL `addBlockedBy` mutation returns success
- **THEN** child body contains `> Blocked by #50` blockquote
- **AND** GitHub UI displays the native "Blocked by" dependency on the child issue
- **AND** no warning is emitted

#### Scenario: Native dependency mutation fails, body annotation persists

- **WHEN** `idd-issue --blocked-by 50` is invoked and the GraphQL mutation fails (repo not enabled / permission / API error)
- **WHEN** `idd-issue --blocked-by 50` is invoked and the GraphQL mutation fails for a reason other than an existing dependency
- **THEN** child body still contains `> Blocked by #50` blockquote
- **AND** the skill SHALL emit a warning naming the mutation failure and the blocked-by target
- **AND** the skill SHALL emit a warning naming the blocked-by target and including the error text GitHub returned
- **AND** the child issue creation SHALL NOT be aborted

#### Scenario: Dependency already exists

- **WHEN** the dependency on `50` already exists — for example `idd-issue --blocked-by 50,50`, or the relationship was created elsewhere — and GitHub's error text contains `Target issue has already been taken`
- **THEN** the skill SHALL NOT emit a warning for target `50`
- **AND** the child issue creation SHALL NOT be aborted

#### Scenario: Multiple blocked-by targets
Expand All @@ -83,8 +89,8 @@ The `idd-issue` skill SHALL accept a `--blocked-by <M>[,<M2>...]` flag where eac
| GraphQL result | Body blockquote | Parent annotation (when --parent used) | Final state |
| ----- | ----- | ----- | ----- |
| Success | Present | Present | All three layers active |
| API failure | Present | Present | UI lacks native warning, but markdown still readable |
| Repo not enabled | Present | Present | Same as API failure, plus one-time warning |
| Dependency already exists (`Target issue has already been taken`) | Present | Present | Same as Success; no warning |
| Any other failure | Present | Present | UI lacks native warning, but markdown still readable; warning on stderr carries GitHub's error text |
| Both --blocked-by and --parent absent | N/A | N/A | Child created normally without dependency annotation |

### Requirement: idd-issue SHALL accept --bundle-mode flag for batch bundle creation
Expand Down
2 changes: 1 addition & 1 deletion plugins/issue-driven-dev/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "issue-driven-dev",
"description": "v3.0.0 (BREAKING): the closing-summary helper may VETO and may never PERMIT. After twelve verify rounds failing in one direction — a real summary the recogniser could not follow classified `missing`, and `missing` being the sole authorisation for `/idd-close --retroactive` to post a duplicate — the power was split along the direction that is sound. \"A marker IS here\" is an observation; \"a marker is NOT here\" is an inference from a failure to recognise, and no matcher over source bytes can answer a question about rendered output in the negative. Gate exit codes are now 1 (recognised) / 2 (undeterminable) / 10 (nothing recognised — NOT permission); there is no exit 0 in gate mode, deliberately, so a caller still reading `rc == 0 means go` breaks loudly. Gate class `missing` → `unrecognised`, every reply carries authorises:false, and a fifth class `mentioned` names the state the tool can actually observe. `--retroactive` loses its unattended path: the skill must read the comment set itself and obtain human confirmation that cannot be disabled. Classification now asks who wrote the comment, so a commenter can no longer move an issue between classes. Also: three more exit-0 parser paths, markup counted as content three layers deep, a quotation reaching `compliant`, the mention gate passing on zero iterations by three routes, untrusted prose reaching a shell command line, and #317 criterion (c) answered correctly for the first time in five attempts. Ten guards were mutation-proven vacuous and rebuilt.",
"version": "3.0.0",
"version": "3.0.1",
"author": {
"name": "Che Cheng"
},
Expand Down
42 changes: 42 additions & 0 deletions plugins/issue-driven-dev/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,48 @@ 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).

## [3.0.1] - 2026-10-02

### Fixed — `--blocked-by` could not create a native dependency (#353)

- **Layer 1 called a mutation GitHub does not have.** Since 2.52.0 (#21) the native-dependency layer sent
`addBlockedByDependency(input:{issueId, blockedByIssueId})`. GitHub's schema has `addBlockedBy(input:{issueId,
blockingIssueId})` and nothing by the other name. Against today's schema that call can only fail, so as far as
we can tell every `--blocked-by` and every `--bundle-mode ordered` run fell back to the body blockquote alone. The normative spec named the same mutation, which is why reviewing
against the spec agreed with the bug.
- **The failure was invisible.** The call sent stderr to `/dev/null` and the warning hard-coded three causes
(repo not enabled / API error / permission), none of them the real one. Layer 1 now captures GitHub's output
and prints it verbatim on failure, and no longer guesses a cause.
- **An existing dependency is not a failure.** When the dependency is already there — the same target listed
twice (`--blocked-by 50,50`), or the relationship created elsewhere — GitHub returns rc=1 with `Target issue
has already been taken` and changes nothing (measured 2026-10-02). Layer 1 now reports that as already
linked, without a warning, and prints GitHub's sentence as the evidence. Only that exact sentence counts; any
other "has already been taken" still warns.
- **Layer 1 messages go to stderr.** The documented bundle orchestration runs the `--blocked-by` handler inside
`CHILD_NUM=$(…)`, so a message Layer 1 printed on stdout would end up in `CHILD_NUM` and be passed to the next
child as its `--blocked-by` value. Layer 2 and the `--parent` handler on the same path still print to stdout;
that, and the other remaining defects of the handler, are tracked in #359.
- **The reference example no longer prints GitHub's reply.** `references/bundle-flags.md` shows the request
captured into `GQL_OUT` and points to `SKILL.md` for the branches; the suite checks the example's shape.
- **Node IDs are bound with `-f`, not `-F`.** `-F` reads a local file for a value starting with `@`, and the
failure branch now prints GitHub's output verbatim.
- **New suite `blocked-by-mutation`** runs the Layer 1 snippet from `SKILL.md` against a stub `gh` in four
modes (success / already exists / another uniqueness failure / other error), plus two targets where the first
fails and the second must still be attempted. It checks the request GitHub receives (mutation, field names,
`-f` bindings, child → `issueId`), that every message stays off stdout, and
that no live file names the old mutation outside a closed list of historical records. Each check has a
positive control that breaks the snippet again and requires the check to fail.
- **Weekly live schema check.** `.github/workflows/live-schema.yml` runs the suite with `IDD_LIVE_GH=1` every
Monday 01:00 UTC (09:00 Taipei) and on demand, introspecting GitHub's real `addBlockedBy` mutation, its input
fields and its payload. A rename on GitHub's side does not come with a PR, so the PR suite cannot catch it;
without `IDD_LIVE_GH=1` the suite prints SKIP rather than claiming the schema was checked. Introspection
leaves out deprecated fields by default, so a deprecation fails the check too. GitHub disables scheduled
workflows in a public repository after 60 days without repository activity; if that happens, re-enable the
workflow from the Actions tab. The first run after merge is started by hand: `gh workflow run live-schema.yml`.
- **Issues created by 2.52.0–3.0.0 have only the body blockquote.** To add the native dependency to one of
them, call `addBlockedBy` once per pair as shown in `references/bundle-flags.md` Layer 1. Re-running
`idd-issue` would create a new child instead.

## [3.0.0] - 2026-09-01

23 commits since 2.112.0, across four `/idd-verify` ensembles. **The major bump is for one
Expand Down
Loading
Loading