appydave-tools 0.70.0 → 0.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/brainstorming-agent.md +227 -0
  3. data/.claude/commands/cli-test.md +251 -0
  4. data/.claude/commands/dev.md +234 -0
  5. data/.claude/commands/po.md +227 -0
  6. data/.claude/commands/progress.md +51 -0
  7. data/.claude/commands/uat.md +321 -0
  8. data/.rubocop.yml +9 -0
  9. data/AGENTS.md +43 -0
  10. data/CHANGELOG.md +12 -0
  11. data/CLAUDE.md +26 -3
  12. data/README.md +15 -0
  13. data/bin/dam +21 -1
  14. data/bin/jump.rb +29 -0
  15. data/bin/subtitle_processor.rb +54 -1
  16. data/bin/zsh_history.rb +846 -0
  17. data/docs/README.md +162 -69
  18. data/docs/architecture/cli/exe-bin-convention.md +434 -0
  19. data/docs/architecture/cli-patterns.md +631 -0
  20. data/docs/architecture/gpt-context/gpt-context-architecture.md +325 -0
  21. data/docs/architecture/gpt-context/gpt-context-implementation-guide.md +419 -0
  22. data/docs/architecture/gpt-context/gpt-context-vision.md +179 -0
  23. data/docs/architecture/testing/testing-patterns.md +762 -0
  24. data/docs/backlog.md +120 -0
  25. data/docs/cli-tests/FR-3-jump-location-tool.md +515 -0
  26. data/docs/specs/fr-002-gpt-context-help-system.md +265 -0
  27. data/docs/specs/fr-003-jump-location-tool.md +779 -0
  28. data/docs/specs/zsh-history-tool.md +820 -0
  29. data/docs/uat/FR-3-jump-location-tool.md +741 -0
  30. data/exe/jump +11 -0
  31. data/exe/{subtitle_manager → subtitle_processor} +1 -1
  32. data/exe/zsh_history +11 -0
  33. data/lib/appydave/tools/configuration/openai.rb +1 -1
  34. data/lib/appydave/tools/dam/file_helper.rb +28 -0
  35. data/lib/appydave/tools/dam/project_listing.rb +4 -30
  36. data/lib/appydave/tools/dam/s3_operations.rb +2 -1
  37. data/lib/appydave/tools/dam/ssd_status.rb +226 -0
  38. data/lib/appydave/tools/dam/status.rb +3 -51
  39. data/lib/appydave/tools/jump/cli.rb +561 -0
  40. data/lib/appydave/tools/jump/commands/add.rb +52 -0
  41. data/lib/appydave/tools/jump/commands/base.rb +43 -0
  42. data/lib/appydave/tools/jump/commands/generate.rb +153 -0
  43. data/lib/appydave/tools/jump/commands/remove.rb +58 -0
  44. data/lib/appydave/tools/jump/commands/report.rb +214 -0
  45. data/lib/appydave/tools/jump/commands/update.rb +42 -0
  46. data/lib/appydave/tools/jump/commands/validate.rb +54 -0
  47. data/lib/appydave/tools/jump/config.rb +233 -0
  48. data/lib/appydave/tools/jump/formatters/base.rb +48 -0
  49. data/lib/appydave/tools/jump/formatters/json_formatter.rb +19 -0
  50. data/lib/appydave/tools/jump/formatters/paths_formatter.rb +21 -0
  51. data/lib/appydave/tools/jump/formatters/table_formatter.rb +183 -0
  52. data/lib/appydave/tools/jump/location.rb +134 -0
  53. data/lib/appydave/tools/jump/path_validator.rb +47 -0
  54. data/lib/appydave/tools/jump/search.rb +230 -0
  55. data/lib/appydave/tools/subtitle_processor/transcript.rb +51 -0
  56. data/lib/appydave/tools/version.rb +1 -1
  57. data/lib/appydave/tools/zsh_history/command.rb +37 -0
  58. data/lib/appydave/tools/zsh_history/config.rb +235 -0
  59. data/lib/appydave/tools/zsh_history/filter.rb +184 -0
  60. data/lib/appydave/tools/zsh_history/formatter.rb +75 -0
  61. data/lib/appydave/tools/zsh_history/parser.rb +101 -0
  62. data/lib/appydave/tools.rb +25 -0
  63. data/package.json +1 -1
  64. metadata +51 -4
