style: 完成所有文件的lint

This commit is contained in:
claude-code-best
2026-05-01 21:39:30 +08:00
parent d136872cc9
commit 6182015005
1333 changed files with 68255 additions and 77882 deletions

View File

@@ -1,121 +1,115 @@
import { describe, expect, test } from "bun:test";
import { groupMessagesByApiRound } from "../grouping";
import { describe, expect, test } from 'bun:test'
import { groupMessagesByApiRound } from '../grouping'
function makeMsg(type: "user" | "assistant" | "system", id: string): any {
function makeMsg(type: 'user' | 'assistant' | 'system', id: string): any {
return {
type,
message: { id, content: `${type}-${id}` },
};
}
}
describe("groupMessagesByApiRound", () => {
describe('groupMessagesByApiRound', () => {
// Boundary fires when: assistant msg with NEW id AND current group has items
test("splits before first assistant if user messages precede it", () => {
const messages = [makeMsg("user", "u1"), makeMsg("assistant", "a1")];
const groups = groupMessagesByApiRound(messages);
test('splits before first assistant if user messages precede it', () => {
const messages = [makeMsg('user', 'u1'), makeMsg('assistant', 'a1')]
const groups = groupMessagesByApiRound(messages)
// user msgs form group 1, assistant starts group 2
expect(groups).toHaveLength(2);
expect(groups[0]).toHaveLength(1);
expect(groups[1]).toHaveLength(1);
});
expect(groups).toHaveLength(2)
expect(groups[0]).toHaveLength(1)
expect(groups[1]).toHaveLength(1)
})
test("single assistant message forms one group", () => {
const messages = [makeMsg("assistant", "a1")];
const groups = groupMessagesByApiRound(messages);
expect(groups).toHaveLength(1);
});
test('single assistant message forms one group', () => {
const messages = [makeMsg('assistant', 'a1')]
const groups = groupMessagesByApiRound(messages)
expect(groups).toHaveLength(1)
})
test("splits at new assistant message ID", () => {
test('splits at new assistant message ID', () => {
const messages = [
makeMsg("user", "u1"),
makeMsg("assistant", "a1"),
makeMsg("assistant", "a2"),
];
const groups = groupMessagesByApiRound(messages);
expect(groups).toHaveLength(3);
});
makeMsg('user', 'u1'),
makeMsg('assistant', 'a1'),
makeMsg('assistant', 'a2'),
]
const groups = groupMessagesByApiRound(messages)
expect(groups).toHaveLength(3)
})
test("keeps same-ID assistant messages in same group (streaming chunks)", () => {
test('keeps same-ID assistant messages in same group (streaming chunks)', () => {
const messages = [
makeMsg("assistant", "a1"),
makeMsg("assistant", "a1"),
makeMsg("assistant", "a1"),
];
const groups = groupMessagesByApiRound(messages);
expect(groups).toHaveLength(1);
expect(groups[0]).toHaveLength(3);
});
makeMsg('assistant', 'a1'),
makeMsg('assistant', 'a1'),
makeMsg('assistant', 'a1'),
]
const groups = groupMessagesByApiRound(messages)
expect(groups).toHaveLength(1)
expect(groups[0]).toHaveLength(3)
})
test("returns empty array for empty input", () => {
expect(groupMessagesByApiRound([])).toEqual([]);
});
test('returns empty array for empty input', () => {
expect(groupMessagesByApiRound([])).toEqual([])
})
test("handles all user messages (no assistant)", () => {
const messages = [makeMsg("user", "u1"), makeMsg("user", "u2")];
const groups = groupMessagesByApiRound(messages);
expect(groups).toHaveLength(1);
});
test('handles all user messages (no assistant)', () => {
const messages = [makeMsg('user', 'u1'), makeMsg('user', 'u2')]
const groups = groupMessagesByApiRound(messages)
expect(groups).toHaveLength(1)
})
test("three API rounds produce correct groups", () => {
test('three API rounds produce correct groups', () => {
const messages = [
makeMsg("user", "u1"),
makeMsg("assistant", "a1"),
makeMsg("user", "u2"),
makeMsg("assistant", "a2"),
makeMsg("user", "u3"),
makeMsg("assistant", "a3"),
];
const groups = groupMessagesByApiRound(messages);
makeMsg('user', 'u1'),
makeMsg('assistant', 'a1'),
makeMsg('user', 'u2'),
makeMsg('assistant', 'a2'),
makeMsg('user', 'u3'),
makeMsg('assistant', 'a3'),
]
const groups = groupMessagesByApiRound(messages)
// [u1], [a1, u2], [a2, u3], [a3] = 4 groups
expect(groups).toHaveLength(4);
});
expect(groups).toHaveLength(4)
})
test("consecutive user messages stay in same group", () => {
const messages = [makeMsg("user", "u1"), makeMsg("user", "u2")];
expect(groupMessagesByApiRound(messages)).toHaveLength(1);
});
test('consecutive user messages stay in same group', () => {
const messages = [makeMsg('user', 'u1'), makeMsg('user', 'u2')]
expect(groupMessagesByApiRound(messages)).toHaveLength(1)
})
test("does not produce empty groups", () => {
const messages = [
makeMsg("assistant", "a1"),
makeMsg("assistant", "a2"),
];
const groups = groupMessagesByApiRound(messages);
test('does not produce empty groups', () => {
const messages = [makeMsg('assistant', 'a1'), makeMsg('assistant', 'a2')]
const groups = groupMessagesByApiRound(messages)
for (const group of groups) {
expect(group.length).toBeGreaterThan(0);
expect(group.length).toBeGreaterThan(0)
}
});
})
test("handles single message", () => {
expect(groupMessagesByApiRound([makeMsg("user", "u1")])).toHaveLength(1);
});
test('handles single message', () => {
expect(groupMessagesByApiRound([makeMsg('user', 'u1')])).toHaveLength(1)
})
test("preserves message order within groups", () => {
const messages = [makeMsg("assistant", "a1"), makeMsg("user", "u2")];
const groups = groupMessagesByApiRound(messages);
expect(groups[0]![0]!.message!.id).toBe("a1");
expect(groups[0]![1]!.message!.id).toBe("u2");
});
test('preserves message order within groups', () => {
const messages = [makeMsg('assistant', 'a1'), makeMsg('user', 'u2')]
const groups = groupMessagesByApiRound(messages)
expect(groups[0]![0]!.message!.id).toBe('a1')
expect(groups[0]![1]!.message!.id).toBe('u2')
})
test("handles system messages", () => {
const messages = [
makeMsg("system", "s1"),
makeMsg("assistant", "a1"),
];
test('handles system messages', () => {
const messages = [makeMsg('system', 's1'), makeMsg('assistant', 'a1')]
// system msg is non-assistant, goes to current. Then assistant a1 is new ID
// and current has items, so split.
const groups = groupMessagesByApiRound(messages);
expect(groups).toHaveLength(2);
});
const groups = groupMessagesByApiRound(messages)
expect(groups).toHaveLength(2)
})
test("tool_result after assistant stays in same round", () => {
test('tool_result after assistant stays in same round', () => {
const messages = [
makeMsg("assistant", "a1"),
makeMsg("user", "tool_result_1"),
makeMsg("assistant", "a1"), // same ID = no new boundary
];
const groups = groupMessagesByApiRound(messages);
expect(groups).toHaveLength(1);
expect(groups[0]).toHaveLength(3);
});
});
makeMsg('assistant', 'a1'),
makeMsg('user', 'tool_result_1'),
makeMsg('assistant', 'a1'), // same ID = no new boundary
]
const groups = groupMessagesByApiRound(messages)
expect(groups).toHaveLength(1)
expect(groups[0]).toHaveLength(3)
})
})

