mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +00:00
feat: 注册所有新命令到命令系统和工具注册表
- commands.ts: 注册所有新命令(memory-stores、vault、schedule 等), 移除 require() 动态加载,统一为 ESM import - tools.ts: 注册 LocalMemoryRecallTool、VaultHttpFetchTool - 补充命令测试(bridge-kick、commit、commit-push-pr、init-verifiers) - 补充工具测试(AgentTool、RemoteTrigger、SkillTool、WebFetch、WebSearch) - 集成测试:autonomy-lifecycle-user-flow 更新 - 探测脚本和功能文档 Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
This commit is contained in:
@@ -15,9 +15,8 @@ import commitPushPr from './commands/commit-push-pr.js'
|
||||
import compact from './commands/compact/index.js'
|
||||
import config from './commands/config/index.js'
|
||||
import { context, contextNonInteractive } from './commands/context/index.js'
|
||||
import cost from './commands/cost/index.js'
|
||||
// cost/index.ts re-exports usage — /cost is now an alias of /usage
|
||||
import diff from './commands/diff/index.js'
|
||||
import ctx_viz from './commands/ctx_viz/index.js'
|
||||
import doctor from './commands/doctor/index.js'
|
||||
import memory from './commands/memory/index.js'
|
||||
import help from './commands/help/index.js'
|
||||
@@ -30,7 +29,9 @@ import login from './commands/login/index.js'
|
||||
import logout from './commands/logout/index.js'
|
||||
import installGitHubApp from './commands/install-github-app/index.js'
|
||||
import installSlackApp from './commands/install-slack-app/index.js'
|
||||
import breakCache from './commands/break-cache/index.js'
|
||||
import breakCache, {
|
||||
breakCacheNonInteractive,
|
||||
} from './commands/break-cache/index.js'
|
||||
import mcp from './commands/mcp/index.js'
|
||||
import mobile from './commands/mobile/index.js'
|
||||
import onboarding from './commands/onboarding/index.js'
|
||||
@@ -45,12 +46,13 @@ import skills from './commands/skills/index.js'
|
||||
import status from './commands/status/index.js'
|
||||
import tasks from './commands/tasks/index.js'
|
||||
import teleport from './commands/teleport/index.js'
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const agentsPlatform =
|
||||
process.env.USER_TYPE === 'ant'
|
||||
? require('./commands/agents-platform/index.js').default
|
||||
: null
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
import agentsPlatform from './commands/agents-platform/index.js'
|
||||
import scheduleCommand from './commands/schedule/index.js'
|
||||
import memoryStoresCommand from './commands/memory-stores/index.js'
|
||||
import skillStoreCommand from './commands/skill-store/index.js'
|
||||
import vaultCommand from './commands/vault/index.js'
|
||||
import localVaultCommand from './commands/local-vault/index.js'
|
||||
import localMemoryCommand from './commands/local-memory/index.js'
|
||||
import securityReview from './commands/security-review.js'
|
||||
import bughunter from './commands/bughunter/index.js'
|
||||
import terminalSetup from './commands/terminalSetup/index.js'
|
||||
@@ -179,6 +181,7 @@ import mockLimits from './commands/mock-limits/index.js'
|
||||
import bridgeKick from './commands/bridge-kick.js'
|
||||
import version from './commands/version.js'
|
||||
import summary from './commands/summary/index.js'
|
||||
import recap from './commands/recap/index.js'
|
||||
import skillLearning from './commands/skill-learning/index.js'
|
||||
import skillSearch from './commands/skill-search/index.js'
|
||||
import {
|
||||
@@ -188,6 +191,7 @@ import {
|
||||
import antTrace from './commands/ant-trace/index.js'
|
||||
import perfIssue from './commands/perf-issue/index.js'
|
||||
import sandboxToggle from './commands/sandbox-toggle/index.js'
|
||||
import tui, { tuiNonInteractive } from './commands/tui/index.js'
|
||||
import chrome from './commands/chrome/index.js'
|
||||
import stickers from './commands/stickers/index.js'
|
||||
import advisor from './commands/advisor.js'
|
||||
@@ -227,7 +231,7 @@ import {
|
||||
import rateLimitOptions from './commands/rate-limit-options/index.js'
|
||||
import statusline from './commands/statusline.js'
|
||||
import effort from './commands/effort/index.js'
|
||||
import stats from './commands/stats/index.js'
|
||||
// stats/index.ts re-exports usage — /stats is now an alias of /usage
|
||||
// insights.ts is 113KB (3200 lines, includes diffLines/html rendering). Lazy
|
||||
// shim defers the heavy module until /insights is actually invoked.
|
||||
const usageReport: Command = {
|
||||
@@ -265,32 +269,19 @@ export type {
|
||||
export { getCommandName, isCommandEnabled } from './types/command.js'
|
||||
|
||||
// Commands that get eliminated from the external build
|
||||
// Public-but-previously-locked commands moved to the main COMMANDS array below:
|
||||
// commit, commitPushPr, bridgeKick, initVerifiers, autofixPr, onboarding
|
||||
// Remaining items here are truly Anthropic-internal (admin/diagnostics endpoints
|
||||
// with no fork backend), so they only show up under USER_TYPE=ant.
|
||||
export const INTERNAL_ONLY_COMMANDS = [
|
||||
backfillSessions,
|
||||
breakCache,
|
||||
bughunter,
|
||||
commit,
|
||||
commitPushPr,
|
||||
ctx_viz,
|
||||
goodClaude,
|
||||
issue,
|
||||
initVerifiers,
|
||||
mockLimits,
|
||||
bridgeKick,
|
||||
version,
|
||||
...(subscribePr ? [subscribePr] : []),
|
||||
resetLimits,
|
||||
resetLimitsNonInteractive,
|
||||
onboarding,
|
||||
share,
|
||||
teleport,
|
||||
antTrace,
|
||||
perfIssue,
|
||||
env,
|
||||
oauthRefresh,
|
||||
debugToolCall,
|
||||
agentsPlatform,
|
||||
autofixPr,
|
||||
].filter(Boolean)
|
||||
|
||||
// Declared as a function so that we don't run this until getCommands is called,
|
||||
@@ -298,6 +289,13 @@ export const INTERNAL_ONLY_COMMANDS = [
|
||||
const COMMANDS = memoize((): Command[] => [
|
||||
addDir,
|
||||
advisor,
|
||||
agentsPlatform,
|
||||
scheduleCommand,
|
||||
memoryStoresCommand,
|
||||
skillStoreCommand,
|
||||
vaultCommand,
|
||||
localVaultCommand,
|
||||
localMemoryCommand,
|
||||
autonomy,
|
||||
provider,
|
||||
agents,
|
||||
@@ -312,7 +310,6 @@ const COMMANDS = memoize((): Command[] => [
|
||||
desktop,
|
||||
context,
|
||||
contextNonInteractive,
|
||||
cost,
|
||||
diff,
|
||||
doctor,
|
||||
effort,
|
||||
@@ -341,7 +338,6 @@ const COMMANDS = memoize((): Command[] => [
|
||||
resume,
|
||||
session,
|
||||
skills,
|
||||
stats,
|
||||
status,
|
||||
statusline,
|
||||
stickers,
|
||||
@@ -398,8 +394,27 @@ const COMMANDS = memoize((): Command[] => [
|
||||
...(jobCmd ? [jobCmd] : []),
|
||||
...(forceSnip ? [forceSnip] : []),
|
||||
summary,
|
||||
recap,
|
||||
skillLearning,
|
||||
skillSearch,
|
||||
autofixPr,
|
||||
commit,
|
||||
commitPushPr,
|
||||
bridgeKick,
|
||||
version,
|
||||
...(subscribePr ? [subscribePr] : []),
|
||||
initVerifiers,
|
||||
env,
|
||||
debugToolCall,
|
||||
perfIssue,
|
||||
breakCache,
|
||||
breakCacheNonInteractive,
|
||||
issue,
|
||||
share,
|
||||
teleport,
|
||||
tui,
|
||||
tuiNonInteractive,
|
||||
onboarding,
|
||||
...(process.env.USER_TYPE === 'ant' && !process.env.IS_DEMO
|
||||
? INTERNAL_ONLY_COMMANDS
|
||||
: []),
|
||||
@@ -684,8 +699,7 @@ export const REMOTE_SAFE_COMMANDS: Set<Command> = new Set([
|
||||
theme, // Change terminal theme
|
||||
color, // Change agent color
|
||||
vim, // Toggle vim mode
|
||||
cost, // Show session cost (local cost tracking)
|
||||
usage, // Show usage info
|
||||
usage, // Show session cost, plan usage, and activity stats (/cost and /stats are aliases)
|
||||
copy, // Copy last message
|
||||
btw, // Quick note
|
||||
feedback, // Send feedback
|
||||
@@ -713,7 +727,7 @@ export const BRIDGE_SAFE_COMMANDS: Set<Command> = new Set(
|
||||
[
|
||||
compact, // Shrink context — useful mid-session from a phone
|
||||
clear, // Wipe transcript
|
||||
cost, // Show session cost
|
||||
usage, // Show session cost (/cost alias)
|
||||
summary, // Summarize conversation
|
||||
releaseNotes, // Show changelog
|
||||
files, // List tracked files
|
||||
|
||||
246
src/commands/__tests__/bridge-kick.test.ts
Normal file
246
src/commands/__tests__/bridge-kick.test.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
|
||||
|
||||
mock.module('bun:bundle', () => ({
|
||||
feature: (_name: string) => false,
|
||||
}))
|
||||
|
||||
// Capture injected faults and handle calls for assertions
|
||||
let mockHandle: any = null
|
||||
let lastFault: any = null
|
||||
let fireCloseCalled: number | null = null
|
||||
let forceReconnectCalled = false
|
||||
let wakePolled = false
|
||||
let describeResult = 'bridge-status: ok'
|
||||
|
||||
mock.module('src/bridge/bridgeDebug.ts', () => ({
|
||||
getBridgeDebugHandle: () => mockHandle,
|
||||
registerBridgeDebugHandle: () => {},
|
||||
clearBridgeDebugHandle: () => {},
|
||||
injectBridgeFault: () => {},
|
||||
wrapApiForFaultInjection: (api: any) => api,
|
||||
}))
|
||||
|
||||
function makeMockHandle() {
|
||||
return {
|
||||
fireClose: (code: number) => {
|
||||
fireCloseCalled = code
|
||||
},
|
||||
forceReconnect: () => {
|
||||
forceReconnectCalled = true
|
||||
},
|
||||
injectFault: (fault: any) => {
|
||||
lastFault = fault
|
||||
},
|
||||
wakePollLoop: () => {
|
||||
wakePolled = true
|
||||
},
|
||||
describe: () => describeResult,
|
||||
}
|
||||
}
|
||||
|
||||
let bridgeKick: any
|
||||
let callFn:
|
||||
| ((args: string) => Promise<{ type: string; value: string }>)
|
||||
| undefined
|
||||
|
||||
beforeEach(async () => {
|
||||
mockHandle = null
|
||||
lastFault = null
|
||||
fireCloseCalled = null
|
||||
forceReconnectCalled = false
|
||||
wakePolled = false
|
||||
const mod = await import('../bridge-kick.js')
|
||||
bridgeKick = mod.default
|
||||
const loaded = await bridgeKick.load()
|
||||
callFn = loaded.call
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockHandle = null
|
||||
})
|
||||
|
||||
describe('bridge-kick command metadata', () => {
|
||||
test('has correct name', () => {
|
||||
expect(bridgeKick.name).toBe('bridge-kick')
|
||||
})
|
||||
|
||||
test('has description', () => {
|
||||
expect(bridgeKick.description).toBeTruthy()
|
||||
})
|
||||
|
||||
test('type is local', () => {
|
||||
expect(bridgeKick.type).toBe('local')
|
||||
})
|
||||
|
||||
test('isEnabled returns true when USER_TYPE=ant', () => {
|
||||
const originalUserType = process.env.USER_TYPE
|
||||
process.env.USER_TYPE = 'ant'
|
||||
expect(bridgeKick.isEnabled()).toBe(true)
|
||||
if (originalUserType === undefined) delete process.env.USER_TYPE
|
||||
else process.env.USER_TYPE = originalUserType
|
||||
})
|
||||
|
||||
test('isEnabled returns false when USER_TYPE is not ant', () => {
|
||||
const originalUserType = process.env.USER_TYPE
|
||||
process.env.USER_TYPE = 'external'
|
||||
expect(bridgeKick.isEnabled()).toBe(false)
|
||||
if (originalUserType === undefined) delete process.env.USER_TYPE
|
||||
else process.env.USER_TYPE = originalUserType
|
||||
})
|
||||
|
||||
test('isEnabled returns false when USER_TYPE not set', () => {
|
||||
const originalUserType = process.env.USER_TYPE
|
||||
delete process.env.USER_TYPE
|
||||
expect(bridgeKick.isEnabled()).toBe(false)
|
||||
if (originalUserType !== undefined) process.env.USER_TYPE = originalUserType
|
||||
})
|
||||
|
||||
test('supportsNonInteractive is false', () => {
|
||||
expect(bridgeKick.supportsNonInteractive).toBe(false)
|
||||
})
|
||||
|
||||
test('has load function', () => {
|
||||
expect(typeof bridgeKick.load).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('bridge-kick call - no handle registered', () => {
|
||||
test('returns error message when no handle registered', async () => {
|
||||
mockHandle = null
|
||||
const result = await callFn!('status')
|
||||
expect(result.type).toBe('text')
|
||||
expect(result.value).toContain('No bridge debug handle')
|
||||
})
|
||||
})
|
||||
|
||||
describe('bridge-kick call - with handle', () => {
|
||||
beforeEach(() => {
|
||||
mockHandle = makeMockHandle()
|
||||
})
|
||||
|
||||
test('close with valid code fires close', async () => {
|
||||
const result = await callFn!('close 1002')
|
||||
expect(result.type).toBe('text')
|
||||
expect(result.value).toContain('1002')
|
||||
expect(fireCloseCalled).toBe(1002)
|
||||
})
|
||||
|
||||
test('close with 1006 fires close(1006)', async () => {
|
||||
await callFn!('close 1006')
|
||||
expect(fireCloseCalled).toBe(1006)
|
||||
})
|
||||
|
||||
test('close with non-numeric code returns error', async () => {
|
||||
const result = await callFn!('close abc')
|
||||
expect(result.type).toBe('text')
|
||||
expect(result.value).toContain('need a numeric code')
|
||||
})
|
||||
|
||||
test('poll transient injects transient fault and wakes poll loop', async () => {
|
||||
const result = await callFn!('poll transient')
|
||||
expect(result.type).toBe('text')
|
||||
expect(result.value).toContain('transient')
|
||||
expect(wakePolled).toBe(true)
|
||||
expect(lastFault?.kind).toBe('transient')
|
||||
expect(lastFault?.method).toBe('pollForWork')
|
||||
})
|
||||
|
||||
test('poll 404 injects fatal fault with not_found_error', async () => {
|
||||
const result = await callFn!('poll 404')
|
||||
expect(result.type).toBe('text')
|
||||
expect(lastFault?.kind).toBe('fatal')
|
||||
expect(lastFault?.status).toBe(404)
|
||||
expect(lastFault?.errorType).toBe('not_found_error')
|
||||
expect(wakePolled).toBe(true)
|
||||
})
|
||||
|
||||
test('poll 401 injects fatal fault with authentication_error default', async () => {
|
||||
await callFn!('poll 401')
|
||||
expect(lastFault?.status).toBe(401)
|
||||
expect(lastFault?.errorType).toBe('authentication_error')
|
||||
})
|
||||
|
||||
test('poll 404 with custom type uses provided type', async () => {
|
||||
await callFn!('poll 404 custom_error')
|
||||
expect(lastFault?.errorType).toBe('custom_error')
|
||||
})
|
||||
|
||||
test('poll with non-numeric non-transient returns error', async () => {
|
||||
const result = await callFn!('poll abc')
|
||||
expect(result.type).toBe('text')
|
||||
expect(result.value).toContain('need')
|
||||
})
|
||||
|
||||
test('register fatal injects 403 fatal fault', async () => {
|
||||
const result = await callFn!('register fatal')
|
||||
expect(result.type).toBe('text')
|
||||
expect(result.value).toContain('403')
|
||||
expect(lastFault?.status).toBe(403)
|
||||
expect(lastFault?.kind).toBe('fatal')
|
||||
expect(lastFault?.method).toBe('registerBridgeEnvironment')
|
||||
})
|
||||
|
||||
test('register fail injects transient fault with count 1', async () => {
|
||||
const result = await callFn!('register fail')
|
||||
expect(result.type).toBe('text')
|
||||
expect(lastFault?.kind).toBe('transient')
|
||||
expect(lastFault?.count).toBe(1)
|
||||
})
|
||||
|
||||
test('register fail 3 injects transient fault with count 3', async () => {
|
||||
await callFn!('register fail 3')
|
||||
expect(lastFault?.count).toBe(3)
|
||||
})
|
||||
|
||||
test('reconnect-session fail injects 404 fault for reconnectSession', async () => {
|
||||
const result = await callFn!('reconnect-session fail')
|
||||
expect(result.type).toBe('text')
|
||||
expect(lastFault?.method).toBe('reconnectSession')
|
||||
expect(lastFault?.status).toBe(404)
|
||||
expect(lastFault?.count).toBe(2)
|
||||
})
|
||||
|
||||
test('heartbeat 401 injects authentication_error', async () => {
|
||||
await callFn!('heartbeat 401')
|
||||
expect(lastFault?.method).toBe('heartbeatWork')
|
||||
expect(lastFault?.status).toBe(401)
|
||||
expect(lastFault?.errorType).toBe('authentication_error')
|
||||
})
|
||||
|
||||
test('heartbeat with non-401 status uses not_found_error', async () => {
|
||||
await callFn!('heartbeat 404')
|
||||
expect(lastFault?.status).toBe(404)
|
||||
expect(lastFault?.errorType).toBe('not_found_error')
|
||||
})
|
||||
|
||||
test('heartbeat with no status defaults to 401', async () => {
|
||||
await callFn!('heartbeat')
|
||||
expect(lastFault?.status).toBe(401)
|
||||
})
|
||||
|
||||
test('reconnect calls forceReconnect', async () => {
|
||||
const result = await callFn!('reconnect')
|
||||
expect(result.type).toBe('text')
|
||||
expect(result.value).toContain('reconnect')
|
||||
expect(forceReconnectCalled).toBe(true)
|
||||
})
|
||||
|
||||
test('status returns bridge description', async () => {
|
||||
const result = await callFn!('status')
|
||||
expect(result.type).toBe('text')
|
||||
expect(result.value).toBe(describeResult)
|
||||
})
|
||||
|
||||
test('unknown subcommand returns usage info', async () => {
|
||||
const result = await callFn!('unknown-cmd')
|
||||
expect(result.type).toBe('text')
|
||||
expect(result.value).toContain('bridge-kick')
|
||||
})
|
||||
|
||||
test('empty args returns usage info', async () => {
|
||||
const result = await callFn!('')
|
||||
expect(result.type).toBe('text')
|
||||
// empty trim → undefined sub → default case
|
||||
expect(result.value).toBeTruthy()
|
||||
})
|
||||
})
|
||||
330
src/commands/__tests__/commit-push-pr.test.ts
Normal file
330
src/commands/__tests__/commit-push-pr.test.ts
Normal file
@@ -0,0 +1,330 @@
|
||||
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
|
||||
import type { Command } from '../../commands.js'
|
||||
|
||||
mock.module('bun:bundle', () => ({
|
||||
feature: (_name: string) => false,
|
||||
}))
|
||||
|
||||
mock.module('src/utils/attribution.ts', () => ({
|
||||
getAttributionTexts: () => ({ commit: '', pr: '' }),
|
||||
getEnhancedPRAttribution: async () => undefined,
|
||||
countUserPromptsInMessages: () => 0,
|
||||
}))
|
||||
|
||||
mock.module('src/utils/undercover.ts', () => ({
|
||||
isUndercover: () => false,
|
||||
getUndercoverInstructions: () => '',
|
||||
shouldShowUndercoverAutoNotice: () => false,
|
||||
}))
|
||||
|
||||
mock.module('src/utils/promptShellExecution.ts', () => ({
|
||||
executeShellCommandsInPrompt: async (content: string) => content,
|
||||
}))
|
||||
|
||||
// IMPORTANT: mock.module is process-global. findGitRoot/findCanonicalGitRoot
|
||||
// are SYNC in the real impl (returning string | null) — using async stubs
|
||||
// here pollutes downstream callers (e.g. jobs/templates.ts) that consume the
|
||||
// return value as a string. Match the real signatures (sync, string | null)
|
||||
// so other test files in the same process keep working.
|
||||
//
|
||||
// Pure functions (normalizeGitRemoteUrl) are inlined with real semantics so
|
||||
// git.test.ts and other consumers of this mock don't see null returns when
|
||||
// the test runs in the full suite.
|
||||
const isLocalHostForMock = (host: string): boolean => {
|
||||
const lower = host.toLowerCase().split(':')[0] ?? ''
|
||||
return lower === 'localhost' || lower === '127.0.0.1' || lower === '::1'
|
||||
}
|
||||
const realNormalizeGitRemoteUrl = (url: string): string | null => {
|
||||
const trimmed = url.trim()
|
||||
if (!trimmed) return null
|
||||
|
||||
const sshMatch = trimmed.match(/^git@([^:]+):(.+?)(?:\.git)?$/)
|
||||
if (sshMatch && sshMatch[1] && sshMatch[2]) {
|
||||
return `${sshMatch[1]}/${sshMatch[2]}`.toLowerCase()
|
||||
}
|
||||
|
||||
const urlMatch = trimmed.match(
|
||||
/^(?:https?|ssh):\/\/(?:[^@]+@)?([^/]+)\/(.+?)(?:\.git)?$/,
|
||||
)
|
||||
if (urlMatch && urlMatch[1] && urlMatch[2]) {
|
||||
const host = urlMatch[1]
|
||||
const p = urlMatch[2]
|
||||
if (isLocalHostForMock(host) && p.startsWith('git/')) {
|
||||
const proxyPath = p.slice(4)
|
||||
const segments = proxyPath.split('/')
|
||||
if (segments.length >= 3 && segments[0]!.includes('.')) {
|
||||
return proxyPath.toLowerCase()
|
||||
}
|
||||
return `github.com/${proxyPath}`.toLowerCase()
|
||||
}
|
||||
return `${host}/${p}`.toLowerCase()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
mock.module('src/utils/git.ts', () => ({
|
||||
getDefaultBranch: async () => 'main',
|
||||
findGitRoot: (_startPath?: string) => '/fake/root',
|
||||
findCanonicalGitRoot: (_startPath?: string) => '/fake/root',
|
||||
gitExe: () => 'git',
|
||||
getIsGit: async () => true,
|
||||
getGitDir: async () => null,
|
||||
isAtGitRoot: async () => true,
|
||||
dirIsInGitRepo: async () => true,
|
||||
getHead: async () => 'abc123',
|
||||
getBranch: async () => 'main',
|
||||
// The following exports are referenced by markdownConfigLoader (and other
|
||||
// transitive consumers) — provide minimal stubs so the mock surface covers
|
||||
// every real export and downstream callers don't see undefined.
|
||||
getRemoteUrl: async () => null,
|
||||
normalizeGitRemoteUrl: realNormalizeGitRemoteUrl,
|
||||
getRepoRemoteHash: async () => null,
|
||||
getIsHeadOnRemote: async () => false,
|
||||
hasUnpushedCommits: async () => false,
|
||||
getIsClean: async () => true,
|
||||
getChangedFiles: async () => [] as string[],
|
||||
getFileStatus: async () => ({
|
||||
added: [],
|
||||
modified: [],
|
||||
deleted: [],
|
||||
renamed: [],
|
||||
untracked: [],
|
||||
}),
|
||||
getWorktreeCount: async () => 1,
|
||||
stashToCleanState: async () => false,
|
||||
getGitState: async () => null,
|
||||
getGithubRepo: async () => null,
|
||||
findRemoteBase: async () => null,
|
||||
preserveGitStateForIssue: async () => null,
|
||||
isCurrentDirectoryBareGitRepo: () => false,
|
||||
}))
|
||||
|
||||
let commitPushPr: Command
|
||||
let originalUserType: string | undefined
|
||||
let originalSafeUser: string | undefined
|
||||
let originalUser: string | undefined
|
||||
|
||||
beforeEach(async () => {
|
||||
originalUserType = process.env.USER_TYPE
|
||||
originalSafeUser = process.env.SAFEUSER
|
||||
originalUser = process.env.USER
|
||||
const mod = await import('../commit-push-pr.js')
|
||||
commitPushPr = mod.default as Command
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (originalUserType === undefined) delete process.env.USER_TYPE
|
||||
else process.env.USER_TYPE = originalUserType
|
||||
|
||||
if (originalSafeUser === undefined) delete process.env.SAFEUSER
|
||||
else process.env.SAFEUSER = originalSafeUser
|
||||
|
||||
if (originalUser === undefined) delete process.env.USER
|
||||
else process.env.USER = originalUser
|
||||
})
|
||||
|
||||
describe('commit-push-pr command metadata', () => {
|
||||
test('has correct name', () => {
|
||||
expect(commitPushPr.name).toBe('commit-push-pr')
|
||||
})
|
||||
|
||||
test('has description', () => {
|
||||
expect(commitPushPr.description).toBeTruthy()
|
||||
expect(typeof commitPushPr.description).toBe('string')
|
||||
})
|
||||
|
||||
test('type is prompt', () => {
|
||||
expect(commitPushPr.type).toBe('prompt')
|
||||
})
|
||||
|
||||
test('has progressMessage', () => {
|
||||
expect((commitPushPr as any).progressMessage).toBeTruthy()
|
||||
})
|
||||
|
||||
test('source is builtin', () => {
|
||||
expect((commitPushPr as any).source).toBe('builtin')
|
||||
})
|
||||
|
||||
test('has allowedTools array with git and gh tools', () => {
|
||||
const tools = (commitPushPr as any).allowedTools as string[]
|
||||
expect(Array.isArray(tools)).toBe(true)
|
||||
expect(tools.some(t => t.includes('git push'))).toBe(true)
|
||||
expect(tools.some(t => t.includes('gh pr create'))).toBe(true)
|
||||
expect(tools.some(t => t.includes('git add'))).toBe(true)
|
||||
expect(tools.some(t => t.includes('git commit'))).toBe(true)
|
||||
})
|
||||
|
||||
test('contentLength getter returns a number', () => {
|
||||
const len = (commitPushPr as any).contentLength
|
||||
expect(typeof len).toBe('number')
|
||||
expect(len).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('commit-push-pr getPromptForCommand', () => {
|
||||
const makeContext = () => ({
|
||||
getAppState: () => ({
|
||||
toolPermissionContext: {
|
||||
alwaysAllowRules: { command: [] },
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
test('returns array with text type for empty args', async () => {
|
||||
const result = await (commitPushPr as any).getPromptForCommand(
|
||||
'',
|
||||
makeContext(),
|
||||
)
|
||||
expect(Array.isArray(result)).toBe(true)
|
||||
expect(result[0].type).toBe('text')
|
||||
})
|
||||
|
||||
test('result text contains pull request instructions', async () => {
|
||||
const result = await (commitPushPr as any).getPromptForCommand(
|
||||
'',
|
||||
makeContext(),
|
||||
)
|
||||
expect(result[0].text).toContain('PR')
|
||||
})
|
||||
|
||||
test('result text contains default branch', async () => {
|
||||
const result = await (commitPushPr as any).getPromptForCommand(
|
||||
'',
|
||||
makeContext(),
|
||||
)
|
||||
expect(result[0].text).toContain('main')
|
||||
})
|
||||
|
||||
test('appends additional user instructions when args provided', async () => {
|
||||
const result = await (commitPushPr as any).getPromptForCommand(
|
||||
'Fix the bug',
|
||||
makeContext(),
|
||||
)
|
||||
expect(result[0].text).toContain('Fix the bug')
|
||||
expect(result[0].text).toContain('Additional instructions')
|
||||
})
|
||||
|
||||
test('does not append additional instructions section for whitespace-only args', async () => {
|
||||
const result = await (commitPushPr as any).getPromptForCommand(
|
||||
' ',
|
||||
makeContext(),
|
||||
)
|
||||
expect(result[0].text).not.toContain('Additional instructions')
|
||||
})
|
||||
|
||||
test('handles null/undefined args gracefully', async () => {
|
||||
const result = await (commitPushPr as any).getPromptForCommand(
|
||||
undefined,
|
||||
makeContext(),
|
||||
)
|
||||
expect(Array.isArray(result)).toBe(true)
|
||||
expect(result[0].type).toBe('text')
|
||||
})
|
||||
|
||||
test('with ant user type and not undercover, includes reviewer arg', async () => {
|
||||
process.env.USER_TYPE = 'external'
|
||||
const result = await (commitPushPr as any).getPromptForCommand(
|
||||
'',
|
||||
makeContext(),
|
||||
)
|
||||
expect(result[0].text).toContain('gh pr create')
|
||||
})
|
||||
|
||||
test('with SAFEUSER env var set, text contains context', async () => {
|
||||
process.env.SAFEUSER = 'testuser'
|
||||
const result = await (commitPushPr as any).getPromptForCommand(
|
||||
'',
|
||||
makeContext(),
|
||||
)
|
||||
expect(result[0].text).toContain('SAFEUSER')
|
||||
})
|
||||
|
||||
test('with ant user type and undercover, strips reviewer args', async () => {
|
||||
process.env.USER_TYPE = 'ant'
|
||||
// isUndercover is mocked as false, so no prefix should be added
|
||||
const result = await (commitPushPr as any).getPromptForCommand(
|
||||
'',
|
||||
makeContext(),
|
||||
)
|
||||
expect(Array.isArray(result)).toBe(true)
|
||||
})
|
||||
|
||||
test('with args containing newlines, appends full multi-line instructions', async () => {
|
||||
const multiline = 'Line one\nLine two\nLine three'
|
||||
const result = await (commitPushPr as any).getPromptForCommand(
|
||||
multiline,
|
||||
makeContext(),
|
||||
)
|
||||
expect(result[0].text).toContain('Line one')
|
||||
expect(result[0].text).toContain('Line three')
|
||||
})
|
||||
|
||||
test('getAppState override in context includes ALLOWED_TOOLS', async () => {
|
||||
let capturedGetAppState: (() => any) | undefined
|
||||
|
||||
// Re-mock executeShellCommandsInPrompt to capture the context argument
|
||||
mock.module('src/utils/promptShellExecution.ts', () => ({
|
||||
executeShellCommandsInPrompt: async (content: string, ctx: any) => {
|
||||
capturedGetAppState = ctx.getAppState.bind(ctx)
|
||||
return content
|
||||
},
|
||||
}))
|
||||
|
||||
// Re-import to pick up the new mock
|
||||
const { default: freshCmd } = await import('../commit-push-pr.js')
|
||||
|
||||
await (freshCmd as any).getPromptForCommand('', {
|
||||
getAppState: () => ({
|
||||
toolPermissionContext: {
|
||||
alwaysAllowRules: { command: ['pre-existing'] },
|
||||
extra: true,
|
||||
},
|
||||
someState: 'value',
|
||||
}),
|
||||
})
|
||||
|
||||
expect(capturedGetAppState).toBeDefined()
|
||||
const resultState = capturedGetAppState!()
|
||||
expect(
|
||||
Array.isArray(resultState.toolPermissionContext.alwaysAllowRules.command),
|
||||
).toBe(true)
|
||||
// Should have replaced with ALLOWED_TOOLS
|
||||
expect(
|
||||
resultState.toolPermissionContext.alwaysAllowRules.command.length,
|
||||
).toBeGreaterThan(0)
|
||||
expect(resultState.someState).toBe('value')
|
||||
})
|
||||
|
||||
test('ant undercover path strips reviewer/slack/changelog sections', async () => {
|
||||
process.env.USER_TYPE = 'ant'
|
||||
|
||||
// Re-mock undercover to return true for this test
|
||||
mock.module('src/utils/undercover.ts', () => ({
|
||||
isUndercover: () => true,
|
||||
getUndercoverInstructions: () => 'UNDERCOVER_INSTRUCTIONS',
|
||||
shouldShowUndercoverAutoNotice: () => false,
|
||||
}))
|
||||
|
||||
// Also re-mock attribution to return commit text
|
||||
mock.module('src/utils/attribution.ts', () => ({
|
||||
getAttributionTexts: () => ({
|
||||
commit: 'Attribution text',
|
||||
pr: 'PR Attribution',
|
||||
}),
|
||||
getEnhancedPRAttribution: async () => 'Enhanced PR Attribution',
|
||||
countUserPromptsInMessages: () => 0,
|
||||
}))
|
||||
|
||||
const { default: freshCmd } = await import('../commit-push-pr.js')
|
||||
|
||||
const result = await (freshCmd as any).getPromptForCommand(
|
||||
'',
|
||||
makeContext(),
|
||||
)
|
||||
expect(Array.isArray(result)).toBe(true)
|
||||
// The undercover path removes slackStep, changelogSection, and reviewer args
|
||||
// The prompt should not contain those sections
|
||||
expect(result[0].text).not.toContain('CHANGELOG:START')
|
||||
expect(result[0].text).not.toContain('Slack')
|
||||
})
|
||||
})
|
||||
273
src/commands/__tests__/commit.test.ts
Normal file
273
src/commands/__tests__/commit.test.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
|
||||
import type { Command } from '../../commands.js'
|
||||
|
||||
// Mock bun:bundle before any imports that use feature()
|
||||
mock.module('bun:bundle', () => ({
|
||||
feature: (_name: string) => false,
|
||||
}))
|
||||
|
||||
// Mock dependencies to avoid side effects
|
||||
mock.module('src/utils/attribution.ts', () => ({
|
||||
getAttributionTexts: () => ({ commit: '', pr: '' }),
|
||||
getEnhancedPRAttribution: async () => undefined,
|
||||
countUserPromptsInMessages: () => 0,
|
||||
}))
|
||||
|
||||
mock.module('src/utils/undercover.ts', () => ({
|
||||
isUndercover: () => false,
|
||||
getUndercoverInstructions: () => '',
|
||||
shouldShowUndercoverAutoNotice: () => false,
|
||||
}))
|
||||
|
||||
mock.module('src/utils/promptShellExecution.ts', () => ({
|
||||
executeShellCommandsInPrompt: async (content: string) => content,
|
||||
}))
|
||||
|
||||
let commit: Command
|
||||
let originalUserType: string | undefined
|
||||
|
||||
beforeEach(async () => {
|
||||
originalUserType = process.env.USER_TYPE
|
||||
const mod = await import('../commit.js')
|
||||
commit = mod.default as Command
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (originalUserType === undefined) {
|
||||
delete process.env.USER_TYPE
|
||||
} else {
|
||||
process.env.USER_TYPE = originalUserType
|
||||
}
|
||||
})
|
||||
|
||||
describe('commit command metadata', () => {
|
||||
test('has correct name', () => {
|
||||
expect(commit.name).toBe('commit')
|
||||
})
|
||||
|
||||
test('has description', () => {
|
||||
expect(commit.description).toBeTruthy()
|
||||
expect(typeof commit.description).toBe('string')
|
||||
})
|
||||
|
||||
test('type is prompt', () => {
|
||||
expect(commit.type).toBe('prompt')
|
||||
})
|
||||
|
||||
test('has progressMessage', () => {
|
||||
expect((commit as any).progressMessage).toBeTruthy()
|
||||
})
|
||||
|
||||
test('source is builtin', () => {
|
||||
expect((commit as any).source).toBe('builtin')
|
||||
})
|
||||
|
||||
test('has allowedTools array', () => {
|
||||
const tools = (commit as any).allowedTools
|
||||
expect(Array.isArray(tools)).toBe(true)
|
||||
expect(tools.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('allowedTools includes git add', () => {
|
||||
const tools = (commit as any).allowedTools as string[]
|
||||
expect(tools.some(t => t.includes('git add'))).toBe(true)
|
||||
})
|
||||
|
||||
test('allowedTools includes git commit', () => {
|
||||
const tools = (commit as any).allowedTools as string[]
|
||||
expect(tools.some(t => t.includes('git commit'))).toBe(true)
|
||||
})
|
||||
|
||||
test('allowedTools includes git status', () => {
|
||||
const tools = (commit as any).allowedTools as string[]
|
||||
expect(tools.some(t => t.includes('git status'))).toBe(true)
|
||||
})
|
||||
|
||||
test('contentLength is 0 (dynamic)', () => {
|
||||
expect((commit as any).contentLength).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('commit command getPromptForCommand', () => {
|
||||
test('returns array with text type', async () => {
|
||||
const mockContext = {
|
||||
getAppState: () => ({
|
||||
toolPermissionContext: {
|
||||
alwaysAllowRules: { command: [] },
|
||||
},
|
||||
}),
|
||||
}
|
||||
const result = await (commit as any).getPromptForCommand('', mockContext)
|
||||
expect(Array.isArray(result)).toBe(true)
|
||||
expect(result.length).toBeGreaterThan(0)
|
||||
expect(result[0].type).toBe('text')
|
||||
})
|
||||
|
||||
test('result text contains git instructions', async () => {
|
||||
const mockContext = {
|
||||
getAppState: () => ({
|
||||
toolPermissionContext: {
|
||||
alwaysAllowRules: { command: [] },
|
||||
},
|
||||
}),
|
||||
}
|
||||
const result = await (commit as any).getPromptForCommand('', mockContext)
|
||||
expect(result[0].text).toContain('git')
|
||||
})
|
||||
|
||||
test('result text contains git status', async () => {
|
||||
const mockContext = {
|
||||
getAppState: () => ({
|
||||
toolPermissionContext: {
|
||||
alwaysAllowRules: { command: [] },
|
||||
},
|
||||
}),
|
||||
}
|
||||
const result = await (commit as any).getPromptForCommand('', mockContext)
|
||||
expect(result[0].text).toContain('git status')
|
||||
})
|
||||
|
||||
test('result text contains commit message instructions', async () => {
|
||||
const mockContext = {
|
||||
getAppState: () => ({
|
||||
toolPermissionContext: {
|
||||
alwaysAllowRules: { command: [] },
|
||||
},
|
||||
}),
|
||||
}
|
||||
const result = await (commit as any).getPromptForCommand('', mockContext)
|
||||
expect(result[0].text).toContain('commit')
|
||||
})
|
||||
|
||||
test('getAppState override preserves alwaysAllowRules', async () => {
|
||||
let capturedAppState: any
|
||||
const mockContext = {
|
||||
getAppState: () => ({
|
||||
toolPermissionContext: {
|
||||
alwaysAllowRules: { command: ['existing-rule'] },
|
||||
otherProp: 'test',
|
||||
},
|
||||
otherState: 'value',
|
||||
}),
|
||||
}
|
||||
|
||||
// Wrap executeShellCommandsInPrompt to capture context
|
||||
mock.module('src/utils/promptShellExecution.ts', () => ({
|
||||
executeShellCommandsInPrompt: async (content: string, ctx: any) => {
|
||||
capturedAppState = ctx.getAppState()
|
||||
return content
|
||||
},
|
||||
}))
|
||||
|
||||
const mod = await import('../commit.js')
|
||||
const freshCommit = mod.default as any
|
||||
|
||||
await freshCommit.getPromptForCommand('', mockContext)
|
||||
// The override should include alwaysAllowRules with command tools
|
||||
if (capturedAppState) {
|
||||
expect(
|
||||
capturedAppState.toolPermissionContext.alwaysAllowRules.command,
|
||||
).toBeDefined()
|
||||
}
|
||||
})
|
||||
|
||||
test('getPromptForCommand with non-ant user_type does not include undercover prefix', async () => {
|
||||
process.env.USER_TYPE = 'external'
|
||||
const mockContext = {
|
||||
getAppState: () => ({
|
||||
toolPermissionContext: {
|
||||
alwaysAllowRules: { command: [] },
|
||||
},
|
||||
}),
|
||||
}
|
||||
const result = await (commit as any).getPromptForCommand('', mockContext)
|
||||
expect(Array.isArray(result)).toBe(true)
|
||||
})
|
||||
|
||||
test('getPromptForCommand with ant user_type and undercover', async () => {
|
||||
process.env.USER_TYPE = 'ant'
|
||||
// isUndercover is mocked to return false, so prefix stays empty
|
||||
const mockContext = {
|
||||
getAppState: () => ({
|
||||
toolPermissionContext: {
|
||||
alwaysAllowRules: { command: [] },
|
||||
},
|
||||
}),
|
||||
}
|
||||
const result = await (commit as any).getPromptForCommand('', mockContext)
|
||||
expect(Array.isArray(result)).toBe(true)
|
||||
expect(result[0].type).toBe('text')
|
||||
})
|
||||
|
||||
test('ant undercover path prepends undercover instructions', async () => {
|
||||
process.env.USER_TYPE = 'ant'
|
||||
|
||||
mock.module('src/utils/undercover.ts', () => ({
|
||||
isUndercover: () => true,
|
||||
getUndercoverInstructions: () => 'SECRET_UNDERCOVER_PREFIX',
|
||||
shouldShowUndercoverAutoNotice: () => false,
|
||||
}))
|
||||
|
||||
mock.module('src/utils/attribution.ts', () => ({
|
||||
getAttributionTexts: () => ({ commit: 'Co-Authored-By: Claude', pr: '' }),
|
||||
getEnhancedPRAttribution: async () => undefined,
|
||||
countUserPromptsInMessages: () => 0,
|
||||
}))
|
||||
|
||||
const { default: freshCommit } = await import('../commit.js')
|
||||
const mockContext = {
|
||||
getAppState: () => ({
|
||||
toolPermissionContext: {
|
||||
alwaysAllowRules: { command: [] },
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
const result = await (freshCommit as any).getPromptForCommand(
|
||||
'',
|
||||
mockContext,
|
||||
)
|
||||
expect(Array.isArray(result)).toBe(true)
|
||||
expect(result[0].text).toContain('SECRET_UNDERCOVER_PREFIX')
|
||||
expect(result[0].text).toContain('Co-Authored-By')
|
||||
})
|
||||
|
||||
test('getAppState override in context passes ALLOWED_TOOLS', async () => {
|
||||
let capturedCtx: any
|
||||
|
||||
mock.module('src/utils/promptShellExecution.ts', () => ({
|
||||
executeShellCommandsInPrompt: async (content: string, ctx: any) => {
|
||||
capturedCtx = ctx
|
||||
return content
|
||||
},
|
||||
}))
|
||||
|
||||
const { default: freshCommit } = await import('../commit.js')
|
||||
const baseAppState = {
|
||||
toolPermissionContext: {
|
||||
alwaysAllowRules: { command: ['old-rule'] },
|
||||
otherProp: 'keep-this',
|
||||
},
|
||||
globalState: 'preserved',
|
||||
}
|
||||
const mockContext = {
|
||||
getAppState: () => baseAppState,
|
||||
}
|
||||
|
||||
await (freshCommit as any).getPromptForCommand('', mockContext)
|
||||
|
||||
expect(capturedCtx).toBeDefined()
|
||||
const overriddenState = capturedCtx.getAppState()
|
||||
expect(overriddenState.globalState).toBe('preserved')
|
||||
expect(
|
||||
Array.isArray(
|
||||
overriddenState.toolPermissionContext.alwaysAllowRules.command,
|
||||
),
|
||||
).toBe(true)
|
||||
expect(
|
||||
overriddenState.toolPermissionContext.alwaysAllowRules.command.some(
|
||||
(t: string) => t.includes('git add'),
|
||||
),
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
113
src/commands/__tests__/init-verifiers.test.ts
Normal file
113
src/commands/__tests__/init-verifiers.test.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
// init-verifiers.ts has no external dependencies that need mocking
|
||||
// It's a simple prompt-type command that returns a static text prompt
|
||||
|
||||
let initVerifiers: any
|
||||
|
||||
// Import once - no async deps
|
||||
const mod = await import('../init-verifiers.js')
|
||||
initVerifiers = mod.default
|
||||
|
||||
describe('init-verifiers command metadata', () => {
|
||||
test('has correct name', () => {
|
||||
expect(initVerifiers.name).toBe('init-verifiers')
|
||||
})
|
||||
|
||||
test('has description', () => {
|
||||
expect(initVerifiers.description).toBeTruthy()
|
||||
expect(typeof initVerifiers.description).toBe('string')
|
||||
})
|
||||
|
||||
test('type is prompt', () => {
|
||||
expect(initVerifiers.type).toBe('prompt')
|
||||
})
|
||||
|
||||
test('has progressMessage', () => {
|
||||
expect(initVerifiers.progressMessage).toBeTruthy()
|
||||
})
|
||||
|
||||
test('source is builtin', () => {
|
||||
expect(initVerifiers.source).toBe('builtin')
|
||||
})
|
||||
|
||||
test('contentLength is 0 (dynamic)', () => {
|
||||
expect(initVerifiers.contentLength).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('init-verifiers getPromptForCommand', () => {
|
||||
test('returns a non-empty array', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(Array.isArray(result)).toBe(true)
|
||||
expect(result.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('first element has type "text"', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(result[0].type).toBe('text')
|
||||
})
|
||||
|
||||
test('text contains Phase 1 auto-detection instructions', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(result[0].text).toContain('Phase 1')
|
||||
})
|
||||
|
||||
test('text contains Phase 2 verification tool setup', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(result[0].text).toContain('Phase 2')
|
||||
})
|
||||
|
||||
test('text contains Phase 3 interactive Q&A', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(result[0].text).toContain('Phase 3')
|
||||
})
|
||||
|
||||
test('text contains Phase 4 generate verifier skill', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(result[0].text).toContain('Phase 4')
|
||||
})
|
||||
|
||||
test('text contains Phase 5 confirm creation', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(result[0].text).toContain('Phase 5')
|
||||
})
|
||||
|
||||
test('text mentions Playwright', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(result[0].text).toContain('Playwright')
|
||||
})
|
||||
|
||||
test('text mentions SKILL.md template', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(result[0].text).toContain('SKILL.md')
|
||||
})
|
||||
|
||||
test('text mentions TodoWrite tool', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(result[0].text).toContain('TodoWrite')
|
||||
})
|
||||
|
||||
test('text mentions verifier naming convention', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(result[0].text).toContain('verifier')
|
||||
})
|
||||
|
||||
test('text mentions authentication handling', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(result[0].text).toContain('Authentication')
|
||||
})
|
||||
|
||||
test('text is a non-empty string', async () => {
|
||||
const result = await initVerifiers.getPromptForCommand()
|
||||
expect(typeof result[0].text).toBe('string')
|
||||
expect(result[0].text.length).toBeGreaterThan(100)
|
||||
})
|
||||
|
||||
test('works with no arguments (no args parameter)', async () => {
|
||||
// getPromptForCommand takes no required params
|
||||
const result = await initVerifiers.getPromptForCommand(undefined, undefined)
|
||||
expect(Array.isArray(result)).toBe(true)
|
||||
expect(result.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,26 @@
|
||||
import { mock, describe, expect, test, afterEach } from 'bun:test'
|
||||
import {
|
||||
mock,
|
||||
describe,
|
||||
expect,
|
||||
test,
|
||||
afterEach,
|
||||
beforeAll,
|
||||
afterAll,
|
||||
} from 'bun:test'
|
||||
import { debugMock } from '../../../../tests/mocks/debug'
|
||||
import { setupAxiosMock } from '../../../../tests/mocks/axios.js'
|
||||
|
||||
const axiosHandle = setupAxiosMock()
|
||||
axiosHandle.stubs.get = async () => ({ data: { servers: [] } })
|
||||
|
||||
beforeAll(() => {
|
||||
axiosHandle.useStubs = true
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
axiosHandle.useStubs = false
|
||||
})
|
||||
|
||||
mock.module('axios', () => ({
|
||||
default: { get: async () => ({ data: { servers: [] } }) },
|
||||
}))
|
||||
mock.module('src/utils/debug.ts', debugMock)
|
||||
|
||||
const { isOfficialMcpUrl, resetOfficialMcpUrlsForTesting } = await import(
|
||||
|
||||
@@ -87,6 +87,8 @@ import { EnterPlanModeTool } from '@claude-code-best/builtin-tools/tools/EnterPl
|
||||
import { EnterWorktreeTool } from '@claude-code-best/builtin-tools/tools/EnterWorktreeTool/EnterWorktreeTool.js'
|
||||
import { ExitWorktreeTool } from '@claude-code-best/builtin-tools/tools/ExitWorktreeTool/ExitWorktreeTool.js'
|
||||
import { ConfigTool } from '@claude-code-best/builtin-tools/tools/ConfigTool/ConfigTool.js'
|
||||
import { LocalMemoryRecallTool } from '@claude-code-best/builtin-tools/tools/LocalMemoryRecallTool/LocalMemoryRecallTool.js'
|
||||
import { VaultHttpFetchTool } from '@claude-code-best/builtin-tools/tools/VaultHttpFetchTool/VaultHttpFetchTool.js'
|
||||
import { TaskCreateTool } from '@claude-code-best/builtin-tools/tools/TaskCreateTool/TaskCreateTool.js'
|
||||
import { TaskGetTool } from '@claude-code-best/builtin-tools/tools/TaskGetTool/TaskGetTool.js'
|
||||
import { TaskUpdateTool } from '@claude-code-best/builtin-tools/tools/TaskUpdateTool/TaskUpdateTool.js'
|
||||
@@ -233,6 +235,8 @@ export function getAllBaseTools(): Tools {
|
||||
AskUserQuestionTool,
|
||||
SkillTool,
|
||||
EnterPlanModeTool,
|
||||
LocalMemoryRecallTool,
|
||||
VaultHttpFetchTool,
|
||||
...(process.env.USER_TYPE === 'ant' ? [ConfigTool] : []),
|
||||
...(process.env.USER_TYPE === 'ant' ? [TungstenTool] : []),
|
||||
...(SuggestBackgroundPRTool ? [SuggestBackgroundPRTool] : []),
|
||||
|
||||
Reference in New Issue
Block a user