Skip to content
Open
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: 1 addition & 2 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@
- '.gitignore'
- '.gitmodules'
- '.oxlintrc.json'
- '.prettierignore'
- '.prettierrc'
- '.oxfmtrc.json'
- 'scripts/**'
- 'packages/configs/**'
- 'patches/**'
Expand Down
46 changes: 46 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"sortPackageJson": false,
"ignorePatterns": [
"node_modules/",
"dist/",
"build/",
".next/",
".nuxt/",
".vscode/",
".idea/",
"*.log",
"*.tgz",
"*.tar.gz",
"package-lock.json",
"yarn.lock",
".DS_Store",
"coverage/",
"packages/acp-extension-claude/",
"packages/acp-extension-codex/",
"packages/acp-extension-core/",
"packages/acp-extension-dsh/",
"packages/acp-extension-grok/",
"packages/acp-extension-kimi/",
"apps/electron/out/",
"apps/electron/dist/",
"apps/electron/.vscode/",
"apps/electron/pnpm-lock.yaml",
"apps/electron/LICENSE.md",
"apps/electron/tsconfig.json",
"apps/electron/tsconfig.*.json"
],
"overrides": [
{
"files": ["apps/electron/**"],
"options": {
"semi": false,
"trailingComma": "none"
}
}
]
}
14 changes: 0 additions & 14 deletions .prettierignore

This file was deleted.

12 changes: 0 additions & 12 deletions .prettierrc

This file was deleted.

12 changes: 6 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@ Use Node.js 22+ and the pnpm version pinned in `package.json`. Install with
`pnpm install`. A parent pnpm workspace owns nested checkouts; the public
preinstall guard rejects a second install. Use a separate clone for standalone
public development. `pnpm start:local` is the canonical desktop command; root
`pnpm build` is the same local composition. Before committing, normally run
`pnpm check` and `pnpm format`. If asked to skip tests, report the narrower
type/build/static validation instead. Conventional Commits (`feat:`, `fix:`,
`docs:`, `chore:`, `test:`); AI commits end with `Model: <runtime-model-id>`.
CI uses `pnpm install --frozen-lockfile`, so manifest changes update
`pnpm-lock.yaml`.
`pnpm build` is the same local composition. Before committing, run
`pnpm check` and `pnpm format`. Root packages share `.oxfmtrc.json`; ACP
submodules stay independently formatted. If tests are explicitly skipped,
report narrower validation. Use Conventional Commits; AI commits end with
`Model: <runtime-model-id>`. CI uses `pnpm install --frozen-lockfile`, so
manifests update `pnpm-lock.yaml`.

## Test quality

Expand Down
5 changes: 2 additions & 3 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"copy:dsh-presets": "node scripts/copy-deepseek-presets.js",
"copy:wasm": "node scripts/copy-loro-wasm.js",
"clean": "rimraf dist dist-dev",
"format": "prettier --write src/**/*.ts",
"format:check": "prettier --check src/**/*.ts",
"format": "oxfmt 'src/**/*.ts'",
"format:check": "oxfmt --check 'src/**/*.ts'",
"typecheck": "tsgo --noEmit",
"test": "vitest run",
"bench:acp-history": "vitest bench tests/acp-history.bench.ts",
Expand Down Expand Up @@ -92,7 +92,6 @@
"loro-repo": "catalog:",
"ora": "^8.2.0",
"posthog-node": "^5.38.8",
"prettier": "^3.6.2",
"proxy-from-env": "^1.1.0",
"rimraf": "^6.1.2",
"tar": "^7.5.7",
Expand Down
28 changes: 12 additions & 16 deletions apps/cli/src/commands/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,12 @@ describe('resolveLocalProjectForApp', () => {
});

