Skip to content

fix(check): user-error handling overhaul — file filters, error boundaries, UserError guards - #467

Merged
rhuanbarreto merged 7 commits into
mainfrom
fix/check-user-error-boundaries
Jul 11, 2026
Merged

fix(check): user-error handling overhaul — file filters, error boundaries, UserError guards#467
rhuanbarreto merged 7 commits into
mainfrom
fix/check-user-error-boundaries

Conversation

@rhuanbarreto

@rhuanbarreto rhuanbarreto commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

What

Started as a fix for Sentry CLI-5 and grew (by review feedback) into a consistent user-error handling overhaul across the CLI.

1. The CLI-5 fix (9c7ba33)

archgate check died with UserError: Path "...\Temp\archgate-check.json" escapes project root — access denied, reported to Sentry as an internal crash (exit 2) when an agent harness passed a temp-file path as a file argument. Three compounding defects:

  • src/engine/runner.ts — out-of-root [files...] filter paths are now skipped with a warning instead of failing the run. Filter paths are never read — only intersected with ADR-scoped files — so an out-of-root path can never match. The rule-context security sandbox (ctx.readFile/grep/glob/ast) is unchanged.
  • src/commands/check.ts — full ARCH-012 error boundary around the action body (previously only loadRuleAdrs was covered).
  • src/cli.tsmain().catch() now handles UserError as an expected failure (logError + exit 1, no Sentry capture), mirroring handleCommandError(). Protects every command.

2. ARCH-012 rule hardening (9c7ba33, 85c7420)

ARCH-012/async-action-error-boundary rewritten on ctx.ast():

  • Flags top-level awaited statements sitting outside the action's try block (awaits of exitWith/handleCommandError exempt as sanctioned exit paths)
  • Flags implicit-return arrow actions (.action(async () => run())) — they structurally cannot contain a boundary (CodeRabbit)
  • Reports ctx.ast() parse failures instead of silently passing the file (CodeRabbit)
  • Dogfooding immediately caught a second live instance of the CLI-5 escape pattern: await resolveBaseRef(...) outside the try in review-context.ts — fixed

3. UserError guard sweep (27c1443, 301c2d6) (review feedback from @rhuanbarreto)

The logError(...) + await exitWith(1) + return guard appeared at ~40 sites in 18 files, including a 10× copy-pasted project-root check:

  • New requireProjectRoot() helper in helpers/paths.ts — returns the root or throws UserError, handled by each action's boundary (exit 1, no Sentry). Replaces all 11 project-root guards.
  • Converted: adr create/show/list/update/sync/import, adr domain add/remove/list, review-context, check, plugin install ("Not logged in"), adr show ("ADR not found")
  • Guards that sat before the try moved inside it → genuine full-body boundary coverage
  • Deliberately unchanged (19 sites): login/init catch-block TLS hint translation, upgrade's custom Sentry flow, session-context/* (Result-style helper errors + cwd-fallback semantics)

Governance updates

  • ARCH-002: main().catch() must treat UserError as expected (exit 1, never Sentry)
  • ARCH-012: boundary must cover the ENTIRE action body; Compliance section documents the new AST enforcement and its remaining sync-statement limitation
  • ARCH-011: codifies requireProjectRoot() for project-requiring commands; bans hand-rolled guards

Reviewer notes

  • New regression tests: tests/engine/runner-file-filter.test.ts; assertions updated in check-action.test.ts and review-context.test.ts (exit spy now sees exitWith(1, { errorKind: "user" }); standardized root-missing message)
  • Behavior changes worth noting: (1) archgate check <path-outside-root> warns and continues instead of exiting 1 — the filter can only narrow, so skipping ≡ matching nothing; (2) check/review-context's missing-project message is now the standardized "No .archgate/ directory found. Run `archgate init` first."
  • Rule verified both directions: fires on bad fixtures (missing try, escaped await, implicit-return body), silent on the clean repo
  • bun run validate passes end-to-end (lint, typecheck, format, 1444 tests, 43/43 ADR checks, knip, build); net −54 lines from the sweep

An agent harness passed a temp-file path outside the project root to
`archgate check`, and the run died with "Path ... escapes project root"
reported to Sentry as an internal crash (exit 2) instead of a user
error (Sentry CLI-5). Three compounding defects, all fixed:

- runner: out-of-root `[files...]` filter paths are now skipped with a
  warning instead of failing the run — filter paths are never read,
  only intersected with ADR-scoped files, so they can never match.
  The rule-context sandbox (readFile/grep/glob/ast) is unchanged.
- check command: full ARCH-012 error boundary around the action body
  (previously only loadRuleAdrs was covered, so runChecks errors
  escaped to main().catch()).
- cli entry: main().catch() now handles UserError as an expected
  failure — logError + exit 1, no Sentry capture — mirroring
  handleCommandError().

Follow-up hardening: the ARCH-012/async-action-error-boundary rule is
rewritten on ctx.ast() and now flags top-level awaited statements that
sit outside the action's try block (awaits of exitWith and
handleCommandError are exempt as sanctioned exit paths). Dogfooding the
new rule caught a second live instance of the same escape pattern in
review-context.ts (await resolveBaseRef outside the try), fixed here.
ARCH-002/ARCH-012 docs updated to codify both policies.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_f38bc82f-c575-448a-a75e-36280fa69c3d)

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rhuanbarreto, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7cc0c1d0-31f2-44c1-b9e2-0589d7c519af

📥 Commits

Reviewing files that changed from the base of the PR and between b7051c8 and 076f6fb.

📒 Files selected for processing (3)
  • .archgate/adrs/ARCH-011-consistent-project-root-resolution.md
  • .claude/agent-memory/archgate-developer/project_oxlint_gotchas.md
  • src/commands/adr/show.ts
📝 Walkthrough

Walkthrough

The CLI distinguishes escaped UserError instances from internal failures and applies full-body error boundaries to command actions. The async-action Archgate rule now uses AST analysis to detect awaited statements outside top-level try blocks. Project-root handling is centralized through requireProjectRoot(), and out-of-root file filters are skipped with warnings. Tests cover absolute paths, traversal paths, and filters containing only out-of-root files.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.87% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main theme: CLI user-error handling, boundary fixes, and file-filter behavior changes.
Description check ✅ Passed The description matches the changeset closely and covers the key code, rule, test, and documentation updates.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 11, 2026

Copy link
Copy Markdown

Deploying archgate-cli with  Cloudflare Pages  Cloudflare Pages

Latest commit: 076f6fb
Status: ✅  Deploy successful!
Preview URL: https://3f397235.archgate-cli.pages.dev
Branch Preview URL: https://fix-check-user-error-boundar.archgate-cli.pages.dev

