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 plugins-claude/git-tools/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "git-tools",
"version": "2.2.1",
"version": "2.2.2",
"description": "GitHub and Gitea tooling — unified CLI wrapper (issues, PRs, CI runs) plus a ship orchestrator that drives the full branch/commit/push/PR/watch/cleanup lifecycle",
"author": {
"name": "Logan Gagne"
Expand Down
22 changes: 17 additions & 5 deletions plugins-claude/git-tools/scripts/git-cli
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,26 @@ parse_body_args() {
COMMENT_JQ='{id, author: (.user.login // .user // ""), body, html_url: (.html_url // ""), created_at}'

# Emit {number, url} JSON from a create command's human output by scraping the
# first issue/PR URL it printed. Used by issue/pr create so write commands return
# parseable JSON instead of gh/tea's human-rendered text. Dies with the raw
# output if no URL is found (so a silent format change surfaces loudly).
# create confirmation URL it printed. Used by issue/pr create so write commands
# return parseable JSON instead of gh/tea's human-rendered text. Dies with the
# raw output if no URL is found (so a silent format change surfaces loudly).
#
# tea renders the created object's body before its confirmation URL, so a body
# linking to another issue puts a decoy URL earlier in the output — hence the
# last URL-only line, and last (never first) in the fallback too. gh prints just
# the bare URL, so both paths are no-ops there. The fallback exists so an
# unrecognised output shape degrades to the old scrape instead of dying.
emit_created_json() {
local raw="$1" url num
url=$(printf '%s\n' "$raw" \
| grep -oiE 'https?://[^[:space:]]+/(issues?|pulls?)/[0-9]+' \
| head -n1 || true)
| grep -oiE '^[[:space:]]*https?://[^[:space:]]+/(issues?|pulls?)/[0-9]+[[:space:]]*$' \
| tail -n1 \
| tr -d '[:space:]' || true)
if [[ -z "$url" ]]; then
url=$(printf '%s\n' "$raw" \
| grep -oiE 'https?://[^[:space:]]+/(issues?|pulls?)/[0-9]+' \
| tail -n1 || true)
fi
[[ -n "$url" ]] || die "could not parse created object URL from output: $raw"
num="${url##*/}"
# The regex guarantees a trailing [0-9]+, so number is always a true integer.
Expand Down
2 changes: 1 addition & 1 deletion plugins-claude/session/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "session",
"version": "4.5.0",
"version": "4.5.1",
"description": "Work session management — issue-driven and freeform doors sharing an explore-then-plan spine, with multi-agent orchestration and a review-gated PR finalizer",
"author": {
"name": "Logan Gagne"
Expand Down
22 changes: 17 additions & 5 deletions plugins-claude/session/scripts/git-cli
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,26 @@ parse_body_args() {
COMMENT_JQ='{id, author: (.user.login // .user // ""), body, html_url: (.html_url // ""), created_at}'

# Emit {number, url} JSON from a create command's human output by scraping the
# first issue/PR URL it printed. Used by issue/pr create so write commands return
# parseable JSON instead of gh/tea's human-rendered text. Dies with the raw
# output if no URL is found (so a silent format change surfaces loudly).
# create confirmation URL it printed. Used by issue/pr create so write commands
# return parseable JSON instead of gh/tea's human-rendered text. Dies with the
# raw output if no URL is found (so a silent format change surfaces loudly).
#
# tea renders the created object's body before its confirmation URL, so a body
# linking to another issue puts a decoy URL earlier in the output — hence the
# last URL-only line, and last (never first) in the fallback too. gh prints just
# the bare URL, so both paths are no-ops there. The fallback exists so an
# unrecognised output shape degrades to the old scrape instead of dying.
emit_created_json() {
local raw="$1" url num
url=$(printf '%s\n' "$raw" \
| grep -oiE 'https?://[^[:space:]]+/(issues?|pulls?)/[0-9]+' \
| head -n1 || true)
| grep -oiE '^[[:space:]]*https?://[^[:space:]]+/(issues?|pulls?)/[0-9]+[[:space:]]*$' \
| tail -n1 \
| tr -d '[:space:]' || true)
if [[ -z "$url" ]]; then
url=$(printf '%s\n' "$raw" \
| grep -oiE 'https?://[^[:space:]]+/(issues?|pulls?)/[0-9]+' \
| tail -n1 || true)
fi
[[ -n "$url" ]] || die "could not parse created object URL from output: $raw"
num="${url##*/}"
# The regex guarantees a trailing [0-9]+, so number is always a true integer.
Expand Down
2 changes: 1 addition & 1 deletion plugins-copilot/git-tools/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "git-tools",
"version": "2.2.1",
"version": "2.2.2",
"description": "GitHub and Gitea tooling — unified CLI wrapper (issues, PRs, CI runs) plus a ship orchestrator that drives the full branch/commit/push/PR/watch/cleanup lifecycle",
"author": {
"name": "Logan Gagne"
Expand Down
2 changes: 1 addition & 1 deletion plugins-copilot/session/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "session",
"version": "4.5.0",
"version": "4.5.1",
"description": "Work session management — issue-driven and freeform doors sharing an explore-then-plan spine, with multi-agent orchestration and a review-gated PR finalizer",
"author": {
"name": "Logan Gagne"
Expand Down
22 changes: 20 additions & 2 deletions tests/git-cli/test-issue-write-json.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,26 @@ case "$1 $2 $3" in
"login list --output") echo '[{"url":"https://git.stonefish.tech","user":"alice"}]'; exit 0 ;;
esac
case "$1 $2" in
"issues create") echo "Created issue https://git.stonefish.tech/owner/repo/issues/42"; exit 0 ;;
"pr create") echo "Created pull https://git.stonefish.tech/owner/repo/pulls/7"; exit 0 ;;
# Realistic tea output, not a synthetic one-liner: tea renders the whole
# created object (title/author/body, indented) before a flush-left
# confirmation URL. Decoy #1 (both cases): a markdown link to issue #1
# embedded mid-body, mirroring how real tracker tickets link back to a
# parent issue — the anchored regex alone rejects this (it's not a bare
# URL line), so it also guards the pre-anchor fallback path. Decoy #2
# (pr create): a *bare* URL on its own indented line, e.g. a "See <url>"
# reference — this one DOES match the anchored "line is just a URL"
# pattern, so only taking the *last* match (not first) tells it apart
# from the true confirmation line that follows. Together these catch a
# regression of the bug where emit_created_json grabbed the first
# issue/pull URL anywhere in the output instead of the trailing
# confirmation line, always reporting the linked issue's number (#1)
# instead of the one actually created (#42 / #7).
"issues create")
printf ' # #42 x (open)\n\n @alice created 2026-06-02\n\n Child of [parent](https://git.stonefish.tech/owner/repo/issues/1).\n\nhttps://git.stonefish.tech/owner/repo/issues/42\n'
exit 0 ;;
"pr create")
printf ' # #7 x (open)\n\n @alice wants to merge\n\n Fixes [parent](https://git.stonefish.tech/owner/repo/issues/1).\n\n See also\n https://git.stonefish.tech/owner/repo/issues/1\n\nhttps://git.stonefish.tech/owner/repo/pulls/7\n'
exit 0 ;;
esac
if [[ "$1" == "api" ]]; then
case "$args" in
Expand Down
22 changes: 17 additions & 5 deletions utils/git-cli
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,26 @@ parse_body_args() {
COMMENT_JQ='{id, author: (.user.login // .user // ""), body, html_url: (.html_url // ""), created_at}'

# Emit {number, url} JSON from a create command's human output by scraping the
# first issue/PR URL it printed. Used by issue/pr create so write commands return
# parseable JSON instead of gh/tea's human-rendered text. Dies with the raw
# output if no URL is found (so a silent format change surfaces loudly).
# create confirmation URL it printed. Used by issue/pr create so write commands
# return parseable JSON instead of gh/tea's human-rendered text. Dies with the
# raw output if no URL is found (so a silent format change surfaces loudly).
#
# tea renders the created object's body before its confirmation URL, so a body
# linking to another issue puts a decoy URL earlier in the output — hence the
# last URL-only line, and last (never first) in the fallback too. gh prints just
# the bare URL, so both paths are no-ops there. The fallback exists so an
# unrecognised output shape degrades to the old scrape instead of dying.
emit_created_json() {
local raw="$1" url num
url=$(printf '%s\n' "$raw" \
| grep -oiE 'https?://[^[:space:]]+/(issues?|pulls?)/[0-9]+' \
| head -n1 || true)
| grep -oiE '^[[:space:]]*https?://[^[:space:]]+/(issues?|pulls?)/[0-9]+[[:space:]]*$' \
| tail -n1 \
| tr -d '[:space:]' || true)
if [[ -z "$url" ]]; then
url=$(printf '%s\n' "$raw" \
| grep -oiE 'https?://[^[:space:]]+/(issues?|pulls?)/[0-9]+' \
| tail -n1 || true)
fi
[[ -n "$url" ]] || die "could not parse created object URL from output: $raw"
num="${url##*/}"
# The regex guarantees a trailing [0-9]+, so number is always a true integer.
Expand Down
Loading