forked from ultraworkers/claw-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidationTips.ts
More file actions
164 lines (151 loc) · 5.34 KB
/
Copy pathvalidationTips.ts
File metadata and controls
164 lines (151 loc) · 5.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import type { ZodIssueCode } from 'zod/v4'
// v4 ZodIssueCode is a value, not a type - use typeof to get the type
type ZodIssueCodeType = (typeof ZodIssueCode)[keyof typeof ZodIssueCode]
export type ValidationTip = {
suggestion?: string
docLink?: string
}
export type TipContext = {
path: string
code: ZodIssueCodeType | string
expected?: string
received?: unknown
enumValues?: string[]
message?: string
value?: unknown
}
type TipMatcher = {
matches: (context: TipContext) => boolean
tip: ValidationTip
}
const DOCUMENTATION_BASE = 'https://code.claude.com/docs/en'
const TIP_MATCHERS: TipMatcher[] = [
{
matches: (ctx): boolean =>
ctx.path === 'permissions.defaultMode' && ctx.code === 'invalid_value',
tip: {
suggestion:
'Valid modes: "acceptEdits" (ask before file changes), "plan" (analysis only), "bypassPermissions" (auto-accept all), or "default" (standard behavior)',
docLink: `${DOCUMENTATION_BASE}/iam#permission-modes`,
},
},
{
matches: (ctx): boolean =>
ctx.path === 'apiKeyHelper' && ctx.code === 'invalid_type',
tip: {
suggestion:
'Provide a shell command that outputs your API key to stdout. The script should output only the API key. Example: "/bin/generate_temp_api_key.sh"',
},
},
{
matches: (ctx): boolean =>
ctx.path === 'cleanupPeriodDays' &&
ctx.code === 'too_small' &&
ctx.expected === '0',
tip: {
suggestion:
'Must be 0 or greater. Set a positive number for days to retain transcripts (default is 30). Setting 0 disables session persistence entirely: no transcripts are written and existing transcripts are deleted at startup.',
},
},
{
matches: (ctx): boolean =>
ctx.path.startsWith('env.') && ctx.code === 'invalid_type',
tip: {
suggestion:
'Environment variables must be strings. Wrap numbers and booleans in quotes. Example: "DEBUG": "true", "PORT": "3000"',
docLink: `${DOCUMENTATION_BASE}/settings#environment-variables`,
},
},
{
matches: (ctx): boolean =>
(ctx.path === 'permissions.allow' || ctx.path === 'permissions.deny') &&
ctx.code === 'invalid_type' &&
ctx.expected === 'array',
tip: {
suggestion:
'Permission rules must be in an array. Format: ["Tool(specifier)"]. Examples: ["Bash(npm run build)", "Edit(docs/**)", "Read(~/.zshrc)"]. Use * for wildcards.',
},
},
{
matches: (ctx): boolean =>
ctx.path.includes('hooks') && ctx.code === 'invalid_type',
tip: {
suggestion:
// gh-31187 / CC-282: prior example showed {"matcher": {"tools": ["BashTool"]}}
// — an object format that never existed in the schema (matcher is z.string(),
// always has been). Users copied the tip's example and got the same validation
// error again. See matchesPattern() in hooks.ts: matcher is exact-match,
// pipe-separated ("Edit|Write"), or regex. Empty/"*" matches all.
'Hooks use a matcher + hooks array. The matcher is a string: a tool name ("Bash"), pipe-separated list ("Edit|Write"), or empty to match all. Example: {"PostToolUse": [{"matcher": "Edit|Write", "hooks": [{"type": "command", "command": "echo Done"}]}]}',
},
},
{
matches: (ctx): boolean =>
ctx.code === 'invalid_type' && ctx.expected === 'boolean',
tip: {
suggestion:
'Use true or false without quotes. Example: "includeCoAuthoredBy": true',
},
},
{
matches: (ctx): boolean => ctx.code === 'unrecognized_keys',
tip: {
suggestion:
'Check for typos or refer to the documentation for valid fields',
docLink: `${DOCUMENTATION_BASE}/settings`,
},
},
{
matches: (ctx): boolean =>
ctx.code === 'invalid_value' && ctx.enumValues !== undefined,
tip: {
suggestion: undefined,
},
},
{
matches: (ctx): boolean =>
ctx.code === 'invalid_type' &&
ctx.expected === 'object' &&
ctx.received === null &&
ctx.path === '',
tip: {
suggestion:
'Check for missing commas, unmatched brackets, or trailing commas. Use a JSON validator to identify the exact syntax error.',
},
},
{
matches: (ctx): boolean =>
ctx.path === 'permissions.additionalDirectories' &&
ctx.code === 'invalid_type',
tip: {
suggestion:
'Must be an array of directory paths. Example: ["~/projects", "/tmp/workspace"]. You can also use --add-dir flag or /add-dir command',
docLink: `${DOCUMENTATION_BASE}/iam#working-directories`,
},
},
]
const PATH_DOC_LINKS: Record<string, string> = {
permissions: `${DOCUMENTATION_BASE}/iam#configuring-permissions`,
env: `${DOCUMENTATION_BASE}/settings#environment-variables`,
hooks: `${DOCUMENTATION_BASE}/hooks`,
}
export function getValidationTip(context: TipContext): ValidationTip | null {
const matcher = TIP_MATCHERS.find(m => m.matches(context))
if (!matcher) return null
const tip: ValidationTip = { ...matcher.tip }
if (
context.code === 'invalid_value' &&
context.enumValues &&
!tip.suggestion
) {
tip.suggestion = `Valid values: ${context.enumValues.map(v => `"${v}"`).join(', ')}`
}
// Add documentation link based on path prefix
if (!tip.docLink && context.path) {
const pathPrefix = context.path.split('.')[0]
if (pathPrefix) {
tip.docLink = PATH_DOC_LINKS[pathPrefix]
}
}
return tip
}