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: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"plugins": [
{
"name": "issue-driven-dev",
"version": "3.1.0",
"description": "v3.1.0: the actionability gate, rounds 2 to 7. Round 1 read `### Complexity` against a closed value domain that the repository's own 159 diagnoses refuted (66 real values wrongly refused) and shipped a three-signal gate no consumer called. Now the tier is the leading whole word (rationale, decoration and ` via` suffixes are legal), the whole line is scanned for a small deferral vocabulary (exit 5, reason `complexity-deferral-marker` — a parked state, not a defect), and `### Blocking` is read per bullet by a shared helper with a leading-token placeholder rule frozen against the repo's 55 real rows — 54 sections under CommonMark (54/55 agree with hand review; round 2 had withheld 30 of the 48 empty ones, #316 itself among them). idd-list / idd-all / idd-implement / idd-plan call the verdict with all three signals before any branch or comment, print it as a machine line plus a delimited raw block, and repeat the gate byte for byte where a later step routes on it (pinned by test). Fences follow CommonMark's two visibility rules; the reader's known divergences from CommonMark are listed with their direction and pinned, not claimed exhaustive. The placeholder rule does not depend on the locale or on a shell function named grep; the matchers that still follow the environment are listed with their measured directions (#336). Changes to the reader are gated: vocabulary rules move only toward withholding unless a named corpus row requires the change; structural rules (headings, fences, bullets, trimming, and the locale of their matchers) are frozen until a requirement names the oracle they converge on (#336). idd-list gains Parked and Needs-diagnosis groups; the #84 blocked surface is preserved verbatim.",
"version": "3.1.1",
"description": "v3.1.1: `idd-issue --blocked-by` calls GitHub's real `addBlockedBy` mutation and prints GitHub's error verbatim (#353). v3.1.0: the actionability gate, rounds 2 to 7. Round 1 read `### Complexity` against a closed value domain that the repository's own 159 diagnoses refuted (66 real values wrongly refused) and shipped a three-signal gate no consumer called. Now the tier is the leading whole word (rationale, decoration and ` via` suffixes are legal), the whole line is scanned for a small deferral vocabulary (exit 5, reason `complexity-deferral-marker` — a parked state, not a defect), and `### Blocking` is read per bullet by a shared helper with a leading-token placeholder rule frozen against the repo's 55 real rows — 54 sections under CommonMark (54/55 agree with hand review; round 2 had withheld 30 of the 48 empty ones, #316 itself among them). idd-list / idd-all / idd-implement / idd-plan call the verdict with all three signals before any branch or comment, print it as a machine line plus a delimited raw block, and repeat the gate byte for byte where a later step routes on it (pinned by test). Fences follow CommonMark's two visibility rules; the reader's known divergences from CommonMark are listed with their direction and pinned, not claimed exhaustive. The placeholder rule does not depend on the locale or on a shell function named grep; the matchers that still follow the environment are listed with their measured directions (#336). Changes to the reader are gated: vocabulary rules move only toward withholding unless a named corpus row requires the change; structural rules (headings, fences, bullets, trimming, and the locale of their matchers) are frozen until a requirement names the oracle they converge on (#336). idd-list gains Parked and Needs-diagnosis groups; the #84 blocked surface is preserved verbatim.",
"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
Expand Up @@ -5,3 +5,4 @@
{"agent":"claude-fable-5-1","complexity":"Spectra","followups_spawned":2,"issue_number":316,"issue_repo":"PsychQuant\/issue-driven-development","outcome":"in_review","recorded_by":"idd-verify-3.0.0","round_trips":5,"scope_files":24,"scope_loc":4229,"scope_signals":[],"ts":"2026-10-05T15:53:41Z","verify_blocking":5,"verify_low":9,"verify_medium":0}
{"agent":"claude-opus-5-5","complexity":"Spectra","followups_spawned":1,"issue_number":316,"issue_repo":"PsychQuant\/issue-driven-development","outcome":"in_review","recorded_by":"idd-verify-3.0.0","round_trips":6,"scope_files":13,"scope_loc":528,"scope_signals":[],"ts":"2026-10-06T05:53:07Z","verify_blocking":3,"verify_low":12,"verify_medium":0}
{"agent":"claude-opus-5-5","complexity":"Spectra","followups_spawned":1,"issue_number":316,"issue_repo":"PsychQuant\/issue-driven-development","outcome":"merged","outcome_ts":"2026-10-07T05:49:59Z","recorded_by":"update-outcome-cli","round_trips":6,"scope_files":13,"scope_loc":528,"scope_signals":[],"ts":"2026-10-07T05:49:59Z","verify_blocking":3,"verify_low":12,"verify_medium":0}
{"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
4 changes: 2 additions & 2 deletions 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.1.0: the actionability gate, rounds 2 to 7. Round 1 read `### Complexity` against a closed value domain that the repository's own 159 diagnoses refuted (66 real values wrongly refused) and shipped a three-signal gate no consumer called. Now the tier is the leading whole word (rationale, decoration and ` via` suffixes are legal), the whole line is scanned for a small deferral vocabulary (exit 5, reason `complexity-deferral-marker` — a parked state, not a defect), and `### Blocking` is read per bullet by a shared helper with a leading-token placeholder rule frozen against the repo's 55 real rows — 54 sections under CommonMark (54/55 agree with hand review; round 2 had withheld 30 of the 48 empty ones, #316 itself among them). idd-list / idd-all / idd-implement / idd-plan call the verdict with all three signals before any branch or comment, print it as a machine line plus a delimited raw block, and repeat the gate byte for byte where a later step routes on it (pinned by test). Fences follow CommonMark's two visibility rules; the reader's known divergences from CommonMark are listed with their direction and pinned, not claimed exhaustive. The placeholder rule does not depend on the locale or on a shell function named grep; the matchers that still follow the environment are listed with their measured directions (#336). Changes to the reader are gated: vocabulary rules move only toward withholding unless a named corpus row requires the change; structural rules (headings, fences, bullets, trimming, and the locale of their matchers) are frozen until a requirement names the oracle they converge on (#336). idd-list gains Parked and Needs-diagnosis groups; the #84 blocked surface is preserved verbatim.",
"version": "3.1.0",
"description": "v3.1.1: `idd-issue --blocked-by` calls GitHub's real `addBlockedBy` mutation and prints GitHub's error verbatim (#353). v3.1.0: the actionability gate, rounds 2 to 7. Round 1 read `### Complexity` against a closed value domain that the repository's own 159 diagnoses refuted (66 real values wrongly refused) and shipped a three-signal gate no consumer called. Now the tier is the leading whole word (rationale, decoration and ` via` suffixes are legal), the whole line is scanned for a small deferral vocabulary (exit 5, reason `complexity-deferral-marker` — a parked state, not a defect), and `### Blocking` is read per bullet by a shared helper with a leading-token placeholder rule frozen against the repo's 55 real rows — 54 sections under CommonMark (54/55 agree with hand review; round 2 had withheld 30 of the 48 empty ones, #316 itself among them). idd-list / idd-all / idd-implement / idd-plan call the verdict with all three signals before any branch or comment, print it as a machine line plus a delimited raw block, and repeat the gate byte for byte where a later step routes on it (pinned by test). Fences follow CommonMark's two visibility rules; the reader's known divergences from CommonMark are listed with their direction and pinned, not claimed exhaustive. The placeholder rule does not depend on the locale or on a shell function named grep; the matchers that still follow the environment are listed with their measured directions (#336). Changes to the reader are gated: vocabulary rules move only toward withholding unless a named corpus row requires the change; structural rules (headings, fences, bullets, trimming, and the locale of their matchers) are frozen until a requirement names the oracle they converge on (#336). idd-list gains Parked and Needs-diagnosis groups; the #84 blocked surface is preserved verbatim.",
"version": "3.1.1",
"author": {
"name": "Che Cheng"
},
Expand Down
44 changes: 44 additions & 0 deletions plugins/issue-driven-dev/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,50 @@ 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.1.1] - 2026-10-07

> Prepared as 3.0.1 on 2026-10-02 (PR #358); renumbered when it merged after 3.1.0.

### 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.1.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.1.0] - 2026-10-07

Round 2 of the actionability gate (#298 → #316, PR #318). Round 1 shipped a shared
Expand Down
Loading
Loading