Code, changes and reviews

Version 2 · Updated

Architecture rules and plans

branchscope.json layers and rules, PLAN.md plans, and how reviews apply them.

Rules make a review flag what matters to your codebase. They live in branchscope.json at the repository root, next to the code they describe, and are versioned with it.

Layers and rules#

A layer is a named selection of graph nodes. A rule says which edges from one layer to another are forbidden (deny) or need a look (review).

{
  "layers": {
    "controllers": { "nodes": ["dotnet:src/Billing.Api|Billing.Api.Controllers*"] },
    "data": { "nodes": ["dotnet:src/Billing.Api|Billing.Api.Data*"] },
    "tables": { "kinds": ["table"] },
    "personal": { "tags": ["pii"] }
  },
  "rules": [
    { "id": "controllers-no-data", "description": "Controllers go through services.", "from": "controllers", "to": "data", "edges": ["imports", "references"], "effect": "deny" },
    { "id": "table-writes", "to": "tables", "edges": ["writes"], "effect": "review" },
    { "id": "pii-reads", "to": "personal", "edges": ["reads"], "effect": "review" }
  ]
}

Layers:

  • What a layer selects: by node-ID globs (nodes, where * matches anything), by kinds, and by tags. A node must match every field given.
  • Finding node IDs: they're in the graph explorer, under each node's name.

Rules:

  • from and to name layers. Leave one out to mean "any node".
  • edges limits the rule to some edge kinds.
  • effect is deny (a violation) or review (flagged for a person).
  • Channels: a layer can select channels by kind ("kinds": ["channel"]) or by ID ("nodes": ["msg:events:invoice-*"]). With such a layer, invoice-events, the rule { "id": "web-no-invoice-events", "from": "web", "to": "invoice-events", "edges": ["publishes"], "effect": "deny" } leaves invoice events to the billing service.

What a change is responsible for:

  • New: an edge that matches in the head but didn't in the base. This includes an old edge that starts matching because a node gained a tag or moved into a layer.
  • Existing: matches in both. These are counted, not blamed, so you can add a rule to a codebase that already breaks it.
  • Fixed: matched in the base, not in the head.
  • Renamed and moved code: an edge that was only renamed or moved stays existing.

The target's rules apply: reviews use the rules of the target branch, so a change can't remove the rule it breaks. A change that edits the rules is noted, and its rules apply once it's merged.

Plans#

A change can say what it means to touch, in a branchscope block in PLAN.md at the repository root:

# Plan

Add invoice creation: an endpoint in the API, a client call in the web app.

```branchscope
{
  "touches": { "files": ["src/Billing.Api/**", "apps/web/**"], "layers": ["controllers"] },
  "mustNotTouch": { "files": ["branchscope.json"], "layers": ["data"] }
}
```
  • Dimensions: files (path globs: * within a segment, ** across segments), nodes (ID globs) and layers.
  • mustNotTouch: anything the change touches that this matches is shown as changes it rules out.
  • touches: anything the change touches outside it is shown as outside what it says it touches. That's for information; plans drift honestly.
  • What counts as touched:
    • every changed file, both sides of a rename included;
    • every added, removed or changed node, both IDs of a renamed one included;
    • the calling side of every changed edge.

Why plans matter for agents: an agent can write its plan first, and the review shows whether the code it produced stayed inside it.

Other settings in branchscope.json#

KeyWhat it's for
servicesNames HTTP services and where they're served from and reached at, so endpoints join across languages ({"billing-api": {"project": "src/Billing.Api", "baseUrls": ["http://localhost:5000"]}})
storesNames databases by EF Core context, for table IDs
httpClientsYour own HTTP wrapper functions, so calls through them become endpoint requests
generatedPath globs of committed generated code, tagged generated
brokersNames message brokers, each with its system, optionally the projects that use it and channel names of configuration keys, so channels join across languages and repositories ({"events": {"system": "kafka", "names": {"env:INVOICE_TOPIC": "invoice-created"}}})
messageClientsYour own publish and subscribe wrapper functions (TypeScript), so calls through them become channel edges

An invalid branchscope.json fails the graph build with a message, rather than guessing.