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
3 changes: 3 additions & 0 deletions .claude/agent-memory/archgate-developer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Skipping steps 2 or 3 is a workflow violation. The user should NEVER have to inv

- **`Bun.env` modifications in parallel test files leak into integration test subprocesses** — Bun test runner runs all test files in a single process sharing `Bun.env`. Tests that set `Bun.env.HOME`, `Bun.env.GIT_CONFIG_NOSYSTEM`, or `Bun.env.GIT_CONFIG_GLOBAL` (e.g., `auth.test.ts`, `credential-store.test.ts`) modify the shared environment. Integration tests that spawn CLI subprocesses via `runCli()` spread `process.env` (which IS `Bun.env`) into the child, inheriting the leaked values. Symptom: git operations in the subprocess fail with "not a git repo" or similar, but the test passes in isolation. Fix: integration tests that rely on git must explicitly reset git-related env vars in the `runCli` call: `runCli(args, dir, { GIT_CONFIG_NOSYSTEM: "", GIT_CONFIG_GLOBAL: "" })`. Applied in `tests/integration/check.test.ts` for the `--base` tests.
- **Cross-command I/O sharing: export from the existing command file, don't create shared files** — When two commands need to share I/O functions (console.log with styleText), you CANNOT put them in `src/helpers/` (ARCH-002 forbids console.log in helpers) or create a new file under `src/commands/<parent>/` without a register function (ARCH-001 requires register\*Command export, ARCH-016 requires docs heading). The correct pattern: export the shared functions from the command file that already defines them (e.g., `plugin/install.ts` exports `installForEditor()` and `printManualInstructions()`) and import them in the other command. Applied in `upgrade.ts` importing from `./plugin/install`.
- **macOS `/var` → `/private/var` symlink breaks temp dir path comparisons in tests** — On macOS, `/var` is a symlink to `/private/var`. `mkdtempSync(join(tmpdir(), ...))` returns `/var/folders/...` but `process.cwd()` after `chdir()` resolves the symlink to `/private/var/folders/...`. Tests that compare `tempDir` against paths derived from `process.cwd()` or `findProjectRoot()` will fail. Fix: always wrap `mkdtempSync` with `realpathSync` in test setup: `tempDir = realpathSync(mkdtempSync(join(tmpdir(), "archgate-test-")))`. This normalizes the path upfront. Discovered in v0.38.0/v0.39.0 release builds — PR CI runs on ubuntu-latest only, so macOS-specific issues are invisible until the release workflow.
- **Always use `bun run test`, never bare `bun test`, in CI workflows** — The package.json `test` script includes `--timeout 60000`, but bare `bun test` uses Bun's default 5000ms timeout. Tests that perform filesystem operations or spawn subprocesses (e.g., session-context tests) can exceed 5s on slow CI runners. The `release-binaries.yml` Windows step originally used `bun test` and hit timeout failures. Same principle as "never use `bunx prettier` directly" — always prefer `bun run <script>` to pick up script-level flags.
- **Don't test that well-known tools exist on PATH** — Tests like `expect(resolveCommand("bun")).toBe("bun")` assert CI environment state, not application logic. They fail when the runner installs tools via shims (e.g., proto on macOS ARM64 where `Bun.which` returns null). Delete such tests entirely — the "returns null for non-existent command" tests already cover `resolveCommand`'s actual logic, and WSL-specific tests cover the `.exe` fallback path.

## Validation Pipeline

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:

- name: Validate (Windows)
if: runner.os == 'Windows'
run: bun run lint && bun run typecheck && bun run format:check && bun test
run: bun run lint && bun run typecheck && bun run format:check && bun run test

