Skip to content

Git and review ​

dqx works with the repositories you already have. It never commits, never pushes and never changes a branch you are on without being asked. What it adds: it fetches the code a project needs, shows which branch every checkout is on, installs the git hooks your team agreed on, gives you copies of a project to try a branch in, and reviews pull requests with Claude before you post anything.

This page is the map. Each part links to the page that goes into detail.

Repositories in a project ​

A project is either single, where the application repository is the project, or multi, a wrapper holding a checkout per site. A multi project can span several repositories: a site each, and the wrapper with the Docker setup. See single and multi layout.

dqx install clones the repositories the project's .dq/config.yml names, on the branch each site says (main when it says none). A repository marked optional that you cannot read is skipped rather than stopping the install. See creating projects.

Bitbucket's SSH host: ssh.bitbucket.org ​

Bitbucket Cloud moves Git over SSH from bitbucket.org to ssh.bitbucket.org. From 12 November 2026 SSH on bitbucket.org is blocked in brownouts, a few hours a day, and on 10 December 2026 it stops for good. HTTPS (https://bitbucket.org/...) and the API are not affected, and the host keys stay the same.

bash
dqx bitbucket-ssh                   # what would change, on this machine and in every known project
dqx bitbucket-ssh --apply           # make the changes
dqx bitbucket-ssh --apply --files   # also composer.json, composer.lock, package.json, .gitmodules

--apply sets this machine up first:

  • git: a global insteadOf rule that sends git@bitbucket.org: and ssh://git@bitbucket.org/ to the new host, for lockfiles and dependencies that still name the old one.
  • ~/.ssh/config: a Host bitbucket.org block gets ssh.bitbucket.org beside it, so its key is used there too. The old file is kept as config.dqx-<date>.bak.
  • ~/.ssh/known_hosts: the new host's keys, each checked against the fingerprints Bitbucket published. A key that does not match writes nothing.

Then the remotes of every repository in every project, and the repository: URLs in .dq/config.yml. dqx sync does the same for one project, but only once ssh knows the new host: before that a moved remote would fail every fetch with "Host key verification failed". dqx doctor says what is still on the old host. The project files that --files changes are the project's own: commit them there. Inside the app container, git sends the old host to the new one by itself.

Branches on the dashboard ​

Every checkout on a card shows its branch. Behind open are the repository on Bitbucket or GitHub, on that branch, and your desktop git client if one is installed. See getting to a project.

Git hooks ​

bash
dqx githooks              # write or refresh the git hooks of every site and package
dqx githooks --site api   # one site, and no package
dqx githooks --dry-run    # say what would change
dqx githooks --off        # remove the hooks dqx wrote, and only those

Without configuration a site gets a pre-commit that runs Pint and Prettier over the staged files. .dq/config.yml says otherwise, per site and per package, for any hook git knows:

yaml
sites:
  - domains:
      primary: api.example.test
    hooks:
      preCommit:                            # shorthand for git.pre-commit
        - vendor/bin/pint ${STAGED_PHP}
      git:
        commit-msg:
          - script: .dq/hooks/ticket-key.sh  # the team's own script
        pre-push:
          - php artisan test                # a command, run in the site
  • A command runs in the site through dqx exec, so it works on every backend.
  • A script is a bash file in the wrapper repository, run on your machine with git's own arguments. commit-msg needs that: the message file git passes is a path on your machine. The path is relative to the project and may not leave it.
  • A hook name git does not know is refused when the config is read, so a typo does not silently do nothing.
  • In a pre-commit, ${STAGED_PHP}, ${STAGED_JS} and ${STAGED_FILES} hold the staged files, and a command naming one runs only when that list has something in it.

A formatter in the pre-commit (Pint, Prettier) rewrites the staged files on disk. The hook stages them again afterwards, so the commit holds the formatted version. Only files that were staged whole: a file staged in part (git add -p) keeps out the lines you left out, so the hook leaves it alone and names it (dqx: staged in part, so not staged again: …). Stage its formatted lines yourself.

A hook dqx writes carries a stamp on its second line (# dqx-hook: <hash>). Edit the hook by hand and the stamp no longer matches: dqx githooks and dqx adopt then leave it alone and say so, and dqx githooks --force replaces it, keeping yours as pre-commit.bak. Put a lasting change in .dq/config.yml instead. A hook from a dqx version before the stamp is refreshed once, which adds it.

A hook dqx wrote and the config no longer states is removed. A hook dqx did not write is left alone unless you pass --force, which keeps the old one as .bak.

On and off. install.gitHooks switches hooks for the project. A site's install.gitHooks and a package's gitHooks override it either way; absent, they follow the project. Switched off, a checkout gets no hook and loses the ones dqx wrote; somebody else's stays.

Worktrees. Every git worktree of a checkout runs the checkout's hooks, so a hook works out at commit time where it is:

  • In the checkout itself, it runs in the site as described above.
  • In a worktree inside the checkout (.claude/worktrees/…, say), which the site's container sees too, the commands run at the site's root with the worktree's path in front of each staged file: the site's Pint formats the worktree's files.
  • Anywhere else, it runs dqx exec --here from the worktree. In a copy made with dqx clone --worktree that is the copy's own site; outside every site's checkout dqx refuses, as no container holds those files. Run the formatters yourself there and commit with --no-verify, or put the worktree inside the checkout.

A hook written by an older dqx does not know this yet: dqx githooks rewrites it.

The other commands follow the same line. In a worktree inside the checkout, dqx exec, dqx php, dqx composer and dqx npm run where you stand, and dqx artisan and dqx test at the worktree's root, not the checkout's: the worktree needs its own vendor/ and .env (dqx composer install from there). A worktree beside the checkout is refused rather than run as the project's only site.

Packages get hooks too: see packages.

dqx install runs the same step where a checkout has none yet (dqx install --only hooks runs just that), and dqx adopt installs them when it brings an older project in: see older projects.

Copies and worktrees ​

dqx clone makes a running copy of a project beside the original, on its own URLs, for trying a branch or a pull request without touching your working copy. A checkout in the copy is either its own clone or a git worktree that shares the original's history: see copies of a project. An agent gets the same through dqx agent-env: see AI assistants.

Reviewing pull requests ​

A pull request on Bitbucket Cloud or GitHub.com gets four kinds of help, and they end in one post: nothing reaches the pull request until you pick it and press Post.

what it doeswho writesClaude sees
AI reviewClaude reads the changed lines and proposes findings; you pick, edit and post themClaude, edited by youthe pull request, read-only, in a worktree
Human reviewyou read the change in the order it runs (flows), open context and whole files, and comment on any line, removed lines includedyounothing, unless you map the flows
Change mapthe human review's Map view: the flows as lines of a metro map, with checked arrows, the unchanged code the change depends on, Find uses and ✨ tracesClaude, checked with git grepthe same as mapping the flows; one quick run per ✨ trace. Find uses and reviewed marks stay on your machine
Suggest with AIon the lines you pick, Claude drafts an explanation and the replacement code into your comment editoryou, drafted by Claudeone file and the picked lines, no tools
  • Reviewing a pull request: the terminal, what Claude can and cannot do, profiles (quick, thorough, security), settings and the Bitbucket token.
  • Reviewing pull requests in the dashboard: the review tab, choosing per run, editing and posting.
  • Human review: read a pull request yourself, in the order the change runs rather than alphabetically, open context and whole files, and post your own comments together with the AI findings. dqx review --map prints the same flows in the terminal.
  • Suggesting a change with AI: a code change for the lines you pick, as a one-click suggestion on GitHub and a code block on Bitbucket.

Everything that sends code to Claude (a review, mapping the flows, a ✨ trace, a suggestion) asks once per repository first: only for repositories where the CTO approved Claude Code. A human review on its own sends nothing; the diff and whole files are read from your own checkout. What dqx posts says where it came from: an AI finding ends in (via dqx/claude), a comment you drafted with Claude in (drafted with dqx/claude) unless you untick it, and your own words carry no footer.

Logins ​

  • Git itself (clone, fetch) uses your SSH key, as it always did.
  • Bitbucket's API (pull requests, comments) needs a personal API token: dqx auth --set bitbucket walks you through it and keeps it in the Keychain.
  • GitHub goes through your own gh auth login; dqx keeps no GitHub token.

Why the SSH key is not enough, and how the token is kept: see a Bitbucket token.

For agents ​

Through the MCP server an agent can read the pull requests and a finished review's findings, and work through them in the code. It can never start a review or post one: both stay with you. See AI assistants.

dqx v1.78.0