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 .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"name": "ccmagic",
"source": "./",
"description": "25 dev workflow skills: tracker-aware ticket lifecycle (Linear, GitHub, JIRA) with an autonomous end-to-end driver, adaptive code review, debugging, design/QA, and git workflow utilities",
"version": "3.20.3",
"version": "3.20.4",
"author": {
"name": "Devon Hillard"
},
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ccmagic",
"description": "Dev workflow skills for Claude Code: tracker-aware ticket lifecycle (Linear, GitHub, JIRA), adaptive code review, debugging, design/QA, and git workflow utilities",
"version": "3.20.3",
"version": "3.20.4",
"author": { "name": "Devon Hillard", "url": "https://github.com/devondragon" },
"repository": "https://github.com/devondragon/ccmagic",
"license": "Apache-2.0"
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to ccmagic are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.20.4] - 2026-10

### Fixed

- `ccm-ci-status` reports a new status, `conflicting` (exit 6), when nothing has registered for the head commit and the PR conflicts with its base. GitHub runs no `pull_request` workflows on a conflicting PR, so `--watch` used to wait out the whole `ci_timeout_minutes` and `auto-ticket` then parked with "CI never registered", which named the symptom and not the cause (AIE-351). `auto-ticket` now stops at once with the conflict as the reason, and `finish-ticket` and `merge` treat it as the conflict blocker the merge gate already reports.

