Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .archgate/adrs/ARCH-001-command-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ files: ["src/commands/**/*.ts"]

## Context

The CLI needs a consistent pattern for defining and registering commands. As the command surface grows (init, check, adr, mcp, upgrade, clean), the registration mechanism must scale without introducing hidden coupling or making the dependency graph opaque.
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:**

Expand Down
58 changes: 1 addition & 57 deletions .archgate/adrs/ARCH-002-error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ Use three exit codes with clear semantics:
- Let unexpected errors crash naturally (exit code 2)
- Provide actionable suggestions in error messages
- Write errors to stderr (via `logError()`), not stdout
- **MCP tools MUST return structured JSON guidance when prerequisites are missing** — use `noProjectResponse()` from `src/mcp/tools/no-project.ts`, which returns `{ error, message, action }` where `action` directs the AI agent to the recovery step (e.g., "Invoke the `@archgate:onboard` skill")
- **The MCP server MUST start even when no project is found** — `startStdioServer()` and `createMcpServer()` accept `string | null` for `projectRoot`; the `mcp` command passes `findProjectRoot()` directly (which returns `null`) rather than guarding with `process.exit(1)`
- **MCP tools that don't depend on `.archgate/` MUST fall back to `process.cwd()`** when `projectRoot` is null — e.g., `claude_code_session_context` reads from `~/.claude/projects/` and uses `process.cwd()` as its path key when no project is found
- **Commands that don't require `.archgate/` SHOULD fall back to `process.cwd()`** when `findProjectRoot()` returns null — e.g., `session-context` reads from `~/.claude/projects/` and uses `process.cwd()` as its path key when no project is found

### Don't

Expand All @@ -58,9 +56,6 @@ Use three exit codes with clear semantics:
- Don't use `console.error()` directly — use `logError()` for consistent formatting
- Don't exit with code 0 when an operation fails
- Don't use exit codes other than 0, 1, or 2
- **Don't call `process.exit()` inside MCP tool handlers** — the MCP server is a long-lived process shared with the AI agent; calling `process.exit()` kills the agent's MCP connection and prevents any recovery
- **Don't guard MCP server startup with a fatal precondition check** — never call `process.exit(1)` before `startStdioServer()` for expected missing state such as "no project found"; instead pass `null` and let tools degrade gracefully
- **Don't throw unhandled exceptions from MCP tool handlers** — always catch errors inside the handler and return structured JSON with an `error` field; uncaught exceptions break the MCP transport protocol and produce unreadable output

## Implementation Pattern

Expand Down Expand Up @@ -111,55 +106,6 @@ try {
}
```

### MCP Tool Pattern

MCP tools run inside a long-lived server process. They MUST NOT call `process.exit()` or throw unhandled exceptions. Missing prerequisites (e.g., no project found) are communicated via structured JSON responses so the AI agent can recover.

```typescript
// GOOD: MCP server starts regardless of project state
// src/commands/mcp.ts
export function registerMcpCommand(program: Command) {
program.command("mcp").action(async () => {
// findProjectRoot() returns string | null — pass directly, never exit here
await startStdioServer(findProjectRoot());
});
}

// GOOD: MCP tool returns guidance when projectRoot is null
// src/mcp/tools/check.ts
async ({ adrId, staged }) => {
if (projectRoot === null) {
return noProjectResponse(); // { error, message, action: "invoke @archgate:onboard" }
}
// ... normal tool logic
};

// GOOD: tool that doesn't need .archgate/ falls back to cwd
// src/mcp/tools/claude-code-session-context.ts
const encodedPath = encodeProjectPath(projectRoot ?? process.cwd());
```

```typescript
// BAD: blocking MCP startup with process.exit
export function registerMcpCommand(program: Command) {
program.command("mcp").action(async () => {
const root = findProjectRoot();
if (!root) {
logError("No archgate project found.");
process.exit(1); // WRONG — kills the agent's MCP connection
}
await startStdioServer(root);
});
}

