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
2 changes: 1 addition & 1 deletion plugins/source-control/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "source-control",
"version": "0.26.10",
"version": "0.26.11",
"description": "Git and GitHub delivery workflow: /commit (Conventional Commits + Co-Authored-By trailer via safe heredoc mechanics), /pull-request (prep, create, CI monitoring, review-comment triage, merge, CI-log fetch), /babysit-prs (self-pacing fleet loop — safe by default; opt-in worker/autopilot tiers add gate-checked merge and thread resolution behind a deterministic Python engine), /babysit-loop (the loop-lane merge lane: a standing or drain loop that invokes babysit-prs per cycle, configured through repo-scoped babysit_loop_* keys on the layered source-control.md seam, with merge authority human-only until the target repo's tracked config adopts the lane, a gate-proven C2-mechanical baseline once adopted, and merge-rung raises binding from the team-tracked layer only), /worktree (create, status, cleanup, audit for parallel-session isolation), /setup (check the effective commit-subject / PR-title convention merged across its config layers and the babysit-prs config, or apply — interview the repo and write the convention config to a chosen layer), and /resolve-conflicts (intent-first merge/rebase conflict resolution with a semantic-conflict sweep — never --abort). The commit-subject / PR-title convention is configurable via a source-control.md config written by a re-runnable setup skill, layered across a ~/.claude user-global file, the tracked team file, and a gitignored .claude/source-control.local.md personal overlay merged per key; Conventional Commits is the default when no convention is declared.",
"author": {
"name": "Melodic Software",
Expand Down
16 changes: 16 additions & 0 deletions plugins/source-control/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@
All notable changes to the `source-control` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.26.11]

### Fixed

- **`fetch-all-pr-comments.sh` now emits `in_reply_to_id` for inline review comments (#587).** The
script's unified schema never projected the GitHub REST field that marks an inline review comment
as a threaded reply, so any caller reading the script's own output saw the key absent (surfacing as
`None`/`null` in downstream tooling) even for comments GraphQL confirmed were properly threaded
replies. Reproduced against live PR #563 data: the raw `pulls/<pr>/comments` response correctly
carries `in_reply_to_id` on reply comments — the script's `jq` projection for the inline surface
simply dropped it. Added `in_reply_to_id: .in_reply_to_id` to the inline mapping (sourced from the
same raw field GraphQL cross-checks against) and `in_reply_to_id: null` to the general/review
mappings, which have no reply-parent concept on their surfaces. Additive schema change — existing
consumers that don't read the new key are unaffected. Regression-tested with a threaded-reply
fixture.

## [0.26.10]

### Changed
Expand Down
25 changes: 20 additions & 5 deletions plugins/source-control/scripts/fetch-all-pr-comments.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,19 @@
# 3. Inline review comments (line-anchored on the diff)
#
# Output: unified JSON array sorted by creation date. Each object:
# {"id":N,"type":"general|review|inline","author":"login","body":"...","path":"...","line":N,"created_at":"ISO"}
# {"id":N,"type":"general|review|inline","author":"login","body":"...","path":"...","line":N,"created_at":"ISO","in_reply_to_id":N|null}
#
# in_reply_to_id is populated ONLY for type "inline" (the only GitHub REST
# surface that threads via this field — general/review comments have no
# reply-parent concept and always carry null). GitHub sets it to the id of
# the THREAD-OPENING comment, not the immediately-preceding reply: every
# reply in a thread carries the same in_reply_to_id (empirically verified
# against live review-thread data — every referenced parent in that set was
# itself a root comment), so grouping by `in_reply_to_id // id` recovers
# thread membership directly. This does not change the documented REST
# cross-check in reference/review-discipline.md / skills/pull-request/SKILL.md
# — this script's schema was the only thing missing the field; the raw API
# was always correct.
#
# Usage:
# fetch-all-pr-comments.sh <pr-number>
Expand Down Expand Up @@ -39,7 +51,7 @@ set -uo pipefail # -e omitted: gh api failures explicitly guarded with || { exit
PR_NUMBER=""