View logs

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Metric Value
Lines 90.8% (7787 / 8578)
Threshold 90% minimum — met
Platforms Linux + Windows

Full HTML report available in workflow artifacts.

Per-directory breakdown
Directory Coverage Lines
src/commands/ 89.0% 1947 / 2188
src/engine/ 92.0% 1699 / 1847
src/formats/ 98.7% 148 / 150
src/helpers/ 90.9% 3993 / 4393

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.archgate/adrs/ARCH-012-command-error-boundaries.rules.ts:
- Around line 157-162: Report a diagnostic when ctx.ast(file, "typescript")
fails instead of silently returning. Update the catch block in the surrounding
rule function to call ctx.report.warning with the affected file and parse error
details, then return so processing remains safe.
- Around line 92-113: findAsyncActionBodies currently ignores implicit-return
async action handlers, allowing ARCH-012 violations to bypass validation. Update
findAsyncActionBodies to retain or report non-BlockStatement handler bodies, and
ensure the rule emits the existing “missing try-catch boundary” diagnostic
directly for expression-bodied handlers while preserving current block-body
checks for missing try/catch and escaped awaits.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 702aef5e-bd62-4ec1-b8b2-31084d239630

📥 Commits

Reviewing files that changed from the base of the PR and between e27c5f3 and 9c7ba33.

📒 Files selected for processing (10)
  • .archgate/adrs/ARCH-002-error-handling.md
  • .archgate/adrs/ARCH-012-command-error-boundaries.md
  • .archgate/adrs/ARCH-012-command-error-boundaries.rules.ts
  • .claude/agent-memory/archgate-developer/MEMORY.md
  • .claude/agent-memory/archgate-developer/project_rules_engine_internals.md
  • src/cli.ts
  • src/commands/check.ts
  • src/commands/review-context.ts
  • src/engine/runner.ts
  • tests/engine/runner-file-filter.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: CodeRabbit
  • GitHub Check: Smoke Test (Windows) / Windows
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (14)
src/commands/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-001-command-structure.md)

src/commands/**/*.ts: Each command module under src/commands/ must export a register*Command(program) function, with one command per file (or one command group via index.ts).
Command files must be thin: they should only parse arguments, call engine/helpers, and format output; business logic must live in src/engine/, src/helpers/, or src/formats/.
Command modules must not use executableDir() for command discovery, must not call .parse() themselves, and must not spawn child processes for subcommand execution.
Subcommand groups should live in nested directories with an index.ts that composes child commands (for example, src/commands/adr/index.ts).

src/commands/**/*.ts: In command files under src/commands/**/*.ts, options that need type narrowing beyond plain strings MUST use new Option() with .addOption() instead of plain .option().
In src/commands/**/*.ts, import Option from @commander-js/extra-typings alongside Command so typed options get full type inference.
In src/commands/**/*.ts, enum-like options with a fixed set of allowed values must use .choices() on an Option to provide runtime validation and compile-time type narrowing.
In src/commands/**/*.ts, options that require type conversion must use .argParser() on an Option object, not a parser function passed as the third argument to .option().
In src/commands/**/*.ts, register typed options with .addOption() rather than .option().
In src/commands/**/*.ts, pass both the choices array and default values with as const when using .choices() and .default() to preserve literal types.
In src/commands/**/*.ts, do not add manual validation for choice options (for example if (!VALID.includes(val))), because Commander handles invalid-value rejection automatically.

src/commands/**/*.ts: Commands that operate on .archgate/ project resources must use findProjectRoot() from src/helpers/paths.ts to locate the project root; direct use of `process.cw...

Files:

  • src/commands/review-context.ts
  • src/commands/check.ts
src/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-003-output-formatting.md)

src/**/*.ts: Use styleText(format, text) from node:util for all terminal colors and formatting in CLI source files; do not use raw ANSI escape codes or third-party color libraries.
Commands that produce structured results and support --json must emit machine-readable JSON to stdout with no colors or decorative formatting.
When a command supports --json, use formatJSON() from src/helpers/output.ts for JSON serialization, and pass forcePretty: true when the user explicitly provided --json.
Use isAgentContext() from src/helpers/output.ts to enable auto-JSON behavior for commands that support both human-readable and JSON output modes.
CLI output must not include emoji; use text symbols and colors instead.
Send normal command output to stdout with console.log(), and send errors, warnings, and debug messages to stderr via logError(), logWarn(), and logDebug().
Keep CLI output concise and scannable by using whitespace and alignment instead of long text blocks.
Respect NO_COLOR automatically by relying on styleText; do not add custom color-environment handling in CLI code.
Do not output progress spinners unless there is a TTY check.
Do not assume piped output means agent context when CI is set; CI runners should still receive human-readable output.

src/**/*.ts: Do not re-export symbols from another module in any source file; statements like export { X } from "./other" and export type { X } from "./other" are forbidden.
Import symbols directly from the module that defines them; do not import from a directory path such as ../formats expecting implicit index.ts resolution.

Make large production thresholds injectable via an optional parameter that defaults to the module constant, so tests can supply a small value instead of generating huge fixtures.

src/**/*.ts: In Archgate CLI TypeScript source files, all subprocess execution must use Bun.spawn with array-based arguments; Bun.$ shell template literals are forbidden.
Do not...

Files:

  • src/commands/review-context.ts
  • src/cli.ts
  • src/engine/runner.ts
  • src/commands/check.ts
**/*.{ts,tsx,js,jsx,mjs,cjs}

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-006-dependency-policy.md)

**/*.{ts,tsx,js,jsx,mjs,cjs}: Use Bun built-ins for file I/O (Bun.file, Bun.write), HTTP, subprocess execution (Bun.spawn), globbing (Bun.Glob), and testing (bun:test).
Do not use Node.js-specific APIs when Bun alternatives exist; for example, use Bun.file() instead of fs.readFile() for simple reads.
Prefer node: built-in modules such as node:util, node:path, and node:fs over npm alternatives.
Do not use utility libraries for single functions, such as importing lodash for one helper like pick.
Do not use path aliases (tsconfig paths); use relative imports with Bun's native module resolution.
Do not install packages globally during development; use bunx for one-off tools.

Files:

  • src/commands/review-context.ts
  • src/cli.ts
  • tests/engine/runner-file-filter.test.ts
  • src/engine/runner.ts
  • src/commands/check.ts
src/**/!(*platform).ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-009-platform-detection-helper.md)

src/**/!(*platform).ts: In src/ TypeScript source files, do not read process.platform directly; use src/helpers/platform.ts (isWindows(), isMacOS(), isLinux(), isWSL(), getPlatformInfo()) for all platform detection.
Use the centralized platform helper instead of duplicating OS/WSL detection logic inline anywhere in src/ TypeScript source.
When behavior differs between Linux and Windows, account for WSL by using isWSL() rather than assuming `