## [3.20.3] - 2026-10

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ Rules that have one right answer are enforced in code, not left to the skill tex
| Script | Answers |
|---|---|
| `ccm-context` | Current branch, ticket ID parsed from it, open PR, worktree or primary checkout, base branch, and the resolved `ccmagic.local.md` config (project over user over defaults) |
| `ccm-ci-status` | Is CI green for this PR: `green`, `no-ci`, `failed`, `pending`, `not-registered`, `unreadable`. Falls back to the Actions and commit-status APIs when a fine-grained PAT gets HTTP 403 on check runs. `--watch --wait-key K` waits with a deadline that holds across repeated calls |
| `ccm-ci-status` | Is CI green for this PR: `green`, `no-ci`, `failed`, `pending`, `not-registered`, `conflicting`, `unreadable`. Falls back to the Actions and commit-status APIs when a fine-grained PAT gets HTTP 403 on check runs. `--watch --wait-key K` waits with a deadline that holds across repeated calls |
| `ccm-merge-gate` | May this PR merge: open, mergeable, CI green (or no CI), no reviewer whose latest review requests changes |
| `ccm-pr-threads` | A PR's review threads (grouped, with an `open` flag), reviews, and conversation comments; `--since-id H` marks reviewer comments newer than a high-water mark. A thread is handled when resolved, or when the author's last reply carries a `ccm-pr-reply` disposition marker; a `fixed` marker counts only once its commit is verified on the branch and touches the thread's file |
| `ccm-validate` | Do the project's checks pass: runs `format`, `lint`, `types`, `test`, and `build`, one command each from a `validate_<check>` config key or detected from `package.json`, `Makefile`, Gradle, Maven, `go.mod`, `Cargo.toml`, or `pyproject.toml`, and uses each command's exit code as the verdict (`pass`, `fail`, `nothing-to-run`). `--only` runs a subset and `--list` shows the commands without running them. Each check is bounded by `validate_timeout_seconds` (default 540, at most 7200). For checks longer than one 10-minute Bash call, `--start` runs them in a detached process, or `--start --attached` only prepares the run (writing its state with no result, so a `--wait` never reads an earlier run's) and `--attach RUN` then runs its worker in the foreground for a harness background task to own (a sandbox that ends a call's processes kills a detached run), and `--wait [SECONDS]` (default 480) waits for it by its heartbeat, printing the plain result when it finishes or `status: running` with exit 6 while it goes on; `/ccmagic:validate` prepares with `--start --attached` in an ordinary Bash call and runs `--attach` as a background Bash call when `validate_timeout_seconds` is above 540. A Gradle or Maven build (run through `./gradlew` or `./mvnw` when present) supplies `test` and `build` ahead of `package.json` and the `Makefile`, which still supply `format`, `lint`, and `types`. When the root `package.json` declares dependencies, a package under `dependencies` or `devDependencies` is not in `node_modules` (a bare `node_modules` directory does not count), and a check that will run goes through Node (a `package.json` script, or a command with `npm`, `npx`, `pnpm`, `yarn`, `bun`, `bunx`, or `node` as a command word), the first check is preceded by the install its lockfile calls for (`npm ci`, or `pnpm`, `yarn`, or `bun` when that tool is installed), and `--install` runs only that; with no lockfile, a missing tool, or a failed install, no check runs and the status is `environment`. Node only: no other ecosystem's dependencies are installed |
Expand Down
13 changes: 12 additions & 1 deletion bin/ccm-ci-status
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# failed 1 at least one check failed, was cancelled, or timed out
# pending 2 checks still running
# not-registered 2 CI is configured but no check or run exists for this commit yet
# conflicting 6 nothing has registered and the PR conflicts with its base;
# GitHub runs no pull_request workflows until that is resolved
# unreadable 3 neither the Checks API nor the Actions/Status APIs could be read
# timeout 5 --watch deadline passed while still pending/not-registered
# (usage error 4)
Expand All @@ -24,6 +26,10 @@
# (403) and nothing else is found, `no-ci` needs branch protection to be
# readable and show no required checks; otherwise the status is `unreadable`.
#
# Before reporting not-registered for a PR, it re-reads the PR's mergeability:
# GitHub cannot build the merge ref for a CONFLICTING PR, so its pull_request
# workflows never start, and waiting out the timeout would only hide that.
#
# --watch polls until the status leaves pending/not-registered. One call never
# runs longer than --max-call-seconds (default 540, under the Bash tool's
# 10-minute limit). --wait-key names a deadline file under the git dir, so the
Expand All @@ -43,7 +49,7 @@ while [ $# -gt 0 ]; do
--timeout-minutes) timeout_min=${2:?}; shift ;;
--interval) interval=${2:?}; shift ;;
--max-call-seconds) max_call=${2:?}; shift ;;
-h|--help) sed -n '2,31p' "$0"; exit 0 ;;
-h|--help) sed -n '2,38p' "$0"; exit 0 ;;
-*) echo "ccm-ci-status: unknown flag $1" >&2; exit 4 ;;
*) pr=${1#\#} ;;
esac
Expand Down Expand Up @@ -109,6 +115,10 @@ no_checks_verdict() {
result no-ci none "no workflow files and no required status checks on $base"
else
code=$([ "$has_workflows" = true ] && echo "workflow files exist" || echo "required status checks exist")
if [ -n "$pr" ] && [ "$(gh pr view "$pr" --json mergeable -q .mergeable 2>/dev/null || true)" = CONFLICTING ]; then
result conflicting none "PR #$pr has merge conflicts with $base, so GitHub will not run its pull_request workflows; nothing has registered for $sha ($code)"
return
fi
result not-registered none "CI is configured ($code) but nothing has registered for $sha yet"
fi
}
Expand Down Expand Up @@ -194,6 +204,7 @@ exit_for() {
pending|not-registered) echo 2 ;;
unreadable) echo 3 ;;
timeout) echo 5 ;;
conflicting) echo 6 ;;
*) echo 3 ;;
esac
}
Expand Down
1 change: 1 addition & 0 deletions skills/auto-ticket/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ Everything this pass pushes is measured against `H`, so bot reviews triggered *b
- `green` or `no-ci` → CI is settled and green (record "no CI configured" in the run summary for `no-ci`).
- `failed` → settled, not green. Carry `reason` and `checks` into 4d.
- `timeout` → **route-and-stop** (reason: "CI did not complete within the timeout", plus the script's `reason`; a `not-registered` timeout means CI is configured but no run appeared for the head SHA).
- `conflicting` → **route-and-stop** (reason: "PR has merge conflicts with its base, so CI cannot run: {reason}"). GitHub runs no `pull_request` workflows on a conflicting PR, so there is nothing to wait for; the conflicts need resolving first.
- `unreadable` → **route-and-stop** (reason: "cannot read CI status: {reason}").

The script handles what this step used to spell out: the fine-grained-PAT 403 fallback to the Actions and commit-status APIs, the "no checks at all" case (no workflows and no required checks means `no-ci`; otherwise it keeps waiting for a run to register), and quick transient `gh` failures. Don't re-derive CI state from `gh pr checks` output.
Expand Down
2 changes: 1 addition & 1 deletion skills/finish-ticket/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ It exits 0 when the PR may merge and 1 when it may not, and prints JSON:

- `blockers`: each reason the PR can't merge. It covers: the PR isn't `OPEN`; it is `CONFLICTING`, or GitHub still reports `UNKNOWN` mergeability after a few re-reads; CI isn't green; a reviewer's latest review is `CHANGES_REQUESTED`.
- `warnings`: currently only "no approving review". A PR can proceed without an approval; show the warning.
- `ci`: the full `ccm-ci-status` result. `ci.status` is `green`, `no-ci` (no workflows and no required checks), `failed`, `pending`, `not-registered` (CI configured but nothing has reported for this commit yet), or `unreadable`. `ci.checks` lists each check with its bucket, for the report. The script already falls back to the Actions and commit-status APIs when a fine-grained PAT gets HTTP 403 on check runs, so a 403 is never on its own a reason to call CI failed or to ask anyone to look at the checks.
- `ci`: the full `ccm-ci-status` result. `ci.status` is `green`, `no-ci` (no workflows and no required checks), `failed`, `pending`, `not-registered` (CI configured but nothing has reported for this commit yet), `conflicting` (nothing has reported and the PR conflicts with its base, so GitHub will not run its `pull_request` workflows; the conflict is already a blocker, so do not wait), or `unreadable`. `ci.checks` lists each check with its bucket, for the report. The script already falls back to the Actions and commit-status APIs when a fine-grained PAT gets HTTP 403 on check runs, so a 403 is never on its own a reason to call CI failed or to ask anyone to look at the checks.

If `ci.status` is `pending` or `not-registered`, wait for CI rather than reporting it as a blocker yet:

Expand Down
2 changes: 1 addition & 1 deletion skills/merge/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fi
"${CLAUDE_PLUGIN_ROOT}/bin/ccm-merge-gate" [PR-NUMBER]
```

It exits 0 when the PR may merge and prints JSON with `blockers` (not open, conflicting or unknown mergeability, CI not green, a reviewer's latest review is `CHANGES_REQUESTED`), `warnings` (no approving review), and the full CI result under `ci`. Report the blockers and warnings as given; do not re-derive them from `gh pr view`. If `ci.status` is `pending` or `not-registered`, wait with `"${CLAUDE_PLUGIN_ROOT}/bin/ccm-ci-status" [PR-NUMBER] --watch --wait-key merge-[PR-NUMBER]` (repeat while it returns `"call_again": true`), then run the gate again.
It exits 0 when the PR may merge and prints JSON with `blockers` (not open, conflicting or unknown mergeability, CI not green, a reviewer's latest review is `CHANGES_REQUESTED`), `warnings` (no approving review), and the full CI result under `ci`. Report the blockers and warnings as given; do not re-derive them from `gh pr view`. A `ci.status` of `conflicting` means CI cannot start until the conflicts are resolved; do not wait for it. If `ci.status` is `pending` or `not-registered`, wait with `"${CLAUDE_PLUGIN_ROOT}/bin/ccm-ci-status" [PR-NUMBER] --watch --wait-key merge-[PR-NUMBER]` (repeat while it returns `"call_again": true`), then run the gate again.

If the gate fails, show the blockers and stop. Merge anyway only if the user explicitly says to. When `merge_guard: on` is set, the PreToolUse hook denies the merge command while the gate fails; after the user's explicit choice, prefix the merge command with `CCMAGIC_MERGE_OVERRIDE=1`.

Expand Down
Loading
Loading