@@ -0,0 +1,820 @@
1
+ # ZSH History Tool - Implementation Specification
2
+
3
+ ## Overview
4
+
5
+ A Ruby CLI tool to parse, filter, and clean ZSH history files. Part of the appydave-tools gem.
6
+
7
+ **Primary Goals:**
8
+ 1. View clean, filtered ZSH history (remove noise)
9
+ 2. Optionally rewrite ~/.zsh_history with filtered content
10
+ 3. Filter by time range (last N days)
11
+ 4. Categorize commands as wanted/unwanted/unsure using configurable patterns
12
+
13
+ **CLI Pattern:** Pattern 1 (Simple Procedural) - single-purpose tool with OptionParser
14
+
15
+ ---
16
+
17
+ ## Table of Contents
18
+
19
+ 1. [Requirements](#requirements)
20
+ 2. [Architecture](#architecture)
21
+ 3. [Data Structures](#data-structures)
22
+ 4. [ZSH History Format](#zsh-history-format)
23
+ 5. [Parsing Algorithm](#parsing-algorithm)
24
+ 6. [Filter System](#filter-system)
25
+ 7. [CLI Interface](#cli-interface)
26
+ 8. [Configuration](#configuration)
27
+ 9. [File Structure](#file-structure)
28
+ 10. [Implementation Phases](#implementation-phases)
29
+ 11. [Testing Strategy](#testing-strategy)
30
+ 12. [Future Enhancements](#future-enhancements)
31
+
32
+ ---
33
+
34
+ ## Requirements
35
+
36
+ ### Functional Requirements
37
+
38
+ | ID | Requirement | Priority |
39
+ |----|-------------|----------|
40
+ | FR-1 | Parse ~/.zsh_history including multi-line commands | Must |
41
+ | FR-2 | Extract timestamps and convert to datetime | Must |
42
+ | FR-3 | Apply exclude patterns to filter out noise | Must |
43
+ | FR-4 | Apply include patterns to keep valuable commands | Must |
44
+ | FR-5 | Filter by date range (--days N) | Must |
45
+ | FR-6 | Display clean history to stdout | Must |
46
+ | FR-7 | Rewrite ~/.zsh_history with filtered content (--write) | Should |
47
+ | FR-8 | Show statistics (counts per category) | Should |
48
+ | FR-9 | Search within history (--grep) | Could |
49
+ | FR-10 | Interactive review of unsure commands | Could |
50
+
51
+ ### Non-Functional Requirements
52
+
53
+ | ID | Requirement |
54
+ |----|-------------|
55
+ | NFR-1 | Process 50,000+ history entries in < 5 seconds |
56
+ | NFR-2 | Create backup before rewriting history file |
57
+ | NFR-3 | Handle corrupted/malformed history entries gracefully |
58
+ | NFR-4 | UTF-8 support for international characters |
59
+
60
+ ---
61
+
62
+ ## Architecture
63
+
64
+ ```
65
+ ┌─────────────────────────────────────────────────────────────────┐
66
+ │ CLI Layer │
67
+ │ bin/zsh_history.rb │
68
+ │ (OptionParser, argument handling, output) │
69
+ └─────────────────────────────────────────────────────────────────┘
70
+
71
+
72
+ ┌─────────────────────────────────────────────────────────────────┐
73
+ │ Service Layer │
74
+ │ │
75
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
76
+ │ │ Parser │ │ Filter │ │ Formatter │ │
77
+ │ │ │ │ │ │ │ │
78
+ │ │ - read() │ │ - apply() │ │ - clean() │ │
79
+ │ │ - parse() │ │ - match() │ │ - stats() │ │
80
+ │ │ - join() │ │ - categorize│ │ - write() │ │
81
+ │ └─────────────┘ └─────────────┘ └─────────────┘ │
82
+ │ │
83
+ └─────────────────────────────────────────────────────────────────┘
84
+
85
+
86
+ ┌─────────────────────────────────────────────────────────────────┐
87
+ │ Config Layer │
88
+ │ ~/.config/appydave/zsh_history.json │
89
+ │ │
90
+ │ { "exclude_patterns": [...], "include_patterns": [...] } │
91
+ └─────────────────────────────────────────────────────────────────┘
92
+ ```
93
+
94
+ ### Component Responsibilities
95
+
96
+ | Component | Responsibility |
97
+ |-----------|----------------|
98
+ | **CLI** | Parse arguments, orchestrate workflow, handle I/O |
99
+ | **Parser** | Read ZSH history file, reconstruct multi-line commands |
100
+ | **Filter** | Apply patterns, categorize commands, filter by date |
101
+ | **Formatter** | Format output for display, generate stats, write files |
102
+ | **Config** | Load/save filter patterns from JSON config |
103
+
104
+ ---
105
+
106
+ ## Data Structures
107
+
108
+ ### Command Struct
109
+
110
+ ```ruby
111
+ Command = Struct.new(
112
+ :timestamp, # Integer - Unix timestamp from history
113
+ :datetime, # Time - Parsed datetime
114
+ :text, # String - Full command text (multi-line joined)
115
+ :is_multiline, # Boolean - Was this a continuation command?
116
+ :category, # Symbol - :wanted, :unwanted, :unsure
117
+ :raw_lines, # Array<String> - Original lines from file
118
+ keyword_init: true
119
+ )
120
+ ```
121
+
122
+ ### FilterResult Struct
123
+
124
+ ```ruby
125
+ FilterResult = Struct.new(
126
+ :wanted, # Array<Command>
127
+ :unwanted, # Array<Command>
128
+ :unsure, # Array<Command>
129
+ :stats, # Hash - { total:, wanted:, unwanted:, unsure: }
130
+ keyword_init: true
131
+ )
132
+ ```
133
+
134
+ ### Config Structure
135
+
136
+ ```ruby
137
+ Config = Struct.new(
138
+ :exclude_patterns, # Array<String> - Regex patterns to exclude
139
+ :include_patterns, # Array<String> - Regex patterns to include
140
+ :history_path, # String - Path to history file (default: ~/.zsh_history)
141
+ keyword_init: true
142
+ )
143
+ ```
144
+
145
+ ---
146
+
147
+ ## ZSH History Format
148
+
149
+ ### Standard Format
150
+
151
+ ```
152
+ : 1699876543:0;cd ~/dev
153
+ : 1699876550:0;git status
154
+ : 1699876560:0;ls -la
155
+ ```
156
+
157
+ **Pattern:** `: <timestamp>:<duration>;<command>`
158
+
159
+ - `timestamp` - Unix epoch seconds
160
+ - `duration` - Command duration (often 0)
161
+ - `command` - The actual command text
162
+
163
+ ### Multi-line Commands
164
+
165
+ Commands ending with `\` continue on the next line:
166
+
167
+ ```
168
+ : 1699876570:0;docker run \
169
+ --name myapp \
170
+ -p 3000:3000 \
171
+ myimage:latest
172
+ ```
173
+
174
+ **Reconstruction:** Join lines, remove trailing `\`, preserve internal whitespace.
175
+
176
+ ### Edge Cases
177
+
178
+ | Case | Example | Handling |
179
+ |------|---------|----------|
180
+ | Corrupted timestamp | `: abc:0;cmd` | Skip or use epoch 0 |
181
+ | Missing semicolon | `: 123:0cmd` | Skip line |
182
+ | Binary/garbled data | `\x00\x01\x02` | Skip line |
183
+ | Empty command | `: 123:0;` | Skip line |
184
+ | Very long command | 10000+ chars | Truncate for display, keep for filtering |
185
+
186
+ ---
187
+
188
+ ## Parsing Algorithm
189
+
190
+ ### Pseudocode
191
+
192
+ ```
193
+ function parse_history(file_path):
194
+ lines = read_all_lines(file_path)
195
+ commands = []
196
+ current_command = nil
197
+
198
+ for each line in lines:
199
+ # Try to match history entry format
200
+ if match = line.match(/^: (\d+):\d+;(.*)$/):
201
+ timestamp = match[1].to_i
202
+ command_text = match[2]
203
+
204
+ # If we were building a multi-line command, finalize it
205
+ if current_command:
206
+ commands.append(current_command)
207
+ current_command = nil
208
+
209
+ # Check if this is a continuation command
210
+ if command_text.ends_with?('\'):
211
+ current_command = Command.new(
212
+ timestamp: timestamp,
213
+ text: command_text.chomp('\\'),
214
+ is_multiline: true,
215
+ raw_lines: [line]
216
+ )
217
+ else:
218
+ commands.append(Command.new(
219
+ timestamp: timestamp,
220
+ text: command_text,
221
+ is_multiline: false,
222
+ raw_lines: [line]
223
+ ))
224
+
225
+ # Line doesn't match format - might be continuation
226
+ else:
227
+ if current_command:
228
+ # Append to current multi-line command
229
+ current_command.raw_lines.append(line)
230
+
231
+ if line.ends_with?('\'):
232
+ current_command.text += "\n" + line.chomp('\\')
233
+ else:
234
+ current_command.text += "\n" + line
235
+ commands.append(current_command)
236
+ current_command = nil
237
+ else:
238
+ # Orphan line - skip or log warning
239
+ log_warning("Orphan line: #{line}")
240
+
241
+ # Don't forget trailing command
242
+ if current_command:
243
+ commands.append(current_command)
244
+
245
+ return commands
246
+ ```
247
+
248
+ ### Implementation Notes
249
+
250
+ 1. **Read entire file** - ZSH history is typically < 10MB, safe to load into memory
251
+ 2. **Handle encoding** - Use `File.read(path, encoding: 'UTF-8', invalid: :replace)`
252
+ 3. **Preserve raw lines** - Needed for accurate rewriting
253
+ 4. **Convert timestamps** - `Time.at(timestamp)` for datetime
254
+
255
+ ---
256
+
257
+ ## Filter System
258
+
259
+ ### Filter Logic
260
+
261
+ ```
262
+ for each command in commands:
263
+ # First, check date range
264
+ if options.days and command.datetime < (now - days):
265
+ skip command entirely
266
+
267
+ # Check exclude patterns first (noise removal)
268
+ for pattern in exclude_patterns:
269
+ if command.text matches pattern:
270
+ command.category = :unwanted
271
+ break
272
+
273
+ # If not excluded, check include patterns
274
+ if command.category != :unwanted:
275
+ for pattern in include_patterns:
276
+ if command.text matches pattern:
277
+ command.category = :wanted
278
+ break
279
+
280
+ # If neither matched, it's unsure
281
+ if command.category == nil:
282
+ command.category = :unsure
283
+ ```
284
+
285
+ ### Default Exclude Patterns
286
+
287
+ Based on analysis of the user's history files:
288
+
289
+ ```json
290
+ {
291
+ "exclude_patterns": [
292
+ "^[a-z]$",
293
+ "^[a-z]{2}$",
294
+ "^ls$",
295
+ "^ls -",
296
+ "^pwd$",
297
+ "^clear$",
298
+ "^exit$",
299
+ "^cd$",
300
+ "^cd -$",
301
+ "^\\.$",
302
+ "^\\.\\.$",
303
+ "^git status$",
304
+ "^git diff$",
305
+ "^git log$",
306
+ "^git pull$",
307
+ "^gs$",
308
+ "^gd$",
309
+ "^gl$",
310
+ "^h$",
311
+ "^history",
312
+ "^which ",
313
+ "^type ",
314
+ "^cat ",
315
+ "^head ",
316
+ "^tail ",
317
+ "^echo \\$",
318
+ "^\\[\\d+\\]",
319
+ "^davidcruwys\\s+\\d+",
320
+ "^zsh: command not found",
321
+ "^X Process completed",
322
+ "^Coverage report",
323
+ "^Line Coverage:",
324
+ "^Finished in \\d",
325
+ "^\\d+ examples, \\d+ failures"
326
+ ]
327
+ }
328
+ ```
329
+
330
+ **Pattern Categories:**
331
+
332
+ | Category | Examples | Rationale |
333
+ |----------|----------|-----------|
334
+ | Single chars | `^[a-z]$` | Typos, not real commands |
335
+ | Navigation | `^cd$`, `^pwd$`, `^ls` | High frequency, low value |
336
+ | Git read-only | `^git status$`, `^git diff$` | Exploratory, not actions |
337
+ | Output lines | `^davidcruwys\\s+\\d+`, `^\[\\d+\]` | Command output, not commands |
338
+ | Error messages | `^zsh: command not found` | Noise |
339
+ | Test output | `^Finished in`, `^\\d+ examples` | RSpec/test runner output |
340
+
341
+ ### Default Include Patterns
342
+
343
+ ```json
344
+ {
345
+ "include_patterns": [
346
+ "^j[a-z]",
347
+ "^dam ",
348
+ "^vat ",
349
+ "^claude ",
350
+ "^c-sonnet",
351
+ "^bun run ",
352
+ "^npm run ",
353
+ "^rake ",
354
+ "^bundle ",
355
+ "^git commit",
356
+ "^git push",
357
+ "^git add",
358
+ "^gac ",
359
+ "^kfeat ",
360
+ "^kfix ",
361
+ "^docker ",
362
+ "^brew install",
363
+ "^gem install",
364
+ "^npm install"
365
+ ]
366
+ }
367
+ ```
368
+
369
+ **Pattern Categories:**
370
+
371
+ | Category | Examples | Rationale |
372
+ |----------|----------|-----------|
373
+ | Jump aliases | `^j[a-z]` | Navigation shortcuts, useful reference |
374
+ | AppyDave tools | `^dam `, `^vat ` | Custom tooling usage |
375
+ | Claude | `^claude `, `^c-sonnet` | AI assistant commands |
376
+ | Build/run | `^bun run `, `^npm run ` | Development workflow |
377
+ | Git writes | `^git commit`, `^git push` | Meaningful actions |
378
+ | Installs | `^brew install`, `^gem install` | System changes worth tracking |
379
+
380
+ ---
381
+
382
+ ## CLI Interface
383
+
384
+ ### Command Structure
385
+
386
+ ```bash
387
+ # Primary command - show clean history
388
+ zsh_history clean [options]
389
+
390
+ # Options
391
+ -d, --days N Only show last N days
392
+ -w, --write Rewrite ~/.zsh_history (creates backup)
393
+ -s, --stats Show statistics only
394
+ -g, --grep PATTERN Search within history
395
+ -u, --unsure Include unsure commands in output
396
+ -a, --all Show all commands (no filtering)
397
+ -v, --verbose Show which pattern matched each command
398
+ -h, --help Show help
399
+ ```
400
+
401
+ ### Usage Examples
402
+
403
+ ```bash
404
+ # View clean history (default: wanted only)
405
+ zsh_history clean
406
+
407
+ # View last 7 days
408
+ zsh_history clean --days 7
409
+
410
+ # View with unsure commands included
411
+ zsh_history clean --unsure
412
+
413
+ # Search for docker commands in last 30 days
414
+ zsh_history clean --days 30 --grep docker
415
+
416
+ # Show statistics
417
+ zsh_history clean --stats
418
+
419
+ # Rewrite history file (creates backup first)
420
+ zsh_history clean --days 90 --write
421
+
422
+ # Debug: see which patterns matched
423
+ zsh_history clean --verbose
424
+ ```
425
+
426
+ ### Output Formats
427
+
428
+ **Default (clean):**
429
+ ```
430
+ 2024-11-15 09:30:22 dam s3-up appydave b70
431
+ 2024-11-15 09:35:10 bun run dev
432
+ 2024-11-15 10:00:00 git commit -m 'update docs'
433
+ ```
434
+
435
+ **Stats:**
436
+ ```
437
+ ZSH History Statistics
438
+ ═══════════════════════════════════════
439
+ Total commands: 12,543
440
+ Wanted: 2,341 (18.7%)
441
+ Unwanted: 9,876 (78.7%)
442
+ Unsure: 326 ( 2.6%)
443
+
444
+ Date range: 2024-01-01 to 2024-11-15 (319 days)
445
+ ```
446
+
447
+ **Verbose:**
448
+ ```
449
+ 2024-11-15 09:30:22 [WANTED: ^dam ] dam s3-up appydave b70
450
+ 2024-11-15 09:31:00 [EXCLUDE: ^ls$] ls
451
+ 2024-11-15 09:35:10 [WANTED: ^bun run ] bun run dev
452
+ ```
453
+
454
+ ---
455
+
456
+ ## Configuration
457
+
458
+ ### Config File Location
459
+
460
+ ```
461
+ ~/.config/appydave/zsh_history.json
462
+ ```
463
+
464
+ ### Full Config Schema
465
+
466
+ ```json
467
+ {
468
+ "history_path": "~/.zsh_history",
469
+ "backup_before_write": true,
470
+ "backup_dir": "~/.config/appydave/backups",
471
+ "exclude_patterns": [
472
+ "^[a-z]$",
473
+ "^ls$",
474
+ "^pwd$",
475
+ "^clear$",
476
+ "^exit$",
477
+ "^cd$",
478
+ "^git status$",
479
+ "^git diff$",
480
+ "^git log$"
481
+ ],
482
+ "include_patterns": [
483
+ "^j[a-z]",
484
+ "^dam ",
485
+ "^claude ",
486
+ "^bun run ",
487
+ "^npm run ",
488
+ "^git commit",
489
+ "^git push"
490
+ ],
491
+ "output": {
492
+ "datetime_format": "%Y-%m-%d %H:%M:%S",
493
+ "show_multiline_indicator": true,
494
+ "max_command_length": 200
495
+ }
496
+ }
497
+ ```
498
+
499
+ ### Config Integration
500
+
501
+ Uses existing `Appydave::Tools::Configuration` system:
502
+
503
+ ```ruby
504
+ module Appydave::Tools::Configuration::Models
505
+ class ZshHistoryConfig < ConfigBase
506
+ # Auto-loads from ~/.config/appydave/zsh_history.json
507
+
508
+ def exclude_patterns
509
+ data['exclude_patterns'] || DEFAULT_EXCLUDE_PATTERNS
510
+ end
511
+
512
+ def include_patterns
513
+ data['include_patterns'] || DEFAULT_INCLUDE_PATTERNS
514
+ end
515
+ end
516
+ end
517
+ ```
518
+
519
+ ---
520
+
521
+ ## File Structure
522
+
523
+ ### New Files to Create
524
+
525
+ ```
526
+ lib/appydave/tools/zsh_history/
527
+ ├── parser.rb # Parse ZSH history file
528
+ ├── filter.rb # Apply patterns, categorize
529
+ ├── formatter.rb # Output formatting, stats
530
+ └── command.rb # Command struct definition
531
+
532
+ lib/appydave/tools/configuration/models/
533
+ └── zsh_history_config.rb # Config model
534
+
535
+ bin/
536
+ └── zsh_history.rb # CLI entry point
537
+
538
+ exe/
539
+ └── zsh_history # Gem executable wrapper
540
+
541
+ spec/appydave/tools/zsh_history/
542
+ ├── parser_spec.rb
543
+ ├── filter_spec.rb
544
+ ├── formatter_spec.rb
545
+ └── fixtures/
546
+ ├── simple_history.txt
547
+ ├── multiline_history.txt
548
+ └── corrupted_history.txt
549
+
550
+ docs/
551
+ ├── specs/
552
+ │ └── zsh-history-tool.md # This document
553
+ └── usage/
554
+ └── zsh-history.md # User guide (create after implementation)
555
+ ```
556
+
557
+ ### Module Registration
558
+
559
+ Add to `lib/appydave/tools.rb`:
560
+
561
+ ```ruby
562
+ require_relative 'tools/zsh_history/command'
563
+ require_relative 'tools/zsh_history/parser'
564
+ require_relative 'tools/zsh_history/filter'
565
+ require_relative 'tools/zsh_history/formatter'
566
+ ```
567
+
568
+ Add config to `lib/appydave/tools/configuration/config.rb`:
569
+
570
+ ```ruby
571
+ register_config(:zsh_history, Models::ZshHistoryConfig)
572
+ ```
573
+
574
+ ---
575
+
576
+ ## Implementation Phases
577
+
578
+ ### Phase 1: Core Parser (MVP)
579
+
580
+ **Goal:** Parse history file and display commands with timestamps
581
+
582
+ **Tasks:**
583
+ 1. Create `Command` struct
584
+ 2. Implement `Parser.parse(file_path)`
585
+ 3. Handle single-line commands
586
+ 4. Handle multi-line commands (\ continuations)
587
+ 5. Basic CLI that outputs parsed commands
588
+ 6. Unit tests for parser
589
+
590
+ **Deliverable:**
591
+ ```bash
592
+ zsh_history clean # Shows all commands with timestamps
593
+ ```
594
+
595
+ ### Phase 2: Filtering
596
+
597
+ **Goal:** Apply include/exclude patterns
598
+
599
+ **Tasks:**
600
+ 1. Create `Filter` class with `apply(commands, config)`
601
+ 2. Implement pattern matching (Regexp)
602
+ 3. Categorize as wanted/unwanted/unsure
603
+ 4. Add `--stats` flag
604
+ 5. Unit tests for filter
605
+
606
+ **Deliverable:**
607
+ ```bash
608
+ zsh_history clean # Shows filtered (wanted) commands
609
+ zsh_history clean --stats # Shows counts
610
+ ```
611
+
612
+ ### Phase 3: Date Filtering
613
+
614
+ **Goal:** Filter by time range
615
+
616
+ **Tasks:**
617
+ 1. Add `--days N` option
618
+ 2. Parse timestamps into Time objects
619
+ 3. Filter commands older than N days
620
+ 4. Handle timezone correctly
621
+
622
+ **Deliverable:**
623
+ ```bash
624
+ zsh_history clean --days 7
625
+ ```
626
+
627
+ ### Phase 4: Configuration
628
+
629
+ **Goal:** Load patterns from config file
630
+
631
+ **Tasks:**
632
+ 1. Create `ZshHistoryConfig` model
633
+ 2. Register with configuration system
634
+ 3. Load exclude/include patterns from JSON
635
+ 4. Use defaults if config doesn't exist
636
+ 5. Document default patterns
637
+
638
+ **Deliverable:**
639
+ ```bash
640
+ # Uses ~/.config/appydave/zsh_history.json
641
+ zsh_history clean
642
+ ```
643
+
644
+ ### Phase 5: Write Mode
645
+
646
+ **Goal:** Rewrite history file
647
+
648
+ **Tasks:**
649
+ 1. Add `--write` flag
650
+ 2. Create backup before writing
651
+ 3. Write filtered commands in ZSH history format
652
+ 4. Validate output before overwriting
653
+ 5. Safety checks (don't write if < 10% of original)
654
+
655
+ **Deliverable:**
656
+ ```bash
657
+ zsh_history clean --days 90 --write
658
+ ```
659
+
660
+ ### Phase 6: Polish
661
+
662
+ **Goal:** User experience improvements
663
+
664
+ **Tasks:**
665
+ 1. Add `--grep` for searching
666
+ 2. Add `--verbose` for debugging patterns
667
+ 3. Add `--unsure` to include unsure commands
668
+ 4. Improve output formatting
669
+ 5. Create user documentation
670
+ 6. Add to CLAUDE.md tool list
671
+
672
+ ---
673
+
674
+ ## Testing Strategy
675
+
676
+ ### Unit Tests
677
+
678
+ **Parser:**
679
+ ```ruby
680
+ RSpec.describe Appydave::Tools::ZshHistory::Parser do
681
+ describe '#parse' do
682
+ it 'parses single-line commands'
683
+ it 'parses multi-line commands with continuations'
684
+ it 'extracts timestamps correctly'
685
+ it 'handles corrupted lines gracefully'
686
+ it 'handles empty file'
687
+ it 'handles missing file'
688
+ end
689
+ end
690
+ ```
691
+
692
+ **Filter:**
693
+ ```ruby
694
+ RSpec.describe Appydave::Tools::ZshHistory::Filter do
695
+ describe '#apply' do
696
+ it 'excludes commands matching exclude patterns'
697
+ it 'includes commands matching include patterns'
698
+ it 'marks unmatched commands as unsure'
699
+ it 'respects pattern priority (exclude first)'
700
+ it 'handles empty pattern lists'
701
+ end
702
+
703
+ describe '#filter_by_date' do
704
+ it 'filters commands older than N days'
705
+ it 'keeps commands within N days'
706
+ it 'handles commands at boundary'
707
+ end
708
+ end
709
+ ```
710
+
711
+ ### Test Fixtures
712
+
713
+ **simple_history.txt:**
714
+ ```
715
+ : 1699876543:0;cd ~/dev
716
+ : 1699876550:0;git status
717
+ : 1699876560:0;ls -la
718
+ : 1699876570:0;dam s3-up appydave b70
719
+ ```
720
+
721
+ **multiline_history.txt:**
722
+ ```
723
+ : 1699876543:0;docker run \
724
+ --name myapp \
725
+ -p 3000:3000 \
726
+ myimage:latest
727
+ : 1699876600:0;echo "done"
728
+ ```
729
+
730
+ **corrupted_history.txt:**
731
+ ```
732
+ : 1699876543:0;valid command
733
+ garbage line here
734
+ : badtimestamp:0;another
735
+ : 1699876600:0;valid again
736
+ ```
737
+
738
+ ### Integration Tests
739
+
740
+ ```ruby
741
+ RSpec.describe 'zsh_history CLI' do
742
+ it 'displays clean history'
743
+ it 'filters by days'
744
+ it 'shows stats'
745
+ it 'creates backup before writing'
746
+ it 'refuses to write if too few commands remain'
747
+ end
748
+ ```
749
+
750
+ ---
751
+
752
+ ## Future Enhancements
753
+
754
+ ### v1.1 - Interactive Review
755
+
756
+ ```bash
757
+ zsh_history review
758
+ # Shows unsure commands one by one
759
+ # User types 'w' (wanted), 'u' (unwanted), 's' (skip)
760
+ # Updates config with new patterns
761
+ ```
762
+
763
+ ### v1.2 - Pattern Suggestions
764
+
765
+ ```bash
766
+ zsh_history suggest
767
+ # Analyzes unsure commands
768
+ # Suggests patterns based on frequency
769
+ # "Found 47 commands starting with 'npx' - add to include? (y/n)"
770
+ ```
771
+
772
+ ### v1.3 - Export/Import
773
+
774
+ ```bash
775
+ zsh_history export --format json > history.json
776
+ zsh_history export --format txt > history.txt
777
+ zsh_history import --from other_machine.txt
778
+ ```
779
+
780
+ ### v1.4 - Deduplication
781
+
782
+ ```bash
783
+ zsh_history clean --dedupe
784
+ # Removes duplicate commands, keeping most recent
785
+ ```
786
+
787
+ ---
788
+
789
+ ## Appendix: Pattern Reference
790
+
791
+ ### Regex Cheat Sheet
792
+
793
+ | Pattern | Matches | Example |
794
+ |---------|---------|---------|
795
+ | `^` | Start of line | `^git` matches "git status" |
796
+ | `$` | End of line | `^ls$` matches only "ls" |
797
+ | `\s` | Whitespace | `^cd\s` matches "cd ~/dev" |
798
+ | `\d+` | One or more digits | `^\d+` matches "123 foo" |
799
+ | `[a-z]` | Single lowercase letter | `^[a-z]$` matches "a", "b" |
800
+ | `.*` | Any characters | `^git.*push` matches "git push origin main" |
801
+ | `\|` | Literal pipe (escaped) | `\|` matches commands with pipes |
802
+
803
+ ### Common Pattern Examples
804
+
805
+ ```ruby
806
+ # Match all npm/yarn commands
807
+ "^(npm|yarn|bun) "
808
+
809
+ # Match all git write operations
810
+ "^git (add|commit|push|rebase|merge|cherry-pick)"
811
+
812
+ # Match any command with a pipe
813
+ "\\|"
814
+
815
+ # Match any command with sudo
816
+ "^sudo "
817
+
818
+ # Match docker/docker-compose
819
+ "^docker(-compose)? "
820
+ ```