it('still resolves the deterministic project id when the daemon is down', async () => {
const send = vi.fn(
async (): Promise<LocalProjectControlResponse> => ({
ok: false,
type: 'local-project/add',
error: 'daemon_unavailable',
message: 'Local CLI daemon is not running.',
})
);
const send = vi.fn(async (): Promise<LocalProjectControlResponse> => ({
ok: false,
type: 'local-project/add',
error: 'daemon_unavailable',
message: 'Local CLI daemon is not running.',
}));

const target = await resolveLocalProjectForApp({
machineId: MACHINE_ID,
Expand All @@ -166,14 +164,12 @@ describe('resolveLocalProjectForApp', () => {
});

it('propagates other control failures', async () => {
const send = vi.fn(
async (): Promise<LocalProjectControlResponse> => ({
ok: false,
type: 'local-project/add',
error: 'path_invalid',
message: 'Selected path is not a directory',
})
);
const send = vi.fn(async (): Promise<LocalProjectControlResponse> => ({
ok: false,
type: 'local-project/add',
error: 'path_invalid',
message: 'Selected path is not a directory',
}));

await expect(
resolveLocalProjectForApp({
Expand Down
31 changes: 13 additions & 18 deletions apps/cli/src/lib/__tests__/concurrent-queue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,16 +314,18 @@ describe('ConcurrentQueue', () => {
const queue = new ConcurrentQueue(2);
const log: string[] = [];

const task = (name: string, delay: number, shouldFail = false) => async () => {
log.push(`${name}:start`);
await new Promise((r) => setTimeout(r, delay));
if (shouldFail) {
log.push(`${name}:error`);
throw new Error(`${name} failed`);
}
log.push(`${name}:end`);
return name;
};
const task =
(name: string, delay: number, shouldFail = false) =>
async () => {
log.push(`${name}:start`);
await new Promise((r) => setTimeout(r, delay));
if (shouldFail) {
log.push(`${name}:error`);
throw new Error(`${name} failed`);
}
log.push(`${name}:end`);
return name;
};

const results = await Promise.allSettled([
queue.enqueue('A', task('A1', 30)),
Expand All @@ -335,14 +337,7 @@ describe('ConcurrentQueue', () => {

// A 系列应该串行
const aLogs = log.filter((l) => l.startsWith('A'));
expect(aLogs).toEqual([
'A1:start',
'A1:end',
'A2:start',
'A2:error',
'A3:start',
'A3:end',
]);
expect(aLogs).toEqual(['A1:start', 'A1:end', 'A2:start', 'A2:error', 'A3:start', 'A3:end']);

// B 系列应该串行
const bLogs = log.filter((l) => l.startsWith('B'));
Expand Down
6 changes: 5 additions & 1 deletion apps/cli/src/lib/acp/tool-call-history.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export { deriveLocationsFromRawInput, deriveLocationsFromToolCallContent, stripToolCallContentForHistory } from '@lody/shared';
export {
deriveLocationsFromRawInput,
deriveLocationsFromToolCallContent,
stripToolCallContentForHistory,
} from '@lody/shared';
24 changes: 10 additions & 14 deletions apps/cli/src/lib/code-collab/code-collab-publish-repair.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,15 @@ describe('CodeCollabV2Service targeted file-index repair', () => {
vi.useRealTimers();
});

const createService = (
options: {
readonly publishFileIndex: (
publication: CodeCollabV2FileIndexPublication
) => Promise<{ changed: boolean }>;
readonly publishFileIndexSignal?: (publication: {
readonly ownerSessionId: SessionId;
readonly updatedAtMs: number;
}) => Promise<void>;
}
): CodeCollabV2Service => {
const createService = (options: {
readonly publishFileIndex: (
publication: CodeCollabV2FileIndexPublication
) => Promise<{ changed: boolean }>;
readonly publishFileIndexSignal?: (publication: {
readonly ownerSessionId: SessionId;
readonly updatedAtMs: number;
}) => Promise<void>;
}): CodeCollabV2Service => {
const service = new CodeCollabV2Service({
resolveWorkspace: async () => ({
ok: false,
Expand Down Expand Up @@ -219,9 +217,7 @@ describe('CodeCollabV2Service targeted file-index repair', () => {

publishFileIndex.mockRejectedValueOnce(new Error('transport failed again'));
state.fileTree['newer.ts'] = true;
await expect(publishOwner(service, OWNER_A, state)).rejects.toThrow(
'transport failed again'
);
await expect(publishOwner(service, OWNER_A, state)).rejects.toThrow('transport failed again');
service.dispose();
await vi.advanceTimersByTimeAsync(30_000);
expect(publishFileIndex).toHaveBeenCalledTimes(3);
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/lib/code-collab/file-index-scan-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ async function lineStatsForUntrackedFile(
function parseDeletedPathsFromNameStatus(stdout: string): Set<string> {
const deleted = new Set<string>();
const tokens = stdout.split('\0').filter(Boolean);
for (let index = 0; index < tokens.length; ) {
for (let index = 0; index < tokens.length;) {
const status = tokens[index] ?? '';
index += 1;
if (status.startsWith('R') || status.startsWith('C')) {
Expand Down
9 changes: 2 additions & 7 deletions apps/cli/src/lib/code-collab/workspace-watch-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,9 @@ export function parseWorkspaceWatchParentMessage(
return null;
}

export function parseWorkspaceWatchChildMessage(
value: unknown
): WorkspaceWatchChildMessage | null {
export function parseWorkspaceWatchChildMessage(value: unknown): WorkspaceWatchChildMessage | null {
if (!isGenerationMessage(value)) return null;
if (
value.type === 'code-collab-watch/dirty' &&
typeof value.root === 'string'
) {
if (value.type === 'code-collab-watch/dirty' && typeof value.root === 'string') {
return { type: value.type, generation: value.generation, root: value.root };
}
if (
Expand Down
7 changes: 6 additions & 1 deletion apps/cli/src/lib/git/resolve-git-branch-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ export const resolveGitBranch = async (
return { kind: 'branch', branch: current };
}

const abbrevRef = await tryExecTrimmed(exec, 'git', ['rev-parse', '--abbrev-ref', 'HEAD'], workdir);
const abbrevRef = await tryExecTrimmed(
exec,
'git',
['rev-parse', '--abbrev-ref', 'HEAD'],
workdir
);
if (!abbrevRef) {
return { kind: 'unresolved' };
}
Expand Down
12 changes: 5 additions & 7 deletions apps/cli/src/lib/local-workspace-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,13 +333,11 @@ export function makeLocalWorkspaceCatalog(
});
const remoteMissing = snapshot.workspaces
.filter((workspace) => !remoteIds.has(workspace.workspaceId))
.map(
(workspace): LocalCatalogWorkspace => ({
...workspace,
state: 'remote_missing',
remoteMissingAt: workspace.remoteMissingAt ?? now,
})
);
.map((workspace): LocalCatalogWorkspace => ({
...workspace,
state: 'remote_missing',
remoteMissingAt: workspace.remoteMissingAt ?? now,
}));
return {
...snapshot,
identity: input.identity,
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/lib/loro/history-auto-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const attachAutoMarkLatestUserHistoryAsRead = (
return;
}

const history = next.history as SessionHistoryInput[] ?? [];
const history = (next.history as SessionHistoryInput[]) ?? [];
const latestUserEntry = findLatestUserHistoryEntry(history);
if (!latestUserEntry || resolveSessionHistoryStatus(latestUserEntry) !== 'pending') {
return;
Expand Down
10 changes: 2 additions & 8 deletions apps/cli/src/lib/pr-poller/github-credential-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { execFile } from 'node:child_process';
import type {
CloudGithubTokenManager,
CloudGithubWriteTokenContext,
} from '@lody/platform';
import type { CloudGithubTokenManager, CloudGithubWriteTokenContext } from '@lody/platform';
import type { Logger } from '@/utils/logger';
import { formatErrorMessage } from '@/utils/format-error';

Expand Down Expand Up @@ -67,10 +64,7 @@ const defaultFetchGhUserId = (): Promise<string | null> =>

export type GitHubCredentialResolverDeps = {
/** Workspace-bound token manager; null disables the managed tier entirely. */
tokenManager: Pick<
CloudGithubTokenManager,
'getWriteTokenInfoForRepo' | 'invalidate'
> | null;
tokenManager: Pick<CloudGithubTokenManager, 'getWriteTokenInfoForRepo' | 'invalidate'> | null;
/** Requester context for managed write tokens (per-workspace wiring, see M3). */
writeTokenContext: CloudGithubWriteTokenContext;
/** Workspace identity used only for the old-backend App-token fallback scope. */
Expand Down
7 changes: 6 additions & 1 deletion apps/cli/src/lib/pr-poller/pr-poll-select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ describe('computeTargetDueAtMs', () => {

it('a priority change reshapes dueness with no stored next-poll time', () => {
const low = target({ lastSuccessAtMs: T0, desiredIntervalMs: 300_000 });
const promoted = { ...low, lane: 'high' as const, desiredIntervalMs: 20_000, minIntervalMs: 20_000 };
const promoted = {
...low,
lane: 'high' as const,
desiredIntervalMs: 20_000,
minIntervalMs: 20_000,
};
expect(computeTargetDueAtMs(low)).toBe(T0 + 300_000);
expect(computeTargetDueAtMs(promoted)).toBe(T0 + 20_000);
});
Expand Down
5 changes: 4 additions & 1 deletion apps/cli/src/lib/pr-poller/pr-poll-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ export function pickNextBatch(
(a.workspaceId + a.repoFullName < b.workspaceId + b.repoFullName ? -1 : 1);
const highs = batches.filter((batch) => batch.lane === 'high').sort(byOldest);
const lows = batches.filter((batch) => batch.lane === 'low').sort(byOldest);
if (lows.length > 0 && (highs.length === 0 || consecutiveHighDispatches >= lowEveryNBatches - 1)) {
if (
lows.length > 0 &&
(highs.length === 0 || consecutiveHighDispatches >= lowEveryNBatches - 1)
) {
return lows[0] ?? null;
}
return highs[0] ?? lows[0] ?? null;
Expand Down
Loading
Loading