Files:

  • src/commands/review-context.ts
  • src/cli.ts
  • src/engine/runner.ts
  • src/commands/check.ts
src/commands/{*.ts,*/index.ts}

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-015-cli-command-documentation-coverage.md)

Every top-level CLI command registered via register*Command(program) must live in src/commands/<name>.ts or src/commands/<name>/index.ts so it can be matched to a docs page.

Files:

  • src/commands/review-context.ts
  • src/commands/check.ts
{src/commands/{*.ts,*/index.ts},docs/src/content/docs/reference/cli/!(index).mdx}

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-015-cli-command-documentation-coverage.md)

{src/commands/{*.ts,*/index.ts},docs/src/content/docs/reference/cli/!(index).mdx}: When a top-level command is added, its docs page must be added in the same change; when a top-level command is removed, its matching docs page must be deleted in the same change.
Keep command and documentation stems aligned when renaming a top-level command file; renaming src/commands/<name>.ts or src/commands/<name>/index.ts must be paired with renaming docs/src/content/docs/reference/cli/<name>.mdx.

Files:

  • src/commands/review-context.ts
  • src/commands/check.ts
{src,tests}/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/LEGAL-001-spdx-license-headers.md)

{src,tests}/**/*.ts: Every TypeScript source file in src/ and tests/ must begin with // SPDX-License-Identifier: Apache-2.0 followed by // Copyright 2026 Archgate.
If a TypeScript file has a shebang line (for example #!/usr/bin/env bun in src/cli.ts), the SPDX license header must appear immediately after the shebang.
Use single-line // comments for the SPDX header; do not use block comments (/* */) or alternate license identifiers.

Files:

  • src/commands/review-context.ts
  • src/cli.ts
  • tests/engine/runner-file-filter.test.ts
  • src/engine/runner.ts
  • src/commands/check.ts
**

⚙️ CodeRabbit configuration file

**: # 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 (>=1.2.21) — not Node.js compatible
  • 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 wizard

Validation 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 ../.githooks

Opt out of a specific hook: git config --local hook.<name>.enabled false. Skip all hooks for a single commit: git commit --no-verify.

#...

Files:

  • src/commands/review-context.ts
  • src/cli.ts
  • tests/engine/runner-file-filter.test.ts
  • src/engine/runner.ts
  • src/commands/check.ts

⚙️ CodeRabbit configuration file

**: This project uses Archgate — an AI governance framework based on
Architecture Decision Records (ADRs). The ADRs in .archgate/adrs/
are the authoritative rules for this codebase. Each ADR has a companion
.rules.ts file with automated checks that run via archgate check.

When reviewing, you must:

  1. Treat ADR violations as blocking issues, not suggestions.
  2. Cite the specific ADR ID when flagging a violation (e.g., "Violates ARCH-006").
  3. Focus on semantic and contextual violations that automated rules cannot catch —
    the .rules.ts files already cover syntactic/structural patterns.
  4. If you are unsure whether something violates an ADR, flag it as a question
    rather than approving it.

Files:

  • src/commands/review-context.ts
  • src/cli.ts
  • tests/engine/runner-file-filter.test.ts
  • src/engine/runner.ts
  • src/commands/check.ts
src/cli.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-001-command-structure.md)

src/cli.ts: The main entry point must explicitly import and register every command; no auto-discovery is allowed.
All async bootstrap logic in src/cli.ts must be wrapped in an async main() function and invoked with main().catch((err) => { logError(String(err)); process.exit(2); }); top-level await is forbidden.
The CLI must run in-process in the same Bun process as the entry point; command execution should not rely on child processes.

The CLI entry point must be src/cli.ts and use the #!/usr/bin/env bun shebang.

In main().catch(), handle UserError as an expected failure by calling logError(), exiting with code 1, and not sending it to Sentry; only other errors follow the exit-2 and captureException() path.

Files:

  • src/cli.ts
tests/**/*.test.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-005-testing-standards.md)

tests/**/*.test.ts: Use Bun's built-in test runner (bun:test) for all test files, and place tests under tests/ mirroring the src/ directory structure with <module-name>.test.ts naming.
Use temp directories (mkdtemp) for filesystem tests, and clean them up in afterEach or afterAll.
Close external SDK instances (servers, clients, transports, connections) in afterEach or afterAll, managing their lifecycle in hooks rather than inside individual test bodies.
When a test creates a temporary git repository and runs git commit, configure local user.email and user.name immediately after git init.
Test public module interfaces, not private implementation details.
Use descriptive test names that explain the expected behavior.
Every runnable test()/it() must contain at least one expect() assertion; smoke tests must make the contract explicit with expect(() => fn()).not.toThrow() or await expect(promise).resolves.toBeUndefined().
Use test.skip, test.skipIf, or test.todo for intentionally empty or disabled tests; do not use bare return or empty callbacks to skip work.
If the first expect() is being added to a previously assertion-less test file, add expect to the bun:test import.
When mocking fetch in tests, assign directly to globalThis.fetch and restore the original or use mock.restore() afterward.
Wrap spyOn() and inline mockImplementation() usage in try/finally, or create and restore spies in hooks, so mockRestore() always runs.
Only raise a per-test timeout above the global bun test --timeout 60000; never set a shorter per-test timeout.
Mock first-party modules with import * as mod plus spyOn(mod, "fn"), not mock.module().
When a test needs to redirect user-scope paths, mock os.homedir() instead of relying on HOME/Bun.env.HOME; restore the spy in test hooks.
Do not depend on network access in unit tests.
Do not leave temp files after test runs.
Do not leave external SDK instances open after tests...

Files:

  • tests/engine/runner-file-filter.test.ts
tests/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-009-platform-detection-helper.md)

In test files, use _resetPlatformCache() to simulate different platforms instead of mocking or mutating process.platform directly.

Files:

  • tests/engine/runner-file-filter.test.ts
.archgate/adrs/**

⚙️ CodeRabbit configuration file

.archgate/adrs/**: ---
id: ARCH-001
title: Command Structure
domain: architecture
rules: true
files: ["src/commands/**/*.ts"]

Context

The CLI needs a consistent pattern for defining and registering commands. As the command surface grows (init, check, adr, review-context, session-context, upgrade, clean), the registration mechanism must scale without introducing hidden coupling or making the dependency graph opaque.

Alternatives considered:

  • Auto-discovery via executableDir() — Commander.js supports automatic command discovery by scanning a directory for executable files. This eliminates manual imports but hides the dependency graph: adding or removing a command has no type-checked reference, making dead command detection impossible. It also requires each command to be a standalone executable, which prevents in-process testing and forces separate process spawning for every subcommand invocation.
  • Plugin-based registration — A plugin system where commands register themselves via a manifest or hook (similar to Oclif or Clipanion). This adds flexibility for third-party extensions but introduces significant complexity for an internal CLI with a known, finite set of commands. The indirection makes it harder to trace which code handles which command.
  • Single-file command map — Define all commands in a single file as a map of name-to-handler. Simple but creates a monolithic file that grows with every command, making merge conflicts frequent and readability poor.

