Skip to content

Releases: Jerry0022/dotclaude

v0.149.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 19:16
250bf9d

Added

  • The /concept decision panel never hides its call-to-action below the fold again. The panel was one scrolling column — iteration chips, a flat table of contents, the connection pill, then the two submit buttons — so a concept with eight rounds and twenty-odd sections pushed "Zur nächsten Iteration" off the screen, and the user scrolled the menu to find the decision. The status area was noise at rest ("Claude verbunden") and silent about the one thing the reviewer wanted to know: is my work saved, and did it go through? The panel is now a flex column with three regions: a pinned "Hier bist du" header (round + section under the reading line), a scrolling middle, and a pinned foot that is hard-capped at 120 px in the smallest and the largest case alike. The foot holds one status line with six mutually exclusive states — saved, saving, connecting, not delivered (a warning background, categorically different from the rest), submitted with progress dots, and read-only round — driven by the draft mirror and the heartbeat, and a split-button: the primary "Zur nächsten Iteration" plus a caret that opens "Mit Feedback implementieren" one level deeper, so the misclick barrier is a menu rather than a 2rem gap. The hint paragraphs moved into tooltips; the cache hint, #panel-frozen and the progress list stay real DOM. In design mode the foot keeps a gutter for the 💬 FAB; on mobile the panel caps at 60vh so the split still holds.

    Iterations and table of contents merged into one tree ("Kompass"). Every round is a node; exactly the round on screen is open and its body is that round's section list — there is no code path that closes it, switching tabs moves it. Older rounds cost one line each ("Iteration 3 · 14 Einträge · 2 verworfen") and fold into an archive only from four previous rounds, which auto-opens whenever a frozen round is viewed. Sections group into Kontext / Varianten only when at least two kinds meet more than twelve entries — kinds are inferred from the existing eval-{id} radios with a data-nav-group override, never from an attribute the author has to remember — and one-open applies inside the TOC only. The scroll spy may open the group that holds the active entry and never closes one; a manual close is honoured for four seconds. Every collapsible element is absent from the DOM below its threshold, so a one-round, three-section concept renders exactly as before, minus nothing. The tab bar keeps its contract — one .iteration-tab per round, appended by string edit — so the append checklist is unchanged. Decided on a two-round concept page (three directions, then three foot variants of the chosen one), red-teamed against the scroll-spy, tab-switch, persistence and wizard contracts before implementation, and pinned by 42 new jsdom tests plus validation-gate entries 56–61.