// BAD: throwing from an MCP tool handler
async ({ adrId }) => {
if (projectRoot === null) {
throw new Error("No project found"); // WRONG — breaks the MCP transport
}
};
```

## Consequences

### Positive
Expand Down Expand Up @@ -194,8 +140,6 @@ Code reviewers MUST verify:
1. Error messages include actionable suggestions where possible
2. Expected failures exit with code 1, not code 2
3. No try-catch blocks that swallow errors without logging or re-throwing
4. MCP tool handlers do not call `process.exit()` — failures return `{ error, message, action }` JSON
5. The `mcp` command does not guard `startStdioServer()` with a `process.exit()` on missing project

## References

Expand Down
2 changes: 1 addition & 1 deletion .archgate/adrs/ARCH-004-no-barrel-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ A barrel file is defined as an `index.ts` file that:
Files named `index.ts` that contain actual logic are **not** barrel files and are permitted. Examples of permitted `index.ts` files:

- `src/commands/adr/index.ts` — defines `registerAdrCommand()` with command group composition logic
- `src/mcp/tools/index.ts` — defines `registerTools()` with MCP tool registration logic
- `src/commands/session-context/index.ts` — defines `registerSessionContextCommand()` with subcommand composition logic

## Do's and Don'ts

Expand Down
46 changes: 16 additions & 30 deletions .archgate/adrs/ARCH-005-testing-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Use Bun's built-in test runner (`bun test`) for all tests. Test files go in `tes
- Don't test private implementation details — test the public API of each module
- Don't depend on network access in unit tests
- Don't leave temp files after test runs
- **Don't leave external SDK instances open after tests** — instances from libraries such as `@modelcontextprotocol/sdk` hold internal references (e.g., `AjvJsonSchemaValidator` backed by `ajv`) that keep Bun's event loop alive on Linux, causing `bun test` to hang indefinitely after all tests complete even though every test passes. Always call the cleanup method in `afterEach`.
- **Don't leave external SDK instances open after tests** — instances from external libraries may hold internal references that keep Bun's event loop alive on Linux, causing `bun test` to hang indefinitely after all tests complete even though every test passes. Always call the cleanup method in `afterEach`.
- **Don't rely on globally-configured git identity in temp git repos** — always set `user.email` and `user.name` locally in any repo that makes commits. Omitting this works locally (where developers have global git config) but fails silently in CI, producing a cryptic `ShellPromise` error with no indication that git identity is the cause.
- Don't skip tests without a tracking issue
- Don't import test utilities from `node:test` — use Bun's built-in `bun:test` module
Expand Down Expand Up @@ -105,33 +105,19 @@ it("returns both staged and unstaged changes", async () => {
});
```

### Good Example — External SDK Cleanup
### Good Example — Temp Directory Cleanup

```typescript
// tests/mcp/resources.test.ts
// tests/helpers/session-context.test.ts
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
import { mkdtempSync, rmSync, mkdirSync } from "node:fs";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerResources } from "../../src/mcp/resources";

describe("registerResources", () => {
let tempDir: string;
let server: McpServer;

// GOOD: lifecycle managed in beforeEach/afterEach, not inside test bodies
beforeEach(() => {
tempDir = mkdtempSync(join(tmpdir(), "archgate-mcp-res-test-"));
mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true });
server = new McpServer({ name: "test", version: "0.0.0" });
});

afterEach(async () => {
await server.close(); // GOOD: releases internal validator references
rmSync(tempDir, { recursive: true, force: true });
});
import { join } from "node:path";
import { tmpdir } from "node:os";
import { encodeProjectPath } from "../../src/helpers/session-context";

