7.9 KiB
Implementation Plan: Safety, Reliability & Idiomatic Refactor
Branch: 001-safety-reliability-refactor | Date: 2026-03-03 | Spec: spec.md
Input: Feature specification from /specs/001-safety-reliability-refactor/spec.md
Summary
Refactor the dns-helper CLI tool to address critical safety and reliability bugs — primarily the unsafe direct-write to the hosts file — and to modernize the codebase for idiomatic Go 1.20 patterns. Key changes: implement atomic write (temp file + rename) with pre-write backup, replace the github.com/miekg/dns dependency with golang.org/x/net/dns/dnsmessage, fix CNAME-chain duplicate IP bug, fix multi-host deletion bug, add input validation and proper error handling, add lock file for concurrency safety, remove dead code, and restructure the project into clear packages with testable interfaces per TDD.
Technical Context
Language/Version: Go 1.20 (maximum; currently go.mod says 1.18, must be updated to 1.20)
Primary Dependencies: Go standard library, golang.org/x/net/dns/dnsmessage (replacing github.com/miekg/dns)
Storage: Local filesystem (system hosts file, backup files, lock file)
Testing: go test ./... with TDD (Red-Green-Refactor); interfaces for filesystem and DNS to enable fakes/stubs
Target Platform: Windows, Linux, macOS (cross-compiled single binary)
Project Type: CLI tool (single binary, no config files)
Performance Goals: N/A (single-shot CLI; DNS timeout default 5s)
Constraints: Go 1.20 ceiling (no 1.21+ features); zero unapproved external dependencies; hosts file must never be corrupted
Scale/Scope: ~500 LOC current, single-purpose utility; 6 source files + 3 platform-specific files
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
Principle I: System File Safety
- Status: VIOLATION IN CURRENT CODE —
writeOverContentInFile()truncates the hosts file directly (O_WRONLY|O_TRUNC). No backup is created. A crash during write leaves the file corrupted. - Plan compliance: FR-001 (atomic write via temp+rename) and FR-002 (pre-write backup) directly address this. PASS after implementation.
Principle II: Standard Library First
- Status: VIOLATION IN CURRENT CODE —
github.com/miekg/dnsis an external dependency outside approved sources. The constitution notes it as a "grandfather clause" legacy dependency with preferred migration. - Plan compliance: FR-014 replaces
miekg/dnswithgolang.org/x/net/dns/dnsmessage(approved source). PASS after implementation.
Principle III: Go 1.20 Compatibility
- Status: VIOLATION IN CURRENT CODE —
go.modspecifiesgo 1.18. While this is compatible, the spec requires updating togo 1.20(FR-010). - Plan compliance: Update
go.modtogo 1.20, verify all code and dependencies are compatible. Verifygolang.org/x/net/dns/dnsmessageis available in a Go 1.20-compatible version. PASS after implementation.
Principle IV: Idiomatic Error Handling
- Status: VIOLATION IN CURRENT CODE —
fileoperations_linux.goandfileoperations_darwin.gocallterm()(which callsos.Exit) instead of returning errors.dns.goprints errors directly viafmt.Printlninstead of returning them.main.gousesdefer os.Exit(1)ininit(). - Plan compliance: FR-008 requires all platform code to return errors. All functions will return errors to callers; only
main()callsos.Exit. PASS after implementation.
Principle V: Simplicity and Single Purpose
- Status: VIOLATION IN CURRENT CODE — Dead code exists: unused
pingsubcommand FlagSet, unusedIsNewfield inworkingData,fileExistsin shared scope duplicated/unused,slicesEqualmay be unused, commented-out debug code indataprep.go. - Plan compliance: FR-009 removes all dead code. Tool remains a single-binary CLI. PASS after implementation.
Principle VI: Test-Driven Development
- Status: VIOLATION IN CURRENT CODE — Zero test files exist. No
*_test.gofiles anywhere. - Plan compliance: All new/refactored code will be developed with TDD. Functions interacting with filesystem/DNS will accept interfaces for testability. PASS after implementation.
Gate Evaluation
All six constitution principles have violations in the current code. All violations are explicitly addressed by the feature spec requirements (FR-001 through FR-016). No unjustified violations exist. GATE: PASS — proceed to Phase 0.
Project Structure
Documentation (this feature)
specs/001-safety-reliability-refactor/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
├── contracts/ # Phase 1 output
└── tasks.md # Phase 2 output (created by /speckit.tasks)
Source Code (repository root)
dns-helper/
├── main.go # Entry point, CLI parsing, os.Exit
├── resolver/
│ ├── resolver.go # DNS resolution interface + implementation
│ └── resolver_test.go # Tests for DNS resolution
├── hostfile/
│ ├── hostfile.go # Hosts file read/parse/write (atomic + backup)
│ ├── hostfile_test.go # Tests for hosts file operations
│ ├── managed.go # Managed block parsing, insertion, removal
│ └── managed_test.go # Tests for managed block logic
├── platform/
│ ├── platform.go # Interface for platform-specific operations
│ ├── platform_windows.go # Windows hosts file location
│ ├── platform_linux.go # Linux hosts file location
│ ├── platform_darwin.go # macOS hosts file location
│ └── platform_test.go # Platform tests
├── lockfile/
│ ├── lockfile.go # Lock file acquisition/release
│ └── lockfile_test.go # Lock file tests
├── go.mod
└── go.sum
Structure Decision: Reorganize from flat single-package into logical sub-packages (resolver, hostfile, platform, lockfile). Each package has a single responsibility and testable interfaces. main.go remains at root as the CLI entry point orchestrating the packages. This follows Go conventions for small-to-medium CLI tools and satisfies Constitution Principles V (simplicity) and VI (TDD/testability).
Constitution Re-Check (Post Phase 1 Design)
All six principles re-evaluated after design phase. No new violations introduced.
| Principle | Status | Evidence |
|---|---|---|
| I. System File Safety | PASS | Atomic temp+rename in same directory (R-002). Pre-write backup in exe dir. Windows retry logic for sharing violations. Hosts file never opened for writing. |
| II. Standard Library First | PASS | miekg/dns replaced by golang.org/x/net/dns/dnsmessage (approved source, R-001). Lock file uses stdlib os.OpenFile. No unapproved dependencies remain. |
| III. Go 1.20 Compatibility | PASS | golang.org/x/net pinned to v0.35.0 (last Go 1.20-compatible release, R-006). os.CreateTemp available since Go 1.16. All APIs verified against Go 1.20. |
| IV. Idiomatic Error Handling | PASS | All package functions return errors. Platform implementations return errors to caller. Only main() calls os.Exit(). Errors include context (path, hostname, server). |
| V. Simplicity and Single Purpose | PASS | Four focused packages with clear single responsibilities. No speculative abstractions. Dead code eliminated. Remains single-binary CLI with subcommand+flags interface. |
| VI. Test-Driven Development | PASS | All packages have _test.go files in structure. Filesystem and DNS accessed through interfaces (R-007) enabling fakes/stubs. TDD workflow documented in quickstart. |
Complexity Tracking
No constitution violations remain unjustified. All violations are addressed by spec requirements.