Gå til innholdet

Skrive regler

Regler er TypeScript-kontroller som linter kodebasen din mot teamets beslutninger. De ligger i tilhørende .rules.ts-filer ved siden av ADR-markdown-filer og kjøres når du utfører archgate check.

.archgate/adrs/
ARCH-001-command-structure.md # Beslutningen
ARCH-001-command-structure.rules.ts # De automatiserte sjekkene

Hver regelfil eksporterer et standard-objekt typet med satisfies RuleSet. Hver nøkkel i rules-objektet blir en regel-ID, og hver regel har en description og en asynkron check-funksjon som mottar et kontekstobjekt.

/// <reference path="../rules.d.ts" />
export default {
rules: {
"my-rule-id": {
description: "What this rule checks",
async check(ctx) {
// Your check logic here
},
},
},
} satisfies RuleSet;

En enkelt regelfil kan definere flere regler:

/// <reference path="../rules.d.ts" />
export default {
rules: {
"first-rule": {
description: "Checks one thing",
async check(ctx) {
// ...
},
},
"second-rule": {
description: "Checks another thing",
async check(ctx) {
// ...
},
},
},
} satisfies RuleSet;

ctx-objektet som sendes til hver check-funksjon gir muligheter for fillesing, søk og rapportering. Her er en detaljert referanse med eksempler.

En matrise med filstier som matcher ADR-ens files-glob fra frontmatter. Hvis ADR-en ikke har noe files-felt, inkluderer dette alle prosjektfiler.

for (const file of ctx.scopedFiles) {
const content = await ctx.readFile(file);
// Check content...
}

Bruk ctx.scopedFiles når regelen din bare skal gjelde filer ADR-en styrer. For eksempel vil en kommandostrukturregel avgrenset til src/commands/**/*.ts bare motta kommandofiler.

En matrise med filstier som skiller seg fra grunngrenen, inkludert ikke-committede endringer i arbeidstreet (stagede, ustagede og usporede ikke-ignorerte filer). Auto-detektert som standard, eller fylt fra --staged / --base <ref>. Nyttig for inkrementell sjekking og regler for avhengigheter mellom filer.

// Incremental checking -- only validate changed files
const filesToCheck = ctx.scopedFiles.filter((f) =>
ctx.changedFiles.includes(f)
);
// Cross-file dependency -- if file A changed, file B must also change
if (ctx.changedFiles.includes("config/database.yml")) {
if (!ctx.changedFiles.includes("deploy/manifest.yml")) {
ctx.report.violation({
message: "config changed but manifest was not bumped",
file: "config/database.yml",
});
}
}

Les innholdet i en fil som en streng. Stien er relativ til prosjektroten.

const content = await ctx.readFile("src/cli.ts");

Les og parse en JSON-fil. Returnerer unknown — cast den til forventet form.

const pkg = (await ctx.readJSON("package.json")) as {
dependencies?: Record<string, string>;
};

Søk i en enkelt fil med et regulært uttrykk. Returnerer en matrise med GrepMatch-objekter, hver med egenskapene file, line, column og content.

