Skip to content

Commit 7331673

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@a19448a2db2acc7c15f7c2366f5b29e05a7cfed2
1 parent 5ae7157 commit 7331673

6 files changed

Lines changed: 98 additions & 104 deletions

File tree

bun.lock

Lines changed: 27 additions & 51 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/utils/__tests__/chat-history.test.ts

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@ import * as fs from 'fs'
33
import * as os from 'os'
44
import * as path from 'path'
55

6-
let tempDataDir = ''
7-
8-
mock.module('../../project-files', () => ({
9-
getProjectDataDir: () => tempDataDir,
10-
}))
11-
126
mock.module('../logger', () => ({
137
logger: {
148
debug: () => {},
@@ -21,6 +15,8 @@ mock.module('../logger', () => ({
2115

2216
import { deleteChatSession, getAllChats } from '../chat-history'
2317

18+
let tempDataDir = ''
19+
2420
function writeChat(chatId: string, prompt: string) {
2521
const chatDir = path.join(tempDataDir, 'chats', chatId)
2622
fs.mkdirSync(chatDir, { recursive: true })
@@ -51,25 +47,27 @@ describe('chat-history', () => {
5147
writeChat('chat-a', 'hello from chat a')
5248
writeChat('chat-b', 'hello from chat b')
5349

54-
expect(deleteChatSession('chat-a')).toBe(true)
50+
expect(deleteChatSession('chat-a', tempDataDir)).toBe(true)
5551

5652
expect(fs.existsSync(path.join(tempDataDir, 'chats', 'chat-a'))).toBe(false)
5753
expect(fs.existsSync(path.join(tempDataDir, 'chats', 'chat-b'))).toBe(true)
58-
expect(getAllChats().map((chat) => chat.chatId)).toEqual(['chat-b'])
54+
expect(
55+
getAllChats(500, tempDataDir).map((chat) => chat.chatId),
56+
).toEqual(['chat-b'])
5957
})
6058

6159
test('deleteChatSession rejects invalid chat ids', () => {
6260
const outsideDir = path.join(tempDataDir, 'outside')
6361
fs.mkdirSync(outsideDir, { recursive: true })
6462

65-
expect(deleteChatSession('../outside')).toBe(false)
66-
expect(deleteChatSession('..')).toBe(false)
63+
expect(deleteChatSession('../outside', tempDataDir)).toBe(false)
64+
expect(deleteChatSession('..', tempDataDir)).toBe(false)
6765

6866
expect(fs.existsSync(outsideDir)).toBe(true)
6967
})
7068

7169
test('deleteChatSession returns false when the chat does not exist', () => {
72-
expect(deleteChatSession('missing-chat')).toBe(false)
70+
expect(deleteChatSession('missing-chat', tempDataDir)).toBe(false)
7371
})
7472

7573
test('getAllChats lists corrupt chats as unreadable instead of hiding them', () => {
@@ -83,7 +81,7 @@ describe('chat-history', () => {
8381
'[{"id":"msg-1","variant":"user","content":"truncat',
8482
)
8583

86-
const chats = getAllChats()
84+
const chats = getAllChats(500, tempDataDir)
8785

8886
const good = chats.find((chat) => chat.chatId === 'chat-good')
8987
expect(good).toBeDefined()
@@ -104,7 +102,7 @@ describe('chat-history', () => {
104102
'{"not":"an array"}',
105103
)
106104

107-
const chats = getAllChats()
105+
const chats = getAllChats(500, tempDataDir)
108106

109107
expect(chats).toHaveLength(1)
110108
expect(chats[0].chatId).toBe('chat-not-array')
@@ -125,7 +123,7 @@ describe('chat-history', () => {
125123
}),
126124
)
127125

128-
const chats = getAllChats()
126+
const chats = getAllChats(500, tempDataDir)
129127

130128
expect(chats).toHaveLength(1)
131129
expect(chats[0].lastPrompt).toBe('prompt from meta')
@@ -147,7 +145,7 @@ describe('chat-history', () => {
147145
}),
148146
)
149147

150-
const chats = getAllChats()
148+
const chats = getAllChats(500, tempDataDir)
151149

152150
expect(chats).toHaveLength(1)
153151
expect(chats[0].lastPrompt).toBe('prompt from messages file')
@@ -173,7 +171,7 @@ describe('chat-history', () => {
173171
// masked by it
174172
fs.writeFileSync(messagesPath, '[{"id":"msg-1","variant":"user","con')
175173

176-
const chats = getAllChats()
174+
const chats = getAllChats(500, tempDataDir)
177175

178176
expect(chats).toHaveLength(1)
179177
expect(chats[0].unreadable).toBe(true)
@@ -187,7 +185,7 @@ describe('chat-history', () => {
187185
'{"messageCount": tru',
188186
)
189187

190-
const chats = getAllChats()
188+
const chats = getAllChats(500, tempDataDir)
191189

192190
expect(chats).toHaveLength(1)
193191
expect(chats[0].lastPrompt).toBe('prompt from messages file')
@@ -200,6 +198,6 @@ describe('chat-history', () => {
200198
fs.mkdirSync(emptyDir, { recursive: true })
201199
fs.writeFileSync(path.join(emptyDir, 'chat-messages.json'), '[]')
202200

203-
expect(getAllChats()).toHaveLength(0)
201+
expect(getAllChats(500, tempDataDir)).toHaveLength(0)
204202
})
205203
})

cli/src/utils/__tests__/trace-writer.test.ts

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,24 @@ import * as fs from 'fs'
33
import * as os from 'os'
44
import * as path from 'path'
55

6-
let tempDir = ''
7-
8-
mock.module('../../project-files', () => ({
9-
getProjectRoot: () => tempDir,
10-
getCurrentChatDir: () => path.join(tempDir, 'chats', 'current'),
11-
}))
12-
13-
// Force tracing on and pin the dev path so the test is deterministic
6+
// Force tracing on so the test is deterministic
147
mock.module('@codebuff/common/env', () => ({
158
IS_DEV: true,
169
}))
1710

1811
import { createTraceWriter } from '../trace-writer'
12+
import { setProjectRoot, tryGetProjectRoot } from '../../project-files'
1913

2014
import type { Message } from '@codebuff/common/types/messages/codebuff-message'
2115

16+
let tempDir = ''
17+
18+
function tracePathFor(dir: string): string {
19+
return path.join(dir, 'debug', 'trace.jsonl')
20+
}
21+
2222
function readTraceLines(): any[] {
23-
const tracePath = path.join(tempDir, 'debug', 'trace.jsonl')
23+
const tracePath = tracePathFor(tempDir)
2424
if (!fs.existsSync(tracePath)) return []
2525
return fs
2626
.readFileSync(tracePath, 'utf8')
@@ -47,16 +47,37 @@ const baseParams = {
4747
}
4848

4949
describe('createTraceWriter', () => {
50+
let originalProjectRoot: string | undefined
51+
5052
beforeEach(() => {
5153
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebuff-trace-'))
54+
originalProjectRoot = tryGetProjectRoot()
5255
})
5356

5457
afterEach(() => {
5558
fs.rmSync(tempDir, { recursive: true, force: true })
59+
if (originalProjectRoot !== undefined) {
60+
setProjectRoot(originalProjectRoot)
61+
}
5662
})
5763

58-
test('writes each message exactly once across steps', () => {
64+
test('falls back to the real project-root-based path when no resolver is injected', () => {
65+
setProjectRoot(tempDir)
5966
const writer = createTraceWriter()!
67+
68+
writer.recordStep({
69+
...baseParams,
70+
step: 1,
71+
messages: [userMessage('hello')],
72+
})
73+
74+
const lines = readTraceLines()
75+
expect(lines).toHaveLength(1)
76+
expect(lines[0].message.role).toBe('user')
77+
})
78+
79+
test('writes each message exactly once across steps', () => {
80+
const writer = createTraceWriter(() => tracePathFor(tempDir))!
6081
const history = [userMessage('hello')]
6182

6283
writer.recordStep({ ...baseParams, step: 1, messages: history })
@@ -72,7 +93,7 @@ describe('createTraceWriter', () => {
7293
})
7394

7495
test('records system prompt once and again only when it changes', () => {
75-
const writer = createTraceWriter()!
96+
const writer = createTraceWriter(() => tracePathFor(tempDir))!
7697

7798
writer.recordStep({
7899
...baseParams,
@@ -105,7 +126,7 @@ describe('createTraceWriter', () => {
105126
})
106127

107128
test('detects history rewrites and re-dumps the new history', () => {
108-
const writer = createTraceWriter()!
129+
const writer = createTraceWriter(() => tracePathFor(tempDir))!
109130

110131
writer.recordStep({
111132
...baseParams,
@@ -133,7 +154,7 @@ describe('createTraceWriter', () => {
133154
})
134155

135156
test('tracks agents independently', () => {
136-
const writer = createTraceWriter()!
157+
const writer = createTraceWriter(() => tracePathFor(tempDir))!
137158

138159
writer.recordStep({
139160
...baseParams,
@@ -155,7 +176,7 @@ describe('createTraceWriter', () => {
155176

156177
test('writes base64 image data in full', () => {
157178
const base64 = 'a'.repeat(10_000)
158-
const writer = createTraceWriter()!
179+
const writer = createTraceWriter(() => tracePathFor(tempDir))!
159180

160181
writer.recordStep({
161182
...baseParams,

cli/src/utils/__tests__/trim-chat-logs.test.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,6 @@ import * as fs from 'fs'
33
import * as os from 'os'
44
import * as path from 'path'
55

6-
let tempDataDir = ''
7-
8-
mock.module('../../project-files', () => ({
9-
getProjectDataDir: () => tempDataDir,
10-
getProjectRoot: () => tempDataDir,
11-
getCurrentChatDir: () => path.join(tempDataDir, 'chats', 'current'),
12-
}))
13-
146
mock.module('../logger', () => ({
157
CHAT_LOG_FILENAME: 'log.jsonl',
168
logger: {
@@ -24,6 +16,8 @@ mock.module('../logger', () => ({
2416

2517
import { trimOversizedChatLogs } from '../chat-history'
2618

19+
let tempDataDir = ''
20+
2721
function writeLog(
2822
chatId: string,
2923
sizeBytes: number,
@@ -60,7 +54,7 @@ describe('trimOversizedChatLogs', () => {
6054
)
6155
fs.writeFileSync(messagesFile, '[]')
6256

63-
trimOversizedChatLogs()
57+
trimOversizedChatLogs(tempDataDir)
6458

6559
expect(fs.existsSync(bigOldLog)).toBe(false)
6660
expect(fs.existsSync(smallOldLog)).toBe(true)
@@ -73,12 +67,12 @@ describe('trimOversizedChatLogs', () => {
7367
ageDays: 5,
7468
})
7569

76-
trimOversizedChatLogs()
70+
trimOversizedChatLogs(tempDataDir)
7771

7872
expect(fs.existsSync(bigRecentLog)).toBe(true)
7973
})
8074

8175
test('does nothing when chats directory does not exist', () => {
82-
expect(() => trimOversizedChatLogs()).not.toThrow()
76+
expect(() => trimOversizedChatLogs(tempDataDir)).not.toThrow()
8377
})
8478
})

cli/src/utils/chat-history.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ export interface ChatHistoryEntry {
2222
unreadable?: boolean
2323
}
2424

25-
function getChatsDir(): string {
26-
return path.join(getProjectDataDir(), 'chats')
25+
function getChatsDir(dataDir: string = getProjectDataDir()): string {
26+
return path.join(dataDir, 'chats')
2727
}
2828

2929
interface ChatDirInfo {
@@ -37,9 +37,12 @@ interface ChatDirInfo {
3737
* List all available chats sorted by most recent first
3838
* @param maxChats - Maximum number of chats to load (default: 500)
3939
*/
40-
export function getAllChats(maxChats: number = 500): ChatHistoryEntry[] {
40+
export function getAllChats(
41+
maxChats: number = 500,
42+
dataDir?: string,
43+
): ChatHistoryEntry[] {
4144
try {
42-
const chatsDir = getChatsDir()
45+
const chatsDir = getChatsDir(dataDir)
4346

4447
if (!fs.existsSync(chatsDir)) {
4548
return []
@@ -152,11 +155,11 @@ const MIN_LOG_AGE_MS = 14 * 24 * 60 * 60 * 1000
152155
* touched in 14+ days. Only debug logs are removed — chat history files are
153156
* untouched.
154157
*/
155-
export function trimOversizedChatLogs(): void {
158+
export function trimOversizedChatLogs(dataDir?: string): void {
156159
let chatsDir: string
157160
let chatIds: string[]
158161
try {
159-
chatsDir = getChatsDir()
162+
chatsDir = getChatsDir(dataDir)
160163
chatIds = fs.readdirSync(chatsDir)
161164
} catch {
162165
return // No project root set or no chats directory yet
@@ -183,7 +186,7 @@ export function trimOversizedChatLogs(): void {
183186
/**
184187
* Delete a saved chat session from local history.
185188
*/
186-
export function deleteChatSession(chatId: string): boolean {
189+
export function deleteChatSession(chatId: string, dataDir?: string): boolean {
187190
try {
188191
const safeChatId = chatId.trim()
189192
if (
@@ -196,7 +199,7 @@ export function deleteChatSession(chatId: string): boolean {
196199
return false
197200
}
198201

199-
const chatsDir = getChatsDir()
202+
const chatsDir = getChatsDir(dataDir)
200203
const chatPath = path.join(chatsDir, safeChatId)
201204

202205
if (!fs.existsSync(chatPath)) {

cli/src/utils/trace-writer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ function getTraceFilePath(): string | null {
5454
* history. Content-only edits that preserve the role sequence are not
5555
* re-traced — acceptable for a debug trace.
5656
*/
57-
export function createTraceWriter(): TraceWriter | undefined {
57+
export function createTraceWriter(
58+
resolveTraceFilePath: () => string | null = getTraceFilePath,
59+
): TraceWriter | undefined {
5860
if (!isTraceEnabled()) {
5961
return undefined
6062
}
@@ -117,7 +119,7 @@ export function createTraceWriter(): TraceWriter | undefined {
117119
if (lines.length === 0) return
118120
// Resolve the path per step (not cached for the writer's lifetime: the
119121
// current chat directory changes when the user starts a new chat).
120-
const filePath = getTraceFilePath()
122+
const filePath = resolveTraceFilePath()
121123
if (!filePath) return
122124
try {
123125
const dir = dirname(filePath)

0 commit comments

Comments
 (0)