ci: self-contained stacked-PR breadcrumb workflow - #48
Merged
Conversation
Ports taskless/taskless's stack-breadcrumb as a portable GitHub Actions workflow: a zero-dependency CJS module loaded via actions/github-script, no pnpm/tsx/package. Two-stage dispatch/reconcile serialized per stack root; logic covered by a node:test suite wired into CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Ports Taskless’s stacked-PR breadcrumb automation into this repo as a self-contained, zero-dependency GitHub Actions workflow plus a pure CommonJS implementation and Node-native unit tests, so stacks can be reconciled without any build/install step.
Changes:
- Added a two-stage GitHub Actions workflow (
dispatch→reconcile) that serializes reconciliation per stack root and updates PR bodies viaactions/github-script. - Introduced a zero-dependency
.cjsmodule implementing stack discovery, breadcrumb rendering, and minimal PR-body region splicing, plus a Nodenode:testsuite. - Wired the new Node test suite into CI and excluded the workflow scripts directory from ESLint.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| eslint.config.js | Ignores .github/scripts/ since the new scripts are validated by their own Node test suite. |
| .github/workflows/stack-breadcrumb.yml | Adds dispatch + reconcile jobs to keep PR stack breadcrumbs updated, with per-root concurrency. |
| .github/workflows/ci.yml | Runs node --test against .github/scripts/*.test.cjs in CI. |
| .github/scripts/stack-breadcrumb.test.cjs | Adds Node-native unit tests covering stack parsing, rendering, and reconcile behavior. |
| .github/scripts/stack-breadcrumb.cjs | Adds the portable, pure logic module for deriving stacks and editing the PR body marker region. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Coerce and validate the root from repository_dispatch client_payload and workflow_dispatch input to a positive integer; an invalid value now fails the run instead of silently building an empty tree and no-op'ing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Filter the open-PR corpus to same-repo PRs in both jobs so a fork PR sharing a branch name cannot corrupt branch-name-keyed tree derivation. - Run the dispatch job from the default branch's copy of the script (not the PR head) so the privileged (contents: write) job never executes PR-supplied code; a resilient require keeps bootstrapping safe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- workflow_dispatch: fail loudly if the input PR is not open or is not a stack root (pointing at the actual root), instead of reconciling the wrong subtree or silently no-op'ing. - repository_dispatch: treat a since-closed root as an explicit logged no-op (the documented race) and normalize to the current true root so a shifted stack still reconciles correctly. - Fix header comment: workflow_dispatch runs from the selected branch, only repository_dispatch is default-branch-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parseStackComment now requires a full <!-- stack … --> … <!-- /stack --> region, so a stray/pasted opening marker without a closing tag is no longer mistaken for a managed region (which could wrongly pull a PR into the resolveAffectedRoots marker route). Also correct the marker-surgery comment to describe spliceRegion's actual whitespace normalization. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
reconcile() still continues past an individual failed write (safe failure), but the job now collects the failed PRs and calls setFailed so an inconsistent breadcrumb surfaces in CI instead of passing silently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removing a stack region no longer globally collapses 3+ newline runs across the whole PR body (which could alter unrelated spacing); it now trims only the blank lines hugging the removed region and rejoins the surrounding text with a single blank line. Also reword the eslint-ignore comment (scripts are covered by node:test, not linted by it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bring taskless/taskless's stacked-PR breadcrumb over as a portable GitHub Actions workflow. It keeps each PR in a stack pointing at its siblings (a rendered
<!-- stack … -->region in the PR body), reconciled serially per stack root with concurrency control.Portable by design — no package
The source lives in
taskless/tasklessas a@taskless/stack-breadcrumbworkspace package (pnpm + tsx + turbo). Here it's reduced to:.github/scripts/stack-breadcrumb.cjs— the entire logic (tree derivation frombase/head, marker-region splicing, breadcrumb render, reconcile planning) as a single zero-dependency CommonJS module. TheghI/O boundary is removed..github/workflows/stack-breadcrumb.yml— two jobs usingactions/github-script, which supplies an authenticated octokit and loads the module withrequire. Nopnpm install, notsx, no build step.Same two-stage design as the original:
opened/reopened/edited/closed), resolve the affected stack root(s) and fire arepository_dispatch.workflow_dispatch), render and propagate the breadcrumb across the tree, serialized per root via aconcurrencygroup.Least-privilege permissions per job (stage 1 POSTs a dispatch; only stage 2 writes PR bodies) and the fork/bot guards are preserved.
Tests
The pure logic is covered by
.github/scripts/stack-breadcrumb.test.cjs(36 cases) using Node's built-innode:test— zero dependencies — ported from the source vitest suite. Wired into CI asnode --test .github/scripts/*.test.cjs. Breadcrumb output is asserted byte-for-byte, so this port matches the original exactly.eslint.config.jsignores.github/scripts/(CJS infra validated by its own test suite), matching howopenspec//plugins//tmp/are already handled.Note
The reconcile stage only activates once this is on
main— GitHub runsrepository_dispatch/workflow_dispatchfrom the default branch's copy of the workflow only. Expected, and documented in the workflow header.🤖 Generated with Claude Code