The explicit register pattern strikes the right balance: each command owns its registration logic, the entry point makes all commands visible at a glance, and in-process execution enables straightforward testing without process spawning.

Decision

Commands live in src/commands/ and export a register*Command(program) function. The main entry point (src/cli.ts) explicitly imports and calls each register function. Subcommands (e.g., adr create, adr list) use nested directories wi...

Files:

  • .archgate/adrs/ARCH-002-error-handling.md
  • .archgate/adrs/ARCH-012-command-error-boundaries.rules.ts
  • .archgate/adrs/ARCH-012-command-error-boundaries.md
src/engine/runner.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-022-ast-aware-rule-context.md)

src/engine/runner.ts: Implement ctx.ast(path, language) inside createRuleContext() with all dispatch hidden from rule authors.
For language: "typescript" and "javascript", ctx.ast() must reuse the existing in-process meriyah parser; no subprocess may be spawned for these branches.
Factor the duplicated parseModule() logic into a shared helper that is used by both rule-scanner.ts and the ctx.ast() TypeScript/JavaScript branch.
For language: "python" and "ruby", ctx.ast() must use Bun.spawn with array-based arguments only, with no shell interpolation.
Before any Python/Ruby interpreter is invoked, ctx.ast() must run the guardrail sequence in order: path safety, language plausibility check, interpreter availability probe, then guarded invocation.
ctx.ast() must use the same safePath() sandboxing as readFile/glob before accessing a target file.
ctx.ast() must reject files whose extension and/or leading content do not plausibly match the requested language before running an interpreter.
ctx.ast() must probe interpreter availability once per check invocation, cache the result, and reuse the first working candidate executable name.
On Windows, the interpreter probe must consider platform-appropriate candidates in order (including py via isWindows()); on non-Windows, it must use the non-Windows candidate order.
The Python branch must run in isolated mode (python -I -c ...) to prevent the working directory from shadowing standard-library imports.
The Python and Ruby serializers must strip a leading UTF-8 BOM before parsing.
ctx.ast() must throw on missing interpreter or parse failure, and must not return null or any other sentinel value.
The thrown error messages from ctx.ast() must distinguish interpreter-unavailable failures from parse-failure errors.
ctx.ast() must not expose Bun.spawn, child_process, or any other raw subprocess primitive on RuleContext; it is the only sanctioned tooling entrypoint.
`ctx.ast(...

Files:

  • src/engine/runner.ts
src/{helpers,engine}/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-002-error-handling.md)

Do not use console.log(), console.warn(), or console.info() directly in helper or engine files; use logInfo() or logWarn() instead.

Files:

  • src/engine/runner.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: archgate/cli

Timestamp: 2026-07-11T01:13:46.707Z
Learning: Synchronous command actions and command group index files that only register subcommands are exempt from the async error-boundary requirement.
📚 Learning: 2026-06-11T12:50:28.661Z
Learnt from: rhuanbarreto
Repo: archgate/cli PR: 406
File: .claude/agent-memory/archgate-developer/feedback_prefer_tests_over_adr_rules.md:8-18
Timestamp: 2026-06-11T12:50:28.661Z
Learning: In `archgate/cli`, for markdown files under `.claude/agent-memory/`, follow the established convention: use YAML frontmatter (with a `name:` field used as the document title) and do not require a top-level `#` (H1) heading. During code review, do not flag missing first-line/first-top-level H1 headings (e.g., MD041) for these agent-memory files since markdownlint is not part of the repo’s `bun run validate` lint pipeline (oxlint/oxfmt only).

Applied to files:

  • .claude/agent-memory/archgate-developer/MEMORY.md
  • .claude/agent-memory/archgate-developer/project_rules_engine_internals.md
🔇 Additional comments (9)
src/engine/runner.ts (1)

16-16: LGTM!

Also applies to: 39-59, 431-448

tests/engine/runner-file-filter.test.ts (1)

21-28: 🩺 Stability & Availability

No git init is needed here. getGitTrackedFiles() returns null for a non-repo, and resolveScopedFiles() treats that as no gitignore filtering, so the two violation assertions still hold as written.

			> Likely an incorrect or invalid review comment.
.archgate/adrs/ARCH-002-error-handling.md (1)

53-53: LGTM!

Also applies to: 64-64

.archgate/adrs/ARCH-012-command-error-boundaries.md (1)

60-68: LGTM!

Also applies to: 89-94

src/cli.ts (1)

47-47: LGTM!

Also applies to: 195-204

src/commands/check.ts (1)

44-190: LGTM!

src/commands/review-context.ts (1)

26-55: LGTM!

.claude/agent-memory/archgate-developer/MEMORY.md (1)

44-44: LGTM!

.claude/agent-memory/archgate-developer/project_rules_engine_internals.md (1)

12-12: LGTM!

Comment thread .archgate/adrs/ARCH-012-command-error-boundaries.rules.ts Outdated
Comment thread .archgate/adrs/ARCH-012-command-error-boundaries.rules.ts
…ailures

Two gaps in the ARCH-012 boundary rule from PR review:

- Implicit-return arrow actions (`.action(async () => run())`) were
  silently skipped even though they structurally cannot contain a
  try-catch — now flagged as a missing boundary with a convert-to-block
  fix hint.
- ctx.ast() parse failures silently treated the file as compliant —
  now surfaced as a warning so a transpiler edge case cannot mask
  coverage loss.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_679364ae-c5ef-4a49-8308-2c7fb29cb9a9)

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 11, 2026
Comment thread src/commands/check.ts Outdated
Comment thread src/commands/check.ts Outdated
…itWith

Review feedback: with the full action-body error boundary in place,
the early-return guards (missing project root, invalid --max-warnings)
can simply throw UserError — handleCommandError logs the message and
exits 1 without Sentry, identical behavior with less ceremony and
consistent with the user-error.ts doctrine.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_3f601024-d94e-4fd0-a0f3-e116fc5c1d5f)

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_97540d10-5886-4208-a7f5-155b59dab8e5)

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 11, 2026
…throws

Extends the PR-review UserError pattern across the CLI. The
`if (!x) { logError(...); await exitWith(1); return; }` guard appeared
at ~40 sites in 18 command files; the project-root variant alone was
copy-pasted 10 times.

- Add requireProjectRoot() to helpers/paths.ts — returns the root or
  throws UserError("No .archgate/ directory found. Run `archgate init`
  first."), handled by each action's ARCH-012 boundary (exit 1, no
  Sentry).
