Using the workspace
Shipping: from commit to merged
Dus takes a finished change the rest of the way. It commits, pushes, opens the pull request, watches CI, fixes failures, answers reviews and merges, and you decide what goes into main.
Writing the code is half the job. Dus also does the other half: it commits the change, pushes it, opens a pull request, watches CI, reads failing logs, fixes them, answers review comments and merges. You decide whether the change goes into your main branch.
Say "ship it", "open a PR" or "merge it", or ask for a change and then "ship it".
What Dus does by itself
-
Commit. Dus writes a message that says what changed and why. It doesn't commit a secret it finds without asking you. It won't commit while conflict markers are left in a file.
-
Push. Dus only pushes feature branches. It never force-pushes, never rewrites a branch that was already pushed, and never pushes straight to
main. -
Open the pull request. The description starts with the task's Proof of Work: what was checked, how, and the commands to run the checks again.
-
Watch CI. Dus keeps working or chatting while CI runs, and it is told when the checks finish. In the conversation, the pipeline shows as one row that updates in place: each check, its state and a link. A row turns red when a check fails and opens by itself.
-
Fix failures. Dus reads the failing job's log, trimmed to the failure with secrets masked, and sorts the failure into one of four kinds:
- code: Dus fixes it, adds a test that would have caught it, and pushes;
- flaky or environment: Dus re-runs the failed jobs once;
- pre-existing: it fails on
maintoo, so Dus tells you instead of hiding it.
After three rounds (you can change this), Dus stops and tells you what is wrong.
-
Answer reviews. Dus reads each review comment. It either applies the comment or replies with why not.
-
Keep up with main. When
mainmoves on, Dus merges it into the branch. Conflicts are resolved one whole file at a time, keeping both sides' changes.
What you decide
Merging into main. When the checks pass and the branch rules are met (required checks, required approvals, merge queue), Dus asks you on a Merge into main card. The card shows what is merged, the checks, the approvals and how to undo it:
- Merge merges this pull request.
- Merge, and let Dus merge green PRs here saves that choice for supported host flows; GitHub still asks for approval on each exact verified head and base.
- Not now leaves the pull request open.
Branch protection always applies. Dus never bypasses a required check or review. For GitHub, Dus requires independent verification of the PR head and prospective merge result, asks for explicit approval, then uses GitHub's merge API with the expected head commit. If a merge queue is required, Dus stops until queue verification is available. For other hosts, a project's own merge command (such as node scripts/gate/merge.mjs <branch>) may be used when its instructions name one.
Some things always ask, in every permission mode:
- a change that weakens a check (a skipped or deleted test, a loosened assertion, a CI step allowed to fail);
- turning on branch protection or required reviews for everyone.
Dus never weakens a check just to get a green result. If Dus thinks a test is wrong, it says why and asks you. A change that skips a test shows that in its Proof of Work, and it can't be marked Verified.
Settings → Shipping
Open Settings → Shipping to change these for the open project:
- Merging into the default branch: Ask me each time (the default) or Dus merges green PRs. GitHub merges currently still ask explicitly so the approval is tied to the exact verified head and base.
- Fix rounds before asking: how many times Dus fixes and pushes again after CI fails.
- Local CI: see below.
When CI can't run
Sometimes CI can't start: a billing problem stops the jobs, no runner can take them, the build minutes are used up, or the jobs stay queued. Dus doesn't keep waiting. It tells you what happened, in the host's own words, with the steps to fix it and a link to the page where you fix it.
Local CI
While your host's CI can't run, this computer can check your commits instead.
- Open Settings → Shipping.
- Turn on Local CI.
Every few minutes, Duscode checks each new commit on the default branch. It runs your project's install and checks in a separate folder, at low priority and one run at a time. It skips a run while your computer is busy. It reports the result on the commit as a check called duscode-local-ci, so it shows in the pull request like any other check. This is free on GitHub.
Local CI keeps a log of every run. It turns itself off once your host's CI passes again, and Settings shows why it switched off.
Local CI needs a remote on GitHub, GitLab or Bitbucket, because that's where it reports the result.
Checking pull requests before they merge
Turn on Also check open pull requests and Local CI checks each open pull request into the default branch twice: on the pull request's own commit, and on the commit you would get by merging it into the latest default branch. The second one catches a change that only breaks once it meets what has landed since. Drafts wait until they're ready.
Each check runs the same steps your hosted CI runs: install from the lockfile, typecheck, the full test suite, and (when the project has them) the release audit and the committed third-party notices check. Every result is kept with the commands, each one's exit code, the Node version and platform, and the commit it ran on. A merge result also notes which base it was built on.
The results show on the pull request under their own names, never as a hosted run:
duscode-local-ciis the pull request's own commit.duscode-local-ci/merge-resultis the merge result. GitHub doesn't know that commit, so it's reported on the pull request's own commit and says what it was merged onto.
When a check fails, Local CI looks at the default branch's own recorded result for the same command. If the default branch already fails it, the description says "already fails on main", so you can tell a problem the pull request didn't cause from one it did. With no record for the default branch, the failure counts as new.
Letting Local CI stand in for hosted CI
By default Local CI only reports. If you want a merge to rest on it while hosted CI can't run, turn on Local CI counts for merging. Duscode then decides a merge from four states:
- Hosted CI passed. Hosted jobs passed and independent typecheck, full tests and release audit passed on both the PR head and its prospective merge result.
- Local CI passed. Hosted jobs could not give a verdict, but independent verification passed on both exact commits and the branch has no unmet required check.
- Checks failed. A genuinely required check failed, or a hosted job failed without an outage. Local CI cannot override a required failure.
- Unable to run. Required checks have not passed, branch rules are unknown, or independent verification is missing or stale. Nothing merges.
Even when Local CI passed, it only counts where the branch's own rules accept it. If the branch requires hosted checks (such as check and release-audit), a Local CI status doesn't satisfy them. Duscode says so and stops. If GitHub explicitly reports the branch is unprotected, that is evidence of no branch protection. If the branch flag and rule APIs are inaccessible, the rules remain unknown and Duscode stops.
The previous merge decision treated every failed Actions job as required, so billing failures blocked an otherwise legitimate merge on an unprotected branch. It also relied on short status descriptions instead of the full Local CI history. The current decision reads GitHub's actual requirements, preserves optional hosted failures as failures, checks full command results and logs for the exact head and current base, and rejects a change to either commit after approval. After GitHub merges, Duscode confirms the PR state, main commit, merge parents and verified tree, then records the evidence. If any confirmation fails, it reports the merge as unverified rather than claiming completion.
Local CI can support a merge when GitHub confirms the branch is unprotected, or when every check the protected branch actually requires has passed. Duscode never changes the branch rules to get a merge through.
When hosted jobs cannot start, Dus can run ship verify for the current PR. It checks the exact PR head and the prospective merge against the current base immediately, even if scheduled Local CI is off or waiting for a less busy machine. It installs dependencies, runs typecheck, the full test suite, release audit and configured checks in isolated worktrees, and retains command results, logs and published statuses. A changed head or base invalidates the evidence. To let this local result count for a merge, the owner must turn on Local CI counts for merging; the separate merge approval is still required on GitHub. A failed required check, unknown rules or an unverified result still blocks the merge.
No CI yet
If the project has no CI, Dus can write a config for you: a GitHub Actions workflow, a .gitlab-ci.yml or a bitbucket-pipelines.yml. It builds the config from the checks your project already runs, with the right runtime version, a cached install and one step for each check. Dus shows you the config and asks before adding it.
Hosts
- GitHub uses your
ghlogin. Rungh auth loginonce in a terminal. - GitLab uses your
glablogin. Runglab auth loginonce. - Bitbucket uses the email and API token you saved in Settings → Apps & connectors → Bitbucket.
Duscode stores no host passwords or tokens of its own.