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
8 changes: 8 additions & 0 deletions .archgate/adrs/ARCH-022-ast-aware-rule-context.rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ export default {
}
return;
}
// Overloaded function declaration: `async function astImpl(…) {…}`
if (n.type === "FunctionDeclaration") {
const id = n.id as (EsTreeNode & { name?: string }) | undefined;
if (id?.name === "astImpl") {
astMethodBody = n.body;
}
return;
}
// Fallback: inline `ast(path, language) { … }` object method, in
// case the implementation is ever moved back onto the object.
if (n.type === "Property") {
Expand Down
22 changes: 10 additions & 12 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#!/usr/bin/env bun
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Archgate
import { Command, Option } from "@commander-js/extra-typings";
import {
Command,
type CommandUnknownOpts,
Option,
} from "@commander-js/extra-typings";
import { semver } from "bun";

import packageJson from "../package.json";
Expand All @@ -24,7 +28,7 @@ import {
finalizeCommand,
} from "./helpers/exit";
import { installGit } from "./helpers/git";
import { type LogLevel, logError, setLogLevel } from "./helpers/log";
import { logError, setLogLevel } from "./helpers/log";
import { createPathIfNotExists, paths } from "./helpers/paths";
import { getPlatformInfo, isSupportedPlatform } from "./helpers/platform";
import {
Expand Down Expand Up @@ -113,11 +117,11 @@ async function main() {

// Apply log level from global option before any command runs
const rootOpts = program.opts();
setLogLevel(rootOpts.logLevel as LogLevel);
setLogLevel(rootOpts.logLevel);
const fullCommand = getFullCommandName(actionCommand);
addBreadcrumb("command", `Running: ${fullCommand}`);
// Collect which options were used (presence only, no values)
const opts = actionCommand.opts() as Record<string, unknown>;
const opts = actionCommand.opts();
const optionFlags: Record<string, boolean> = {};
const optionsUsed: string[] = [];
for (const key of Object.keys(opts)) {
Expand Down Expand Up @@ -169,22 +173,16 @@ async function main() {
/**
* Reconstruct the full command name from Commander's command chain.
* E.g., "adr create" from the "create" subcommand of "adr".
*
* Typed against the loose Commander "unknown opts" shape because it's called
* from the `preAction` / `postAction` hook callback, where Commander gives us
* a `CommandUnknownOpts`, not the narrowly-typed `Command<[], {}, {}>`.
*/
function getFullCommandName(
command: { name(): string; parent: unknown } | null
): string {
function getFullCommandName(command: CommandUnknownOpts | null): string {
const parts: string[] = [];
let current = command;
while (current) {
const name = current.name();
if (name && name !== "archgate") {
parts.unshift(name);
}
current = current.parent as typeof command;
current = current.parent;
}
return parts.join(" ") || "root";
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/adr/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function registerAdrCreateCommand(adr: Command) {
])
);

domain = answers.domain as AdrDomain;
domain = answers.domain;
title = answers.title;
files = answers.files
? answers.files
Expand Down
2 changes: 1 addition & 1 deletion src/commands/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,6 @@ export function registerCheckCommand(program: Command) {

const exitCode = getExitCode(result, summary);
// Only 0, 1, and 2 are emitted by getExitCode()
await exitWith(exitCode as 0 | 1 | 2);
await exitWith(exitCode);
});
}
18 changes: 7 additions & 11 deletions src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import { Option } from "@commander-js/extra-typings";
import { loadCredentials } from "../helpers/credential-store";
import { detectEditors, promptEditorSelection } from "../helpers/editor-detect";
import { exitWith, handleCommandError } from "../helpers/exit";
import { EDITOR_LABELS, initProject } from "../helpers/init-project";
import {
EDITOR_LABELS,
EDITOR_TARGETS,
SIGNUP_EDITORS,
initProject,
} from "../helpers/init-project";
import type { EditorTarget } from "../helpers/init-project";
import { logError, logInfo, logWarn } from "../helpers/log";
import { runLoginFlow } from "../helpers/login-flow";
Expand Down Expand Up @@ -42,19 +47,10 @@ const EDITOR_DIRS: Record<EditorTarget, string> = {
opencode: "(user-scope)",
};

/** Map init editor flags to signup editor identifiers. */
const SIGNUP_EDITORS: Record<EditorTarget, string> = {
claude: "claude-code",
cursor: "cursor",
vscode: "vscode",
copilot: "copilot-cli",
opencode: "opencode",
};

const editorOption = new Option(
"--editor <editor>",
"editor integration (omit to auto-detect and select)"
).choices(["claude", "cursor", "vscode", "copilot", "opencode"] as const);
).choices(EDITOR_TARGETS);

export function registerInitCommand(program: Command) {
program
Expand Down
4 changes: 2 additions & 2 deletions src/commands/plugin/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
promptEditorSelection,
} from "../../helpers/editor-detect";
import { exitWith, handleCommandError } from "../../helpers/exit";
import { EDITOR_LABELS } from "../../helpers/init-project";
import { EDITOR_LABELS, EDITOR_TARGETS } from "../../helpers/init-project";
import type { EditorTarget } from "../../helpers/init-project";
import { logError, logInfo, logWarn } from "../../helpers/log";
import { findProjectRoot } from "../../helpers/paths";
Expand All @@ -32,7 +32,7 @@ import { configureVscodeSettings } from "../../helpers/vscode-settings";
const editorOption = new Option(
"--editor <editor>",
"target editor (omit to auto-detect and select)"
).choices(["claude", "cursor", "vscode", "copilot", "opencode"] as const);
).choices(EDITOR_TARGETS);

/**
* Install the archgate plugin for a single editor.
Expand Down
4 changes: 2 additions & 2 deletions src/commands/plugin/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
promptSingleEditorSelection,
} from "../../helpers/editor-detect";
import { handleCommandError } from "../../helpers/exit";
import type { EditorTarget } from "../../helpers/init-project";
import { EDITOR_TARGETS, type EditorTarget } from "../../helpers/init-project";
import {
buildCursorMarketplaceUrl,
buildMarketplaceUrl,
Expand All @@ -18,7 +18,7 @@ import {
const editorOption = new Option(
"--editor <editor>",
"target editor (omit to auto-detect and select)"
).choices(["claude", "cursor", "vscode", "copilot", "opencode"] as const);
).choices(EDITOR_TARGETS);

export function registerPluginUrlCommand(plugin: Command) {
plugin
Expand Down
2 changes: 1 addition & 1 deletion src/engine/ast-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export function parseAstJson(
language: string
): Record<string, unknown> | unknown[] {
try {
return JSON.parse(stdout) as Record<string, unknown> | unknown[];
return JSON.parse(stdout);
} catch {
throw new Error(
`Failed to parse "${path}" as ${language}: interpreter produced invalid JSON output`
Expand Down
4 changes: 2 additions & 2 deletions src/engine/git-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ export async function resolveScopedFiles(
fileWarnThreshold?: number;
}
): Promise<string[]> {
const hasExplicitFiles = (adrFileGlobs?.length ?? 0) > 0;
const patterns = hasExplicitFiles ? adrFileGlobs! : ["**/*"];
const patterns = adrFileGlobs?.length ? adrFileGlobs : ["**/*"];
const hasExplicitFiles = Boolean(adrFileGlobs?.length);
const respectGitignore = options?.respectGitignore !== false;
const label = options?.adrId ? `ADR ${options.adrId}` : "resolveScopedFiles";
const fileWarnThreshold =
Expand Down
27 changes: 12 additions & 15 deletions src/engine/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,10 @@ export function reportConsole(
const loc = v.file ? (v.line ? `${v.file}:${v.line}` : v.file) : "";
const sevColor =
v.severity === "error"
? "red"
? ("red" as const)
: v.severity === "warning"
? "yellow"
: "dim";
? ("yellow" as const)
: ("dim" as const);

const sevLabel =
v.severity === "error"
Expand All @@ -191,7 +191,7 @@ export function reportConsole(
: "info";

console.log(
` ${styleText(sevColor as "red", `[${sevLabel}]`)} ${v.message}${loc ? ` ${styleText("dim", loc)}` : ""}`
` ${styleText(sevColor, `[${sevLabel}]`)} ${v.message}${loc ? ` ${styleText("dim", loc)}` : ""}`
);

if (v.fix && verbose) {
Expand Down Expand Up @@ -312,23 +312,20 @@ export function reportCI(
* one for telemetry, and walking `result.results` a second time here is pure
* duplication.
*/
export function getExitCode(
result: CheckResult,
summary?: ReportSummary
): number {
export function getExitCode(result: CheckResult, summary?: ReportSummary) {
if (summary) {
if (summary.ruleErrors > 0) return 2;
if (summary.failed > 0) return 1;
if (summary.warningsExceeded) return 1;
return 0;
if (summary.ruleErrors > 0) return 2 as const;
if (summary.failed > 0) return 1 as const;
if (summary.warningsExceeded) return 1 as const;
return 0 as const;
}
const hasErrors = result.results.some((r) => r.error);
if (hasErrors) return 2;
if (hasErrors) return 2 as const;

const hasViolations = result.results.some((r) =>
r.violations.some((v) => v.severity === "error")
);
if (hasViolations) return 1;
if (hasViolations) return 1 as const;

return 0;
return 0 as const;
}
Loading