View File

@@ -1,77 +1,80 @@
import { mock, describe, expect, test } from "bun:test";
import { mock, describe, expect, test } from 'bun:test'
mock.module("bun:bundle", () => ({ feature: () => false }));
mock.module('bun:bundle', () => ({ feature: () => false }))
const { formatCompactSummary } = await import("../prompt");
const { formatCompactSummary } = await import('../prompt')
describe("formatCompactSummary", () => {
test("strips <analysis>...</analysis> block", () => {
const input = "<analysis>my thought process</analysis>\n<summary>the summary</summary>";
const result = formatCompactSummary(input);
expect(result).not.toContain("<analysis>");
expect(result).not.toContain("my thought process");
});
describe('formatCompactSummary', () => {
test('strips <analysis>...</analysis> block', () => {
const input =
'<analysis>my thought process</analysis>\n<summary>the summary</summary>'
const result = formatCompactSummary(input)
expect(result).not.toContain('<analysis>')
expect(result).not.toContain('my thought process')
})
test("replaces <summary>...</summary> with 'Summary:\\n' prefix", () => {
const input = "<summary>key points here</summary>";
const result = formatCompactSummary(input);
expect(result).toContain("Summary:");
expect(result).toContain("key points here");
expect(result).not.toContain("<summary>");
});
const input = '<summary>key points here</summary>'
const result = formatCompactSummary(input)
expect(result).toContain('Summary:')
expect(result).toContain('key points here')
expect(result).not.toContain('<summary>')
})
test("handles analysis + summary together", () => {
const input = "<analysis>thinking</analysis><summary>result</summary>";
const result = formatCompactSummary(input);
expect(result).not.toContain("thinking");
expect(result).toContain("result");
});
test('handles analysis + summary together', () => {
const input = '<analysis>thinking</analysis><summary>result</summary>'
const result = formatCompactSummary(input)
expect(result).not.toContain('thinking')
expect(result).toContain('result')
})
test("handles summary without analysis", () => {
const input = "<summary>just the summary</summary>";
const result = formatCompactSummary(input);
expect(result).toContain("just the summary");
});
test('handles summary without analysis', () => {
const input = '<summary>just the summary</summary>'
const result = formatCompactSummary(input)
expect(result).toContain('just the summary')
})
test("handles analysis without summary", () => {
const input = "<analysis>just analysis</analysis>and some text";
const result = formatCompactSummary(input);
expect(result).not.toContain("just analysis");
expect(result).toContain("and some text");
});
test('handles analysis without summary', () => {
const input = '<analysis>just analysis</analysis>and some text'
const result = formatCompactSummary(input)
expect(result).not.toContain('just analysis')
expect(result).toContain('and some text')
})
test("collapses multiple newlines to double", () => {
const input = "hello\n\n\n\nworld";
const result = formatCompactSummary(input);
expect(result).not.toMatch(/\n{3,}/);
});
test('collapses multiple newlines to double', () => {
const input = 'hello\n\n\n\nworld'
const result = formatCompactSummary(input)
expect(result).not.toMatch(/\n{3,}/)
})
test("trims leading/trailing whitespace", () => {
const input = " \n hello \n ";
const result = formatCompactSummary(input);
expect(result).toBe("hello");
});
test('trims leading/trailing whitespace', () => {
const input = ' \n hello \n '
const result = formatCompactSummary(input)
expect(result).toBe('hello')
})
test("handles empty string", () => {
expect(formatCompactSummary("")).toBe("");
});
test('handles empty string', () => {
expect(formatCompactSummary('')).toBe('')
})
test("handles plain text without tags", () => {
const input = "just plain text";
expect(formatCompactSummary(input)).toBe("just plain text");
});
test('handles plain text without tags', () => {
const input = 'just plain text'
expect(formatCompactSummary(input)).toBe('just plain text')
})
test("handles multiline analysis content", () => {
const input = "<analysis>\nline1\nline2\nline3\n</analysis><summary>ok</summary>";
const result = formatCompactSummary(input);
expect(result).not.toContain("line1");
expect(result).toContain("ok");
});
test('handles multiline analysis content', () => {
const input =
'<analysis>\nline1\nline2\nline3\n</analysis><summary>ok</summary>'
const result = formatCompactSummary(input)
expect(result).not.toContain('line1')
expect(result).toContain('ok')
})
test("preserves content between analysis and summary", () => {
const input = "<analysis>thoughts</analysis>middle text<summary>final</summary>";
const result = formatCompactSummary(input);
expect(result).toContain("middle text");
expect(result).toContain("final");
});
});
test('preserves content between analysis and summary', () => {
const input =
'<analysis>thoughts</analysis>middle text<summary>final</summary>'
const result = formatCompactSummary(input)
expect(result).toContain('middle text')
expect(result).toContain('final')
})
})

