Monorepos
Describe the parts of a monorepo to dmx, so that work on one part stays focused on that part.
A monorepo keeps the code for several services or packages in one repository. This section shows how to describe those parts to dmx, so that when it works on one of them, it reads the memory, runs the tests, and follows the rules that belong to that part.
If your repository holds a single service, you can skip this section.
This page introduces the ideas. Setting up components then walks through applying them to a repository. These pages assume you've used dmx before: that you know how it takes a piece of work through loops, and keeps what it learns about the project in a memory bank.
The example
Every page in this section uses the same example: Acme, a payments company, and its monorepo. The repository holds three parts, each owned by a different team.
ledger is a Go service that records every account's balance. The ledger team owns it.
payments is a Go service that charges customers. Each time it takes a payment, it calls ledger's API to record the money moving. The payments team owns it.
billing-ui is a web app where customers see their invoices and pay them. It calls payments' API. The billing team owns it.
So billing-ui uses payments, and payments uses ledger. On disk, the repository looks like this:
acme/
├── services/
│ ├── ledger/ the ledger service
│ └── payments/ the payments service
├── web/
│ └── billing-ui/ the billing web app
├── .github/workflows/ CI for the whole repository
├── go.work links the two Go services
└── Makefile builds and tests everythingThe last three entries don't belong to any one part. They're shared by the whole repository.
Components
A component is a part of the repository that dmx treats as a unit. Usually it's one service or package, owned by one team. Acme has three components: ledger, payments and billing-ui.
You declare a component with a folder under .dmx/components/, named after the component. The folder holds a component.yaml file:
# .dmx/components/ledger/component.yaml
description: Ledger service, records account balances
paths: ["services/ledger/**"]description says what the component is, in a line. paths says which files make up the component; the next section explains it.
The folder is also where everything else about the component lives. Most importantly, it holds the component's memory: memory bank files, such as systemPatterns.md, that record how the component works. dmx reads a component's memory only when the work involves that component.
The memory bank files at the top of .dmx/ stay where they are. They're the repository-wide memory bank, for what's true of the repository as a whole.
Paths
A component's paths list the files that belong to it, as patterns relative to the root of the repository. services/ledger/** means "everything under services/ledger/".
Each file belongs to at most one component. services/ledger/rounding.go belongs to ledger, and web/billing-ui/src/Invoice.tsx belongs to billing-ui.
A file that no component's paths match is a repo-wide file. At Acme, go.work, the Makefile and the CI workflows are repo-wide. A change to a repo-wide file can affect any component, so dmx treats it with that in mind. For example, it runs a wider set of tests.
Dependencies
One component depends on another when it uses it. At Acme, payments depends on ledger, because payments calls ledger's API. You record that in payments' component.yaml:
# .dmx/components/payments/component.yaml
description: Payment processing service
paths: ["services/payments/**"]
depends_on: [ledger]billing-ui, in turn, has depends_on: [payments].
A dependency is a warning sign that a change in one component can break another. When ledger changes, payments might stop working, and through payments, billing-ui might too. dmx uses dependencies to decide whose tests to run after a change, and whose memory is worth reading during one.
dmx doesn't work dependencies out for itself. You declare them, so they say what your team knows to be true.
How dmx uses components
In dmx, each piece of work is a ticket: a bug to fix or a change to make, taken from spec through to pull request. Suppose Acme's payments team picks up a ticket to fix how refund fees are rounded.
When the ticket starts, dmx records which components it's about, in this case payments. That list is called the ticket's components, and dmx uses it throughout the ticket:
- What the agent reads. The agent reads the repository-wide memory bank and payments' memory, plus ledger's memory, because payments uses ledger. It doesn't read billing-ui's.
- Which tests run. Before work moves on, dmx runs payments' tests and the tests of everything that depends on payments, here billing-ui.
- Which checks apply. If the payments team has added checks of its own, they run on this ticket.
- Where the ticket is filed. If the payments team tracks its work in its own Jira project, the ticket goes there.
- Who reviews what. What the agent learns about payments goes into payments' memory, in a folder the payments team owns.
If the branch also touches ledger's code, ledger joins the ticket's components, and all of the above extends to it.
Next steps
- Setting up components: declare the components of your own repository, step by step.
- Working on a ticket: follow a ticket through a repository with components.
- Tests: how dmx chooses which tests to run, and how to keep them reliable.
- component.yaml reference: every setting, in one place.