What's wrong
The four verbs in FileDeduplicator/Verbs/ duplicate two blocks almost word for word.
- The path prompt.
ValidateArgs() in Scan.cs:14-30, DryRun.cs:14-30, Deduplicate.cs:14-30 and Stats.cs:15-31 is the same method: PathString is "." or "" → prompt → Console.ReadLine()?.Trim() → abort when the input is empty.
- The pipeline. Steps 1-3 of each
Run (Scan.cs:37-61, DryRun.cs:37-61, Deduplicate.cs:37-61, Stats.cs:38-56) are the same sequence: FileScanner.ScanForFiles → print the count → return on zero → FileHasher.HashFiles → Deduplicator.GroupByHash → Deduplicator.FindDuplicates. For Scan and DryRun, lines 14-61 differ only in the verb type and one banner line.
On top of that, Scan.cs:64-83 renders its own KEEP/DELETE listing and wasted-space total instead of using DuplicateReport.PlanDeletions. DuplicateReport's own remarks say all verbs should build this listing in one place so they cannot drift apart.
Why it matters
Several open issues change exactly this duplicated code. Each fix has to land four times, identically, or the verbs start to behave differently:
If a fix reaches three verbs and misses the fourth, one verb reports a different set of duplicates, or accepts different input, than the others. The codebase already treats that kind of divergence as its worst defect: DryRunEquivalenceTests exists because DryRun has to predict Deduplicate exactly.
Suggested fix
- Move the prompt into
BaseVerb<T>, and have each verb supply only its prompt text (for example, through an abstract PromptText property).
- Add one method, for example
DuplicateFinder.Find(AbsoluteDirectoryPath) -> ScanResult, that returns the scanned files, the hashes and the duplicate groups. Each verb calls it once and keeps only its own reporting.
- Make Scan render from
DuplicateReport.PlanDeletions, like DryRun and Deduplicate do. It can keep its [KEEP]/[DELETE] suffix format.
Acceptance criteria
What's wrong
The four verbs in
FileDeduplicator/Verbs/duplicate two blocks almost word for word.ValidateArgs()inScan.cs:14-30,DryRun.cs:14-30,Deduplicate.cs:14-30andStats.cs:15-31is the same method:PathString is "." or ""→ prompt →Console.ReadLine()?.Trim()→ abort when the input is empty.Run(Scan.cs:37-61,DryRun.cs:37-61,Deduplicate.cs:37-61,Stats.cs:38-56) are the same sequence:FileScanner.ScanForFiles→ print the count → return on zero →FileHasher.HashFiles→Deduplicator.GroupByHash→Deduplicator.FindDuplicates. For Scan and DryRun, lines 14-61 differ only in the verb type and one banner line.On top of that,
Scan.cs:64-83renders its own KEEP/DELETE listing and wasted-space total instead of usingDuplicateReport.PlanDeletions.DuplicateReport's own remarks say all verbs should build this listing in one place so they cannot drift apart.Why it matters
Several open issues change exactly this duplicated code. Each fix has to land four times, identically, or the verbs start to behave differently:
-p .explicitly is treated as "no path" and prompts, swallowing the next stdin line as the path #145: an explicit-p .is treated as "no path" (thePathString is "." or ""check, in four places).ReadLine()?.Trim(), in four places).returns in each copy).If a fix reaches three verbs and misses the fourth, one verb reports a different set of duplicates, or accepts different input, than the others. The codebase already treats that kind of divergence as its worst defect:
DryRunEquivalenceTestsexists because DryRun has to predict Deduplicate exactly.Suggested fix
BaseVerb<T>, and have each verb supply only its prompt text (for example, through an abstractPromptTextproperty).DuplicateFinder.Find(AbsoluteDirectoryPath) -> ScanResult, that returns the scanned files, the hashes and the duplicate groups. Each verb calls it once and keeps only its own reporting.DuplicateReport.PlanDeletions, like DryRun and Deduplicate do. It can keep its[KEEP]/[DELETE]suffix format.Acceptance criteria
ValidateArgsand the scan/hash/group sequence each exist in exactly one place.DuplicateReport.VerbOutputTests,VerbReuseTestsandDryRunEquivalenceTestspass unchanged.-p .explicitly is treated as "no path" and prompts, swallowing the next stdin line as the path #145, A quoted path pasted at the "Enter the path" prompt is scanned as <cwd>/"<path>" and reported as "Directory not found" #152, Every verb reads and SHA-256-hashes every file in full, even files whose size no other file shares #147 and Every verb exits with code 0 even when the directory is missing, deletions fail, or the arguments are invalid #144, so each of those is made once.