test("does not throw when registering resources", () => {
expect(() => registerResources(server, tempDir)).not.toThrow();
describe("encodeProjectPath", () => {
test("replaces forward slashes with dashes", () => {
expect(encodeProjectPath("/home/user/project")).toBe("-home-user-project");
});
});
```
Expand All @@ -156,11 +142,11 @@ it("writes output", async () => {
// No cleanup — file persists after test
});

// BAD: SDK instance created inside test body — no guaranteed cleanup path
it("registers resources", () => {
const server = new McpServer({ name: "test", version: "0.0.0" });
// server.close() never called — event loop held open on Linux
expect(() => registerResources(server, tempDir)).not.toThrow();
// BAD: external resource created inside test body — no guaranteed cleanup path
it("processes data", () => {
const tempDir = mkdtempSync(join(tmpdir(), "test-"));
// tempDir never cleaned up — leaks files after test run
expect(processData(tempDir)).toBeTruthy();
});

// BAD: git commit without local identity — works locally, fails in CI
Expand Down Expand Up @@ -202,8 +188,8 @@ globalThis.fetch = (() =>
- **Mitigation:** The project pins a specific Bun version via `.prototools` (currently 1.3.8). Test runner API changes are caught during controlled Bun upgrades with full test suite validation.
- **Coverage reporting gaps** — `bun test --coverage` may not report accurate coverage for all code paths, especially for dynamically imported modules.
- **Mitigation:** Coverage is a guideline (80% target), not a hard gate. Critical modules (engine, formats) are tested thoroughly regardless of coverage numbers.
- **Third-party SDK event loop retention** — External SDK instances that hold internal resource references (e.g., `AjvJsonSchemaValidator` inside `@modelcontextprotocol/sdk`) keep Bun's event loop alive on Linux after all tests complete, causing `bun test` to hang indefinitely. This does not surface on macOS (event loop drains normally there), making it a Linux-CI-only failure that is hard to reproduce locally.
- **Mitigation:** Always manage SDK lifecycle in `beforeEach`/`afterEach` and call the cleanup method (`close()`, `destroy()`, `disconnect()`) in `afterEach`. Add `timeout-minutes` to CI jobs as a safety net — the `code-pull-request.yml` job is set to 10 minutes to cap any future regressions.
- **Third-party SDK event loop retention** — External SDK instances that hold internal resource references may keep Bun's event loop alive on Linux after all tests complete, causing `bun test` to hang indefinitely. This does not surface on macOS (event loop drains normally there), making it a Linux-CI-only failure that is hard to reproduce locally.
- **Mitigation:** Always manage external resource lifecycle in `beforeEach`/`afterEach` and call the cleanup method (`close()`, `destroy()`, `disconnect()`) in `afterEach`. Add `timeout-minutes` to CI jobs as a safety net — the `code-pull-request.yml` job is set to 10 minutes to cap any future regressions.

## Compliance and Enforcement

Expand Down
1 change: 0 additions & 1 deletion .archgate/adrs/ARCH-006-dependency-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ Keep production dependencies minimal. Prefer Bun built-ins over external package
| ----------------------------- | ------------------- | ------------------------------------------------------------------ |
| `@commander-js/extra-typings` | CLI framework | Bun has no built-in CLI argument parsing with subcommand support |
| `inquirer` | Interactive prompts | Bun has no built-in interactive prompt library |
| `@modelcontextprotocol/sdk` | MCP server | Protocol-specific SDK; no built-in MCP support |
| `zod` | Schema validation | Used for ADR frontmatter validation; no built-in schema validation |

**Adding a new dependency requires:**
Expand Down
10 changes: 4 additions & 6 deletions .archgate/adrs/GEN-001-documentation-site.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ rules: false

## Context

The Archgate CLI needs a public documentation site for users, contributors, and AI agents. A README and inline code comments are insufficient for a project with multiple commands, an MCP server, a rule API, and editor integrations. Without a dedicated docs site:
The Archgate CLI needs a public documentation site for users, contributors, and AI agents. A README and inline code comments are insufficient for a project with multiple commands, a rule API, and editor integrations. Without a dedicated docs site:

1. **Discoverability is poor** — New users cannot browse guides, reference pages, or examples without reading source code
2. **Onboarding is slow** — Contributors must reverse-engineer conventions from existing code rather than reading a structured guide
Expand All @@ -16,7 +16,7 @@ The Archgate CLI needs a public documentation site for users, contributors, and

**Alternatives considered:**

- **README-only documentation** — Keeping all documentation in `README.md` is simple and requires no build tooling. However, README files become unwieldy beyond 500 lines, lack navigation, and cannot provide the structured multi-page experience users expect from a CLI tool. The Archgate README would need to cover 9 CLI commands, 5 MCP tools, a full Rule API, 3 editor integrations, and multiple guides — far too much for a single file.
- **README-only documentation** — Keeping all documentation in `README.md` is simple and requires no build tooling. However, README files become unwieldy beyond 500 lines, lack navigation, and cannot provide the structured multi-page experience users expect from a CLI tool. The Archgate README would need to cover 10+ CLI commands, a full Rule API, 3 editor integrations, and multiple guides — far too much for a single file.
- **Docusaurus (React-based)** — A mature documentation framework with a large ecosystem. However, Docusaurus is built on React and requires Node.js, adding a heavyweight runtime and dependency tree that conflicts with the project's Bun-first philosophy. Its configuration is more complex than needed for a documentation site of this scope.
- **VitePress (Vue-based)** — A fast, Vue-powered documentation generator. While lighter than Docusaurus, it still requires a framework runtime (Vue) and has less flexibility for custom content than Astro. Its Markdown extensions are proprietary rather than standard MDX.
- **Starlight (Astro-based)** — An Astro integration purpose-built for documentation sites. It uses standard MDX, runs under Bun via `bunx --bun astro`, produces static HTML with zero client-side JavaScript by default, and provides built-in search (Pagefind), sidebar navigation, and dark mode. Its component-based architecture allows embedding interactive elements without framework lock-in.
Expand Down Expand Up @@ -116,11 +116,9 @@ docs/
ci-integration.mdx
claude-code-plugin.mdx
cursor-integration.mdx
mcp-server.mdx
pre-commit-hooks.mdx
reference/
cli-commands.mdx
mcp-tools.mdx
rule-api.mdx
adr-schema.mdx
examples/
Expand Down Expand Up @@ -188,7 +186,7 @@ The resource URI format is `adr://\{id\}`.
- **Single source of truth** — All user-facing documentation lives in one structured, navigable site rather than scattered across README, source comments, and planning docs
- **Search built-in** — Starlight integrates Pagefind for full-text search across all documentation pages with zero configuration
- **Consistent with CLI toolchain** — Built with Bun (`bunx --bun astro`), aligning with the project's Bun-first philosophy established in [ARCH-006](./ARCH-006-dependency-policy.md)
- **AI-friendly structure** — AI agents can reference well-structured MDX pages for accurate code generation; the MCP server guide documents how agents interact with Archgate
- **AI-friendly structure** — AI agents can reference well-structured MDX pages for accurate code generation and understanding of how to interact with Archgate
- **Zero client-side JavaScript** — Astro renders static HTML by default; the docs site loads instantly without framework hydration overhead
- **Automatic deployment** — The `deploy-docs.yml` workflow deploys on every merge to `main` that touches `docs/`, with no manual steps

Expand All @@ -202,7 +200,7 @@ The resource URI format is `adr://\{id\}`.

- **Astro/Starlight breaking changes** — Major version upgrades to Astro or Starlight may change the Content Layer API, configuration format, or component interfaces.
- **Mitigation:** Dependencies are pinned to major versions (`astro@^5`, `@astrojs/starlight@^0.34`). Upgrades are performed explicitly with full build verification. Astro follows semver and publishes migration guides for major releases.
- **Documentation drift from source code** — Reference pages (CLI Commands, Rule API, MCP Tools, ADR Schema) may fall out of sync as the CLI evolves.
- **Documentation drift from source code** — Reference pages (CLI Commands, Rule API, ADR Schema) may fall out of sync as the CLI evolves.
- **Mitigation:** The "DO keep reference pages accurate to CLI source code" rule requires docs updates in the same PR that changes CLI APIs. Code reviewers MUST verify this during review.
- **GitHub Pages deployment failures** — Build or deployment failures in `deploy-docs.yml` may leave stale documentation live.
- **Mitigation:** The workflow uses `workflow_dispatch` for manual re-deployment. Build failures are visible in the Actions tab. The docs build is isolated from CLI CI, so docs failures never block CLI releases.
Expand Down
2 changes: 1 addition & 1 deletion .archgate/adrs/GEN-002-docs-i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ The sidebar in `docs/astro.config.mjs` does NOT need per-locale duplication. Sta

- **Translation drift** -- Portuguese content may fall behind after significant English rewrites.
- **Mitigation:** The same-PR policy catches most drift at review time. The 1:1 parity rule catches structural drift (added/removed pages). Periodic manual audits can catch semantic drift within existing files.
- **Stale technical content** -- Reference pages (CLI Commands, Rule API, MCP Tools) change frequently and translations may lag.
- **Stale technical content** -- Reference pages (CLI Commands, Rule API) change frequently and translations may lag.
- **Mitigation:** Reference pages contain mostly code blocks and tables with technical values that remain in English. Only surrounding prose needs translation, reducing the update surface.
- **Incorrect translations misleading users** -- Machine-translated or poorly reviewed content may contain errors.
- **Mitigation:** The ADR explicitly requires human review for all translations. Code blocks stay in English, eliminating the highest-risk translation errors (wrong commands, wrong API calls).
Expand Down
3 changes: 0 additions & 3 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
{
"agent": "archgate:developer",
"enableAllProjectMcpServers": true,
"enabledMcpjsonServers": ["archgate"],
"permissions": {
"allow": [
"mcp__plugin_archgate_archgate__*",
"Skill(archgate:architect)",
"Skill(archgate:quality-manager)",
"Skill(archgate:adr-author)"
Expand Down
8 changes: 0 additions & 8 deletions .cursor/mcp.json

This file was deleted.

8 changes: 0 additions & 8 deletions .mcp.json

This file was deleted.

Loading
Loading