- Convert adr create/show/list/update/sync/import, adr domain
  add/remove/list, review-context, check, and plugin install (the
  "Not logged in" guard). show.ts's "ADR not found" also throws.
- Guards that sat BEFORE the try block now live inside it, giving
  those actions full-body boundary coverage.
- Deliberately unchanged: login/init (catch-block TLS translation),
  upgrade (custom Sentry flow), session-context (Result-style helper
  errors + cwd fallback semantics).
- ARCH-011 codifies the convention; test assertions updated to the
  standardized message.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_7d808520-7d56-4836-8ac2-9d8b8bde8066)

@rhuanbarreto

Copy link
Copy Markdown
Contributor Author

Scope expansion (requested in review follow-up): commit 301c2d6 sweeps the logError(...) + await exitWith(1) + return guard pattern across the CLI — it appeared at ~40 sites in 18 files, including a 10× copy-pasted project-root guard.

  • New requireProjectRoot() helper (throws UserError, handled by each action's ARCH-012 boundary) replaces all 11 project-root guards
  • plugin install "Not logged in" and adr show "not found" guards now throw UserError
  • Guards that previously sat before the try block moved inside it — full-body boundary coverage
  • Deliberately unchanged (19 remaining sites): login/init catch-block TLS hint translation, upgrade's custom Sentry flow, and session-context/* (Result-style helper errors + cwd-fallback semantics) — converting those would change helper contracts without simplifying
  • ARCH-011 updated to codify the convention; net −54 lines

@rhuanbarreto rhuanbarreto changed the title fix(check): stop miscategorizing out-of-root file filters as crashes fix(check): user-error handling overhaul — out-of-root file filters, error boundaries, UserError guards Jul 11, 2026
requireProjectRoot() was inserted between findProjectRoot's doc comment
and the function, orphaning the doc. Move requireProjectRoot below
findProjectRoot instead.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_2f7e8dbe-7a47-4422-89f9-a0d00114a2d9)

@rhuanbarreto rhuanbarreto changed the title fix(check): user-error handling overhaul — out-of-root file filters, error boundaries, UserError guards fix(check): user-error handling overhaul — file filters, error boundaries, UserError guards Jul 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.archgate/adrs/ARCH-011-consistent-project-root-resolution.md:
- Line 41: Update the affected Markdown sentence in ARCH-011 to add spaces
around the inline code spans `session-context`, `findProjectRoot()`, and `null`,
while preserving the existing wording and formatting.

In `@src/commands/adr/show.ts`:
- Around line 6-10: Update the ADR-not-found error construction in the command’s
show flow to pass an actionable suggestion as the additional UserError detail,
guiding users to create or initialize the governance directory. Follow the
existing requireProjectRoot() pattern and preserve the current error message and
handling behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 80057813-e0a5-447e-9285-b31820f81e56

📥 Commits

Reviewing files that changed from the base of the PR and between 85c7420 and b7051c8.

📒 Files selected for processing (18)
  • .archgate/adrs/ARCH-011-consistent-project-root-resolution.md
  • .claude/agent-memory/archgate-developer/MEMORY.md
  • .claude/agent-memory/archgate-developer/feedback_throw_usererror_in_guards.md
  • src/commands/adr/create.ts
  • src/commands/adr/domain/add.ts
  • src/commands/adr/domain/list.ts
  • src/commands/adr/domain/remove.ts
  • src/commands/adr/import.ts
  • src/commands/adr/list.ts
  • src/commands/adr/show.ts
  • src/commands/adr/sync.ts
  • src/commands/adr/update.ts
  • src/commands/check.ts
  • src/commands/plugin/install.ts
  • src/commands/review-context.ts
  • src/helpers/paths.ts
  • tests/commands/check-action.test.ts
  • tests/commands/review-context.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: CodeRabbit
  • GitHub Check: Smoke Test (Windows) / Windows
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (csharp)
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (13)
tests/**/*.test.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-005-testing-standards.md)

tests/**/*.test.ts: Use Bun's built-in test runner (bun:test) for all test files, and place tests under tests/ mirroring the src/ directory structure with <module-name>.test.ts naming.
Use temp directories (mkdtemp) for filesystem tests, and clean them up in afterEach or afterAll.
Close external SDK instances (servers, clients, transports, connections) in afterEach or afterAll, managing their lifecycle in hooks rather than inside individual test bodies.
When a test creates a temporary git repository and runs git commit, configure local user.email and user.name immediately after git init.
Test public module interfaces, not private implementation details.
Use descriptive test names that explain the expected behavior.
Every runnable test()/it() must contain at least one expect() assertion; smoke tests must make the contract explicit with expect(() => fn()).not.toThrow() or await expect(promise).resolves.toBeUndefined().
Use test.skip, test.skipIf, or test.todo for intentionally empty or disabled tests; do not use bare return or empty callbacks to skip work.
If the first expect() is being added to a previously assertion-less test file, add expect to the bun:test import.
When mocking fetch in tests, assign directly to globalThis.fetch and restore the original or use mock.restore() afterward.
Wrap spyOn() and inline mockImplementation() usage in try/finally, or create and restore spies in hooks, so mockRestore() always runs.
Only raise a per-test timeout above the global bun test --timeout 60000; never set a shorter per-test timeout.
Mock first-party modules with import * as mod plus spyOn(mod, "fn"), not mock.module().
When a test needs to redirect user-scope paths, mock os.homedir() instead of relying on HOME/Bun.env.HOME; restore the spy in test hooks.
Do not depend on network access in unit tests.
Do not leave temp files after test runs.
Do not leave external SDK instances open after tests...

Files:

  • tests/commands/check-action.test.ts
  • tests/commands/review-context.test.ts
**/*.{ts,tsx,js,jsx,mjs,cjs}

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-006-dependency-policy.md)

**/*.{ts,tsx,js,jsx,mjs,cjs}: Use Bun built-ins for file I/O (Bun.file, Bun.write), HTTP, subprocess execution (Bun.spawn), globbing (Bun.Glob), and testing (bun:test).
Do not use Node.js-specific APIs when Bun alternatives exist; for example, use Bun.file() instead of fs.readFile() for simple reads.
Prefer node: built-in modules such as node:util, node:path, and node:fs over npm alternatives.
Do not use utility libraries for single functions, such as importing lodash for one helper like pick.
Do not use path aliases (tsconfig paths); use relative imports with Bun's native module resolution.
Do not install packages globally during development; use bunx for one-off tools.

