mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
732 lines
26 KiB
TypeScript
732 lines
26 KiB
TypeScript
/**
|
||
* promptEngineeringAudit.test.ts
|
||
*
|
||
* 验证 prompts.ts 中从 Opus 4.7 官方 prompt 借鉴的提示词工程改进。
|
||
* 对应审计文档: docs/features/opus-4.7-prompt-engineering-audit.md
|
||
*
|
||
* 测试策略: 通过 getSystemPrompt() 生成完整 system prompt,
|
||
* 然后检查关键段落是否存在。大部分被测函数是 module-private,
|
||
* 只能通过最终输出间接验证。
|
||
*/
|
||
|
||
import { describe, test, expect, mock, beforeEach } from 'bun:test'
|
||
|
||
// --- MACRO 全局注入 (编译时 define 在测试中不可用) ---
|
||
;(globalThis as any).MACRO = {
|
||
VERSION: '2.1.888',
|
||
BUILD_TIME: '2026-04-22T00:00:00Z',
|
||
FEEDBACK_CHANNEL: '',
|
||
ISSUES_EXPLAINER: 'report issues on GitHub',
|
||
NATIVE_PACKAGE_URL: '',
|
||
PACKAGE_URL: '',
|
||
VERSION_CHANGELOG: '',
|
||
}
|
||
|
||
// --- Mock 链 (阻断副作用) ---
|
||
|
||
mock.module('src/bootstrap/state.js', () => ({
|
||
getIsNonInteractiveSession: () => false,
|
||
sessionId: 'test-session',
|
||
getCwd: () => '/test/project',
|
||
}))
|
||
mock.module('src/utils/cwd.js', () => ({
|
||
getCwd: () => '/test/project',
|
||
}))
|
||
mock.module('src/utils/git.js', () => ({
|
||
getIsGit: async () => true,
|
||
}))
|
||
mock.module('src/utils/worktree.js', () => ({
|
||
getCurrentWorktreeSession: () => null,
|
||
}))
|
||
mock.module('src/constants/common.js', () => ({
|
||
getSessionStartDate: () => '2026-04-22',
|
||
}))
|
||
mock.module('src/utils/settings/settings.js', () => ({
|
||
getInitialSettings: () => ({ language: undefined }),
|
||
}))
|
||
mock.module('src/commands/poor/poorMode.js', () => ({
|
||
isPoorModeActive: () => false,
|
||
}))
|
||
mock.module('src/utils/env.js', () => ({
|
||
env: { platform: 'linux' },
|
||
}))
|
||
mock.module('src/utils/envUtils.js', () => ({
|
||
isEnvTruthy: () => false,
|
||
}))
|
||
mock.module('src/utils/model/model.js', () => ({
|
||
getCanonicalName: (id: string) => id,
|
||
getMarketingNameForModel: (id: string) => {
|
||
if (id.includes('opus-4-7')) return 'Claude Opus 4.7'
|
||
if (id.includes('opus-4-6')) return 'Claude Opus 4.6'
|
||
if (id.includes('sonnet-4-6')) return 'Claude Sonnet 4.6'
|
||
return null
|
||
},
|
||
}))
|
||
mock.module('src/commands.js', () => ({
|
||
getSkillToolCommands: async () => [],
|
||
}))
|
||
mock.module('src/constants/outputStyles.js', () => ({
|
||
getOutputStyleConfig: async () => null,
|
||
}))
|
||
mock.module('src/utils/embeddedTools.js', () => ({
|
||
hasEmbeddedSearchTools: () => false,
|
||
}))
|
||
mock.module('src/utils/permissions/filesystem.js', () => ({
|
||
isScratchpadEnabled: () => false,
|
||
getScratchpadDir: () => '/tmp/scratchpad',
|
||
}))
|
||
mock.module('src/utils/betas.js', () => ({
|
||
shouldUseGlobalCacheScope: () => false,
|
||
}))
|
||
mock.module('src/utils/undercover.js', () => ({
|
||
isUndercover: () => false,
|
||
}))
|
||
mock.module('src/utils/model/antModels.js', () => ({
|
||
getAntModelOverrideConfig: () => null,
|
||
}))
|
||
mock.module('src/utils/mcpInstructionsDelta.js', () => ({
|
||
isMcpInstructionsDeltaEnabled: () => false,
|
||
}))
|
||
mock.module('src/memdir/memdir.js', () => ({
|
||
loadMemoryPrompt: async () => null,
|
||
}))
|
||
mock.module('src/utils/debug.js', () => ({
|
||
logForDebugging: () => {},
|
||
}))
|
||
mock.module('src/services/analytics/growthbook.js', () => ({
|
||
getFeatureValue_CACHED_MAY_BE_STALE: () => false,
|
||
}))
|
||
mock.module('bun:bundle', () => ({
|
||
feature: (_name: string) => false,
|
||
}))
|
||
mock.module('src/constants/systemPromptSections.js', () => ({
|
||
systemPromptSection: (_name: string, fn: () => any) => fn(),
|
||
DANGEROUS_uncachedSystemPromptSection: (_name: string, fn: () => any) => fn(),
|
||
resolveSystemPromptSections: async (sections: any[]) =>
|
||
sections.filter(s => s !== null),
|
||
}))
|
||
|
||
// 工具常量 mock
|
||
const TOOL_NAMES = {
|
||
Bash: 'Bash',
|
||
Read: 'Read',
|
||
Edit: 'Edit',
|
||
Write: 'Write',
|
||
Glob: 'Glob',
|
||
Grep: 'Grep',
|
||
Agent: 'Agent',
|
||
AskUserQuestion: 'AskUserQuestion',
|
||
TaskCreate: 'TaskCreate',
|
||
DiscoverSkills: 'DiscoverSkills',
|
||
Skill: 'Skill',
|
||
Sleep: 'Sleep',
|
||
}
|
||
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/BashTool/toolName.js',
|
||
() => ({ BASH_TOOL_NAME: TOOL_NAMES.Bash }),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/FileReadTool/prompt.js',
|
||
() => ({ FILE_READ_TOOL_NAME: TOOL_NAMES.Read }),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/FileEditTool/constants.js',
|
||
() => ({ FILE_EDIT_TOOL_NAME: TOOL_NAMES.Edit }),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/FileWriteTool/prompt.js',
|
||
() => ({ FILE_WRITE_TOOL_NAME: TOOL_NAMES.Write }),
|
||
)
|
||
mock.module('@claude-code-best/builtin-tools/tools/GlobTool/prompt.js', () => ({
|
||
GLOB_TOOL_NAME: TOOL_NAMES.Glob,
|
||
}))
|
||
mock.module('@claude-code-best/builtin-tools/tools/GrepTool/prompt.js', () => ({
|
||
GREP_TOOL_NAME: TOOL_NAMES.Grep,
|
||
}))
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/AgentTool/constants.js',
|
||
() => ({
|
||
AGENT_TOOL_NAME: TOOL_NAMES.Agent,
|
||
VERIFICATION_AGENT_TYPE: 'verification',
|
||
}),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/AgentTool/forkSubagent.js',
|
||
() => ({ isForkSubagentEnabled: () => false }),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/AgentTool/builtInAgents.js',
|
||
() => ({ areExplorePlanAgentsEnabled: () => false }),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/AgentTool/built-in/exploreAgent.js',
|
||
() => ({
|
||
EXPLORE_AGENT: { agentType: 'explore' },
|
||
EXPLORE_AGENT_MIN_QUERIES: 5,
|
||
}),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/AskUserQuestionTool/prompt.js',
|
||
() => ({ ASK_USER_QUESTION_TOOL_NAME: TOOL_NAMES.AskUserQuestion }),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/TodoWriteTool/constants.js',
|
||
() => ({ TODO_WRITE_TOOL_NAME: 'TodoWrite' }),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/TaskCreateTool/constants.js',
|
||
() => ({ TASK_CREATE_TOOL_NAME: TOOL_NAMES.TaskCreate }),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/DiscoverSkillsTool/prompt.js',
|
||
() => ({ DISCOVER_SKILLS_TOOL_NAME: TOOL_NAMES.DiscoverSkills }),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/SkillTool/constants.js',
|
||
() => ({ SKILL_TOOL_NAME: TOOL_NAMES.Skill }),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/SleepTool/prompt.js',
|
||
() => ({ SLEEP_TOOL_NAME: TOOL_NAMES.Sleep }),
|
||
)
|
||
mock.module(
|
||
'@claude-code-best/builtin-tools/tools/REPLTool/constants.js',
|
||
() => ({ isReplModeEnabled: () => false }),
|
||
)
|
||
|
||
// --- 导入被测模块 ---
|
||
|
||
import {
|
||
getSystemPrompt,
|
||
prependBullets,
|
||
computeSimpleEnvInfo,
|
||
getScratchpadInstructions,
|
||
} from './prompts.js'
|
||
import type { Tools } from '../Tool.js'
|
||
|
||
// --- 辅助 ---
|
||
|
||
const standardTools: Tools = [
|
||
{ name: 'Bash' },
|
||
{ name: 'Read' },
|
||
{ name: 'Edit' },
|
||
{ name: 'Write' },
|
||
{ name: 'Glob' },
|
||
{ name: 'Grep' },
|
||
{ name: 'Agent' },
|
||
{ name: 'AskUserQuestion' },
|
||
{ name: 'TaskCreate' },
|
||
] as any
|
||
|
||
async function getFullPrompt(
|
||
tools: Tools = standardTools,
|
||
model = 'claude-opus-4-7',
|
||
): Promise<string> {
|
||
const sections = await getSystemPrompt(tools, model)
|
||
return sections.join('\n\n')
|
||
}
|
||
|
||
// =====================================================================
|
||
// 第一部分: 提示词工程技巧验证
|
||
// 对应审计文档 第一部分 #1-#10
|
||
// =====================================================================
|
||
|
||
describe('Opus 4.7 Prompt Engineering Audit', () => {
|
||
// ------------------------------------------------------------------
|
||
// #1 决策树结构 (Decision Tree)
|
||
// TXT 来源: {request_evaluation_checklist} — Step 0→1→2→3
|
||
// ------------------------------------------------------------------
|
||
describe('#1 Decision tree for tool selection', () => {
|
||
test('prompt contains step-based tool selection guidance', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Step 0')
|
||
expect(prompt).toContain('Step 1')
|
||
expect(prompt).toContain('Step 2')
|
||
expect(prompt).toContain('Step 3')
|
||
})
|
||
|
||
test('decision tree has "stop at the first match" semantics', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('stop at the first match')
|
||
})
|
||
|
||
test('Step 0 teaches when NOT to use tools', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Step 0')
|
||
expect(prompt).toContain('answer directly, no tool call')
|
||
})
|
||
|
||
test('Step 1 prioritizes dedicated tools over Bash', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Step 1')
|
||
expect(prompt).toContain('dedicated tool')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #2 反模式先行 (Anti-Pattern First)
|
||
// TXT 来源: {unnecessary_computer_use_avoidance}, {artifact_usage_criteria}
|
||
// ------------------------------------------------------------------
|
||
describe('#2 Anti-pattern guidance (when NOT to use tools)', () => {
|
||
test('prompt says when NOT to use tools', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Do NOT use')
|
||
})
|
||
|
||
test('includes explicit "Do not use tools when" section', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Do not use tools when')
|
||
})
|
||
|
||
test('anti-pattern covers knowledge questions', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain(
|
||
'programming concepts, syntax, or design patterns',
|
||
)
|
||
})
|
||
|
||
test('anti-pattern covers content already in context', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('already visible in context')
|
||
})
|
||
|
||
test('includes file creation anti-pattern', async () => {
|
||
const prompt = await getFullPrompt()
|
||
const hasFileAntiPattern =
|
||
prompt.includes('Do not create files unless') ||
|
||
prompt.includes('prefer editing an existing file')
|
||
expect(hasFileAntiPattern).toBe(true)
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #6 渐进式回退链 (Progressive Fallback Chain)
|
||
// TXT 来源: {core_search_behaviors}, {past_chats_tools}
|
||
// ------------------------------------------------------------------
|
||
describe('#6 Progressive fallback chain', () => {
|
||
test('Grep/Glob fallback chain exists', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('fallback chain')
|
||
})
|
||
|
||
test('fallback includes broader pattern as first retry', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Broader pattern')
|
||
})
|
||
|
||
test('fallback includes alternate naming conventions', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('camelCase vs snake_case')
|
||
})
|
||
|
||
test('fallback ends with asking user after exhaustion', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('ask for guidance')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #3 Few-Shot 场景示例 (Few-Shot Examples)
|
||
// TXT 来源: {examples}, {visualizer_examples}, {past_chats_tools}
|
||
// ------------------------------------------------------------------
|
||
describe('#3 Few-shot examples', () => {
|
||
test('contains tool selection examples with arrow notation', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('→')
|
||
expect(prompt).toContain('Tool selection examples')
|
||
})
|
||
|
||
test('has multiple concrete Request→Action pairs (>=5)', async () => {
|
||
const prompt = await getFullPrompt()
|
||
const arrowCount = (prompt.match(/[""].+?[""] → /g) || []).length
|
||
expect(arrowCount).toBeGreaterThanOrEqual(5)
|
||
})
|
||
|
||
test('examples cover different tool types', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Glob("**/*.tsx")')
|
||
expect(prompt).toContain('Bash("bun test")')
|
||
expect(prompt).toContain('Grep("TODO")')
|
||
expect(prompt).toContain('answer directly')
|
||
})
|
||
|
||
test('examples include negative cases (what NOT to use)', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('not Bash find')
|
||
expect(prompt).toContain('not Bash sed')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #4 语言信号识别 (Linguistic Signal Detection)
|
||
// TXT 来源: {past_chats_tools}, {file_creation_advice}
|
||
// ------------------------------------------------------------------
|
||
describe('#4 Linguistic signal detection', () => {
|
||
test('file creation signals teach when to create vs inline', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Linguistic signals')
|
||
expect(prompt).toContain('write a script')
|
||
expect(prompt).toContain('create a config')
|
||
})
|
||
|
||
test('inline answer signals are listed', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('show me how')
|
||
expect(prompt).toContain('answer inline')
|
||
})
|
||
|
||
test('20-line threshold for file creation', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('20 lines')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #5 成本不对称分析 (Asymmetric Cost Analysis)
|
||
// TXT 来源: {tool_discovery} "treat tool_search as essentially free"
|
||
// ------------------------------------------------------------------
|
||
describe('#5 Cost asymmetry framing', () => {
|
||
test('prompt has cost asymmetry for actions (existing)', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('cost of pausing to confirm is low')
|
||
})
|
||
|
||
test('frames search tools as cheap', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('cheap operations')
|
||
})
|
||
|
||
test('expanded cost asymmetry with multiple scenarios', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Cost asymmetry principle')
|
||
expect(prompt).toContain('costs user trust')
|
||
expect(prompt).toContain('breaks their flow')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #7 反过度解释 (Anti-Over-Explanation)
|
||
// TXT 来源: {sharing_files}, {request_evaluation_checklist}
|
||
// ------------------------------------------------------------------
|
||
describe('#7 Anti-over-explanation', () => {
|
||
test('prompt contains no-machinery-narration rule (existing)', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain("Don't narrate internal machinery")
|
||
})
|
||
|
||
test('includes anti-postamble guidance', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Do not restate')
|
||
expect(prompt).toContain('the user can read the diff')
|
||
})
|
||
|
||
test('discourages offering unchosen approach', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('unchosen approach')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #8 查询构造教学 (Query Construction Teaching)
|
||
// TXT 来源: {search_usage_guidelines}, {past_chats_tools}
|
||
// ------------------------------------------------------------------
|
||
describe('#8 Query construction guidance', () => {
|
||
test('includes Grep query construction advice', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('query construction')
|
||
expect(prompt).toContain('content words')
|
||
})
|
||
|
||
test('Grep guidance teaches content words vs meta-descriptions', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('authenticate|login|signIn')
|
||
expect(prompt).toContain('not "auth handling code"')
|
||
})
|
||
|
||
test('Grep guidance teaches pipe alternation for naming variants', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('userId|user_id|userID')
|
||
})
|
||
|
||
test('includes Glob query construction advice', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Glob query construction')
|
||
expect(prompt).toContain('**/*Auth*.ts')
|
||
})
|
||
|
||
test('Glob guidance teaches narrowing by extension', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('**/*.test.ts')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #9 Prompt 注入防御 (Prompt Injection Defense)
|
||
// TXT 来源: {anthropic_reminders}, {request_evaluation_checklist}
|
||
// ------------------------------------------------------------------
|
||
describe('#9 Prompt injection defense', () => {
|
||
test('prompt warns about prompt injection in tool results (existing)', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('prompt injection')
|
||
})
|
||
|
||
test('distinguishes file instructions from user instructions', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('not from the user')
|
||
})
|
||
})
|
||
|
||
// =====================================================================
|
||
// 第二部分: 行为规则验证
|
||
// 对应审计文档 第二部分 #11-#18
|
||
// =====================================================================
|
||
|
||
// ------------------------------------------------------------------
|
||
// #11 格式化纪律 (Formatting Discipline)
|
||
// TXT 来源: {lists_and_bullets}
|
||
// ------------------------------------------------------------------
|
||
// ------------------------------------------------------------------
|
||
// #10 分步搜索策略 (Multi-Step Search Strategy)
|
||
// TXT 来源: {tool_discovery}, {core_search_behaviors}
|
||
// ------------------------------------------------------------------
|
||
describe('#10 Multi-step search strategy', () => {
|
||
test('scales search effort to task complexity', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Scale search effort to task complexity')
|
||
})
|
||
|
||
test('gives concrete complexity tiers', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Single file fix')
|
||
expect(prompt).toContain('Cross-cutting change')
|
||
expect(prompt).toContain('Architecture investigation')
|
||
})
|
||
})
|
||
|
||
describe('#11 Formatting discipline', () => {
|
||
test('prompt contains prose-first guidance (existing)', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('direct answer in prose')
|
||
})
|
||
|
||
test('discourages over-formatting', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('over-formatting')
|
||
expect(prompt).toContain('natural language')
|
||
})
|
||
|
||
test('bullet points must be 1-2 sentences, not fragments', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('1-2 sentences')
|
||
expect(prompt).toContain('not sentence fragments')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #22 先搜再说不知道 (Search Before Saying Unknown)
|
||
// TXT 来源: {tool_discovery}
|
||
// ------------------------------------------------------------------
|
||
describe('#22 Search before saying unknown', () => {
|
||
test('instructs to search before claiming something does not exist', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Search first, report results second')
|
||
})
|
||
|
||
test('explicitly says do not say "I don\'t see that file"', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain("don't see that file")
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #12 温暖语气 (Warm Tone)
|
||
// TXT 来源: {tone_and_formatting}
|
||
// ------------------------------------------------------------------
|
||
describe('#12 Warm tone', () => {
|
||
test('avoids negative assumptions about user abilities', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('negative assumptions')
|
||
})
|
||
|
||
test('pushback should be constructive', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('constructively')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #20 风险感知时说得更少 (Say Less When Risky)
|
||
// TXT 来源: {refusal_handling}
|
||
// ------------------------------------------------------------------
|
||
describe('#20 Say less when risky', () => {
|
||
test('security-sensitive code should say less about details', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('saying less about implementation details')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #23 不解释为什么搜索 (Don't Justify Search)
|
||
// TXT 来源: {search_usage_guidelines}
|
||
// ------------------------------------------------------------------
|
||
describe("#23 Don't justify search", () => {
|
||
test('instructs not to justify why searching', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain("Don't justify why you're searching")
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #13 产品线信息 (Product Information)
|
||
// TXT 来源: {product_information}
|
||
// ------------------------------------------------------------------
|
||
describe('#13 Product information', () => {
|
||
test('env info contains Claude Code product description', async () => {
|
||
const envInfo = await computeSimpleEnvInfo('claude-opus-4-7')
|
||
expect(envInfo).toContain('Claude Code')
|
||
expect(envInfo).toContain('CLI')
|
||
})
|
||
|
||
test('env info contains model family', async () => {
|
||
const envInfo = await computeSimpleEnvInfo('claude-opus-4-7')
|
||
expect(envInfo).toContain('Claude 4.5/4.6/4.7')
|
||
})
|
||
|
||
test('env info contains correct model IDs', async () => {
|
||
const envInfo = await computeSimpleEnvInfo('claude-opus-4-7')
|
||
expect(envInfo).toContain('claude-opus-4-7')
|
||
expect(envInfo).toContain('claude-sonnet-4-6')
|
||
expect(envInfo).toContain('claude-haiku-4-5')
|
||
})
|
||
|
||
test('mentions Chrome/Excel/Cowork products', async () => {
|
||
const envInfo = await computeSimpleEnvInfo('claude-opus-4-7')
|
||
expect(envInfo).toContain('Chrome')
|
||
expect(envInfo).toContain('Excel')
|
||
expect(envInfo).toContain('Cowork')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #15 对话结束尊重 (Conversation End Respect)
|
||
// TXT 来源: {refusal_handling} line 51
|
||
// ------------------------------------------------------------------
|
||
describe('#15 Conversation end respect', () => {
|
||
test('discourages "anything else?" appendages', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('the user will ask if they need more')
|
||
})
|
||
})
|
||
|
||
// ------------------------------------------------------------------
|
||
// #16 每回复最多一个问题 (One Question Per Response)
|
||
// TXT 来源: {tone_and_formatting} line 71
|
||
// ------------------------------------------------------------------
|
||
describe('#16 One question per response', () => {
|
||
test('limits questions per response', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('one question per response')
|
||
})
|
||
})
|
||
|
||
// =====================================================================
|
||
// 第三部分: 已存在功能的回归测试
|
||
// 确保现有的从 TXT 对齐的锚点不被破坏
|
||
// =====================================================================
|
||
|
||
describe('Existing behavioral anchors (regression)', () => {
|
||
test('default_stance: default to helping', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Default to helping')
|
||
expect(prompt).toContain('concrete, specific risk of serious harm')
|
||
})
|
||
|
||
test('anti-collapse: no self-abasement', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('self-abasement')
|
||
expect(prompt).toContain('maintain self-respect')
|
||
})
|
||
|
||
test('cutoff silence: do not proactively mention cutoff', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain(
|
||
"Don't proactively mention your knowledge cutoff",
|
||
)
|
||
})
|
||
|
||
test('no-machinery-narration: describe in user terms', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain("Don't narrate internal machinery")
|
||
expect(prompt).toContain('Describe the action in user terms')
|
||
})
|
||
|
||
test('tool_discovery: search before saying unavailable', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('visible tool list is partial by design')
|
||
expect(prompt).toContain(
|
||
'Only state something is unavailable after the search returns no match',
|
||
)
|
||
})
|
||
|
||
test('false-claims mitigation: report outcomes faithfully', async () => {
|
||
const prompt = await getFullPrompt()
|
||
expect(prompt).toContain('Report outcomes faithfully')
|
||
})
|
||
|
||
test('CYBER_RISK_INSTRUCTION: allows security testing', async () => {
|
||
const prompt = await getFullPrompt()
|
||
// TS 允许安全测试 (TXT 完全禁止 — 这是有意的差异)
|
||
expect(prompt).not.toContain(
|
||
'does not write or explain or work on malicious code',
|
||
)
|
||
})
|
||
})
|
||
|
||
// =====================================================================
|
||
// 第四部分: prependBullets 工具函数
|
||
// =====================================================================
|
||
|
||
describe('prependBullets utility', () => {
|
||
test('flat items get single bullet', () => {
|
||
const result = prependBullets(['A', 'B'])
|
||
expect(result).toEqual([' - A', ' - B'])
|
||
})
|
||
|
||
test('nested arrays get double-indented bullets', () => {
|
||
const result = prependBullets(['A', ['sub1', 'sub2'], 'B'])
|
||
expect(result).toEqual([' - A', ' - sub1', ' - sub2', ' - B'])
|
||
})
|
||
|
||
test('empty array returns empty', () => {
|
||
expect(prependBullets([])).toEqual([])
|
||
})
|
||
})
|
||
|
||
// =====================================================================
|
||
// 第五部分: 环境信息与模型 cutoff
|
||
// =====================================================================
|
||
|
||
describe('Knowledge cutoff correctness', () => {
|
||
test('Opus 4.7 cutoff is January 2026', async () => {
|
||
const envInfo = await computeSimpleEnvInfo('claude-opus-4-7')
|
||
expect(envInfo).toContain('January 2026')
|
||
})
|
||
|
||
test('Opus 4.6 cutoff is May 2025', async () => {
|
||
const envInfo = await computeSimpleEnvInfo('claude-opus-4-6')
|
||
expect(envInfo).toContain('May 2025')
|
||
})
|
||
|
||
test('Sonnet 4.6 cutoff is August 2025', async () => {
|
||
const envInfo = await computeSimpleEnvInfo('claude-sonnet-4-6')
|
||
expect(envInfo).toContain('August 2025')
|
||
})
|
||
|
||
test('Opus 4.7 frontier model name is correct', async () => {
|
||
const envInfo = await computeSimpleEnvInfo('claude-opus-4-7')
|
||
expect(envInfo).toContain('Claude Opus 4.7')
|
||
})
|
||
})
|
||
})
|