Skip to content

Captures and instances

Markdown for agents

Many architectures have a set of folders with the same shape: features, modules, packages. Each folder must stay apart from the others. A capture lets one part describe all of these folders, and still tell them apart.

A capture is a name in braces in a path pattern. It takes one folder name from the path of each file.

Pattern File Captures
src/features/{feature}/** src/features/cart/views/page.tsx feature = cart
src/{area}/{module}/** src/billing/invoices/invoice.ts area = billing, module = invoices

Braces with a comma are not a capture. src/{ui,lib}/** matches src/ui or src/lib.

An instance is the files with the same captures

Section titled “An instance is the files with the same captures”

An instance is the set of files that have the same value in each capture of their part. With the pattern src/{area}/{module}/**, this repository has three instances of the part module:

  • Directorysrc/
    • Directorybilling/
      • Directoryinvoices/ instance billing/invoices
        • invoice.ts
        • number.ts
      • Directorypayments/ instance billing/payments
        • refund.ts
    • Directoryshipping/
      • Directoryinvoices/ instance shipping/invoices
        • label.ts

billing/invoices and shipping/invoices are two instances. They have the same module, but a different area.

This config keeps the modules apart with one shorthand:

archangel.config.ts
import { defineConfig } from '@enhance-eng/archangel';
export default defineConfig({
parts: {
module: 'src/{area}/{module}/**',
},
rules: ({ isolate }) => [
isolate('module', {
because: 'A module changes alone. Modules talk through events, not imports.',
examples: {
rejected: ['src/sales/orders/a.ts imports src/sales/quotes/b.ts'],
},
}),
],
});

archangel explain shows the instance of the file, and the instance of each import:

Terminal
$ npx archangel explain src/billing/invoices/invoice.ts
src/billing/invoices/invoice.ts
part: "module", instance "billing/invoices"
groups: none
rules that apply: isolate(module)
isolate(module): files in module may not import module in a different instance (judges its imports and imports of it)
imports:
line 1 './number' -> src/billing/invoices/number.ts ("module", instance "billing/invoices")
line 2 '../payments/refund' -> src/billing/payments/refund.ts ("module", instance "billing/payments") breaks isolate(module)
imported by: none

The import on line 1 stays in the instance, so isolate permits it. The import on line 2 goes to another instance, so isolate refuses it:

Terminal
$ npx archangel check
error[isolate]: "billing/invoices" of "module" imports "billing/payments" of "module" through "../payments/refund", and instances of "module" may not import each other
--> src/billing/invoices/invoice.ts:2:24
|
2 | import { refund } from '../payments/refund';
| ^^^^^^^^^^^^^^^^^^^^ move what both need into an element that instances of "module" may share
|
= note: rule: isolate(module)
= note: because: A module changes alone. Modules talk through events, not imports.
checked 5 files and 3 imports: 1 problem
For what a code means, run `archangel docs isolate`.

The rule has no name, so archangel names it after the rule itself: isolate(module). Use this name in an exception, in the baseline and in off.

These rules use instances:

Rule What it does with instances
A table row A row may import files of its own part or kind in the same instance. An import of another instance of its own name needs the name in the row’s list.
isolate('feature') An instance may not import another instance of the same part.
entry('feature', 'index.ts') Code outside an instance may import it only through its entry files.
mayOnlyImport When files is one part with a capture, the rule permits imports in the same instance only. To permit each instance, add the part to the list.
instance: 'same' or 'different' An import condition then applies only to imports in the same instance, or only to imports between two instances. files must be a part with a capture.
mustContain Each instance must have a file that matches the pattern.

So the table row feature: ['shared'] reads: “a feature may import shared, and files of its own feature”. Write feature: ['shared', 'feature'] to also permit imports between two features.

To write a rule about one instance only, select it by its captures:

rule('login-no-react', {
files: { element: 'feature', capture: { feature: 'login' } },
mayNotImport: { packages: ['react'] },
examples: { rejected: ['src/features/login/a.ts imports react'] },
})

isolate needs a part with a capture, because without one the part has only one instance. If you name a part without a capture, archangel shows the error rule-needs-capture. With defineConfig, the TypeScript compiler finds this error before archangel runs.