Skip to content

ADR Schema

Every Archgate ADR is a Markdown file stored in .archgate/adrs/ with YAML frontmatter that defines the decision’s identity and scope. This page documents the frontmatter schema, markdown section structure, and validation behavior.

The YAML frontmatter block sits between --- delimiters at the top of the file.

---
id: ARCH-001
title: Command Structure
domain: architecture
rules: true
files: ["src/commands/**/*.ts"]
---
FieldTypeRequiredDescription
idstringYesUnique identifier. Must be non-empty. Convention: PREFIX-NNN (e.g., ARCH-001).
titlestringYesHuman-readable title of the decision. Must be non-empty.
domainenumYesDomain category. One of: backend, frontend, data, architecture, general.
rulesbooleanYesWhether this ADR has a companion .rules.ts file with automated checks.
filesstring[]NoGlob patterns that scope which files the rules apply to.

The ADR identifier. By convention, it uses the domain prefix followed by a zero-padded sequence number (e.g., ARCH-001, BE-003). The archgate adr create command generates IDs automatically.

Any non-empty string is valid, but following the prefix convention keeps ADRs organized and sortable.

A short, descriptive name for the architectural decision. Displayed in archgate adr list output and used as the heading when AI agents reference the ADR.

Groups related ADRs together. The domain also determines the ID prefix used by archgate adr create.

Set to true when this ADR has a companion .rules.ts file. When archgate check runs, it skips ADRs where rules is false.

An optional array of glob patterns that scope the rule’s file coverage. When present, ctx.scopedFiles in the rules file only contains files matching these patterns. When absent, all project files are in scope.

files: ["src/commands/**/*.ts"]

Multiple patterns can be specified:

files: ["src/api/**/*.ts", "src/middleware/**/*.ts"]

Each domain maps to a prefix used in the ADR ID convention.

DomainPrefixExample ID
backendBEBE-001
frontendFEFE-001
dataDATADATA-001
architectureARCHARCH-001
generalGENGEN-001

The archgate adr create command uses this mapping to auto-generate IDs.


ADR files follow a naming convention that encodes the ID and a human-readable slug:

{ID}-{slug}.md # The document
{ID}-{slug}.rules.ts # The companion rules file (optional)

For example:

ARCH-001-command-structure.md
ARCH-001-command-structure.rules.ts

The slug is a kebab-case version of the title, auto-generated by archgate adr create.


After the frontmatter, the ADR body follows a standard section structure. While Archgate does not enforce specific sections, the following structure is recommended for consistency.

Describes the problem or situation that prompted the decision. Include alternatives that were considered and why they were rejected.

## Context
The CLI returns errors in inconsistent formats. Some commands print raw
stack traces, others print nothing, and a few use `console.error()` with
custom formatting.
**Alternatives considered:**
- **No standard** -- Let each command handle errors its own way. Simple
but leads to an inconsistent user experience.
- **Try/catch wrapper** -- A global try/catch at the CLI entry point.
Loses context about which command failed.

States the decision itself and its key constraints. This is the primary section AI agents read before writing code.

## Decision
All commands MUST use `logError()` from `src/helpers/log.ts` for error
output. Commands MUST NOT call `console.error()` directly.

Concrete, actionable guidance split into two sub-sections. These act as a quick-reference checklist for developers and AI agents.

## Do's and Don'ts
### Do
- Use `logError(message, detail?)` for all error output
- Include a suggested fix in the detail parameter when possible
- Exit with code 1 for user errors, code 2 for internal errors
### Don't
- Don't call `console.error()` directly in command files
- Don't print stack traces to users
- Don't exit without printing an error message first

Split into three sub-sections that document trade-offs.

## Consequences
### Positive
- Consistent error formatting across all commands
- Machine-parseable error output when combined with `--json`
### Negative
- Requires importing `logError` in every command file
- Cannot use built-in error formatting from libraries
### Risks
- New contributors may use `console.error()` by habit. Mitigated by the
automated rule that scans for direct `console.error()` calls.

Describes how the decision is enforced through automated rules and manual review.

## Compliance and Enforcement
### Automated Enforcement
- **Archgate rule** ARCH-002/no-console-error: Scans command files for
`console.error()` calls. Severity: error.
### Manual Enforcement
Code reviewers MUST verify:
1. Error messages are actionable and include context
2. Exit codes match the error type (1 for user, 2 for internal)

Links to related ADRs, external documentation, or design documents.

## References
- [ARCH-001 -- Command Structure](./ARCH-001-command-structure.md)
- [Node.js process.exit documentation](https://nodejs.org/api/process.html#processexitcode)

When rules: true, Archgate looks for a companion file with the same name but .rules.ts extension.

ARCH-002-error-handling.md # rules: true in frontmatter
ARCH-002-error-handling.rules.ts # companion rules file

The rules file must export a default RuleSet created via defineRules():

import { defineRules } from "archgate/rules";
export default defineRules({
"no-console-error": {
description: "Use logError() instead of console.error()",
async check(ctx) {
for (const file of ctx.scopedFiles) {
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,
fix: "Import logError from src/helpers/log and use it instead",
});
}
}
},
},
});

See the Rule API for the complete TypeScript API reference.


The YAML frontmatter is validated at parse time using a Zod schema. Invalid frontmatter causes a parse error with a descriptive message.

If a required field is missing, the ADR fails to parse:

Invalid ADR frontmatter in ARCH-001-example.md:
- domain: Required

If domain is not one of the valid values:

Invalid ADR frontmatter in ARCH-001-example.md:
- domain: Invalid enum value. Expected 'backend' | 'frontend' | 'data' | 'architecture' | 'general', received 'security'

If rules is a string instead of a boolean:

Invalid ADR frontmatter in ARCH-001-example.md:
- rules: Expected boolean, received string

ADRs that fail validation are skipped by archgate check and reported as errors.