Files:

  • tests/commands/check-action.test.ts
  • src/commands/adr/import.ts
  • src/commands/adr/domain/remove.ts
  • src/commands/adr/create.ts
  • src/helpers/paths.ts
  • src/commands/adr/list.ts
  • src/commands/plugin/install.ts
  • src/commands/adr/show.ts
  • src/commands/adr/sync.ts
  • tests/commands/review-context.test.ts
  • src/commands/adr/domain/add.ts
  • src/commands/adr/update.ts
  • src/commands/adr/domain/list.ts
  • src/commands/review-context.ts
  • src/commands/check.ts
tests/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-009-platform-detection-helper.md)

In test files, use _resetPlatformCache() to simulate different platforms instead of mocking or mutating process.platform directly.

Files:

  • tests/commands/check-action.test.ts
  • tests/commands/review-context.test.ts
{src,tests}/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/LEGAL-001-spdx-license-headers.md)

{src,tests}/**/*.ts: Every TypeScript source file in src/ and tests/ must begin with // SPDX-License-Identifier: Apache-2.0 followed by // Copyright 2026 Archgate.
If a TypeScript file has a shebang line (for example #!/usr/bin/env bun in src/cli.ts), the SPDX license header must appear immediately after the shebang.
Use single-line // comments for the SPDX header; do not use block comments (/* */) or alternate license identifiers.

Files:

  • tests/commands/check-action.test.ts
  • src/commands/adr/import.ts
  • src/commands/adr/domain/remove.ts
  • src/commands/adr/create.ts
  • src/helpers/paths.ts
  • src/commands/adr/list.ts
  • src/commands/plugin/install.ts
  • src/commands/adr/show.ts
  • src/commands/adr/sync.ts
  • tests/commands/review-context.test.ts
  • src/commands/adr/domain/add.ts
  • src/commands/adr/update.ts
  • src/commands/adr/domain/list.ts
  • src/commands/review-context.ts
  • src/commands/check.ts
**

⚙️ CodeRabbit configuration file

**: # 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 (>=1.2.21) — not Node.js compatible
  • 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 wizard

Validation 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 ../.githooks

Opt out of a specific hook: git config --local hook.<name>.enabled false. Skip all hooks for a single commit: git commit --no-verify.

#...

Files:

  • tests/commands/check-action.test.ts
  • src/commands/adr/import.ts
  • src/commands/adr/domain/remove.ts
  • src/commands/adr/create.ts
  • src/helpers/paths.ts
  • src/commands/adr/list.ts
  • src/commands/plugin/install.ts
  • src/commands/adr/show.ts
  • src/commands/adr/sync.ts
  • tests/commands/review-context.test.ts
  • src/commands/adr/domain/add.ts
  • src/commands/adr/update.ts
  • src/commands/adr/domain/list.ts
  • src/commands/review-context.ts
  • src/commands/check.ts

⚙️ CodeRabbit configuration file

**: This project uses Archgate — an AI governance framework based on
Architecture Decision Records (ADRs). The ADRs in .archgate/adrs/
are the authoritative rules for this codebase. Each ADR has a companion
.rules.ts file with automated checks that run via archgate check.

When reviewing, you must:

  1. Treat ADR violations as blocking issues, not suggestions.
  2. Cite the specific ADR ID when flagging a violation (e.g., "Violates ARCH-006").
  3. Focus on semantic and contextual violations that automated rules cannot catch —
    the .rules.ts files already cover syntactic/structural patterns.
  4. If you are unsure whether something violates an ADR, flag it as a question
    rather than approving it.

Files:

  • tests/commands/check-action.test.ts
  • src/commands/adr/import.ts
  • src/commands/adr/domain/remove.ts
  • src/commands/adr/create.ts
  • src/helpers/paths.ts
  • src/commands/adr/list.ts
  • src/commands/plugin/install.ts
  • src/commands/adr/show.ts
  • src/commands/adr/sync.ts
  • tests/commands/review-context.test.ts
  • src/commands/adr/domain/add.ts
  • src/commands/adr/update.ts
  • src/commands/adr/domain/list.ts
  • src/commands/review-context.ts
  • src/commands/check.ts
src/commands/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-001-command-structure.md)

src/commands/**/*.ts: Each command module under src/commands/ must export a register*Command(program) function, with one command per file (or one command group via index.ts).
Command files must be thin: they should only parse arguments, call engine/helpers, and format output; business logic must live in src/engine/, src/helpers/, or src/formats/.
Command modules must not use executableDir() for command discovery, must not call .parse() themselves, and must not spawn child processes for subcommand execution.
Subcommand groups should live in nested directories with an index.ts that composes child commands (for example, src/commands/adr/index.ts).

src/commands/**/*.ts: In command files under src/commands/**/*.ts, options that need type narrowing beyond plain strings MUST use new Option() with .addOption() instead of plain .option().
In src/commands/**/*.ts, import Option from @commander-js/extra-typings alongside Command so typed options get full type inference.
In src/commands/**/*.ts, enum-like options with a fixed set of allowed values must use .choices() on an Option to provide runtime validation and compile-time type narrowing.
In src/commands/**/*.ts, options that require type conversion must use .argParser() on an Option object, not a parser function passed as the third argument to .option().
In src/commands/**/*.ts, register typed options with .addOption() rather than .option().
In src/commands/**/*.ts, pass both the choices array and default values with as const when using .choices() and .default() to preserve literal types.
In src/commands/**/*.ts, do not add manual validation for choice options (for example if (!VALID.includes(val))), because Commander handles invalid-value rejection automatically.

Command modules must export register*Command(program) and contain I/O only; business logic belongs elsewhere.

src/commands/**/*.ts: All commands that operate on .archgate/ project res...

Files:

  • src/commands/adr/import.ts
  • src/commands/adr/domain/remove.ts
  • src/commands/adr/create.ts
  • src/commands/adr/list.ts
  • src/commands/plugin/install.ts
  • src/commands/adr/show.ts
  • src/commands/adr/sync.ts
  • src/commands/adr/domain/add.ts
  • src/commands/adr/update.ts
  • src/commands/adr/domain/list.ts
  • src/commands/review-context.ts
  • src/commands/check.ts
src/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-003-output-formatting.md)

src/**/*.ts: Use styleText(format, text) from node:util for all terminal colors and formatting in CLI source files; do not use raw ANSI escape codes or third-party color libraries.
Commands that produce structured results and support --json must emit machine-readable JSON to stdout with no colors or decorative formatting.
When a command supports --json, use formatJSON() from src/helpers/output.ts for JSON serialization, and pass forcePretty: true when the user explicitly provided --json.
Use isAgentContext() from src/helpers/output.ts to enable auto-JSON behavior for commands that support both human-readable and JSON output modes.
CLI output must not include emoji; use text symbols and colors instead.
Send normal command output to stdout with console.log(), and send errors, warnings, and debug messages to stderr via logError(), logWarn(), and logDebug().
Keep CLI output concise and scannable by using whitespace and alignment instead of long text blocks.
Respect NO_COLOR automatically by relying on styleText; do not add custom color-environment handling in CLI code.
Do not output progress spinners unless there is a TTY check.
Do not assume piped output means agent context when CI is set; CI runners should still receive human-readable output.

