Run Biome formatter and add formatting guidance to CLAUDE.md

This commit is contained in:
Mohamed Boudra
2026-04-12 10:40:14 +07:00
parent 40e27f5a00
commit 56512d852b
7 changed files with 60 additions and 55 deletions
+3
View File
@@ -35,6 +35,8 @@ npm run dev # Start daemon + Expo in Tmux
npm run cli -- ls -a -g # List all agents npm run cli -- ls -a -g # List all agents
npm run cli -- daemon status # Check daemon status npm run cli -- daemon status # Check daemon status
npm run typecheck # Always run after changes npm run typecheck # Always run after changes
npm run format # Auto-format with Biome
npm run format:check # Check formatting without writing
``` ```
See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for full setup, build sync requirements, and debugging. See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for full setup, build sync requirements, and debugging.
@@ -45,6 +47,7 @@ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for full setup, build sync requir
- **NEVER assume a timeout means the service needs restarting** — timeouts can be transient. - **NEVER assume a timeout means the service needs restarting** — timeouts can be transient.
- **NEVER add auth checks to tests** — agent providers handle their own auth. - **NEVER add auth checks to tests** — agent providers handle their own auth.
- **Always run typecheck after every change.** - **Always run typecheck after every change.**
- **Run `npm run format` before committing.** This repo uses Biome for formatting. Do not manually fix formatting — let the formatter handle it.
- **NEVER make breaking changes to WebSocket or message schemas.** The primary compatibility path is old mobile app clients talking to newly updated daemons. Users update desktop and daemon first, then keep running the old app for a while. Every schema change MUST be backward-compatible for old clients against new daemons: - **NEVER make breaking changes to WebSocket or message schemas.** The primary compatibility path is old mobile app clients talking to newly updated daemons. Users update desktop and daemon first, then keep running the old app for a while. Every schema change MUST be backward-compatible for old clients against new daemons:
- New fields: always `.optional()` with a sensible default or `.transform()` fallback. - New fields: always `.optional()` with a sensible default or `.transform()` fallback.
- Never change a field from optional to required. - Never change a field from optional to required.
@@ -37,18 +37,17 @@ describe("splitMarkdownBlocks", () => {
it("splits mixed paragraph, code fence, and paragraph content into three blocks", () => { it("splits mixed paragraph, code fence, and paragraph content into three blocks", () => {
expect( expect(
splitMarkdownBlocks("Intro paragraph\n\n```ts\nconst a = 1;\n\nconst b = 2;\n```\n\nOutro paragraph"), splitMarkdownBlocks(
).toEqual([ "Intro paragraph\n\n```ts\nconst a = 1;\n\nconst b = 2;\n```\n\nOutro paragraph",
"Intro paragraph", ),
"```ts\nconst a = 1;\n\nconst b = 2;\n```", ).toEqual(["Intro paragraph", "```ts\nconst a = 1;\n\nconst b = 2;\n```", "Outro paragraph"]);
"Outro paragraph",
]);
}); });
it("keeps everything from an unclosed fence start as one block for streaming content", () => { it("keeps everything from an unclosed fence start as one block for streaming content", () => {
expect( expect(splitMarkdownBlocks("Before fence\n\n```ts\nconst a = 1;\n\nconst b = 2;")).toEqual([
splitMarkdownBlocks("Before fence\n\n```ts\nconst a = 1;\n\nconst b = 2;"), "Before fence",
).toEqual(["Before fence", "```ts\nconst a = 1;\n\nconst b = 2;"]); "```ts\nconst a = 1;\n\nconst b = 2;",
]);
}); });
it("returns an empty array for empty input", () => { it("returns an empty array for empty input", () => {
@@ -43,10 +43,7 @@ export function splitMarkdownBlocks(text: string): string[] {
continue; continue;
} }
if ( if (fenceDelimiter[0] === activeFenceCharacter && fenceDelimiter.length >= activeFenceLength) {
fenceDelimiter[0] === activeFenceCharacter &&
fenceDelimiter.length >= activeFenceLength
) {
activeFenceCharacter = null; activeFenceCharacter = null;
activeFenceLength = 0; activeFenceLength = 0;
} }
@@ -47,11 +47,11 @@ function createWorkspaceRuntimeSnapshot(
...overrides?.github, ...overrides?.github,
pullRequest: pullRequest:
overrides?.github && "pullRequest" in overrides.github overrides?.github && "pullRequest" in overrides.github
? overrides.github.pullRequest ?? null ? (overrides.github.pullRequest ?? null)
: base.github.pullRequest, : base.github.pullRequest,
error: error:
overrides?.github && "error" in overrides.github overrides?.github && "error" in overrides.github
? overrides.github.error ?? null ? (overrides.github.error ?? null)
: base.github.error, : base.github.error,
}, },
}; };
@@ -309,7 +309,9 @@ describe("workspace git watch targets", () => {
}); });
expect(workspaceGitService.getSnapshot).toHaveBeenCalledWith("/tmp/repo"); expect(workspaceGitService.getSnapshot).toHaveBeenCalledWith("/tmp/repo");
expect(emitted.find((message) => message.type === "checkout_pr_status_response")?.payload).toEqual({ expect(
emitted.find((message) => message.type === "checkout_pr_status_response")?.payload,
).toEqual({
cwd: "/tmp/repo", cwd: "/tmp/repo",
status: { status: {
url: "https://github.com/acme/repo/pull/456", url: "https://github.com/acme/repo/pull/456",
@@ -366,7 +368,9 @@ describe("workspace git watch targets", () => {
expect(workspaceGitService.refresh).toHaveBeenCalledWith("/tmp/repo", { expect(workspaceGitService.refresh).toHaveBeenCalledWith("/tmp/repo", {
priority: "high", priority: "high",
}); });
expect(emitted.find((message) => message.type === "checkout_pr_status_response")?.payload).toEqual({ expect(
emitted.find((message) => message.type === "checkout_pr_status_response")?.payload,
).toEqual({
cwd: "/tmp/repo", cwd: "/tmp/repo",
status: { status: {
url: "https://github.com/acme/repo/pull/457", url: "https://github.com/acme/repo/pull/457",
@@ -165,20 +165,22 @@ function createWorkspaceRuntimeSnapshot(
...overrides?.github, ...overrides?.github,
pullRequest: pullRequest:
overrides?.github && "pullRequest" in overrides.github overrides?.github && "pullRequest" in overrides.github
? overrides.github.pullRequest ?? null ? (overrides.github.pullRequest ?? null)
: base.github.pullRequest, : base.github.pullRequest,
error: error:
overrides?.github && "error" in overrides.github overrides?.github && "error" in overrides.github
? overrides.github.error ?? null ? (overrides.github.error ?? null)
: base.github.error, : base.github.error,
}, },
}; };
} }
function createSessionForWorkspaceTests(options: { function createSessionForWorkspaceTests(
appVersion?: string | null; options: {
workspaceGitService?: ReturnType<typeof createNoopWorkspaceGitService>; appVersion?: string | null;
} = {}): Session { workspaceGitService?: ReturnType<typeof createNoopWorkspaceGitService>;
} = {},
): Session {
const logger = { const logger = {
child: () => logger, child: () => logger,
trace: vi.fn(), trace: vi.fn(),
@@ -238,8 +240,8 @@ function createSessionForWorkspaceTests(options: {
checkoutDiffWatcherCount: 0, checkoutDiffWatcherCount: 0,
checkoutDiffFallbackRefreshTargetCount: 0, checkoutDiffFallbackRefreshTargetCount: 0,
}), }),
dispose: () => {}, dispose: () => {},
} as any, } as any,
workspaceGitService: (options.workspaceGitService ?? createNoopWorkspaceGitService()) as any, workspaceGitService: (options.workspaceGitService ?? createNoopWorkspaceGitService()) as any,
mcpBaseUrl: null, mcpBaseUrl: null,
stt: null, stt: null,
@@ -1,8 +1,5 @@
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import type { import type { CheckoutStatusGit, PullRequestStatusResult } from "../utils/checkout-git.js";
CheckoutStatusGit,
PullRequestStatusResult,
} from "../utils/checkout-git.js";
import { import {
WorkspaceGitServiceImpl, WorkspaceGitServiceImpl,
type WorkspaceGitRuntimeSnapshot, type WorkspaceGitRuntimeSnapshot,
@@ -65,11 +62,11 @@ function createSnapshot(
...overrides?.github, ...overrides?.github,
pullRequest: pullRequest:
overrides?.github && "pullRequest" in overrides.github overrides?.github && "pullRequest" in overrides.github
? overrides.github.pullRequest ?? null ? (overrides.github.pullRequest ?? null)
: base.github.pullRequest, : base.github.pullRequest,
error: error:
overrides?.github && "error" in overrides.github overrides?.github && "error" in overrides.github
? overrides.github.error ?? null ? (overrides.github.error ?? null)
: base.github.error, : base.github.error,
}, },
}; };
@@ -139,7 +136,7 @@ function createService(options?: {
logger: createLogger() as any, logger: createLogger() as any,
paseoHome: "/tmp/paseo-test", paseoHome: "/tmp/paseo-test",
deps: { deps: {
watch: options?.watch ?? (((() => createWatcher()) as unknown) as any), watch: options?.watch ?? ((() => createWatcher()) as unknown as any),
getCheckoutStatus: getCheckoutStatus:
options?.getCheckoutStatus ?? vi.fn(async (cwd: string) => createCheckoutStatus(cwd)), options?.getCheckoutStatus ?? vi.fn(async (cwd: string) => createCheckoutStatus(cwd)),
getCheckoutShortstat: getCheckoutShortstat:
@@ -149,8 +146,7 @@ function createService(options?: {
deletions: 0, deletions: 0,
})), })),
getPullRequestStatus: getPullRequestStatus:
options?.getPullRequestStatus ?? options?.getPullRequestStatus ?? vi.fn(async () => createPullRequestStatusResult()),
vi.fn(async () => createPullRequestStatusResult()),
resolveGhPath: options?.resolveGhPath ?? vi.fn(async () => "/usr/bin/gh"), resolveGhPath: options?.resolveGhPath ?? vi.fn(async () => "/usr/bin/gh"),
resolveAbsoluteGitDir: options?.resolveAbsoluteGitDir ?? vi.fn(async () => "/tmp/repo/.git"), resolveAbsoluteGitDir: options?.resolveAbsoluteGitDir ?? vi.fn(async () => "/tmp/repo/.git"),
hasOriginRemote: options?.hasOriginRemote ?? vi.fn(async () => false), hasOriginRemote: options?.hasOriginRemote ?? vi.fn(async () => false),
@@ -325,10 +321,7 @@ describe("WorkspaceGitServiceImpl", () => {
}), }),
); );
const nowValues = [ const nowValues = [new Date("2026-04-12T00:00:00.000Z"), new Date("2026-04-12T00:05:00.000Z")];
new Date("2026-04-12T00:00:00.000Z"),
new Date("2026-04-12T00:05:00.000Z"),
];
const service = createService({ const service = createService({
getPullRequestStatus, getPullRequestStatus,
now: () => nowValues.shift() ?? new Date("2026-04-12T00:05:00.000Z"), now: () => nowValues.shift() ?? new Date("2026-04-12T00:05:00.000Z"),
@@ -383,20 +376,18 @@ describe("WorkspaceGitServiceImpl", () => {
aheadOfOrigin: 2, aheadOfOrigin: 2,
}), }),
); );
const getPullRequestStatus = vi const getPullRequestStatus = vi.fn<() => Promise<PullRequestStatusResult>>().mockResolvedValue(
.fn<() => Promise<PullRequestStatusResult>>() createPullRequestStatusResult({
.mockResolvedValue( status: {
createPullRequestStatusResult({ url: "https://github.com/acme/repo/pull/123",
status: { title: "Runtime payloads",
url: "https://github.com/acme/repo/pull/123", state: "open",
title: "Runtime payloads", baseRefName: "main",
state: "open", headRefName: "feature/runtime-payloads",
baseRefName: "main", isMerged: false,
headRefName: "feature/runtime-payloads", },
isMerged: false, }),
}, );
}),
);
const service = createService({ const service = createService({
getCheckoutStatus, getCheckoutStatus,
@@ -258,7 +258,10 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
this.scheduleWorkspaceRefresh(target); this.scheduleWorkspaceRefresh(target);
}); });
} catch (error) { } catch (error) {
this.logger.warn({ err: error, cwd: target.cwd, watchPath }, "Failed to start workspace git watcher"); this.logger.warn(
{ err: error, cwd: target.cwd, watchPath },
"Failed to start workspace git watcher",
);
} }
if (!watcher) { if (!watcher) {
@@ -332,7 +335,10 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
const snapshot = await this.refreshSnapshot(target.cwd); const snapshot = await this.refreshSnapshot(target.cwd);
this.rememberSnapshot(target, snapshot, { notify: true }); this.rememberSnapshot(target, snapshot, { notify: true });
} catch (error) { } catch (error) {
this.logger.warn({ err: error, cwd: target.cwd }, "Failed to refresh workspace git snapshot"); this.logger.warn(
{ err: error, cwd: target.cwd },
"Failed to refresh workspace git snapshot",
);
} }
} while (target.refreshQueued); } while (target.refreshQueued);
})(); })();
@@ -378,7 +384,10 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
} }
target.fetchInFlight = true; target.fetchInFlight = true;
this.logger.debug({ repoGitRoot: target.repoGitRoot, cwd: target.cwd }, "Running background git fetch"); this.logger.debug(
{ repoGitRoot: target.repoGitRoot, cwd: target.cwd },
"Running background git fetch",
);
try { try {
await this.deps.runGitFetch(target.cwd); await this.deps.runGitFetch(target.cwd);