Fixed

  • The concept port registry had silted up to 1159 entries with no bridge alive. 1131 of them came from the test suites, which spawn real bridge servers and tear them down with SIGKILL — skipping /shutdown, the watchdog, atexit and any signal handler — so every npm test run left dozens of entries in the user's real registry; the rest were bridges lost to hard kills and power cuts. pick now sweeps first: an entry is deleted when its port no longer accepts a TCP connect (or the file is not JSON), and a bound port is never touched, so a live foreign bridge stays protected. The recorded pid is deliberately not a keep criterion — Windows reuses pids within days, and 35 weeks-old entries survived a pid-based sweep for exactly that reason. Server and picker honour CONCEPT_BRIDGE_REGISTRY_DIR, which the three server-spawning suites point at a temp directory; the server also drops its entry on SIGINT/SIGTERM/SIGBREAK, where the default SIGTERM action used to skip atexit.

  • The bridge launch procedure resolved the plugin scripts from the oldest cached version. ls -d ~/.claude/plugins/cache/dotclaude/devops/*/scripts/… | head -1 returns the lexically first version directory, and several coexist after updates — so a session ran a 0.145.1 bridge server and watcher beside hooks already on 0.148.0, and the resume hook generated the same stale prefix into the cron. Every occurrence now version-sorts and takes the highest (sort -V | tail -1), preferring $CLAUDE_PLUGIN_ROOT when the shell has it; CONVENTIONS.md carries the rule.


Install: /plugin marketplace add Jerry0022/dotclaude

v0.148.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 13:25
b671ff0

Added

  • A concept card no longer counts its own plumbing as unfinished work. While a /concept page is open, three detached Bash tasks run for the whole session — the bridge server, the keepalive pulser and the pickup waker. They never yield a result; they are the waiting. The pending gate counted them anyway, so every card rendered mid-concept read "⏳ NOCH NICHT FERTIG. 3 Tasks (Start the concept bridge server on port 8840, Launch the keepalive pulser …) laufen — ich MELDE mich", naming infrastructure where the only true statement was "waiting for your decisions". The transcript scanner now recognizes those tasks — by the script they run or the role their launch description names — and never opens them, so stop.flow.guard ignores them; a real agent or npm test launched alongside is still reported. The PostToolUse reminder that fires on a background launch says [concept] for these instead of [pending], so the model is not steered into listing them.

    The card gained a concept field for the state a concept can actually be in. concept: { phase }waiting (default), iterating or implementing — replaces the CTA of every variant, and outranks pending, with "🧭 CONCEPT läuft. Warte auf deine Entscheidungen auf der Seite / Arbeite an der nächsten Iteration / Arbeite an der Implementierung — ich MELDE mich". Real content work still goes into pending and is folded into that line rather than replacing it — Arbeite an der Implementierung mit Agent \devops:frontend`, … mit 1 Workflow + 2 Agenten— while the pending block and the dim name row keep naming each item. A JSON string, a bare phase string and an object are all accepted; an open concept with an unknown phase reads as waiting, because the only wrong card here is one that asks for a SHIP. The concept skill documents the mid-concept cards and that the final card, rendered after cleanup, carries noconcept` at all.


Install: /plugin marketplace add Jerry0022/dotclaude

v0.147.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 12:40
81bfd10

Added

  • The completion card's pending layer knew agents and Bash tasks, so a whole Workflow run counted as one agent — or as nothing at all. A workflow fans out to dozens of agents of its own, so reporting three of them as "3 Agenten" understated the work by an order of magnitude, and the Stop gate never saw a workflow launch at all: a card that forgot to declare one was not blocked, because the transcript scanner only knew two launch markers. What looked like working support was the model typing "Workflow x" into an agent item by hand. workflow is now its own kind end to end — detected from Workflow launched in background. Task ID: <id>, named from the script's meta.name (anchored to the export const meta literal, since a bare name: match happily picks up an agent label defined earlier in the same script), else the script path, else the launch summary, and closed by the matching task-notification.

    The names moved out of the CTA. Three names plus two counts in one heading is a line that wraps, so the CTA now carries the shape — 3 Workflows + 1 Agent + 1 Task laufen — and the names sit one row above it, in the same dim blockquote style as the version and branch rows: the first three, then a +N tail counting every remaining item. Both +N on a card cut at the same three, so the block above and the row below can never disagree about the rest. A single item is still named in the CTA itself and gets no row, because repeating one name is not information.

Fixed

  • Quoted text was treated as an event: one grep could block every later card in the session. The transcript scanner matched launch markers in any tool result and task-notifications in any line, so a Grep hit on the hook's own source, a Read of a transcript, or a script printing a launch line opened a phantom item that no notification would ever close — and an item that never closes blocks the Stop gate forever. This was not hypothetical: running the scanner over a live session transcript found two, both produced by ordinary work in this repo. Three guards now separate event from quotation: a launch counts only from a tool that can announce that kind of launch (bound per marker — Bash may announce its own task but also prints everybody else's), only when the announcement is the tool result rather than a line inside somebody's output, and a completion counts only when the notification is a real transcript entry rather than the payload of a tool_result.

  • Pending labels reached three places that read text as structure, unsanitized. A name arrives as model-authored text — a workflow's meta.name, an agent description — and lands inside a markdown code span, inside a JSON example, and inside the Stop hook's reason, which Claude reads as instructions. A backtick ended the code span early, a newline split the card's tightest row, and an unbounded name turned it into a wall of text. Labels are now stripped of structural characters and clamped at one choke point on each side.


Install: /plugin marketplace add Jerry0022/dotclaude

v0.146.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 11:26
b2c71ed

Added

  • /concept now re-checks an approved plan against the default branch before implementing it. A concept session lives for hours or days while main keeps moving — other sessions, other PRs, other ships. Clicking "Mit Feedback implementieren" implemented the plan as approved, so a renamed file, a changed contract or work somebody had already done reached the diff unremarked and was found in review, if at all. The implement path now runs a reality check before writing a single line: on material drift it does not implement, it appends one extra round — mechanically an ordinary iteration, marked data-reality-check, with its own "Realitäts-Check" tab and an explainer naming the commits that landed — carrying every decision the drift raises. Submitting that round with implement writes the code.

    The hard requirement was that this must never become a trap, so it has two independent guards. The marker on the section short-circuits the check, which is exact: implementing from a reality-check round always implements. And the baseline advances on every submission of such a round — iterate as well as implement — so the same drift cannot force a second one even if the marker were lost to a bad rewrite, a legacy page or a resumed session working from a page it did not write. Both would have to fail on the same round to produce two forced rounds in a row. An ordinary iterate round re-arms the check, which is the user opting back in rather than a deadlock: the next implement is measured against the advanced baseline, so only drift that landed since can raise it again.

    The split between code and judgment is deliberate. scripts/concept-drift.js produces facts only — the commits, the changed paths (a rename contributes both sides, which is the drift class that matters most), and their intersection with the paths the concept names — while the force classes stay with the model: a file deleted or renamed, a contract changed, the functionality already built, a user decision that lost its basis. Version bumps, CHANGELOG, docs, tests-only changes and formatting never force a round, and no drift card may be shown without the SHA and path it comes from. Every unresolvable condition fails safe — no repo, no remote, a default branch that is not called main, a detached HEAD, an offline or slow fetch, a shallow clone, a baseline erased by a force-push — and lets the implement through; a network hiccup must never block an implement order. The check announces itself as its own progress step before the fetch, so the added wait cannot read as a stalled submission, and it never reports phase: "implemented" on that path — a panel showing "Implementierung abgeschlossen" over an empty diff would be worse than the problem being solved.


Install: /plugin marketplace add Jerry0022/dotclaude

v0.145.1

Choose a tag to compare

@github-actions github-actions released this 06 Sep 00:18
9ca95c3

Fixed

  • /setup-cleanup's "active sessions are UNTOUCHABLE" rule protected branches but not the sessions themselves. The rule was written as a list of forbidden commands — git branch -D, git push origin --delete, checkout — and a list of verbs silently permits every verb it forgot to name. A cross-repo sweep obeyed it exactly: it refused to delete a live session's branch, then removed that session's worktree anyway, because git worktree remove was not on the list. The session kept running and lost nothing, but only because the branch it had already committed to survived elsewhere.

    The protected set is now a set of subjects — the branch, its worktree directory, the repo it lives in, and any process running inside it — with the commands demoted to examples, and it must be rebuilt immediately before each destructive action rather than once at classification time, because a sweep across many repos outlives the session state it was classified against. deep-knowledge/git-hygiene.md gains the general rule and the four operation classes a guard has to cover (ref, worktree, file, and process mutation), plus the reason a partial guard is worse than none: it reads as coverage. Detection was widened in the same pass — a git worktree list --porcelain scan that only collects branch refs/heads/ lines misses detached worktrees entirely, and scanning the .claude/worktrees/ directory misses worktrees registered outside it; both were live blind spots, not hypotheticals.


Install: /plugin marketplace add Jerry0022/dotclaude

v0.143.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 14:31
e9a88a7

Fixed

  • Switching between /concept iteration tabs could destroy every comment on the page. Four independent single lines caused it, and two of them fired precisely when things were already going wrong. restorePanelToReady() ended in localStorage.removeItem(STORAGE_KEY) — deleting every comment, rating and selection — and it runs both when the bridge answers 507 (nothing was persisted anywhere, so the local copy was the last one) and after the five-minute safety timeout, i.e. exactly when Claude has stopped responding because of a usage limit. The submit handler emptied the feedback dock, although the submitted round stays live until Claude appends the next section: a detour into an older tab came home to a blank dock on a round that had not even been answered yet. saveState() persisted frozen rounds and the frozen-painted dock, so browsing a past tab — which is what the tabs are for, and which ends every showScreen() in a saveState() — wrote the old round's submitted answers over the live round's unsent ones. And restoreState() deleted the whole blob on TTL expiry and on a page-version change.

    Typed keys are now namespaced per round (text:i3:d1-s1) — screen ids, {decisionId}-note keys and annotation ids all repeat across rounds, and the dock is one shared overlay outside section[data-iteration] — which is what makes keeping the text both safe and unambiguous. Nothing deletes the state blob any more: TTL and version changes prune it key by key, keep every text: entry, archive the original, and say so on the page. The dock is marked read-only on submit instead of emptied, and re-armed by unmarkDockSubmitted() on every path that hands control back.

Added

  • A concept page's unsent comments are now mirrored to disk, fsynced, on every autosave. journal.jsonl only ever saw a payload the user pressed submit on; everything typed before that lived exclusively in localStorage, which does not survive a wiped profile, a private window, a quota error or a power cut — and which nothing else had a copy of. The bridge gains a draft store (POST /draft, GET /draft): an append-only revision log plus an atomically replaced snapshot per page, written with the same never-ack-what-is-not-on-disk rule as a submission. Because the log is append-only, GET /draft can answer recovered — the per-key union of the last non-empty value ever posted, minus the keys the page reported as deliberately cleared — so even a client that posts a blank blob, the shape every past data-loss bug took, cannot destroy anything. The page flushes with sendBeacon on pagehide and merges recovered back on load into any key it is missing or holds empty. The bridge is a plain Python process with no dependency on Claude, so it keeps accepting drafts long after a usage limit has silenced the session. GET /recovery lists the drafts, so a resumed session can tell "the user typed nothing" from "the user typed for an hour". Verified end to end in a browser: the bridge was SIGKILLed, localStorage wiped completely, the bridge restarted — every comment came back from disk. Gate entries 49–53 pin all of it as engine entries, so an existing page picks them up on its next iteration append.

  • Two suites that execute rather than read: comment-durability.test.js runs the shipped engine text in jsdom and reproduces all four losses (19 of its 20 tests fail against the previous code), and draft-durability.test.js SIGKILLs a real bridge, corrupts its log and restarts it. A torn final journal line — the signature of a power cut mid-append — also used to swallow the next record; the store now terminates it once per process, and _durable_write fsyncs the directory after the atomic replace.

  • The ship gate stopped failing on contention. These suites test hooks, the bridge server and git-sync by spawning them, so one vitest worker is never one process; the default (~cores-1) put 30+ runnable processes on a 12-core machine and the one that lost the scheduler was vitest's own main process, whose workers' onTaskUpdate RPC then timed out and exited the run 1 beside 2466 passing tests. Worker count is capped at a third of the cores.


Install: /plugin marketplace add Jerry0022/dotclaude

v0.142.0

Choose a tag to compare

@github-actions github-actions released this 04 Sep 22:17
b2701f3

Added

  • /claude-strict — the deliverable is exactly what the prompt names, and only the attributes the prompt leaves open are Claude's call. "Make the box border thinner" used to invite a padding fix, a doc update and a tidy-up of the two neighbouring borders; nothing in the plugin held the line, and several always-on mandates (docs in the same change, pre-mortem guards, "make reasonable decisions independently") pushed the other way. The new skill fixes the scope in the vocabulary of the request — a visual request closes the set of visible things that may change, a technical request closes the set of symbols — while unnamed attributes (colour, pixel value, wording) are chosen and reported in a four-line strict report (requested / done / chosen / untouched). Grey zones are settled, not left to mood: a syntactic companion (import, export entry) is in scope, an assertion pinning the exact old value may follow, any other test failure reverts and reports, an ambiguous object is asked about interactively and assumed (and led with) autonomously.

    Two modes, one state file per worktree: /claude-strict <task> binds the turn and every workflow it starts (a concept session or an autonomous run is auto-bound to its state file and released when that file disappears); /claude-strict on|off binds the current worktree + branch, never the whole project — a branch switch deactivates it with a single notice. Enforcement is mechanical: prompt.strict.enforce injects the ≤1.4 KB contract once per turn (also on cron and waker turns, so concept iterations stay strict without re-typing), pre.strict.agent-gate refuses an Agent spawn whose prompt lacks the block and says how to fix it (recursive — plugin hooks run inside subagents; worktree agents inherit via <parent>-<role>), and stop.strict.release binds or releases inline modes. code-defaults.md § Strict Mode states the precedence over doc-maintenance and pre-mortem outputs; the agent prompt template gains item 9 (forward the block verbatim). Spec: docs/superpowers/specs/2026-09-04-claude-strict-design.md.


Install: /plugin marketplace add Jerry0022/dotclaude

v0.139.1

Choose a tag to compare

@github-actions github-actions released this 04 Sep 18:07
9380427

Fixed

  • A /concept page still stacked one 📎 attachment bar per hidden dock field after the fix that was meant to end it, scrolled the dock behind a raw platform scrollbar, and let the page header shine through frozen design iterations. Measured on a real four-iteration page: twelve visible attachment bars for three visible textareas, 1062px of dock content in a 430px box, and the document <h1> plus the iteration intro painted through the mockup right under the screen indicator after every ☰ switch. Three causes. The page's attachment engine predated the earlier fix (parentElement.appendChild(bar), no .attach-bar half in the hiding rule) and still passed the validation gate, because entry 44 grepped a single literal that the page happened to contain; and an iteration append re-uses the page's existing engine forever, so the defect survived every later round of the same concept. In design mode a page that started as a decision round keeps its document header and each iteration's intro in normal flow while the design section paints over them position: absolute; inset: 0 — and the generic frozen-iteration opacity: 0.85 turned that into bleed-through.

    The attachment engine now injects its own visibility CSS once (#attach-visibility-styles), so a page that copies the JavaScript without the layout rule still hides a bar with its field. Gate entry 44 requires both hiding literals, the afterend mount and the injected style, and rejects the legacy parentElement.appendChild(bar) and attach-hint shapes; new entries 46 and 47 pin the design-mode rules that hide the document header and intro and keep a frozen design iteration fully opaque, and entry 48 warns on a missing scrollbar skin. The three scroll boxes (.feedback-dock, .concept-decision-panel, section[data-screen]) carry a thin border-coloured scrollbar in every template. Step 5c of the skill gains an engine drift check before every append: the gate runs over the whole existing page and any failing shared block is re-synced from the reference before the new iteration lands — so a page generated before a template fix picks the fix up on its next round instead of never.


Install: /plugin marketplace add Jerry0022/dotclaude

v0.139.0

Choose a tag to compare

@github-actions github-actions released this 04 Sep 15:28
d4899f7

Backlog run 2026-09-03/04 (PR #325, eight issues). The plugin's own ship server was unreachable while this work was made — the very defect fixed first below — so the eight fixes landed as one integration PR and this entry versions them.

Fixed

  • The plugin's MCP servers timed out at session start, and the plugin itself was the reason. Claude Code starts every SessionStart hook concurrently with every stdio MCP server and gives each server thirty seconds to answer initialize. On 2026-09-03 all three plugin servers — and unrelated third-party ones — hit that timeout in session after session, although each of them boots in under two and a half seconds on its own. The thirty seconds were being consumed by the plugin's own start-up work: four git fetch calls, an npm install, a ten-thousand-file copy of node_modules into the very directory the servers use as their working directory, a recursive scan of the repository and a process sweep, all at once, all with Windows Defender inspecting every write. The issues server made it worse by running gh issue list synchronously before it had even created its transport. A session that loses dotclaude-ship cannot ship at all. (#324)

    Nothing runs before connect in any server any more; the issues warm-up is asynchronous and happens afterwards. Every server answers a health_check, a stdout-discipline test keeps the JSON-RPC wire clean, and a boot probe (scripts/mcp-boot-probe.js) fails the suite when any server takes longer than five seconds to answer. The plugin updater and the dependency installer now sit behind cooldowns (six and twenty-four hours) that an explicit /auto-update, the post-ship sync, or a genuinely broken cache bypass; node_modules is never copied again but linked and refreshed only when the dependency manifest changes; a repair of a version directory a live session is standing on is handed to a detached child that waits out the connect window; the git check and the changelog hook lost an unbounded and a duplicated fetch; and every SessionStart hook carries an explicit timeout. The rule behind all of it is written down in CONVENTIONS.md under "Boot Discipline". MCP_TIMEOUT=60000 remains the documented stopgap for a slow machine.

  • /setup-cleanup silently dropped removable branches whose name is a prefix of a worktree branch. The protected set was matched by substring, so feat/x-abc123 counted as protected whenever feat/x-abc123-def456 had a worktree — conservative, but invisible: the candidate list was simply one entry short. Membership is now exact full-ref equality, prefix and substring tests are forbidden, and a mandatory count check at the end of the classification (candidates plus protected set must equal all local branches) turns any future gap into a visible warning. (#323)

  • /setup-cleanup locked almost every worktree on a squash-merge workflow as "work in progress". Being ahead of origin/main counted as having changes, and a squash-merged branch is ahead forever. In one real repository that classified 34 of 41 worktrees as untouchable while two of them carried unsaved work. has-changes is now decided solely by git status --porcelain; commits ahead become the separate, non-blocking commits_ahead attribute together with an own_content badge from the same two-dot check the skill already used for branches. (#322)

  • /claude-batch <free text> no longer disappears, and a dead marker repairs itself. The routing table gains an explicit row for a free-text argument — it becomes note #1 and switches the mode on — and a marker pre-check now runs on every route: a stored marker that cannot work asks the marker question immediately instead of being re-reported on each status run and never fixed. (#306)

  • The committed project map no longer carries a random worktree slug as its title. gen-project-map resolves a stable name — package.json name of the main checkout, else the main checkout's directory, and only for non-git trees the given root — so a map regenerated from .claude/worktrees/<slug>/ is byte-identical to one generated from the main checkout. (#303)

  • A /concept design page could go completely blank after switching designs and using the panel. The screen navigation compared against the design that was active when the navigation was built, never re-built on a design switch, and showScreen() hid every screen when handed an id that belonged to no screen of the active design. The handler now resolves the active design at click time and showScreen() falls back to the first screen; gate invariant P15b requires exactly one visible screen after any switch and insists on getClientRects() rather than getComputedStyle().display for the assertion. Verified with a fifty-four-click sweep across two iterations, three designs and every navigation entry. (#299)

  • Feedback typed into a /concept design-page dock was destroyed by a reload or a design switch. The state restore ran before the dock textareas existed, and the next save then rewrote the stored blob without them. The restore now runs immediately after the dock is built and before anything can save; saveState() merges over the stored blob and never blanks a note the user has not touched since load. Browser QA caught a second ordering defect in the first fix (the load handler's own showScreen() saved before the restore) — fixed and re-verified: a note survives a reload, three design switches, an iteration round trip and a second note. (#297)

Added

  • Both floating buttons on a /concept design page now say what they are. The feedback button was a bare emoji circle that users could not find. Both buttons carry locale-sourced title and aria-label that swap with aria-expanded, and the feedback button pulses three times until it is first used (disabled under prefers-reduced-motion). The shared-shape invariant is untouched — the labels are tooltips only, and the pulse rule declares nothing but animation. (#298)

Install: /plugin marketplace add Jerry0022/dotclaude

v0.138.1

Choose a tag to compare

@github-actions github-actions released this 03 Sep 15:24
62da7c1

Fixed

  • A post-merge watcher that died took its result with it, and announced itself as still running for a day afterwards. The watcher is spawned detached, so a machine shutdown or a killed session ends it without any of its terminal writes ever happening, and its state file stays on status: "watching" with finishedAt: null forever. The only defence was a flat twenty-four hour staleness constant in the SessionStart reader — a number with no relationship to the watcher's own lifetime, which is at most five minutes of run detection plus --max-wait plus the deploy probe. For the whole of that day the reader greeted every session with "Ship verify still running", counting up: the reported figure had reached 1205 minutes for a process that had been dead since minute forty. Then, on expiry, it set acknowledged: true while leaving status: "watching" — so the entry was never reported at all, the outcome of that ship was silently lost, and because the status never changed the branch was re-entered at every subsequent session, re-stamping staleAt with a fresh timestamp each time. Eight entries were sitting in that state here, the oldest since 2026-08-17, all carrying the same current-day staleAt.

    Every state file now carries maxWaitSec and deadlineAt, so a reader decides "working" or "died" from the run's own numbers rather than a constant. Past that instant the entry is treated as abandoned and reconciled against gh run list --commit <merge-sha>, which answers the counterfactual the watcher can no longer answer itself; the result is written as a terminal state, surfaced exactly once, and cannot re-enter the watching branch. Reconciliation trusts only the per-commit query — the branch-scoped fallback misses tag-triggered runs entirely, so an empty result there yields inconclusive with a gh pr checks hint rather than a fabricated "no workflow ran". Matches are further restricted to runs created before the deadline, because in this repo the Release workflow fires on the bare vX.Y.Z tag that /promote later adds to the already-merged commit: without that restriction a promotion's CI would be reported as the ship's own. The watcher additionally writes a terminal inconclusive when it is terminated gracefully; a hard kill remains the reader's problem, which is what the deadline is for.


Install: /plugin marketplace add Jerry0022/dotclaude