src/**/*.ts: Do not re-export symbols from another module in any source file; statements like export { X } from "./other" and export type { X } from "./other" are forbidden.
Import symbols directly from the module that defines them; do not import from a directory path such as ../formats expecting implicit index.ts resolution.

Make large production thresholds injectable via an optional parameter that defaults to the module constant, so tests can supply a small value instead of generating huge fixtures.

src/**/*.ts: In Archgate CLI TypeScript source files, all subprocess execution must use Bun.spawn with array-based arguments; Bun.$ shell template literals are forbidden.
Do not...

Files:

  • src/commands/adr/import.ts
  • src/commands/adr/domain/remove.ts
  • src/commands/adr/create.ts
  • src/helpers/paths.ts
  • src/commands/adr/list.ts
  • src/commands/plugin/install.ts
  • src/commands/adr/show.ts
  • src/commands/adr/sync.ts
  • src/commands/adr/domain/add.ts
  • src/commands/adr/update.ts
  • src/commands/adr/domain/list.ts
  • src/commands/review-context.ts
  • src/commands/check.ts
src/**/!(*platform).ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-009-platform-detection-helper.md)

src/**/!(*platform).ts: In src/ TypeScript source files, do not read process.platform directly; use src/helpers/platform.ts (isWindows(), isMacOS(), isLinux(), isWSL(), getPlatformInfo()) for all platform detection.
Use the centralized platform helper instead of duplicating OS/WSL detection logic inline anywhere in src/ TypeScript source.
When behavior differs between Linux and Windows, account for WSL by using isWSL() rather than assuming `

Files:

  • src/commands/adr/import.ts
  • src/commands/adr/domain/remove.ts
  • src/commands/adr/create.ts
  • src/helpers/paths.ts
  • src/commands/adr/list.ts
  • src/commands/plugin/install.ts
  • src/commands/adr/show.ts
  • src/commands/adr/sync.ts
  • src/commands/adr/domain/add.ts
  • src/commands/adr/update.ts
  • src/commands/adr/domain/list.ts
  • src/commands/review-context.ts
  • src/commands/check.ts
src/helpers/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-002-error-handling.md)

In helper files, use logInfo() or logWarn() instead of direct console.log() or console.warn() calls.

Files:

  • src/helpers/paths.ts
src/commands/plugin/install.ts

📄 CodeRabbit inference engine (CLAUDE.md)

When adding a new editor target, extend .choices([...]), add a case in installForEditor, and update the manual-instructions catch path.

Files:

  • src/commands/plugin/install.ts
.archgate/adrs/**

⚙️ CodeRabbit configuration file

.archgate/adrs/**: ---
id: ARCH-001
title: Command Structure
domain: architecture
rules: true
files: ["src/commands/**/*.ts"]

Context

The CLI needs a consistent pattern for defining and registering commands. As the command surface grows (init, check, adr, review-context, session-context, upgrade, clean), the registration mechanism must scale without introducing hidden coupling or making the dependency graph opaque.

Alternatives considered:

  • Auto-discovery via executableDir() — Commander.js supports automatic command discovery by scanning a directory for executable files. This eliminates manual imports but hides the dependency graph: adding or removing a command has no type-checked reference, making dead command detection impossible. It also requires each command to be a standalone executable, which prevents in-process testing and forces separate process spawning for every subcommand invocation.
  • Plugin-based registration — A plugin system where commands register themselves via a manifest or hook (similar to Oclif or Clipanion). This adds flexibility for third-party extensions but introduces significant complexity for an internal CLI with a known, finite set of commands. The indirection makes it harder to trace which code handles which command.
  • Single-file command map — Define all commands in a single file as a map of name-to-handler. Simple but creates a monolithic file that grows with every command, making merge conflicts frequent and readability poor.

The explicit register pattern strikes the right balance: each command owns its registration logic, the entry point makes all commands visible at a glance, and in-process execution enables straightforward testing without process spawning.

Decision

Commands live in src/commands/ and export a register*Command(program) function. The main entry point (src/cli.ts) explicitly imports and calls each register function. Subcommands (e.g., adr create, adr list) use nested directories wi...

Files:

  • .archgate/adrs/ARCH-011-consistent-project-root-resolution.md
src/commands/{*.ts,*/index.ts}

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-015-cli-command-documentation-coverage.md)

Every top-level CLI command registered via register*Command(program) must live in src/commands/<name>.ts or src/commands/<name>/index.ts so it can be matched to a docs page.

Files:

  • src/commands/review-context.ts
  • src/commands/check.ts
{src/commands/{*.ts,*/index.ts},docs/src/content/docs/reference/cli/!(index).mdx}

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-015-cli-command-documentation-coverage.md)

{src/commands/{*.ts,*/index.ts},docs/src/content/docs/reference/cli/!(index).mdx}: When a top-level command is added, its docs page must be added in the same change; when a top-level command is removed, its matching docs page must be deleted in the same change.
Keep command and documentation stems aligned when renaming a top-level command file; renaming src/commands/<name>.ts or src/commands/<name>/index.ts must be paired with renaming docs/src/content/docs/reference/cli/<name>.mdx.

Files:

  • src/commands/review-context.ts
  • src/commands/check.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: archgate/cli

Timestamp: 2026-07-11T12:45:14.402Z
Learning: Code reviewers must verify that error messages include actionable suggestions where possible and that no try-catch block swallows errors without logging or re-throwing.
Learnt from: CR
Repo: archgate/cli

Timestamp: 2026-07-11T12:45:25.203Z
Learning: The top-level main().catch() in cli.ts must remain as a safety net for truly unexpected errors and should not be the primary command error handler.
Learnt from: CR
Repo: archgate/cli

Timestamp: 2026-07-11T12:45:25.203Z
Learning: Code reviewers must verify that every new asynchronous command has an error boundary covering the entire action body.
📚 Learning: 2026-06-11T12:50:28.661Z
Learnt from: rhuanbarreto
Repo: archgate/cli PR: 406
File: .claude/agent-memory/archgate-developer/feedback_prefer_tests_over_adr_rules.md:8-18
Timestamp: 2026-06-11T12:50:28.661Z
Learning: In `archgate/cli`, for markdown files under `.claude/agent-memory/`, follow the established convention: use YAML frontmatter (with a `name:` field used as the document title) and do not require a top-level `#` (H1) heading. During code review, do not flag missing first-line/first-top-level H1 headings (e.g., MD041) for these agent-memory files since markdownlint is not part of the repo’s `bun run validate` lint pipeline (oxlint/oxfmt only).