const matches = await ctx.grep(file, /console\.error\(/);
for (const match of matches) {
ctx.report.violation({
message: "Use logError() instead of console.error()",
file: match.file,
line: match.line,
});
}

Søk på tvers av flere filer som matcher et glob-mønster. Returnerer en flat matrise med GrepMatch-objekter fra alle matchende filer. Filer ignorert av .gitignore ekskluderes som standard. Sett respectGitignore: false i ADR-frontmatter for å inkludere dem.

const matches = await ctx.grepFiles(/TODO:/, "src/**/*.ts");
for (const match of matches) {
ctx.report.warning({
message: "TODO comment found",
file: match.file,
line: match.line,
});
}

Finn filer etter glob-mønster. Returnerer en matrise med filstier relative til prosjektroten. Filer ignorert av .gitignore ekskluderes som standard. Sett respectGitignore: false i ADR-frontmatter for å inkludere dem.

const testFiles = await ctx.glob("tests/**/*.test.ts");

Parse en kildefil til dens språknative AST. Støttede språk: "typescript", "javascript", "python" og "ruby". TypeScript og JavaScript parses i prosessen til en ESTree Program; Python og Ruby starter systemtolkens parser fra standardbiblioteket som en underprosess. Kaster ved parsefeil eller manglende tolk — den returnerer aldri null.

const program = await ctx.ast("src/cli.ts", "typescript");

Et valgfritt tredje argument dekker to flere behov:

  • { rev: "base" } parser filen ved den grunnleggende git-revisjonen i stedet for arbeidstreet — kombiner den med en parsing av arbeidstreet for å oppdage om den kjørbare strukturen har endret seg (se nedenfor).
  • { comments: true } fester en strukturert comments-matrise til treet (typescript/javascript/python), slik at kommentarstyringsregler kan jobbe mot kommentar-tokener i stedet for regulære uttrykk.

Se Strukturelle sjekker med ctx.ast() nedenfor for komplette eksempler, og Regel-API-referansen for den returnerte formen per språk og de fullstendige alternativene.

Les en fils kildekode ved den grunnleggende git-revisjonen — fellespunktet (merge base) mellom --base-referansen og HEAD. Returnerer null når ingen basis er løst (ingen --base) eller filen ikke fantes ved basisen (en lagt-til fil), slik at en enkelt null-sjekk dekker begge tilfellene. For en strukturell sammenligning, foretrekk ctx.ast(path, language, { rev: "base" }).

const before = await ctx.fileAtBase("src/config.ts");
if (before !== null && before !== (await ctx.readFile("src/config.ts"))) {
// filen endret seg siden basisen
}

Rapporteringsgrensesnittet med tre alvorlighetsgradsmetoder:

  • ctx.report.violation(detail) — feil-alvorlighetsgrad (exit-kode 1, blokkerer CI)
  • ctx.report.warning(detail) — advarsel-alvorlighetsgrad (logges, men blokkerer ikke)
  • ctx.report.info(detail) — informasjon (logges for synlighet)

Hver metode aksepterer et objekt med:

FeltTypePåkrevdBeskrivelse
messagestringJaHva bruddet er
filestringNeiSti til den problematiske filen
linenumberNeiLinjenummer for bruddet
fixstringNeiForeslått løsning (vises til utvikleren)
ctx.report.violation({
message: "Command file must export a register*Command function",
file: "src/commands/check.ts",
line: 5,
fix: "Add: export function registerCheckCommand(program: Command) { ... }",
});

Den absolutte stien til prosjektets rotkatalog. Nyttig når du trenger å konstruere absolutte stier.

Regulære uttrykk fungerer for overflatemønstre, men kommer til kort for strukturelle spørsmål som “inneholder denne filen bare re-eksporter?” eller “er dette en naken except:-klausul?” — flerlinjesetninger, kommentarer og strenginnhold slår alle beina under linjebasert matching. ctx.ast() parser en fil til et ekte syntakstre slik at regelen din kan sjekke strukturen direkte.

Det returnerte treet er språknativt, ikke enhetlig på tvers av språk:

  • TypeScript / JavaScript — en ESTree Program parset i prosessen av meriyah. TypeScript transpileres først, så typenivå-syntaks (interface, typealiaser, export type { ... } from) fjernes fra treet; en fil som bare inneholder setninger på typenivå parses til en tom Program-body.
  • Python — treet fra standardbibliotekets ast-modul serialisert til JSON. Hver node er et objekt med et _type-felt pluss nodens egne felt og posisjonene lineno / col_offset.
  • RubyRipper.sexp-utdata fra standardbibliotekets Ripper: nestede matriser som ["program", [["command", ...]]] med [line, column]-posisjonspar.

ctx.ast() kaster ved parsefeil eller manglende tolk — den returnerer aldri null. Kastet er isolert til regelen som feiler og vises som en regelkjøringsfeil (exit-kode 2), slik at et ødelagt miljø dukker opp som en synlig feil i stedet for en falsk godkjenning.

En barrel-fil er en index.ts der hver setning på toppnivå er en re-eksport. Med ESTree-Program-et blir det spørsmålet en direkte sjekk av setningstyper i stedet for en linjematchende heuristikk:

/// <reference path="../rules.d.ts" />
export default {
rules: {
"no-barrel-files": {
description: "index.ts files must not be pure re-export barrels",
async check(ctx) {
const indexFiles = ctx.scopedFiles.filter((f) =>
f.endsWith("/index.ts")
);
const checks = indexFiles.map(async (file) => {
const program = await ctx.ast(file, "typescript");
const isBarrel =
program.body.length > 0 &&
program.body.every(
(node) =>
node.type === "ExportAllDeclaration" ||
(node.type === "ExportNamedDeclaration" && node.source !== null)
);
if (isBarrel) {
ctx.report.violation({
message: `Barrel file detected: ${file} contains only re-exports`,
file,
fix: "Delete the barrel and import directly from the source modules",
});
}
});
await Promise.all(checks);
},
},
},
} satisfies RuleSet;

Fordi TypeScript transpileres før parsing, fjernes export type { ... } from-re-eksporter — en barrel som bare inneholder type-re-eksporter parses til en tom body, som body.length > 0-vakten hopper over. Hvis du også trenger å flagge rene type-barrels, kombiner AST-sjekken med en tekstsjekk. Den samme transpileringen forskyver også posisjoner: loc-linjenumre refererer til den transpilerte teksten, ikke den opprinnelige .ts-filen, så en "typescript"-regel må finne konstruksjonen på nytt i den opprinnelige kildekoden (for eksempel med ctx.readFile() og indexOf) før den rapporterer en line — eller utelate line.

Gå gjennom ESTree-treet etter CallExpression-noder der callee er identifikatoren require. En rekursiv gjennomgang av objektverdier og matriser dekker alle nodetyper uten å måtte liste dem opp:

/// <reference path="../rules.d.ts" />
function findRequireCalls(node: unknown, lines: number[]): void {
if (Array.isArray(node)) {
for (const item of node) findRequireCalls(item, lines);
return;
}
if (node === null || typeof node !== "object") return;
const n = node as EsTreeNode;
const callee = n.callee as EsTreeNode | undefined;
if (
n.type === "CallExpression" &&
callee?.type === "Identifier" &&
callee.name === "require"
) {
if (n.loc) lines.push(n.loc.start.line);
}
for (const value of Object.values(n)) findRequireCalls(value, lines);
}
export default {
rules: {
"no-require-in-esm": {
description: ".mjs files must not call CommonJS require()",
async check(ctx) {
const files = await ctx.glob("src/**/*.mjs");
const checks = files.map(async (file) => {
const program = await ctx.ast(file, "javascript");
const lines: number[] = [];
findRequireCalls(program, lines);
for (const line of lines) {
ctx.report.violation({
message: "CommonJS require() call in an ES module",
file,
line,
fix: "Use a static import or await import() instead",
});
}
});
await Promise.all(checks);
},
},
},
} satisfies RuleSet;

loc.start.line-rapporteringen her er gyldig bare fordi "javascript" parser den opprinnelige kildekoden utranspilert — en "typescript"-regel må i stedet finne linjen i den opprinnelige kildekoden, siden dens loc refererer til det transpilerte resultatet.

I Pythons ast-modul er en except:-klausul en ExceptHandler-node der type-feltet holder uttrykket for unntaket som fanges — en naken except: har "type": null. Gå gjennom JSON-treet etter den formen:

/// <reference path="../rules.d.ts" />
function findBareExcepts(node: unknown, lines: number[]): void {
if (Array.isArray(node)) {
for (const item of node) findBareExcepts(item, lines);
return;
}
if (node === null || typeof node !== "object") return;
const n = node as PythonAstNode;
if (n._type === "ExceptHandler" && n.type === null) {
if (n.lineno !== undefined) lines.push(n.lineno);
}
for (const value of Object.values(n)) findBareExcepts(value, lines);
}
export default {
rules: {
"no-bare-except": {
description: "Python code must not use bare except: clauses",
async check(ctx) {
const files = await ctx.glob("**/*.py");
const checks = files.map(async (file) => {
const tree = await ctx.ast(file, "python");
const lines: number[] = [];
findBareExcepts(tree, lines);
for (const line of lines) {
ctx.report.violation({
message:
"Bare except: catches every exception, including SystemExit",
file,
line,
fix: "Catch a specific exception class, e.g. except ValueError:",
});
}
});
await Promise.all(checks);
},
},
},
} satisfies RuleSet;

Ripper.sexp representerer puts "hello" som ["command", ["@ident", "puts", [1, 0]], [...args]][line, column]-paret ligger inne i @ident-tokenet. Gå gjennom de nestede matrisene etter den formen:

/// <reference path="../rules.d.ts" />
function findPutsCalls(node: unknown, lines: number[]): void {
if (!Array.isArray(node)) return;
const [kind, first] = node;
if (
kind === "command" &&
Array.isArray(first) &&
first[0] === "@ident" &&
first[1] === "puts"
) {
const [line] = first[2] as [number, number];
lines.push(line);
}
for (const child of node) findPutsCalls(child, lines);
}
export default {
rules: {
"no-puts": {
description: "Ruby code must use the application logger, not puts",
async check(ctx) {
const files = await ctx.glob("app/**/*.rb");
const checks = files.map(async (file) => {
const sexp = await ctx.ast(file, "ruby");
const lines: number[] = [];
findPutsCalls(sexp, lines);
for (const line of lines) {
ctx.report.violation({
message: "puts writes to stdout directly",
file,
line,
fix: "Replace puts with logger.info",
});
}
});
await Promise.all(checks);
},
},
},
} satisfies RuleSet;

Ripper bruker en annen nodeform for varianten med parenteser — puts("hello") vises under et method_add_arg / fcall-par i stedet for command — så en produksjonsregel ville matchet fcall-tokenet på samme måte.

Noen regler bryr seg ikke om en fils nåværende tilstand, men om hva som har endret seg. Et vanlig tilfelle: frafalle et krav (en versjonsheving, en endringsloggoppføring) når en endring bare gjelder dokumentasjon — den kjørbare strukturen er urørt, og bare kommentarer eller formatering har flyttet seg. ctx.ast(path, language, { rev: "base" }) parser filen ved den grunnleggende git-revisjonen, slik at du kan sammenligne den mot parsingen av arbeidstreet. Kommentarer er fraværende fra ESTree- og Python-ast-formene — men det er ikke nodeposisjonene: loc- / lineno-feltene forskyves når en kommentar eller tom linje flytter linjene under seg, så en rå serialisering skiller seg selv for en ren dokumentasjonsendring. Sammenlign en posisjonsfri projeksjon av de to trærne i stedet. (Python-dokstrenger er vanlige strengnoder i ast-treet, så en endret dokstreng er en reell treendring — fjern dem også hvis regelen din skal behandle dokumentasjonsendringer som nøytrale.)

export default {
rules: {
"bump-required-on-behavior-change": {
description: "A .py change that alters behavior must bump the version",
async check(ctx) {
// Fjern posisjonsmetadata slik at bare kjørbar struktur sammenlignes: en
// kommentar eller tom linje forskyver lineno/col_offset uten å endre
// atferd. (Fjern dokstrenger her også hvis dokendringer skal være nøytrale.)
const POS = ["lineno", "col_offset", "end_lineno", "end_col_offset"];
const structure = (node: unknown): unknown => {
if (Array.isArray(node)) return node.map(structure);
if (node && typeof node === "object") {
return Object.fromEntries(
Object.entries(node)
.filter(([k]) => !POS.includes(k))
.map(([k, v]) => [k, structure(v)])
);
}
return node;
};
const changed = ctx.changedFiles.filter((f) => f.endsWith(".py"));
for (const file of changed) {
// Ingen basismotpart (f.eks. en lagt-til fil) -- hopp over.
if ((await ctx.fileAtBase(file)) === null) continue;
const before = await ctx.ast(file, "python", { rev: "base" });
const after = await ctx.ast(file, "python");
if (
JSON.stringify(structure(before)) !==
JSON.stringify(structure(after))
) {
ctx.report.violation({
message: `${file} changed behavior -- bump the version`,
file,
});
}
}
},
},
},
} satisfies RuleSet;

archgate check finner en basis automatisk (eller tar en eksplisitt --base <ref>), og ctx.changedFiles fylles fra den — inkludert ikke-committet arbeid. Per-fil-vakten er det som gjør regelen trygg: når ingen basis er løst, eller for en fil som ikke fantes ved basisen, returnerer ctx.fileAtBase() null, og løkken hopper over den filen. Fordi basisparsing går gjennom ctx.ast() og dens git-tilgang er innebygd i Archgate, kjører regelen aldri git eller en tolk selv — noe regelsandkassen uansett ville blokkert.

ctx.ast(path, language, { comments: true }) fester en comments-matrise til treet, slik at kommentarpolicy-regler jobber mot strukturerte tokener (type, value, loc) i stedet for linje-for-linje regulære uttrykk. Kommentarposisjoner er nøyaktige mot originalkilden selv for TypeScript. Støttet for typescript, javascript og python.

const tree = await ctx.ast(file, "typescript", { comments: true });
for (const comment of tree.comments ?? []) {
if (comment.value.split("\n").length > 10) {
ctx.report.warning({
message: "Comment block too long -- move rationale to an ADR",
file,
line: comment.loc.start.line,
});
}
}

Hver regel kan sette en standard alvorlighetsgrad i konfigurasjonen sin. Alvorlighetsgraden bestemmer hvordan brudd behandles:

AlvorlighetsgradExit-kodeOppførsel
error1Blokkerer CI, må fikses
warning0Logges, men blokkerer ikke
info0Informasjon, logges for synlighet

Sett alvorlighetsgraden i regeldefinisjonen:

export default {
rules: {
"my-rule": {
description: "...",
severity: "warning",
async check(ctx) {
// Violations from this rule are warnings, not errors
ctx.report.violation({ message: "..." });
},
},
},
} satisfies RuleSet;

Hvis severity utelates, er standardverdien error.

Du kan også rapportere med ulike alvorlighetsgrader innenfor samme regel ved å bruke ctx.report.violation(), ctx.report.warning() og ctx.report.info() direkte.

Hver regel har en kjøringstidsgrense på 30 sekunder. Hvis en regel overskrider denne grensen, behandles den som en feil. Dette forhindrer at uendelige sjekker blokkerer pipelinen.

Hold reglene raske ved å:

  • Bruke ctx.grepFiles() i stedet for å lese hver fil manuelt
  • Bruke Promise.all() for å sjekke filer parallelt
  • Avgrense regler med files-feltet i frontmatter for å begrense antall filer som behandles

fix-feltet er en valgfri streng som vises til utvikleren sammen med bruddmeldingen. Den beskriver hvilken handling som må utføres for å løse problemet. Fikser brukes ikke automatisk — de er veiledning.

ctx.report.violation({
message: `Unapproved dependency: "chalk"`,
file: "package.json",
fix: "Use styleText() from node:util instead of chalk",
});

Når den vises, kommer fiksen under bruddmeldingen:

ARCH-006/no-unapproved-deps
package.json
Unapproved dependency: "chalk"
Fix: Use styleText() from node:util instead of chalk
  1. Bruk Promise.all() for parallelle filsjekker. Når du sjekker flere filer uavhengig, prosesser dem parallelt i stedet for sekvensielt.

    // Good: parallel
    const checks = files.map(async (file) => {
    const content = await ctx.readFile(file);
    // ...
    });
    await Promise.all(checks);
    // Avoid: sequential
    for (const file of files) {
    const content = await ctx.readFile(file);
    // ...
    }
  2. Bruk ctx.changedFiles for inkrementell sjekking. ctx.changedFiles fylles automatisk med grenforskjellen pluss ikke-committede endringer i arbeidstreet (eller stagede filer med --staged). Filtrer ctx.scopedFiles mot den for å bare sjekke det som er endret, eller bruk den direkte for regler om avhengigheter mellom filer. Du trenger ikke å gardere deg mot et tomt snitt: når endringssettet ikke er tomt og ingenting i det matcher ADR-ens files-globs, hoppes ADR-en over i sin helhet og regelen kalles aldri.

  3. Hold reglene fokusert på ett anliggende. En regel som sjekker både navnekonvensjoner og importmønstre bør deles i to regler med separate ID-er.

  4. Bruk ctx.grepFiles() fremfor manuell iterering. Når du søker etter et mønster på tvers av mange filer, er ctx.grepFiles() mer effektivt enn å lese hver fil og kjøre et regulært uttrykk.

  5. Gi handlingsbare fix-meldinger. En fiks som “Ikke gjør dette” er ikke nyttig. Fortell utvikleren nøyaktig hva som skal gjøres i stedet.

  6. Filtrer bort ikke-relevante filer tidlig. Hvis regelen din bare gjelder for bestemte filer innenfor omfanget, filtrer ctx.scopedFiles før prosessering:

    const commandFiles = ctx.scopedFiles.filter((f) => !f.endsWith("index.ts"));
  7. Håndter manglende filer elegant. Hvis regelen din leser en spesifikk fil som package.json, pakk lesingen i en try/catch og returner tidlig hvis filen ikke eksisterer.

Det finnes to måter å håndtere unntak i Archgate: undertrykkelse på motornivå (fungerer med alle regler automatisk) og egendefinerte direktiver på regelnivå (implementert av regelforfatteren for domenespesifikke opt-outs).

Archgate støtter inline archgate-ignore-kommentarer som undertrykker brudd uten å endre selve regelen. Motoren analyserer disse kommentarene og filtrerer matchende brudd før rapportering.

Neste-linje-undertrykkelse: undertrykker bruddet på den umiddelbart følgende linjen:

// archgate-ignore ARCH-006/no-unapproved-deps legacy-avhengighet, migrering planlagt for Q3
import chalk from "chalk";

Filnivå-undertrykkelse: undertrykker alle matchende brudd hvor som helst i filen:

// archgate-ignore-file ARCH-005/test-mirrors-src generert fil, ingen manuell test

Flere regler: stable kommentarer for å undertrykke mer enn én regel på samme linje:

// archgate-ignore ARCH-006/no-unapproved-deps legacy-avhengighet
// archgate-ignore ARCH-003/use-style-text tredjepartsbibliotek håndterer farger
import chalk from "chalk";

Sammenhengende undertrykkelseskommentarer sikter alle mot den første ikke-undertrykkelseslinjen etter blokken.

Formatet er ADR-ID/rule-id etterfulgt av en begrunnelse. Begrunnelsen er påkrevd. En undertrykkelse uten begrunnelse ignoreres og gir en advarsel:

[suppression] Suppression for ARCH-006/no-unapproved-deps is missing a reason src/foo.ts:1

Både // og # kommentarstiler støttes, slik at undertrykkelser fungerer i TypeScript, JavaScript, YAML, Python, shell-skript og andre filtyper reglene dine kan skanne.

Markdown-filer (.md, .mdx) godtar i tillegg HTML-kommentarformen, som forblir usynlig på den gjengitte siden — # ville blitt en overskrift og // ville vist seg som brødtekst:

<!-- archgate-ignore ARCH-021/no-escaped-backtick bakoverfnutten er sitert fra en shell-utskrift -->
<!-- archgate-ignore-file ARCH-021/ai-writing-signs ordrett utdrag fra en ekstern RFC -->

Kommentaren må fylle hele linjen, og direktiver inne i inngjerdede kodeblokker ignoreres slik at dokumenterte eksempler forblir uvirksomme.

For domenespesifikke opt-outs kan regelforfattere implementere sine egne kommentarbaserte direktiver inne i check-funksjonen. Dette mønsteret gir regelen full kontroll over direktivsyntaksen, plassering og validering.

// I din .rules.ts-fil:
async check(ctx) {
const files = await ctx.glob("src/components/**/*Connected.tsx");
for (const file of files) {
const content = await ctx.readFile(file);
// Støtt opt-out-direktiv øverst i filen
if (/^\/\/\s*@no-presentational:/u.test(content.trimStart())) continue;
// ... regellogikk som kan rapportere et brudd ...
ctx.report.violation({
message: "Manglende presentasjonskomponent",
file,
fix: 'Legg til "// @no-presentational: <begrunnelse>" øverst i filen for å gjøre opt-out',
});
}
}

Utvikleren gjør opt-out ved å legge til direktivet i filen sin:

// @no-presentational: denne komponenten bare omdirigerer, ingen UI å rendre
import { useNavigate } from "react-router";
TilnærmingBest forHvem kontrollerer
archgate-ignoreAd-hoc-unntak for alle reglerUtvikleren som bruker regelen
Egendefinert direktivDomenespesifikke opt-outs med strukturerte begrunnelserRegelforfatteren

Bruk archgate-ignore når en utvikler trenger å undertrykke et engangsbrudd. Bruk egendefinerte direktiver når opt-outen er et førsteklasses konsept i regelens domene, for eksempel å markere en komponent som med vilje uparet, eller en fil som automatisk generert.

  • Vanlige regelmønstre — Kopier-og-lim-inn-mønstre organisert etter kategori: avhengighetshåndtering, importrestriksjoner, filstruktur, kodekvalitet, databaseskjema og arkitekturgrenser.
  • Regel-API-referanse — Fullstendig referanse for alle regel-API-typer og -funksjoner.
  • CI-integrasjon — Koble archgate check til pipelinen din for å håndheve regler på hver PR.