or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdcore-application.mddata-types.mdindex.mdrule-development.mdrule-system.mdshell-integration.mduser-interface.mdutilities.md

rule-development.mddocs/

0

# Rule Development

1

2

Creating custom correction rules, rule structure, and integration patterns. This guide enables extending the correction system with new command-specific logic to handle additional CLI tools and error patterns not covered by built-in rules.

3

4

## Capabilities

5

6

### Rule Function Requirements

7

8

Every rule module must implement these core functions for integration with the correction system.

9

10

```python { .api }

11

def match(command, settings):

12

"""

13

Determines if this rule applies to the given command.

14

15

Parameters:

16

- command (Command): The failed command with script, stdout, stderr

17

- settings (Settings): Application settings and configuration

18

19

Returns:

20

bool: True if this rule can provide corrections for the command

21

22

This function should analyze the command to determine applicability.

23

Common patterns:

24

- Check command.script for command name or arguments

25

- Check command.stderr for specific error messages

26

- Check command.stdout for output patterns

27

- Use settings for rule configuration

28

"""

29

30

def get_new_command(command, settings):

31

"""

32

Generates corrected command(s) for the matched command.

33

34

Parameters:

35

- command (Command): The failed command to correct

36

- settings (Settings): Application settings and configuration

37

38

Returns:

39

str or list[str]: Single correction or list of possible corrections

40

41

This function should return the corrected command string(s).

42

Multiple corrections are ranked by order in the list.

43

"""

44

```

45

46

### Optional Rule Attributes

47

48

Rules can define optional attributes to customize their behavior and integration.

49

50

```python { .api }

51

enabled_by_default = True

52

"""

53

bool: Whether this rule should be enabled by default.

54

55

Default: True if not specified

56

Set to False for experimental rules or rules that may conflict

57

"""

58

59

priority = 1000

60

"""

61

int: Rule priority for ordering corrections.

62

63

Lower numbers = higher priority (executed first)

64

Default: conf.DEFAULT_PRIORITY (1000) if not specified

65

66

Priority guidelines:

67

- 100-500: Critical fixes (sudo, permission issues)

68

- 500-1000: Common command corrections

69

- 1000-1500: Typo fixes and suggestions

70

- 1500+: Experimental or low-confidence corrections

71

"""

72

73

requires_output = True

74

"""

75

bool: Whether this rule needs command output to function.

76

77

Default: True if not specified

78

Set to False for rules that only analyze command.script

79

80

Rules with requires_output=False can work with:

81

- Commands that produce no output

82

- Timeouts or killed processes

83

- Script-only analysis

84

"""

85

86

def side_effect(old_cmd, new_cmd, settings):

87

"""

88

Optional function executed when the corrected command runs.

89

90

Parameters:

91

- old_cmd (Command): Original failed command

92

- new_cmd (str): Corrected command being executed

93

- settings (Settings): Application settings

94

95

Returns:

96

None

97

98

Use for additional actions like:

99

- Updating configuration files

100

- Creating directories or files

101

- Logging corrections

102

- Cleaning up state

103

"""

104

```

105

106

## Rule Development Patterns

107

108

### Basic Command Name Matching

109

110

Simple rules that match based on command name or structure.

111

112

```python

113

# Example: Fix 'gti' typo to 'git'

114

def match(command, settings):

115

return command.script.startswith('gti ')

116

117

def get_new_command(command, settings):

118

return command.script.replace('gti ', 'git ', 1)

119

120

enabled_by_default = True

121

priority = 900

122

requires_output = False

123

```

124

125

### Error Message Pattern Matching

126

127

Rules that analyze error output to determine corrections.

128

129

```python

130

# Example: Add sudo for permission denied errors

131

def match(command, settings):

132

return ('permission denied' in command.stderr.lower() or

133

'operation not permitted' in command.stderr.lower())

134

135

def get_new_command(command, settings):

136

return f'sudo {command.script}'

137

138

enabled_by_default = True

139

priority = 100 # High priority for permission fixes

140

```

141

142

### Complex Analysis with Multiple Corrections

143

144

Advanced rules that provide multiple correction options.

145

146

```python

147

import re

148

from thefuck.utils import get_closest

149

150

def match(command, settings):

151

return (command.script.startswith('git ') and

152

'is not a git command' in command.stderr)

153

154

def get_new_command(command, settings):

155

# Extract the invalid git command

156

match = re.search(r"git: '(\w+)' is not a git command", command.stderr)

157

if not match:

158

return []

159

160

invalid_cmd = match.group(1)

161

git_commands = ['push', 'pull', 'commit', 'checkout', 'branch', 'merge', 'status']

162

163

# Find similar commands

164

closest = get_closest(invalid_cmd, git_commands, cutoff=0.6)

165

if closest:

166

base_cmd = command.script.replace(invalid_cmd, closest, 1)

167

return [base_cmd]

168

169

return []

170

171

enabled_by_default = True

172

priority = 1000

173

```

174

175

### Application-Specific Rules with Decorators

176

177

Rules using utility decorators for cleaner code.

178

179

```python

180

from thefuck.utils import for_app

181

from thefuck.specific.sudo import sudo_support

182

183

@sudo_support

184

@for_app('docker')

185

def match(command, settings):

186

return 'permission denied' in command.stderr.lower()

187

188

@sudo_support

189

def get_new_command(command, settings):

190

# sudo_support decorator automatically adds/removes sudo

191

return command.script

192

193

enabled_by_default = True

194

priority = 200

195

```

196

197

### Rules with Side Effects

198

199

Rules that perform additional actions when corrections are executed.

200

201