- name: Build binary
run: |
Expand Down
1 change: 1 addition & 0 deletions src/helpers/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export function isMacOS(): boolean {

/**
* Returns true if the process is running on Linux (including WSL).
* @public
*/
export function isLinux(): boolean {
return getPlatformInfo().runtime === "linux";
Expand Down
11 changes: 9 additions & 2 deletions tests/commands/adr/import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
mkdtempSync,
readdirSync,
readFileSync,
realpathSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
Expand Down Expand Up @@ -130,8 +131,14 @@ describe("import action handler", () => {
let exitSpy: ReturnType<typeof spyOn>;

beforeEach(() => {
tempDir = mkdtempSync(join(tmpdir(), "archgate-import-test-"));
upstreamDir = mkdtempSync(join(tmpdir(), "archgate-upstream-"));
// realpathSync normalizes macOS /var → /private/var symlink so paths
// match what process.cwd() and mock.module resolve to at runtime.
tempDir = realpathSync(
mkdtempSync(join(tmpdir(), "archgate-import-test-"))
);
upstreamDir = realpathSync(
mkdtempSync(join(tmpdir(), "archgate-upstream-"))
);
originalCwd = process.cwd();
Bun.env.ARCHGATE_PROJECT_CEILING = tempDir;
logSpy = spyOn(console, "log").mockImplementation(() => {});
Expand Down
6 changes: 4 additions & 2 deletions tests/commands/session-context/claude-code.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
spyOn,
test,
} from "bun:test";
import { mkdirSync, mkdtempSync } from "node:fs";
import { mkdirSync, mkdtempSync, realpathSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

Expand Down Expand Up @@ -76,7 +76,9 @@ describe("claude-code action handler", () => {
let exitSpy: ReturnType<typeof spyOn>;

beforeEach(() => {
tempDir = mkdtempSync(join(tmpdir(), "archgate-cc-test-"));
// realpathSync normalizes macOS /var → /private/var symlink so the
// path matches what process.cwd() returns after chdir.
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "archgate-cc-test-")));
originalCwd = process.cwd();
// Create .archgate/ so findProjectRoot returns this dir
mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true });
Expand Down
8 changes: 6 additions & 2 deletions tests/commands/session-context/copilot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
spyOn,
test,
} from "bun:test";
import { mkdirSync, mkdtempSync } from "node:fs";
import { mkdirSync, mkdtempSync, realpathSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

Expand Down Expand Up @@ -80,7 +80,11 @@ describe("copilot action handler", () => {
let exitSpy: ReturnType<typeof spyOn>;

beforeEach(() => {
tempDir = mkdtempSync(join(tmpdir(), "archgate-copilot-test-"));
// realpathSync normalizes macOS /var → /private/var symlink so the
// path matches what process.cwd() returns after chdir.
tempDir = realpathSync(
mkdtempSync(join(tmpdir(), "archgate-copilot-test-"))
);
originalCwd = process.cwd();
mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true });
Bun.env.ARCHGATE_PROJECT_CEILING = tempDir;
Expand Down
8 changes: 6 additions & 2 deletions tests/commands/session-context/cursor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
spyOn,
test,
} from "bun:test";
import { mkdirSync, mkdtempSync } from "node:fs";
import { mkdirSync, mkdtempSync, realpathSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

Expand Down Expand Up @@ -80,7 +80,11 @@ describe("cursor action handler", () => {
let exitSpy: ReturnType<typeof spyOn>;

beforeEach(() => {
tempDir = mkdtempSync(join(tmpdir(), "archgate-cursor-test-"));
// realpathSync normalizes macOS /var → /private/var symlink so the
// path matches what process.cwd() returns after chdir.
tempDir = realpathSync(
mkdtempSync(join(tmpdir(), "archgate-cursor-test-"))
);
originalCwd = process.cwd();
mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true });
Bun.env.ARCHGATE_PROJECT_CEILING = tempDir;
Expand Down
8 changes: 6 additions & 2 deletions tests/commands/session-context/opencode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
spyOn,
test,
} from "bun:test";
import { mkdirSync, mkdtempSync } from "node:fs";
import { mkdirSync, mkdtempSync, realpathSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

Expand Down Expand Up @@ -80,7 +80,11 @@ describe("opencode action handler", () => {
let exitSpy: ReturnType<typeof spyOn>;

beforeEach(() => {
tempDir = mkdtempSync(join(tmpdir(), "archgate-opencode-test-"));
// realpathSync normalizes macOS /var → /private/var symlink so the
// path matches what process.cwd() returns after chdir.
tempDir = realpathSync(
mkdtempSync(join(tmpdir(), "archgate-opencode-test-"))
);
originalCwd = process.cwd();
mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true });
Bun.env.ARCHGATE_PROJECT_CEILING = tempDir;
Expand Down
Loading
Loading