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
4 changes: 2 additions & 2 deletions .claude/agent-memory/archgate-developer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Every work loop MUST end with these steps — no exceptions, even for trivial changes:

1. **`bun run validate`** — lint, typecheck, format, test, ADR check (fail-fast)
1. **`bun run validate`** — lint, typecheck, format, test, ADR check, knip, build check (fail-fast)
2. **`@architect` skill** — Invoke via `Skill tool` with skill `"archgate:architect"`. Validates structural ADR compliance beyond automated rules.
3. **`@quality-manager` skill** — Invoke via `Skill tool` with skill `"archgate:quality-manager"`. Captures learnings and governance gaps.

Expand Down Expand Up @@ -54,7 +54,7 @@ Skipping steps 2 or 3 is a workflow violation. The user should NEVER have to inv

## Validation Pipeline

- `bun run validate` is the mandatory gate: lint → typecheck → format:check → test → ADR check → build:check
- `bun run validate` is the mandatory gate: lint → typecheck → format:check → test → ADR check → knip → build:check
- All ADR rule severities are `error` (not `warning`) — violations are hard blockers
- The pipeline is fail-fast — fix failures in order

Expand Down
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Archgate is a CLI tool for AI governance via Architecture Decision Records (ADRs
- **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 | **Commits:** Conventional Commits
- **Linter:** Oxlint | **Formatter:** Oxfmt | **Dead exports:** Knip | **Commits:** Conventional Commits

## Commands

Expand All @@ -18,14 +18,15 @@ bun run typecheck # tsc --build
bun run format # oxfmt --write
bun run format:check # oxfmt --check
bun test # all tests
bun run validate # MANDATORY: lint + typecheck + format + test + ADR check + build check
bun run knip # dead export detection
bun run validate # MANDATORY: lint + typecheck + format + 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 → test → ADR check → build check. Mirrors CI in `.github/workflows/code-pull-request.yml`.
**`bun run validate` must pass before any task is considered complete.** Fail-fast pipeline: lint → typecheck → format → test → ADR check → knip → build check. Mirrors CI in `.github/workflows/code-pull-request.yml`.

## Architecture

Expand Down
123 changes: 122 additions & 1 deletion bun.lock

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/knip@6/schema.json",
"entry": ["tests/**/*.test.ts"],
"project": ["src/**/*.ts", "tests/**/*.ts"],
"ignore": ["tests/fixtures/**"],
"ignoreDependencies": [
"@commitlint/cli",
"@simple-release/npm",
"conventional-changelog-angular"
],
"ignoreBinaries": ["tsc"],
"ignoreExportsUsedInFile": true,
"tags": ["-internal"]
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,15 @@
"docs:preview": "cd docs && bunx --bun astro preview",
"format": "oxfmt --write .",
"format:check": "oxfmt --check .",
"knip": "knip",
"lint": "oxlint --deny-warnings .",
"postinstall": "node scripts/postinstall.cjs",
"test": "bun test --timeout 60000",
"test:coverage": "bun test --timeout 60000 --coverage --reporter=junit --reporter-outfile=coverage/junit.xml",
"test:watch": "bun test --watch --timeout 60000",
"typecheck": "tsc --build",
"validate": "bun run lint && bun run typecheck && bun run format:check && bun run test && bun run check && bun run build:check",
"validate:coverage": "bun run lint && bun run typecheck && bun run format:check && bun run test:coverage && bun run check && bun run build:check"
"validate": "bun run lint && bun run typecheck && bun run format:check && bun run test && bun run check && bun run knip && bun run build:check",
"validate:coverage": "bun run lint && bun run typecheck && bun run format:check && bun run test:coverage && bun run check && bun run knip && bun run build:check"
},
"devDependencies": {
"@commander-js/extra-typings": "14.0.0",
Expand All @@ -68,6 +69,7 @@
"czg": "1.13.0",
"fast-check": "4.7.0",
"inquirer": "13.4.2",
"knip": "6.12.1",
"meriyah": "7.1.0",
"oxfmt": "0.47.0",
"oxlint": "1.62.0",
Expand Down
1 change: 1 addition & 0 deletions src/commands/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ export function registerUpgradeCommand(program: Command) {
});
}

