cli
CLAUDE.md73 starsChanged 21 days agoApache-2.0View on GitHub
Archgate is a CLI tool for AI governance via Architecture Decision Records (ADRs) — combining human-readable docs with machine-checkable rules. The CLI dogfoods itself via ADRs in .archgate/adrs/. AI features are delivered as a Claude Code plugin (../plugins/claude-code), not via direct API calls. bun run validate must pass before any task is considered complete. Fail-fast pipeline: lint → typecheck → format:check → test → ADR check → knip → build check. Mirrors CI in .github/workflows/code-pull-request.yml. Config-based hooks in .githooks run…
Files
CLAUDE.md
Archgate is a CLI tool for AI governance via Architecture Decision Records (ADRs) — combining human-readable docs with machine-checkable rules. The CLI dogfoods itself via ADRs in .archgate/adrs/. AI features are delivered as a Claude Code plugin (../plugins/claude-code), not via direct API calls.
Technology Stack
- Runtime: Bun — not Node.js compatible (minimum version enforced in
src/cli.ts) - Language: TypeScript (strict mode, ESNext, ES modules)
- CLI framework: Commander.js (
@commander-js/extra-typings) - Linter: Oxlint | Formatter: Oxfmt | Dead exports: Knip | Commits: Conventional Commits
Commands
bun run src/cli.ts <command> # run CLI locally
bun run lint # oxlint
bun run typecheck # tsc --build
bun run format # oxfmt --write
bun run format:check # oxfmt --check
bun run test # all tests (not bare `bun test` — picks up --timeout; see GEN-003)
bun run knip # dead export detection
bun run validate # MANDATORY: lint + typecheck + format:check + test + ADR check + knip + build check
bun run build:check # verify build compiles (CI builds binaries via release workflow)
bun run commit # conventional commit wizardValidation Gate
bun run validate must pass before any task is considered complete. Fail-fast pipeline: lint → typecheck → format:check → test → ADR check → knip → build check. Mirrors CI in .github/workflows/code-pull-request.yml.
Git Hooks (Git 2.54+)
Config-based hooks in .githooks run validation locally before commits and pushes:
- pre-commit: lint + typecheck + format:check (~15s)
- pre-push: full
bun run validate(~60s, mirrors CI)
Activate once per clone:
git config --local include.path ../.githooksOpt out of a specific hook: git config --local hook.<name>.enabled false. Skip all hooks for a single commit: git commit --no-verify.
Agent Memory
Claude Code sessions in this repo keep a small persistent memory at .claude/agent-memory/archgate-developer/ (index: MEMORY.md).
It is deliberately small. Governance lives in ADRs, companion .rules.ts, the oxlint plugins in lint/, and the test suite — all re-validated on every bun run validate. Memory holds only what no check can reach: Claude Code harness behaviour, GitHub state, external-tool quirks, and the agent's own working process. Anything a rule could enforce belongs in the rule, which stays true; a memory entry is validated by nobody and rots silently.
Before adding an entry, ask which layer should carry it instead: a static syntax invariant belongs in a custom oxlint rule, executable behaviour in a test, cross-file governance in an ADR companion .rules.ts, and behaviour every archgate user shares in a built-in CheckResult diagnostic. Only when none of those can reach it does it belong here. Nothing here is required reading for contributors or tools without access to it.
Architecture
Commands
Entry point: src/cli.ts (shebang #!/usr/bin/env bun). Commands registered via register*Command(program). See src/commands/ for all command implementations — each file exports a register*Command(program) function.
Key Paths
~/.archgate/— CLI cache;~/.archgate/bin/— binary install location.archgate/adrs/— ADRs;.archgate/lint/— linter rulessrc/formats/— Zod schemas + types (adr.ts,rules.ts)src/helpers/— utilities (paths, log, git, templates, adr-writer, etc.)tests/— mirrorssrc/; fixtures intests/fixtures/
Formats & Validation
Zod schemas are the single source of truth. Types derived via z.infer<> — never define separate interfaces. Use safeParse(). Reuse AdrFrontmatterSchema.shape.* to avoid duplicating enums.
Distribution
Archgate ships as a compiled binary (bun build --compile) published to GitHub Releases, plus thin shim packages for each supported ecosystem — npm (shims/npm/archgate.cjs), PyPI, RubyGems, Maven, NuGet, and Go (all under shims/). A shim bundles no binary: on first run it detects the platform, downloads the matching binary from GitHub Releases, verifies its SHA256 checksum, caches it to ~/.archgate/bin/, and forwards to it (see ARCH-017). Runtime dependencies (commander, inquirer, zod) are bundled into the compiled binary, so they belong in devDependencies, not dependencies.
Conventions
- Commands export
register*Command(program), handle I/O only — no business logic - OS: macOS, Linux, and Windows
- Output:
styleText()fromnode:util;--jsonfor machine-readable; auto-compact JSON in agent contexts (non-TTY, non-CI); no emoji - Exit codes: 0 = success, 1 = violation, 2 = internal error, 130 = user cancellation (SIGINT)
- Deps: minimal; prefer Bun built-ins (see ARCH-006)
Toolchain
See .prototools for pinned tool versions. Minimum user-facing Bun version is enforced in src/cli.ts.
Self-Governance ADRs
The CLI dogfoods itself — see .archgate/adrs/ for the full list of ADRs and their companion .rules.ts files. Read them before making architectural changes.
ADR Format
YAML frontmatter (id, title, domain, rules, optional files). Sections: Context, Decision, Do's and Don'ts, Consequences, Compliance, References. Companion .rules.ts exports a plain object satisfies RuleSet.
Adding a New Editor Target
Editor integrations share the EditorTarget union. Adding a new editor requires coordinated edits — missing any one breaks detection, init, or tests:
src/helpers/init-project.ts— extendEditorTargetunion,EDITOR_LABELS, theconfigureEditorSettingsswitch, and (when authenticated install applies) thetryInstallPluginbranchsrc/helpers/plugin-install.ts— addis<Editor>CliAvailable()and any install/download helper. For tarball-based editors (no marketplace CLI), useinstallEditorPluginBundle()— it handles directory creation, old-file cleanup, and tarball extraction in one call. If the editor also ships a GUI/Desktop distribution with no CLI binary at all, add a broaderis<Editor>Available()that OR's in a shared-state fallback (e.g., the editor's user-scope config directory already existing) — see the opencode Desktop note belowsrc/helpers/editor-detect.ts— append to thePromise.alland the returned arraysrc/commands/init.ts— extendEDITOR_DIRS,SIGNUP_EDITORS, the--editor.choices([...] as const), andprintManualInstructionssrc/commands/plugin/install.ts— extend.choices([...] as const)and add a case toinstallForEditor+ the manual-instructionscatchsrc/commands/plugin/url.ts— extend.choices([...] as const)and branch before the URL ternary- Tests that assert the exact choice list:
tests/commands/plugin/install.test.ts,tests/commands/plugin/url.test.ts, andtests/helpers/editor-detect.test.ts(length + id order) - To make the editor's transcripts readable, also extend
src/helpers/harness-detect.ts(DetectedHarnessunion and theSIGNALStable), thelistFor/readForswitches insrc/helpers/session-context-auto.ts, and theEDITORSchoice list insrc/commands/session-context.ts
A session reader must be verified against a live session of the editor, not against fixtures. Every harness records a turn where the agent only issued tool calls as a message whose prose content is empty; a reader that maps events to transcript entries without checking emits it as a blank entry. Skip a turn whose content preview is empty after trimming. The same class of gap hides in event shapes: one editor's CLI and desktop distribution can write the same conversation in different shapes, and a reader taught only one of them returns zero turns for the other. Fixtures agree with whatever the reader already assumes, so neither is visible until a real transcript is read.
Two detections answer different questions — keep them apart. editor-detect.ts asks whether an editor is installed, so a config directory or a binary on PATH is proof. harness-detect.ts asks which editor is running this process, where only a variable the editor injects into its subprocesses counts; an installed-but-idle editor must never register there. Reaching for copilotConfigDir() or a PATH probe in the runtime path would make every user of that editor look like they are inside it.
User-scope editors (e.g., opencode) write to a path resolved in paths.ts rather than the project tree — configureEditorSettings returns that path for the init summary and the real work happens in tryInstallPlugin.
Match the target editor's actual path resolution — don't assume Windows conventions. opencode uses xdg-basedir, which falls back to ~/.config on all platforms (Windows: C:\Users\<user>\.config\…, not %APPDATA%\…). opencodeAgentsDir() must mirror that exactly. Verify the editor's own path helper before writing a resolver for a new user-scope editor.
opencode ships two distributions — CLI detection alone misses the Desktop app. The Electron-based Desktop app (@opencode-aidesktop on Windows) ships no CLI binary, so isOpencodeCliAvailable() (PATH check) can't detect it. Both distributions share opencodeConfigDir() (~/.config/opencode/), so isOpencodeAvailable() in plugin-install.ts also treats that directory's existence as installed. All three call sites (editor-detect.ts, init-project.ts, commands/plugin/install.ts) use the broader isOpencodeAvailable() — use it for any new opencode-gated behavior too.
GitHub Copilot has the same two-distribution shape — and a declarative install path. The Copilot desktop app ships no CLI binary but shares copilotConfigDir() (~/.copilot/) with the copilot CLI, so copilot-gated behavior must use isCopilotAvailable() (PATH check OR config dir exists), never isCopilotCliAvailable() alone. The plugin install is declarative-first: installCopilotPlugin() always writes the marketplace + plugin declaration into ~/.copilot/settings.json (extraKnownMarketplaces + enabledPlugins — Copilot's marketplace registry and auto-install list, read by both distributions on startup; an existing archgate entry is overwritten to correct stale URLs), then additionally runs copilot plugin install archgate@archgate when the CLI is on PATH. mode: "declarative" in the result means the user must restart the Copilot app for the install to take effect. mode: "existing" means the CLI install failed but copilot plugin list already shows archgate@archgate: on Windows the running Copilot app locks the installed plugin's files, so a reinstall fails with os error 5 (access denied) and the existing install is kept. The settings merge lives in copilot-user-settings.ts (user-scope); copilot-settings.ts is project-scope (.github/copilot/).
In this repositoryOne other file it gives its agents.
- .cursor/rules/archgate-governance.mdcCursor rule
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