View File

@@ -38,10 +38,7 @@ function makeSystemMessage(
return msg
}
function makeSnipBoundary(
uuid: string,
removedUuids: string[],
): Message {
function makeSnipBoundary(uuid: string, removedUuids: string[]): Message {
return makeSystemMessage(uuid, 'snip_boundary', {
snipMetadata: { removedUuids },
content: '[snip] Conversation history before this point has been snipped.',
@@ -126,7 +123,7 @@ describe('snipCompactIfNeeded', () => {
expect(result.executed).toBe(true)
expect(result.messages).toHaveLength(2)
expect(result.messages.map((m) => m.uuid) as string[]).toEqual(['c', 'bnd'])
expect(result.messages.map(m => m.uuid) as string[]).toEqual(['c', 'bnd'])
expect(result.tokensFreed).toBeGreaterThan(0)
expect(result.boundaryMessage).toBe(boundary)
})
@@ -154,7 +151,7 @@ describe('snipCompactIfNeeded', () => {
expect(result.executed).toBe(true)
expect(result.messages).toHaveLength(2)
expect(result.messages.map((m) => m.uuid) as string[]).toEqual(['bnd', 'c'])
expect(result.messages.map(m => m.uuid) as string[]).toEqual(['bnd', 'c'])
})
test('handles empty removedUuids array', () => {
@@ -183,7 +180,12 @@ describe('snipCompactIfNeeded', () => {
expect(result.executed).toBe(true)
expect(result.boundaryMessage!.uuid as string).toBe('bnd2')
// 'b' removed by boundary2, 'a' not in boundary2's removedUuids
expect(result.messages.map((m) => m.uuid) as string[]).toEqual(['a', 'bnd1', 'bnd2', 'c'])
expect(result.messages.map(m => m.uuid) as string[]).toEqual([
'a',
'bnd1',
'bnd2',
'c',
])
})
test('respects force option (no functional difference — both execute)', () => {

View File

@@ -32,10 +32,7 @@ function makeSystemMessage(
return msg
}
function makeSnipBoundary(
uuid: string,
removedUuids: string[],
): Message {
function makeSnipBoundary(uuid: string, removedUuids: string[]): Message {
return makeSystemMessage(uuid, 'snip_boundary', {
snipMetadata: { removedUuids },
content: '[snip]',
@@ -87,7 +84,7 @@ describe('projectSnippedView', () => {
const boundary = makeSnipBoundary('bnd', ['a', 'c'])
const result = projectSnippedView([a, b, c, boundary])
expect(result.map((m) => m.uuid) as string[]).toEqual(['b', 'bnd'])
expect(result.map(m => m.uuid) as string[]).toEqual(['b', 'bnd'])
})
test('preserves boundary messages themselves', () => {
@@ -108,7 +105,12 @@ describe('projectSnippedView', () => {
const boundary2 = makeSnipBoundary('bnd2', ['c'])
const result = projectSnippedView([a, boundary1, b, c, boundary2, d])
expect(result.map((m) => m.uuid) as string[]).toEqual(['bnd1', 'b', 'bnd2', 'd'])
expect(result.map(m => m.uuid) as string[]).toEqual([
'bnd1',
'b',
'bnd2',
'd',
])
})
test('returns all messages when boundary has empty removedUuids', () => {
@@ -116,7 +118,7 @@ describe('projectSnippedView', () => {
const boundary = makeSnipBoundary('bnd', [])
const result = projectSnippedView([a, boundary])
expect(result.map((m) => m.uuid) as string[]).toEqual(['a', 'bnd'])
expect(result.map(m => m.uuid) as string[]).toEqual(['a', 'bnd'])
})
test('handles empty message array', () => {

View File

@@ -1,3 +1,8 @@
// Auto-generated stub — replace with real implementation
export {};
export const getCachedMCConfig: () => { enabled?: boolean; systemPromptSuggestSummaries?: boolean; supportedModels?: string[]; [key: string]: unknown } = () => ({});
export {}
export const getCachedMCConfig: () => {
enabled?: boolean
systemPromptSuggestSummaries?: boolean
supportedModels?: string[]
[key: string]: unknown
} = () => ({})

View File

@@ -267,7 +267,9 @@ export function truncateHeadForPTLRetry(
let acc = 0
dropCount = 0
for (const g of groups) {
acc += roughTokenCountEstimationForMessages(g as Parameters<typeof roughTokenCountEstimationForMessages>[0])
acc += roughTokenCountEstimationForMessages(
g as Parameters<typeof roughTokenCountEstimationForMessages>[0],
)
dropCount++
if (acc >= tokenGap) break
}
@@ -762,7 +764,7 @@ export async function compactConversation(
context.setStreamMode?.('requesting')
context.setResponseLength?.(() => 0)
context.onCompactProgress?.({ type: 'compact_end' })
context.setSDKStatus?.("" as SDKStatus)
context.setSDKStatus?.('' as SDKStatus)
}
}
@@ -1105,7 +1107,7 @@ export async function partialCompactConversation(
context.setStreamMode?.('requesting')
context.setResponseLength?.(() => 0)
context.onCompactProgress?.({ type: 'compact_end' })
context.setSDKStatus?.("" as SDKStatus)
context.setSDKStatus?.('' as SDKStatus)
}
}
@@ -1333,8 +1335,18 @@ async function streamCompactSummary({
let next = await streamIter.next()
while (!next.done) {
const event = next.value as StreamEvent | AssistantMessage | SystemAPIErrorMessage
const streamEvent = event as { type: string; event: { type: string; content_block: { type: string }; delta: { type: string; text: string } } }
const event = next.value as
| StreamEvent
| AssistantMessage
| SystemAPIErrorMessage
const streamEvent = event as {
type: string
event: {
type: string
content_block: { type: string }
delta: { type: string; text: string }
}
}
if (
!hasStartedStreaming &&

View File

@@ -436,7 +436,9 @@ export function evaluateTimeBasedTrigger(
return null
}
const gapMinutes =
(Date.now() - new Date(lastAssistant.timestamp as string | number).getTime()) / 60_000
(Date.now() -
new Date(lastAssistant.timestamp as string | number).getTime()) /
60_000
if (!Number.isFinite(gapMinutes) || gapMinutes < config.gapThresholdMinutes) {
return null
}

View File

@@ -1,22 +1,25 @@
// Auto-generated stub — replace with real implementation
export {};
export {}
import type { Message } from 'src/types/message';
import type { CompactionResult } from './compact.js';
import type { Message } from 'src/types/message'
import type { CompactionResult } from './compact.js'
export const isReactiveOnlyMode: () => boolean = () => false;
export const isReactiveOnlyMode: () => boolean = () => false
export const reactiveCompactOnPromptTooLong: (
messages: Message[],
cacheSafeParams: Record<string, unknown>,
options: { customInstructions?: string; trigger?: string },
) => Promise<{ ok: boolean; reason?: string; result?: CompactionResult }> = async () => ({ ok: false });
export const isReactiveCompactEnabled: () => boolean = () => false;
export const isWithheldPromptTooLong: (message: Message) => boolean = () => false;
export const isWithheldMediaSizeError: (message: Message) => boolean = () => false;
) => Promise<{ ok: boolean; reason?: string; result?: CompactionResult }> =
async () => ({ ok: false })
export const isReactiveCompactEnabled: () => boolean = () => false
export const isWithheldPromptTooLong: (message: Message) => boolean = () =>
false
export const isWithheldMediaSizeError: (message: Message) => boolean = () =>
false
export const tryReactiveCompact: (params: {
hasAttempted: boolean;
querySource: string;
aborted: boolean;
messages: Message[];
cacheSafeParams: Record<string, unknown>;
}) => Promise<CompactionResult | null> = async () => null;
hasAttempted: boolean
querySource: string
aborted: boolean
messages: Message[]
cacheSafeParams: Record<string, unknown>
}) => Promise<CompactionResult | null> = async () => null

View File

@@ -135,7 +135,9 @@ async function initSessionMemoryCompactConfig(): Promise<void> {
export function hasTextBlocks(message: Message): boolean {
if (message.type === 'assistant') {
const content = message.message!.content
return Array.isArray(content) && content.some(block => block.type === 'text')
return (
Array.isArray(content) && content.some(block => block.type === 'text')
)
}
if (message.type === 'user') {
const content = message.message!.content

View File

@@ -56,5 +56,5 @@ export function projectSnippedView(messages: Message[]): Message[] {
return messages
}
return messages.filter((msg) => !removedSet.has(msg.uuid))
return messages.filter(msg => !removedSet.has(msg.uuid))
}