awslabs / awslabs/aidlc-workflows
[RFC]: Integrate Construction through pull requests (pr-integration stage + aidlc-pr tool)
- Dominant language
- TypeScript
- Stars
- 4.6k
- Forks
- 827
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 158
Description
The implementation of this design is up as PR #969; this RFC is the design rationale, the experience it targets, and the open questions, posted for maintainer discussion. It cross-references #801 (composes with the proposed code-review stage) and #795 / #879 (team-mode fusion seam).
## The gap, verified
AI-DLC's delivery boundary today is a local git merge. When a Bolt's gate
approves, `aidlc-bolt complete --merge` consolidates metadata and
`aidlc-worktree merge --slug --target --strategy `
merges the bolt branch into the target on the local checkout. That is the
whole story: no stage among the 33 contains a push or PR step, the
merge-dispatch contract validates strategy against exactly
`squash|merge|rebase` (aidlc-bolt.ts), and the only remote interaction in the
merge tooling is a fetch before a rebase.
At most organizations this flow cannot complete. Protected trunks reject
direct pushes; pull requests with required approvals and required checks are
how work reaches the integration branch. Teams that hit this today improvise:
close the bolt for bookkeeping, hand-run `git push` and `gh pr create`, and
clean up with `aidlc-worktree discard` - which records the successfully
merged bolt as ABORTED in the audit trail, stamps completion before review
concluded (so review feedback has no loop-back home), and force-deletes
without verifying the merge happened.
This RFC makes PR-based integration a first-class, evidence-backed part of
Construction - and makes the PR itself better than what a human types into
`gh pr create`, because the workflow already holds everything a great PR
needs.
## Relationship to neighboring proposals
- **#801 (code-review stage)**: composes, does not overlap. #801 is internal
review (agent-reviewed code before build-and-test). This RFC is the
external integration boundary. #801's outputs (code-review.md, findings,
waivers, traceability) become premium inputs to the PR body: external
reviewers start from an evidence dossier, and external review cycles get
shorter because the machine review already happened, documented.
- **#795/#879 (team-owned units)**: designed standalone, fuses cleanly. The
fusion seam is named in one sentence: the platform merge splits #879's
pinned merge-back transaction into "the platform merges the tree" and "the
main session verifies and folds the metadata." Under team mode, the
facilitator's merge gate becomes PR review, enforced by branch protection
instead of prose.
## The experience (an enterprise team on gitflow)
**Affirmation, once.** During practices-discovery, a detection tool has
already read the repo: "Your `develop` is protected - direct pushes
rejected, 2 approvals required, CI must pass. You have a PR template and
CODEOWNERS, and your branches look like `feature/PAY-231-slug`. I'll
integrate through pull requests: merge commits to develop, your template,
your branch pattern, reviewers from CODEOWNERS. Sound right?" One
confirmation lands in `## Way of Working`. Teams that affirm direct merge
keep today's flow byte-identical.
**The bolt finishes.** Instead of a local merge, the stage composes the PR
record: title, body written into the team's own PULL_REQUEST_TEMPLATE, with
the AI-DLC evidence dossier as a collapsed appendix - the story trace, the
design decisions, the internal review findings and waivers (#801 when
present), the test evidence. One gate: the operator sees the rendered PR and
approves the outward push. The tool pushes the branch, opens the PR,
requests any practices-named reviewers (CODEOWNERS assignment fires on its
own), and records the URL in the audit. Then:
> "PR #214 opened for payments-service - CI running, 0/2 approvals. Next
> up: notifications-worker."
The unit enters `integrating` and the engine routes the next eligible unit
immediately. One unit in review never idles the session.
**The wait is designed to be invisible.** No polling, no parked workflow.
When nothing is eligible - every unit integrating or blocked on one - the
session ends honestly with a turn-terminal status: "All in-flight work is
awaiting external review: #214 (CI green, 1/2 approvals, 3h)." That is a
statement, not a wedge: the workflow resumes the moment there is truth worth
acting on.
**Feedback arrives.** On resume, the session greets from what it last knew
("as of yesterday 17:42: #214 open, CI running") and refreshes when routing
needs the truth. A formal changes-requested review opens the offer: "Rosa
requested changes - 3 comments, 2 on error handling in retry.ts. Address
them now, or keep building?" The operator decides. The comments arrive as
findings in the still-alive bolt worktree; the standard revision loop runs:
fix, optionally re-run code-review on the changed files, push (the same PR
updates itself), re-request review. Every round leaves GATE-visible audit
rows, so the external review history mirrors into the workflow record.
**Merge lands.** The sweep verifies the merge and settles the unit as a
receipt - the same receipt-plus-evidence pattern unit completion already
uses. Metadata consolidates, the worktree retires with the honest reason
"integrated via PR #214", dependents unblock in the DAG. The one human gate
of the stage still fires exactly once, when everything is settled, as today.
**The escapes are honest.** A PR closed without merging is a halt-and-ask
(reopen - verified viable, reviews intact - or new PR, or abandon). Three
feedback rounds trigger the standard escape hatch, except "Accept as-is" can
never mean "merge anyway" against branch protection; the third option is
escalate or park the bolt.
## The experience, team mode (destination, post-#879)
The same machinery, fanned out: each team's claimed unit ends in a PR;
cross-team review routes through CODEOWNERS; the facilitator merges nothing
and reads a board; overnight, CI and remote reviewers keep the workshop
moving while every laptop in the room is shut; each platform merge folds the
unit home and opens the next batch's claims. A full two-day workshop
narrative exists and lands with the fusion increment; the design here
requires only that the seam sentence above stays true.
## The pieces
**1. Practices + detection decide existence; scope decides ceremony.**
`aidlc-pr detect` reads (all verified at plain read level): ruleset detail
via `rules/branches` (approvals, dismiss-stale, per-branch merge methods,
required checks), the `.protected` boolean for classic protection, merge
methods and auto-merge via GraphQL, the PR template via
`pullRequestTemplates` (case-insensitive - path probing provably misses
lowercase templates), CODEOWNERS via the contents chain. Two hard-won rules:
effective policy is the UNION of classic and rulesets (they disagree in the
wild, on this very repo); and classic protection detail is unreadable below
admin while GraphQL silently returns an empty list - detect must never
infer "unprotected" from silence, and falls back to asking the human for the
one fact it cannot see. Some repos structurally cannot have protection
(free-plan private): detect reports that tier honestly. Ceremony depth
resolves like the walking-skeleton stance: Way of Working states the stance;
the scope file's `integration:` field resolves scope-dependent cases
(express: direct where the remote permits, feature: pr). Configuration never
overrides protection reality.
**2. Greenfield establishes; brownfield conforms.** On brownfield the
integration process is a fact detect reads and honors. On greenfield the
workflow may BUILD it as a deliverable: at graduation moments (ci-pipeline
stage, team mode, scope promotion) it offers to set up protection, seed a PR
template from the pr-record format, add CODEOWNERS. A solo dev on a fresh
`git init` never hears the word PR; an org-greenfield repo with day-zero
protection is detected as what it is.
**3. The stage.** `pr-integration`: construction phase,
`for_each: unit-of-work`, `execution: CONDITIONAL` (on the affirmed
integration mode), `requires_stage: [code-generation]`, lead agent
aidlc-pipeline-deploy-agent. It ships `scopes: []` - SKIP in every grid
cell, activated per intent by the affirmation - so the stage graph moves for
nobody until a team affirms PRs. The per-unit Construction block ends at
code-generation (build-and-test runs once, not per unit), so this RFC states
plainly: the PR's own CI is the per-unit test gate; the workflow's
build-and-test remains the whole-stage backstop.
**4. The tool.** `aidlc-pr.ts`, verbs `detect / open / sweep /
sync-feedback / finalize`. Deterministic, audit-emitting (PR_OPENED,
PR_FEEDBACK, PR_MERGED + the UNIT_INTEGRATING lifecycle receipt), and
observe-only: it never merges, never arms auto-merge. Every gh call is
wrapped in `timeout 10` (gh has no timeout knob; packet-drop hangs 30s;
timeout kills silently so the tool owns the messaging), a sweep opens with
one connectivity probe, and offline degrades to "last known + age"
everywhere. The sweep state machine encodes the verified field rules:
terminal state first (merged PRs can read CHANGES_REQUESTED - observed);
per-reviewer verdicts folded from the full reviews history, never
latestReviews (it empties on re-request and hides CRs behind COMMENTED
reviews - both observed); stale approvals detected via commit_id against
head (21 of 100 recent PRs on this repo merged on stale approvals);
dismissals read from the timeline (the reviews endpoint overwrites the
original verdict); dismissal is asymmetric (pushes dismiss approvals, never
CRs); mergeability polled past its ~15s UNKNOWN window; and every write
verified by reading back (a bot re-request returns HTTP 200 and silently
does nothing - success codes prove nothing).
**5. Routing: pipelining without polling.** A new `integrating` unit state:
skip-and-continue semantics (deliberately NOT paused, which stops the whole
loop), terminal to the active-unit checkpoint (or `unit start` refuses every
subsequent unit), invisible to the settle predicate (so the stage gate
cannot fire early). The routing pick partitions integrating units out and
routes the next eligible one; when everything left is integrating, a new
turn-terminal `awaiting-integration` directive (cloned from parked's five
registration points) ends the session honestly and self-clears on the next
sweep. Network lives at exactly two surfaces: the routing decision that a
sweep can answer, and `/aidlc --status`. The Stop hook runs the engine every
turn end, so the sweep hard-skips under the existing stop-hook probe
environment - hooks stay zero-network, an invariant this repo already
enforces.
**6. Settlement is a receipt, not an approval.** The platform merge,
verified by the sweep, settles the unit through the existing
receipt-plus-evidence pattern. No approve-path, report-path, or
human-presence rule changes. The humans who approved the PR made the
judgement; the tool proves it happened; the workflow's own gate still fires
once at all-settled.
**7. Feedback trust.** Only a formal CHANGES_REQUESTED review opens a
revision round (verified distinguishable from COMMENTED reviews and issue
comments by endpoint + state). Drive-by comments are surfaced, never acted
on. Review comments are data - findings to evaluate - never instructions.
And the fix-push always gates, even under an autonomy grant: autonomy covers
publishing self-originated work (push + PR-open at bolt completion); any
push whose content was shaped by external input needs the operator's
go-ahead. Re-requesting review targets humans only.
**8. Stacking, bounded by evidence.** Dependent units may stack (child
branch off the parent's open branch, PR targeting it) ONLY when the affirmed
strategy preserves ancestry (merge/rebase) AND the repo does not auto-delete
branches on merge. Both bounds are verified: squash-merging a parent leaves
phantom commits in the child that corrupt review scope even when content is
byte-identical; and deleting a merged parent's branch CLOSES the child PR
rather than retargeting it - so the sweep retargets children BEFORE any
branch deletion, and on delete-on-merge repos (where a human's merge click
auto-deletes instantly) stacking disables and dependents wait for merge. The
recovery for a closed child exists (restore ref, reopen, retarget) and the
halt-and-ask knows it.
**9. Multi-repo coordination.** A bolt spanning sibling repos opens one PR
per repo, cross-linked by an `AIDLC-Coordinated:` body marker (authoritative;
timeline cross-references are only a discovery index). The bolt integrates
when ALL merge. Partial states are first-class: {merged} + {open} is
representable, and {merged} + {closed, changes-requested} is the verified
halt-and-ask payload - there is no rollback pretense, the ask names the
already-merged siblings. Revision rounds, detection, and practices are per
repo.
**10. Auto-merge is the humans' tool, and a hazard the sweep respects.** An
armed auto-merge survives request-changes rounds and pushes, then fires
unattended the moment requirements clear, attributed to the arming user
(verified). AI-DLC never arms it; the state machine handles "merged while a
revision round was open" as a normal transition.
## The plugin seam (why core placement pays twice)
pr-integration lands in core precisely so plugins can build on it. Four of
the five seams below exist in the plugin mechanism today and activate the
moment the stage file ships; the two authoring commitments are what this RFC
adds so the seams are actually useful.
- **The stage is an ordinary contribution target.** Plugin contribution
targets validate against the real core stage list (generated
plugin-authoring-context.json, source-walk fallback:
core/tools/aidlc-plugin-validate.ts:751-773, 401-436), so
`target: pr-integration` becomes legal as soon as the stage exists.
Commitment 1: the stage body is authored with stable numbered steps and
named compartments, so `after-step:`/`before-step:`/`in:` fragment anchors
have durable purchase across revisions.
- **The dossier extends by consumption.** Commitment 2: the evidence-dossier
appendix is assembled by enumerating the stage's `consumes` (core sections
pinned first, in a fixed order), never from a hardcoded section list. A
plugin that `adds.consumes` its artifact onto pr-integration (an
implemented merge surface: scripts/plugin-hooks-template/compose.ts,
mergeConsumes + IMPLEMENTED_ADDS) thereby lands its evidence in every PR
body - a compliance plugin's attestation, a test plugin's coverage report,
a security plugin's scan summary - with zero new mechanism.
- **Sensors can gate the outward push.** `adds.sensors` onto pr-integration
gives a plugin a deterministic advisory check at the stage's write/gate
points (dossier completeness, checklist present, artifact hygiene) before
the operator approves the push.
- **The audit events are a public read contract.** PR_OPENED / PR_FEEDBACK /
PR_MERGED and the UNIT_INTEGRATING receipt are documented in the audit
format as a stable surface. Plugin tools compute review-cycle metrics from
the ledger alone: time-to-merge, feedback rounds per PR, share of PRs
merging with zero PR_FEEDBACK - the DORA-flavored reporting the framework
does not itself ship.
- **Post-merge plugin stages.** A plugin stage may
`requires_stage: [pr-integration]` for post-integration work (release
notes, attestation filing, downstream sync); the fixture already proves the
pattern against a core stage
(plugins/test-pro/stages/operation/test-pro-full-suite.md:22-23 depends on
deployment-execution).
- **`integration:` is scope schema, not core-scope-special.** The
ceremony-depth field resolves for plugin scopes exactly as for core scopes,
so a plugin-shipped scope can affirm its own integration stance.
Limits, named rather than papered over: plugins cannot ship hooks, so every
deterministic guard on the push path stays core-owned (by design - the
fix-push gate and the autonomy boundary must not be relaxable by an
installed plugin); `adds.requires_stage` is not an implemented merge
surface, so a plugin cannot insert itself BEFORE pr-integration by edge -
required `adds.consumes` coupling is the supported form; and review-policy
packs (org-specific review passes feeding the dossier) ultimately want the
memory/rules contribution surface, which is designed but not yet shipped and
is out of this RFC's scope.
## What does not change
Solo and direct-merge workflows are byte-identical: the stage ships SKIP
everywhere, the routing partition is empty when unaffirmed, the new event
types are knob-gated, and no hook gains a network call. Park stays what it
is - a human leaving. Gates, receipts, the revision loop, and the audit
taxonomy are extended, not altered. Nothing in this RFC touches the
approve path.
## Evidence
Every load-bearing claim above was verified in a five-probe spike
(2026-08-26): a live protected-repo testbed with a bot second-reviewer
identity for the full feedback matrix (three comment shapes, dismissal
asymmetry, supersede, auto-merge lifecycle, stacking under merge and
squash), a multi-repo coordination round-trip including reject-after-merge
and reopen recovery, a read-level detection matrix proven against
zero-permission third-party repos, offline failure characterization with
measured timings, an engine seam map with file:line predicates for every
touch point, and a field-shape catalog mined read-only from this repo's own
PR history. Two design assumptions were REFUTED by the spike and the design
above already reflects the corrections (child-PR retargeting on branch
deletion; bot re-requestability). The consolidated claims table is below;
the verified rules are encoded as tests in PR #969 (t328/t329/t330).
## Implementation
PR #969 implements the full design as one dormant-by-default increment: the
stage file, the tool, the detection verb, the `integrating` receipt and
routing partition behind a strict-value Runtime State knob, the
`awaiting-integration` terminal directive, and `/aidlc --status` wiring -
with no new hooks and no approve-path changes. Its test plan pins solo
byte-identity against a pre-change golden, the sweep state machine against
fixtures recorded from the spike, and the deadlock traps named above.
## Open questions for maintainers
1. Stage placement: PR #969 ships it as Construction 3.6 with
`requires_stage: [code-generation]` and the PR's own CI as the per-unit
test gate (Build and Test becomes 3.7, CI Pipeline 3.8). Should it instead
wait for the whole-stage build-and-test on the final unit?
2. GitLab: vocabulary stays PR-everywhere and a provider seam lands only
when a concrete customer asks - acceptable?
3. Should the greenfield "establish" offers (create protection, seed
template) live in this RFC's scope or split into a follow-up?
## Appendix: consolidated claims table
Every verified claim, its verdict, and the design consequence (five-probe spike, 2026-08-26)
## Detection (probe 1)
| Claim | Verdict | Design consequence |
|---|---|---|
| Ruleset protection detail (approvals, dismiss-stale, merge methods, checks, merge queue) readable at plain read level via `rules/branches/` | CONFIRMED | Primary detection source |
| Classic protection detail readable below admin | REFUTED | Admin-only; GraphQL `branchProtectionRules` returns a SILENT empty list at read level on provably-protected repos - never infer "unprotected" from it; this is the one ask-the-human fallback |
| "Any protection at all" readable at read level | CONFIRMED | `branches/` `.protected` boolean, covers classic AND rulesets |
| Merge methods + auto-merge readable at read level | CONFIRMED (GraphQL only) | REST allow_* fields null below admin; `autoMergeAllowed` needs raw GraphQL (not a gh repo view field in 2.96.0) |
| PR template discoverable | CONFIRMED (GraphQL only) | `pullRequestTemplates` is case-insensitive; path probing missed our own repo's lowercase template |
| CODEOWNERS discoverable | CONFIRMED | Contents API chain .github/ then root then docs/ |
| Effective policy = one layer | REFUTED | Union of classic + rulesets; they DISAGREE on aidlc-workflows main (code-owner review, squash-only pin); v2's approval gate is classic-only |
| Every repo can have protection | REFUTED | Free-plan private repos 403 on protection/rulesets/auto-merge; detect needs an "absent-protection" tier |
## Offline (probe 4)
| Claim | Verdict | Design consequence |
|---|---|---|
| gh fails fast offline | PARTIAL | Dial-refused ~50ms, but packet-drop hangs 30s and DNS/stall-after-connect are uncovered; gh has NO timeout env var. Rule: wrap every call in `timeout 10`, map exit 124 to a tool-owned "GitHub unreachable" message (timeout kills with zero stderr), classify fast dial-error stderr as offline, open every sweep with one connectivity probe |
## Sweep field semantics (mining + probe 2 + probe 5)
| Claim | Verdict | Design consequence |
|---|---|---|
| reviewDecision is trustworthy standalone | REFUTED | A MERGED PR can read CHANGES_REQUESTED (real: #902, review landed 78s post-merge). Rule: terminal state FIRST, review logic only on OPEN |
| latestReviews = latest verdict per reviewer | REFUTED twice | Goes EMPTY when re-request pending (real: #879); collapses to latest-per-author regardless of state, so a COMMENTED review hides a standing CR (probe 2). Rule: fold full `pulls/N/reviews` chronologically per author, ignore COMMENTED; never use latestReviews |
| Stale approvals detectable | CONFIRMED | `commit_id` vs headRefOid; REST/GraphQL only (absent from gh pr view --json reviews); 21 of 100 recent real PRs merged on stale approvals |
| Dismissal history readable from reviews endpoint | REFUTED | Endpoint OVERWRITES verdict with DISMISSED; original verdict/who/why only in timeline `review_dismissed` event |
| dismiss_stale=true dismisses all reviews on push | REFUTED (asymmetry) | Only APPROVALS are dismissed; CHANGES_REQUESTED never auto-dismisses; post-dismissal reviewDecision = REVIEW_REQUIRED; a dismissed approval also neutralizes the same author's earlier CR |
| dismiss_stale=false: CR survives fix-push | CONFIRMED | Review stays pinned to old commit; decision stays CHANGES_REQUESTED |
| Same-actor APPROVE supersedes own CR | CONFIRMED | No dismissal needed (also observed across close/reopen, probe 5) |
| Closed vs merged distinguishable | CONFIRMED | REST `state` is closed for BOTH; use `merged` bool or GraphQL MERGED; closing does NOT clear reviews, so CLOSED + CHANGES_REQUESTED = "died after rejection" without history digging |
| mergeStateStatus usable immediately | REFUTED | UNKNOWN ~15s after every change and permanently on terminals; poll until non-UNKNOWN, never a verdict; real-world vocabulary collapses to {DIRTY, BLOCKED}; drafts read BLOCKED (check isDraft) |
| Bot re-requestable as reviewer | REFUTED | REST returns HTTP 200 and SILENTLY drops the bot; GraphQL rejects the Bot id. Rules: re-request targets humans only; verify reviewRequests after every write (success codes prove nothing) |
## Feedback round + auto-merge (probe 2)
| Claim | Verdict | Design consequence |
|---|---|---|
| CR review vs COMMENTED review vs issue comment distinguishable | CONFIRMED | Endpoint + state field; only CR moves reviewDecision - the formal-CR-only trigger rule is implementable |
| Bot approval satisfies required approvals | CONFIRMED | Testbed mechanics validated end to end |
| Armed auto-merge is inert until approval | CONFIRMED (hazard) | Arming accepted while REVIEW_REQUIRED, SURVIVES request-changes rounds and pushes, fires unattended ~30s after requirements clear, attributed to the arming user. Sweep state machine must handle "merged mid-revision-round"; vindicates observe-only (AIDLC never arms) |
## Stacking (probe 2)
| Claim | Verdict | Design consequence |
|---|---|---|
| Child PR auto-retargets when parent branch deleted post-merge | REFUTED | Deletion CLOSES the child (base_ref_deleted then closed, 1s apart). Rules: `gh pr edit --base` the child BEFORE parent branch deletion (verified safe twice); repos with delete-branch-on-merge auto-close children the instant a human merges - detect reads deleteBranchOnMerge and stacking disables when on; recovery = restore ref at old SHA + reopen + edit-base |
| Stack clean under merge commits (after manual retarget) | CONFIRMED | Child shows only its own commits/files, merges cleanly |
| Stack broken under squash | CONFIRMED | Phantom parent commits/files corrupt review scope even when byte-identical; any divergence = conflicts. Stack only under merge/rebase stands |
## Multi-repo (probe 5)
| Claim | Verdict | Design consequence |
|---|---|---|
| Coordination group recoverable | CONFIRMED | `AIDLC-Coordinated:` body marker is authoritative; timeline cross-references (create-time AND edit-time) are the discovery index but cannot distinguish siblings from casual mentions; create-then-backfill-URLs is safe |
| Partial-merge + reject-after-merge states observable | CONFIRMED | {MERGED, APPROVED} + {CLOSED, CHANGES_REQUESTED} pair = the exact halt-and-ask payload; key "side done" on state, never mergeStateStatus (UNKNOWN on terminals) |
| Reopen-after-close viable recovery | CONFIRMED | Branch survives close; reviews persist across reopen; approve-then-merge works |
## Engine seams (probe 3)
| Claim | Verdict | Design consequence |
|---|---|---|
| Gate carve-out needed for platform-resolved approval | REFUTED (good news) | Settlement = sweep-verified RECEIPT (unit-complete pattern, receipt + evidence); the single human gate still fires at all-settled; ZERO approve-path changes |
| Paused semantics reusable for integrating | REFUTED | Paused is whole-loop stop + approval blocker; integrating needs skip-and-continue; the integrating checkpoint must read TERMINAL to activeUnitCheckpoint or `unit start` refuses every next unit (pipeline deadlock) |
| Sweep-at-routing is hook-safe | REFUTED as stated | Stop hook runs `orchestrate next` EVERY turn end (10s timeout); sweep inside next() must hard-skip under AIDLC_STOP_HOOK_PROBE (already-branched pattern) or live only in --status |
| v1 core footprint small + dormant | CONFIRMED | 7 knob-gated touches; stage ships scopes:[] = SKIP in every grid cell, activated per intent; hooks have zero network today (invariant to preserve); no new hooks |
| Per-unit block includes build-and-test | REFUTED | Per-unit Construction ends at code-generation (build-and-test runs once, not per unit); pr-integration requires code-generation; the PR's own CI is the per-unit test gate, workflow build-and-test remains the whole-stage backstop - RFC states this explicitly |
Contributor guide
Research direction
This RFC is design rationale for the implementation already posted as PR #969, not an unclaimed starter task. If reviewing it, read the referenced aidlc-bolt.ts merge-dispatch contract, the proposed pr-integration stage, and aidlc-pr.ts; compare the implementation with the RFC's evidence, feedback, routing, and settlement requirements. Done means the PR and its open questions are resolved by maintainers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, github, typescript
- Domain
- ci-cd, developer-experience, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 20/100