```python

202

import os

203

from pathlib import Path

204

205

def match(command, settings):

206

return (command.script.startswith('cd ') and

207

'no such file or directory' in command.stderr.lower())

208

209

def get_new_command(command, settings):

210

# Extract the directory name

211

parts = command.script.split()

212

if len(parts) > 1:

213

directory = parts[1]

214

return f'mkdir -p {directory} && cd {directory}'

215

return command.script

216

217

def side_effect(old_cmd, new_cmd, settings):

218

"""Log directory creation for future reference."""

219

parts = new_cmd.split('&&')[0].strip().split()

220

if len(parts) > 2:

221

created_dir = parts[2]

222

log_file = Path.home() / '.thefuck' / 'created_dirs.log'

223

with log_file.open('a') as f:

224

f.write(f"{created_dir}\n")

225

226

enabled_by_default = True

227

priority = 1200

228

```

229

230

## Advanced Rule Patterns

231

232

### Multi-Application Rules

233

234

Rules that handle multiple related applications.

235

236

```python

237

def match(command, settings):

238

package_managers = ['apt-get', 'yum', 'dnf', 'pacman']

239

return (any(command.script.startswith(pm) for pm in package_managers) and

240

'permission denied' in command.stderr.lower())

241

242

def get_new_command(command, settings):

243

return f'sudo {command.script}'

244

245

enabled_by_default = True

246

priority = 150

247

```

248

249

### Context-Aware Rules

250

251

Rules that use environment context for better corrections.

252

253

```python

254

import os

255

from thefuck.utils import which

256

257

def match(command, settings):

258

return (command.script.startswith('python ') and

259

'command not found' in command.stderr)

260

261

def get_new_command(command, settings):

262

corrections = []

263

264

# Try python3 if available

265

if which('python3'):

266

corrections.append(command.script.replace('python ', 'python3 ', 1))

267

268

# Try py if on Windows

269

if os.name == 'nt' and which('py'):

270

corrections.append(command.script.replace('python ', 'py ', 1))

271

272

return corrections

273

274

enabled_by_default = True

275

priority = 800

276

```

277

278

### History-Based Rules

279

280

Rules that use command history for intelligent corrections.

281

282

```python

283

from thefuck.utils import get_valid_history_without_current

284

285

def match(command, settings):

286

return 'command not found' in command.stderr

287

288

def get_new_command(command, settings):

289

history = get_valid_history_without_current(command)

290

291

# Look for similar commands in history

292

invalid_cmd = command.script.split()[0]

293

for hist_cmd in history[:20]: # Check recent history

294

if hist_cmd.startswith(invalid_cmd[:-1]): # Similar prefix

295

return hist_cmd

296

297

return []

298

299

enabled_by_default = False # Experimental

300

priority = 1800

301

```

302

303

## Rule Installation and Testing

304

305

### User Rule Location

306

307

Custom rules should be placed in the user's rules directory:

308

309

```

310

~/.thefuck/rules/my_custom_rule.py

311

```

312

313

### Rule Testing Template

314

315

```python

316

# Template for testing custom rules

317

from thefuck.types import Command

318

319

def test_rule():

320

"""Test the custom rule functionality."""

321

# Test command that should match

322

test_command = Command(

323

script="my_command --flag",

324

stdout="",

325

stderr="my_command: error message"

326

)

327

328

# Test match function

329

assert match(test_command, {}) == True

330

331

# Test correction generation

332

correction = get_new_command(test_command, {})

333

assert correction == "expected_correction"

334

335

print("Rule tests passed!")

336

337

if __name__ == "__main__":

338

test_rule()

339

```

340

341

### Debugging Rules

342

343

```python

344

from thefuck import logs

345

346

def match(command, settings):

347

logs.debug(f"Testing rule against: {command.script}", settings)

348

349

result = # ... rule logic ...

350

351

logs.debug(f"Rule match result: {result}", settings)

352

return result

353

354

def get_new_command(command, settings):

355

logs.debug(f"Generating correction for: {command.script}", settings)

356

357

correction = # ... correction logic ...

358

359

logs.debug(f"Generated correction: {correction}", settings)

360

return correction

361

```

362

363

## Best Practices

364

365

### Rule Design Guidelines

366

367

1. **Specificity**: Make match conditions as specific as possible to avoid false positives

368

2. **Performance**: Keep match functions fast - they're called frequently

369

3. **Robustness**: Handle edge cases and malformed commands gracefully

370

4. **Testing**: Test rules with various command variations and error messages

371

5. **Documentation**: Include clear docstrings explaining rule purpose and behavior

372

373

### Error Handling

374

375

```python

376

def match(command, settings):

377

try:

378

# Rule logic that might fail

379

return analyze_command(command)

380

except Exception as e:

381

# Log error but don't crash

382

logs.exception(f"Rule match error: {e}", settings)

383

return False

384

385

def get_new_command(command, settings):

386

try:

387

# Correction logic

388

return generate_correction(command)

389

except Exception as e:

390

logs.exception(f"Rule correction error: {e}", settings)

391

return [] # Return empty list if correction fails

392

```

393

394

### Integration Patterns

395

396

```python

397

# Use existing utilities for common operations

398

from thefuck.utils import replace_argument, get_closest, for_app

399

from thefuck.specific.sudo import sudo_support

400

401

# Combine decorators for powerful rule behavior

402

@sudo_support

403

@for_app('git')

404

def match(command, settings):

405

return 'permission denied' in command.stderr

406

407

@sudo_support # This will automatically handle sudo addition/removal

408

def get_new_command(command, settings):

409

return command.script # sudo_support handles the rest

410

```

411

412

This comprehensive rule development system allows users to extend thefuck's capabilities to handle any command-line tool or error pattern, making it a truly extensible correction system.