/** @internal test hooks — consumed via dynamic import() in upgrade.test.ts */
export {
isBinaryInstall as _isBinaryInstall,
isProtoInstall as _isProtoInstall,
Expand Down
10 changes: 5 additions & 5 deletions src/engine/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { ReportSummary } from "./reporter";
import { buildSummary } from "./reporter";
import { runChecks } from "./runner";

export interface AdrBriefing {
interface AdrBriefing {
id: string;
title: string;
domain: AdrDomain;
Expand All @@ -15,13 +15,13 @@ export interface AdrBriefing {
dosAndDonts: string;
}

export interface DomainContext {
interface DomainContext {
domain: AdrDomain;
changedFiles: string[];
adrs: AdrBriefing[];
}

export interface ReviewContext {
interface ReviewContext {
allChangedFiles: string[];
truncatedFiles: boolean;
domains: DomainContext[];
Expand Down Expand Up @@ -69,7 +69,7 @@ export function extractAdrSections(
return result;
}

export interface BriefAdrOptions {
interface BriefAdrOptions {
/** Max chars per section. 0 = unlimited. Default: 2000. */
maxSectionChars?: number;
}
Expand Down Expand Up @@ -190,7 +190,7 @@ const EMPTY_SUMMARY: ReportSummary = {
durationMs: 0,
};

export interface BuildReviewContextOptions {
interface BuildReviewContextOptions {
runChecks?: boolean;
staged?: boolean;
domain?: AdrDomain;
Expand Down
5 changes: 0 additions & 5 deletions src/engine/git-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,6 @@ export function getGitTrackedFiles(
return promise;
}

/** Reset the tracked-files cache. For testing only. */
export function _resetGitFilesCache(): void {
trackedFilesCache.clear();
}

/** Resolve scoped files for an ADR based on its files globs. Respects .gitignore. */
export async function resolveScopedFiles(
projectRoot: string,
Expand Down
11 changes: 3 additions & 8 deletions src/engine/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ import { projectPaths } from "../helpers/paths";
import { ensureRulesShim } from "../helpers/rules-shim";
import { scanRuleSource } from "./rule-scanner";

export interface LoadedAdr {
interface LoadedAdr {
adr: AdrDocument;
ruleSet: RuleSet;
}

export interface BlockedAdr {
interface BlockedAdr {
adr: AdrDocument;
error: string;
violations: Array<{
Expand Down Expand Up @@ -137,7 +137,7 @@ function checkRuleSyntax(source: string): SyntaxViolation[] {
return violations;
}

export interface ParsedAdrEntry {
interface ParsedAdrEntry {
file: string;
adr: AdrDocument;
}
Expand All @@ -153,11 +153,6 @@ export interface ParsedAdrEntry {
*/
const parsedAdrsCache = new Map<string, Promise<ParsedAdrEntry[]>>();

/** Reset the parsed-ADRs cache. For testing only. */
export function _resetAdrParseCache(): void {
parsedAdrsCache.clear();
}

/**
* Read and parse every ADR markdown file in the project, caching the result
* per-process. Returns entries in directory order. Unparseable files are
Expand Down
2 changes: 1 addition & 1 deletion src/engine/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export interface ReportSummary {
durationMs: number;
}

export interface BuildSummaryOptions {
interface BuildSummaryOptions {
/** Maximum violations per rule. When exceeded, only the first N are kept. Omit or 0 for unlimited. */
maxViolationsPerRule?: number;
}
Expand Down
2 changes: 1 addition & 1 deletion src/engine/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function safeGlob(pattern: string): void {
}
const RULE_TIMEOUT_MS = 30_000;

export interface RuleResult {
interface RuleResult {
ruleId: string;
adrId: string;
description: string;
Expand Down
2 changes: 1 addition & 1 deletion src/engine/source-positions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* original TypeScript source, skipping matches in comments and strings.
*/

export interface SourcePos {
interface SourcePos {
line: number;
column: number;
endLine: number;
Expand Down
2 changes: 1 addition & 1 deletion src/formats/project-config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { z } from "zod";

export const DOMAIN_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
export const DOMAIN_PREFIX_PATTERN = /^[A-Z][A-Z0-9_]*$/;
const DOMAIN_PREFIX_PATTERN = /^[A-Z][A-Z0-9_]*$/;

export const DomainNameSchema = z
.string()
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/adr-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ ${opts.body}
});
}

export interface CreateAdrResult {
interface CreateAdrResult {
id: string;
fileName: string;
filePath: string;
Expand Down Expand Up @@ -138,7 +138,7 @@ export async function findAdrFileById(
return results.find((adr) => adr?.frontmatter.id === id) ?? null;
}

export interface UpdateAdrResult {
interface UpdateAdrResult {
id: string;
fileName: string;
filePath: string;
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const GITHUB_CLIENT_ID = "Ov23liZUI9Aiv2ZrSAgn";
// Types
// ---------------------------------------------------------------------------

export interface DeviceCodeResponse {
interface DeviceCodeResponse {
device_code: string;
user_code: string;
verification_uri: string;
Expand Down Expand Up @@ -151,7 +151,7 @@ export async function pollForAccessToken(
throw new Error("Device code expired. Please try again.");
}

export interface GitHubUserInfo {
interface GitHubUserInfo {
login: string;
email: string | null;
}
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/init-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,21 @@ export const EDITOR_LABELS: Record<EditorTarget, string> = {
opencode: "opencode",
};

export interface InitOptions {
interface InitOptions {
editor?: EditorTarget;
/** When true, attempt to install the archgate plugin using stored credentials. */
installPlugin?: boolean;
}

export interface PluginResult {
interface PluginResult {
installed: boolean;
/** For claude manual: marketplace URL; for cursor: file count summary */
detail?: string;
/** When true, plugin was auto-installed via editor CLI (no manual steps needed). */
autoInstalled?: boolean;
}

export interface InitResult {
interface InitResult {
projectRoot: string;
adrsDir: string;
lintDir: string;
Expand Down
5 changes: 0 additions & 5 deletions src/helpers/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ export function setLogLevel(level: LogLevel): void {
}
}

/** Get the current log level. */
export function getLogLevel(): LogLevel {
return currentLevel;
}

function isEnabled(level: LogLevel): boolean {
return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[currentLevel];
}
Expand Down
18 changes: 0 additions & 18 deletions src/helpers/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,6 @@ export function copilotSessionStateDir(): string {
return join(archgateHomeDir(), ".copilot", "session-state");
}

/**
* Resolve the opencode data storage directory.
*
* Opencode stores sessions, messages, and parts under
* `$XDG_DATA_HOME/opencode/storage/` (defaulting to
* `~/.local/share/opencode/storage/`). This is distinct from the
* config directory (`$XDG_CONFIG_HOME/opencode/`) used by
* `opencodeAgentsDir()`.
*
* Resolved at call time (not cached) so tests can override HOME /
* XDG_DATA_HOME.
*/
export function opencodeStorageDir(): string {
const xdg = usableEnv(Bun.env.XDG_DATA_HOME);
const base = xdg ?? join(archgateHomeDir(), ".local", "share");
return join(base, "opencode", "storage");
}

/**
* Resolve the opencode SQLite database path.
*
Expand Down
7 changes: 0 additions & 7 deletions src/helpers/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,6 @@ export function isSupportedPlatform(): boolean {
return runtime === "darwin" || runtime === "linux" || runtime === "win32";
}

/**
* Reset the cached platform info. For testing only.
*/
export function _resetPlatformCache(): void {
cachedPlatformInfo = null;
}

// ---------------------------------------------------------------------------
// Path Conversion (async, WSL only)
// ---------------------------------------------------------------------------
Expand Down
5 changes: 0 additions & 5 deletions src/helpers/repo-probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,6 @@ export function _resetPublicProbeCache(): void {
cachedPublicProbe = null;
}

/** Inject a probe result. For testing only. */
export function _setPublicProbeForTest(value: boolean | null): void {
cachedPublicProbe = Promise.resolve(value);
}

// ---------------------------------------------------------------------------
// Dispatch
// ---------------------------------------------------------------------------
Expand Down
8 changes: 2 additions & 6 deletions src/helpers/repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,11 @@
import { createHash } from "node:crypto";

import { logDebug } from "./log";
import {
_resetPublicProbeCache,
_setPublicProbeForTest,
isPublicRepo,
} from "./repo-probe";
import { _resetPublicProbeCache, isPublicRepo } from "./repo-probe";

// Re-export the public-visibility probe so commands / telemetry can import
// everything repo-related from one place.
export { isPublicRepo, _setPublicProbeForTest };
export { isPublicRepo };

// ---------------------------------------------------------------------------
// Types
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/session-context-copilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import {
getContentPreview,
} from "./session-context";

export interface CopilotSessionSummary {
interface CopilotSessionSummary {
sessionId: string;
sessionFile: string;
totalEntries: number;
relevantEntries: number;
transcript: Array<{ role: string; contentPreview: string }>;
}

export interface ReadCopilotSessionOptions extends ReadSessionOptions {
interface ReadCopilotSessionOptions extends ReadSessionOptions {
sessionId?: string;
}

Expand Down
4 changes: 2 additions & 2 deletions src/helpers/session-context-opencode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import {
getContentPreview,
} from "./session-context";

export interface OpencodeSessionSummary {
interface OpencodeSessionSummary {
sessionId: string;
totalEntries: number;
relevantEntries: number;
transcript: Array<{ role: string; contentPreview: string }>;
}

export interface ReadOpencodeSessionOptions extends ReadSessionOptions {
interface ReadOpencodeSessionOptions extends ReadSessionOptions {
sessionId?: string;
}

Expand Down
Loading
Loading