Applied to files:

  • .claude/agent-memory/archgate-developer/feedback_throw_usererror_in_guards.md
  • .claude/agent-memory/archgate-developer/MEMORY.md
📚 Learning: 2026-07-02T16:03:33.031Z
Learnt from: rhuanbarreto
Repo: archgate/cli PR: 446
File: src/helpers/session-context-opencode.ts:81-100
Timestamp: 2026-07-02T16:03:33.031Z
Learning: For synchronous helper functions that use Bun’s sqlite sync API (i.e., they must remain synchronous), it’s acceptable to use `existsSync` from `node:fs` to check whether the SQLite database file exists. Avoid using `Bun.file(path).exists()` for this purpose because it’s async and would force the helper to become async (no equivalent synchronous Bun alternative). If the DB file is missing, throw/return a clear, actionable "No database found" error (per ARCH-006) rather than letting the sqlite open fail with a generic DB-open error.

Applied to files:

  • src/helpers/paths.ts
🪛 markdownlint-cli2 (0.22.1)
.claude/agent-memory/archgate-developer/feedback_throw_usererror_in_guards.md

[warning] 8-8: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.archgate/adrs/ARCH-011-consistent-project-root-resolution.md

[warning] 41-41: Spaces inside code span elements

(MD038, no-space-in-code)

🔇 Additional comments (17)
src/helpers/paths.ts (1)

8-8: LGTM!

Also applies to: 176-194

src/commands/check.ts (1)

18-22: LGTM! Past-review feedback on both guards (project-root and --max-warnings) is fully addressed here — both now throw UserError and are handled uniformly by handleCommandError.

Also applies to: 45-57, 99-115, 117-145, 147-176, 176-181

src/commands/adr/create.ts (1)

7-9: LGTM!

Also applies to: 31-32

src/commands/adr/domain/add.ts (1)

5-7: LGTM!

Also applies to: 19-20

src/commands/adr/domain/list.ts (1)

7-9: LGTM!

Also applies to: 18-19

tests/commands/check-action.test.ts (1)

171-175: LGTM!

src/commands/review-context.ts (1)

7-9: LGTM!

Also applies to: 26-34

src/commands/adr/domain/remove.ts (1)

5-7: LGTM!

Also applies to: 21-22

src/commands/adr/import.ts (1)

18-20: LGTM!

Also applies to: 42-42

src/commands/adr/list.ts (1)

9-11: LGTM!

Also applies to: 21-22

src/commands/adr/sync.ts (1)

14-16: LGTM!

Also applies to: 145-146

src/commands/adr/update.ts (1)

6-8: LGTM!

Also applies to: 32-42

src/commands/plugin/install.ts (1)

30-30: LGTM!

Also applies to: 247-250

tests/commands/review-context.test.ts (1)

102-103: LGTM!

.claude/agent-memory/archgate-developer/MEMORY.md (1)

27-27: LGTM!

Also applies to: 45-45

.claude/agent-memory/archgate-developer/feedback_throw_usererror_in_guards.md (1)

1-13: LGTM!

.archgate/adrs/ARCH-011-consistent-project-root-resolution.md (1)

45-49: LGTM!

Comment thread .archgate/adrs/ARCH-011-consistent-project-root-resolution.md Outdated
Comment thread src/commands/adr/show.ts
…acing

- adr show's "ADR not found" UserError now suggests `archgate adr list`
  (ARCH-002: actionable suggestions accompany error messages).
- ARCH-011: rephrase the requireProjectRoot() Do to drop escaped
  backticks nested inside a code span — oxfmt mis-parses the span
  boundaries and eats the spaces after subsequent code spans on the
  line, which is what produced the malformed markdown CodeRabbit
  flagged (and silently re-produced it after a naive space fix).

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_e892c9a2-d950-4dac-8213-7e556663e3ed)

@rhuanbarreto
rhuanbarreto merged commit 7ae6440 into main Jul 11, 2026
23 checks passed
@rhuanbarreto
rhuanbarreto deleted the fix/check-user-error-boundaries branch July 11, 2026 13:14
@archgatebot archgatebot Bot mentioned this pull request Jul 11, 2026
rhuanbarreto pushed a commit that referenced this pull request Jul 13, 2026
# archgate

## [0.48.2](v0.48.1...v0.48.2)
(2026-07-11)

### Bug Fixes

* **check:** user-error handling overhaul — file filters, error
boundaries, UserError guards
([#467](#467))
([7ae6440](7ae6440))

---
This PR was generated with
[simple-release](https://github.com/TrigenSoftware/simple-release).

<details>
<summary>📄 Cheatsheet</summary>
<br>



You can configure the bot's behavior through a pull request comment
using the `!simple-release/set-options` command.

### Command Format

````md
!simple-release/set-options

```json
{
  "bump": {},
  "publish": {}
}
```
````

### Useful Parameters

#### Bump

| Parameter | Type | Description |
|-----------|------|-------------|
| `version` | `string` | Force set specific version |
| `as` | `'major' \| 'minor' \| 'patch' \| 'prerelease'` | Release type
|
| `prerelease` | `string` | Pre-release identifier (e.g., "alpha",
"beta") |
| `firstRelease` | `boolean` | Whether this is the first release |
| `skip` | `boolean` | Skip version bump |
| `byProject` | `Record<string, object>` | Per-project bump options for
monorepos |

#### Publish

| Parameter | Type | Description |
|-----------|------|-------------|
| `skip` | `boolean` | Skip publishing |
| `access` | `'public' \| 'restricted'` | Package access level |
| `tag` | `string` | Tag for npm publication |

### Usage Examples

#### Force specific version

````md
!simple-release/set-options

```json
{
  "bump": {
    "version": "2.0.0"
  }
}
```
````

#### Force major bump

````md
!simple-release/set-options

```json
{
  "bump": {
    "as": "major"
  }
}
```
````

#### Create alpha pre-release

````md
!simple-release/set-options

```json
{
  "bump": {
    "prerelease": "alpha"
  }
}
```
````

#### Publish with specific access and tag

````md
!simple-release/set-options

```json
{
  "bump": {
    "prerelease": "beta"
  },
  "publish": {
    "access": "public",
    "tag": "beta"
  }
}
```
````

### Access Restrictions

The command can only be used by users with permissions:
- repository owner
- organization member
- collaborator

### Notes

- The last comment with `!simple-release/set-options` command takes
priority
- JSON must be valid, otherwise the command will be ignored
- Parameters apply only to the current release execution
- The command can be updated by editing the comment or adding a new one


</details>

<!--
  Please do not edit this comment.
  simple-release-pull-request: true
  simple-release-branch-from: release
  simple-release-branch-to: main
-->

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant