mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 22:35:51 +00:00
Feat/integrate lint preview (#285)
* feat: 适配 zed acp 协议 * docs: 完善 acp 文档 * feat: integrate feature branches + daemon/job 命令层级化 + 跨平台后台引擎 Cherry-picked from origin/lint/preview (637c908), excluding lint-only changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct detectMimeFromBase64 to decode raw bytes from base64 Cherry-picked from origin/lint/preview (ee36954). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: daemon 子进程 spawn 跨平台修复 + CliLaunchSpec 集中化重构 Cherry-picked from origin/lint/preview (c5f52cd), excluding lint-only formatting changes. - 新建 src/utils/cliLaunch.ts: 集中化 CLI 子进程启动层 - 修复 --daemon-worker=kind 等号格式解析 - 修复 daemon/bg fast path 缺少 setShellIfWindows() - 修复 checkPathExists 用 existsSync 替代 execSync('dir') - 7 个 spawn 站点迁移到 CliLaunchSpec Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge tsconfig.base.json into tsconfig.json with full compiler options The cherry-pick from637c908dropped jsx/strict/etc settings when removing tsconfig.base.json. This commit restores them in a single tsconfig.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge tsconfig.base.json into tsconfig.json with full compiler options The cherry-pick from637c908dropped jsx/strict/etc settings when removing tsconfig.base.json. This commit restores them in a single tsconfig.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
241
src/utils/__tests__/autonomyAuthority.test.ts
Normal file
241
src/utils/__tests__/autonomyAuthority.test.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
||||
import { join } from 'node:path'
|
||||
import {
|
||||
AUTONOMY_AGENTS_PATH_POSIX,
|
||||
AUTONOMY_DIR,
|
||||
buildAutonomyTurnPrompt,
|
||||
loadAutonomyAuthority,
|
||||
resetAutonomyAuthorityForTests,
|
||||
} from '../autonomyAuthority'
|
||||
import {
|
||||
cleanupTempDir,
|
||||
createTempDir,
|
||||
createTempSubdir,
|
||||
writeTempFile,
|
||||
} from '../../../tests/mocks/file-system'
|
||||
|
||||
const AGENTS_REL = join(AUTONOMY_DIR, 'AGENTS.md')
|
||||
const HEARTBEAT_REL = join(AUTONOMY_DIR, 'HEARTBEAT.md')
|
||||
|
||||
let tempDir = ''
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await createTempDir('autonomy-authority-')
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
resetAutonomyAuthorityForTests()
|
||||
if (tempDir) {
|
||||
await cleanupTempDir(tempDir)
|
||||
}
|
||||
})
|
||||
|
||||
describe('autonomyAuthority', () => {
|
||||
test('loadAutonomyAuthority merges AGENTS.md files from root to current directory', async () => {
|
||||
const nestedDir = await createTempSubdir(tempDir, 'packages/app')
|
||||
await writeTempFile(tempDir, AGENTS_REL, 'root authority')
|
||||
await writeTempFile(nestedDir, AGENTS_REL, 'nested authority')
|
||||
await writeTempFile(
|
||||
tempDir,
|
||||
HEARTBEAT_REL,
|
||||
[
|
||||
'# Heartbeat',
|
||||
'tasks:',
|
||||
' - name: inbox',
|
||||
' interval: 30m',
|
||||
' prompt: "Check inbox"',
|
||||
].join('\n'),
|
||||
)
|
||||
|
||||
const snapshot = await loadAutonomyAuthority({
|
||||
rootDir: tempDir,
|
||||
currentDir: nestedDir,
|
||||
})
|
||||
|
||||
expect(snapshot.agentsFiles.map(file => file.relativePath)).toEqual([
|
||||
AUTONOMY_AGENTS_PATH_POSIX,
|
||||
`packages/app/${AUTONOMY_AGENTS_PATH_POSIX}`,
|
||||
])
|
||||
expect(snapshot.agentsContent).toContain('root authority')
|
||||
expect(snapshot.agentsContent).toContain('nested authority')
|
||||
expect(snapshot.heartbeatContent).toContain('# Heartbeat')
|
||||
expect(snapshot.heartbeatTasks).toEqual([
|
||||
{
|
||||
name: 'inbox',
|
||||
interval: '30m',
|
||||
prompt: 'Check inbox',
|
||||
steps: [],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('loadAutonomyAuthority reads HEARTBEAT.md only from the workspace root', async () => {
|
||||
const nestedDir = await createTempSubdir(tempDir, 'child')
|
||||
await writeTempFile(
|
||||
tempDir,
|
||||
HEARTBEAT_REL,
|
||||
'# Root heartbeat\nRemember the root task',
|
||||
)
|
||||
await writeTempFile(
|
||||
nestedDir,
|
||||
HEARTBEAT_REL,
|
||||
'# Nested heartbeat\nThis should not be used',
|
||||
)
|
||||
|
||||
const snapshot = await loadAutonomyAuthority({
|
||||
rootDir: tempDir,
|
||||
currentDir: nestedDir,
|
||||
})
|
||||
|
||||
expect(snapshot.heartbeatFile?.path).toBe(join(tempDir, HEARTBEAT_REL))
|
||||
expect(snapshot.heartbeatContent).toContain('Root heartbeat')
|
||||
expect(snapshot.heartbeatContent).not.toContain('Nested heartbeat')
|
||||
})
|
||||
|
||||
test('buildAutonomyTurnPrompt returns the original prompt when no authority files exist', async () => {
|
||||
const prompt = await buildAutonomyTurnPrompt({
|
||||
basePrompt: 'Run the scheduled task.',
|
||||
trigger: 'scheduled-task',
|
||||
rootDir: tempDir,
|
||||
currentDir: tempDir,
|
||||
})
|
||||
|
||||
expect(prompt).toBe('Run the scheduled task.')
|
||||
})
|
||||
|
||||
test('buildAutonomyTurnPrompt injects AGENTS.md and HEARTBEAT.md for automated turns', async () => {
|
||||
const nestedDir = await createTempSubdir(tempDir, 'nested')
|
||||
await writeTempFile(tempDir, AGENTS_REL, 'root rules')
|
||||
await writeTempFile(nestedDir, AGENTS_REL, 'nested rules')
|
||||
await writeTempFile(tempDir, HEARTBEAT_REL, 'Check heartbeat directives')
|
||||
|
||||
const scheduledPrompt = await buildAutonomyTurnPrompt({
|
||||
basePrompt: 'Review the nightly report.',
|
||||
trigger: 'scheduled-task',
|
||||
rootDir: tempDir,
|
||||
currentDir: nestedDir,
|
||||
})
|
||||
const tickPrompt = await buildAutonomyTurnPrompt({
|
||||
basePrompt: '<tick>12:00:00</tick>',
|
||||
trigger: 'proactive-tick',
|
||||
rootDir: tempDir,
|
||||
currentDir: nestedDir,
|
||||
})
|
||||
|
||||
expect(scheduledPrompt).toContain(
|
||||
'This prompt was generated automatically. Follow the workspace authority below before acting.',
|
||||
)
|
||||
expect(scheduledPrompt).toContain('<autonomy_authority>')
|
||||
expect(scheduledPrompt).toContain('root rules')
|
||||
expect(scheduledPrompt).toContain('nested rules')
|
||||
expect(scheduledPrompt).toContain('Check heartbeat directives')
|
||||
expect(scheduledPrompt).toContain('Review the nightly report.')
|
||||
|
||||
expect(tickPrompt).toContain(
|
||||
'This is an autonomous proactive turn. Follow the workspace authority below before acting.',
|
||||
)
|
||||
expect(tickPrompt).toContain('<tick>12:00:00</tick>')
|
||||
})
|
||||
|
||||
test('proactive prompts surface due HEARTBEAT.md tasks only when their interval elapses', async () => {
|
||||
await writeTempFile(
|
||||
tempDir,
|
||||
HEARTBEAT_REL,
|
||||
[
|
||||
'tasks:',
|
||||
' - name: inbox',
|
||||
' interval: 30m',
|
||||
' prompt: "Check inbox"',
|
||||
].join('\n'),
|
||||
)
|
||||
|
||||
const first = await buildAutonomyTurnPrompt({
|
||||
basePrompt: '<tick>12:00:00</tick>',
|
||||
trigger: 'proactive-tick',
|
||||
rootDir: tempDir,
|
||||
currentDir: tempDir,
|
||||
nowMs: 0,
|
||||
})
|
||||
const second = await buildAutonomyTurnPrompt({
|
||||
basePrompt: '<tick>12:10:00</tick>',
|
||||
trigger: 'proactive-tick',
|
||||
rootDir: tempDir,
|
||||
currentDir: tempDir,
|
||||
nowMs: 10 * 60_000,
|
||||
})
|
||||
const third = await buildAutonomyTurnPrompt({
|
||||
basePrompt: '<tick>12:31:00</tick>',
|
||||
trigger: 'proactive-tick',
|
||||
rootDir: tempDir,
|
||||
currentDir: tempDir,
|
||||
nowMs: 31 * 60_000,
|
||||
})
|
||||
|
||||
expect(first).toContain('Due HEARTBEAT.md tasks:')
|
||||
expect(first).toContain('- inbox (30m): Check inbox')
|
||||
expect(second).not.toContain('Due HEARTBEAT.md tasks:')
|
||||
expect(third).toContain('Due HEARTBEAT.md tasks:')
|
||||
})
|
||||
|
||||
test('managed HEARTBEAT.md tasks parse nested steps and are not duplicated into the inline due-task section', async () => {
|
||||
await writeTempFile(
|
||||
tempDir,
|
||||
HEARTBEAT_REL,
|
||||
[
|
||||
'tasks:',
|
||||
' - name: inbox',
|
||||
' interval: 30m',
|
||||
' prompt: "Check inbox"',
|
||||
' - name: weekly-report',
|
||||
' interval: 7d',
|
||||
' prompt: "Ship the weekly report"',
|
||||
' steps:',
|
||||
' - name: gather',
|
||||
' prompt: "Gather weekly inputs"',
|
||||
' - name: draft',
|
||||
' prompt: "Draft the weekly report"',
|
||||
' wait_for: manual',
|
||||
].join('\n'),
|
||||
)
|
||||
|
||||
const snapshot = await loadAutonomyAuthority({
|
||||
rootDir: tempDir,
|
||||
currentDir: tempDir,
|
||||
})
|
||||
const prompt = await buildAutonomyTurnPrompt({
|
||||
basePrompt: '<tick>12:00:00</tick>',
|
||||
trigger: 'proactive-tick',
|
||||
rootDir: tempDir,
|
||||
currentDir: tempDir,
|
||||
nowMs: 0,
|
||||
})
|
||||
|
||||
expect(snapshot.heartbeatTasks).toEqual([
|
||||
{
|
||||
name: 'inbox',
|
||||
interval: '30m',
|
||||
prompt: 'Check inbox',
|
||||
steps: [],
|
||||
},
|
||||
{
|
||||
name: 'weekly-report',
|
||||
interval: '7d',
|
||||
prompt: 'Ship the weekly report',
|
||||
steps: [
|
||||
{
|
||||
name: 'gather',
|
||||
prompt: 'Gather weekly inputs',
|
||||
},
|
||||
{
|
||||
name: 'draft',
|
||||
prompt: 'Draft the weekly report',
|
||||
waitFor: 'manual',
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
expect(prompt).toContain('- inbox (30m): Check inbox')
|
||||
expect(prompt).not.toContain('- weekly-report (7d): Ship the weekly report')
|
||||
expect(prompt).not.toContain('- gather (')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user