usage() {
sed -n '2,27p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
sed -n '2,39p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
exit 0
}

Expand Down Expand Up @@ -127,7 +139,8 @@ if ! GENERAL=$(printf '%s' "$GENERAL_RAW" | jq -c '
body: .body,
path: null,
line: null,
created_at: .created_at
created_at: .created_at,
in_reply_to_id: null
}
' 2>/dev/null); then
printf 'fetch-all-pr-comments: jq failed parsing issues/%s/comments response\n' "$PR_NUMBER" >&2
Expand All @@ -151,7 +164,8 @@ if ! REVIEWS=$(printf '%s' "$REVIEWS_RAW" | jq -c '
body: .body,
path: null,
line: null,
created_at: (.submitted_at // .created_at)
created_at: (.submitted_at // .created_at),
in_reply_to_id: null
}
' 2>/dev/null); then
printf 'fetch-all-pr-comments: jq failed parsing pulls/%s/reviews response\n' "$PR_NUMBER" >&2
Expand All @@ -174,7 +188,8 @@ if ! INLINE=$(printf '%s' "$INLINE_RAW" | jq -c '
body: .body,
path: .path,
line: (.line // .original_line),
created_at: .created_at
created_at: .created_at,
in_reply_to_id: .in_reply_to_id
}
' 2>/dev/null); then
printf 'fetch-all-pr-comments: jq failed parsing pulls/%s/comments response\n' "$PR_NUMBER" >&2
Expand Down
21 changes: 20 additions & 1 deletion plugins/source-control/scripts/fetch-all-pr-comments.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,17 @@ cat >"$FIXTURE_INLINE" <<'JSON'
"path": "src/Auth.cs",
"line": 42,
"original_line": null,
"in_reply_to_id": null,
"created_at": "2026-05-20T09:30:00Z"
},
{
"id": 3002,
"user": {"login": "codex[bot]"},
"user": {"login": "kyle-sexton"},
"body": "Stale count reference",
"path": "src/Counter.cs",
"line": null,
"original_line": 15,
"in_reply_to_id": 3001,
"created_at": "2026-05-20T12:00:00Z"
}
]
Expand Down Expand Up @@ -158,6 +160,23 @@ assert_eq "inline comments have path" "2" "$inline_with_path"
fallback_line=$(printf '%s' "$out" | jq '[.[] | select(.id == 3002)] | .[0].line')
assert_eq "original_line fallback" "15" "$fallback_line"

# Case 6a: a top-level inline comment reports in_reply_to_id null (regression
# guard for #587 — the field must be sourced from the raw API response, not
# silently dropped by the jq projection)
top_level_reply_id=$(printf '%s' "$out" | jq '[.[] | select(.id == 3001)] | .[0].in_reply_to_id')
assert_eq "top-level inline comment has null in_reply_to_id" "null" "$top_level_reply_id"

# Case 6b: a threaded inline reply reports the true parent id
threaded_reply_id=$(printf '%s' "$out" | jq '[.[] | select(.id == 3002)] | .[0].in_reply_to_id')
assert_eq "threaded inline reply reports parent id" "3001" "$threaded_reply_id"

# Case 6c: general and review comments (no reply-parent concept) carry null
general_reply_id=$(printf '%s' "$out" | jq '[.[] | select(.id == 1001)] | .[0].in_reply_to_id')
assert_eq "general comment has null in_reply_to_id" "null" "$general_reply_id"

review_reply_id=$(printf '%s' "$out" | jq '[.[] | select(.id == 2001)] | .[0].in_reply_to_id')
assert_eq "review comment has null in_reply_to_id" "null" "$review_reply_id"

# Case 7: gh api failure exits 2
FAIL_STUB="$TEST_TMPDIR/bin-fail/gh"
mkdir -p "$TEST_TMPDIR/bin-fail"
Expand Down
Loading