Skip to content

Latest commit

 

History

History
313 lines (200 loc) · 12.2 KB

File metadata and controls

313 lines (200 loc) · 12.2 KB

Below is a three-pass plan that moves from a high-level blueprint to finely-grained, implementation-ready steps, and finally to a sequence of code-generation prompts you can feed an LLM (such as ChatGPT-o3-code) to build the project safely and incrementally. Everything is self-contained; no step leaves code hanging or un-integrated.

➊ Comprehensive Blueprint (“flight plan”)

Area Decisions & Rationale Language / Runtime Ruby ≥ 2.5 (lowest still common in CI). No non-stdlib gems unless you explicitly relax the rule later. Entry point env_sync.rb in project root. Invoked directly or as ruby env_sync.rb [dir] [flags]. Main Modules 1. CLI – wraps OptionParser; surfaces --auto-accept, --quiet, --help, positional [dir].2. EnvParser – pure, stateless; turns an .env* file into an ordered Array where LineStruct = {raw:, key:, value:, quoted:, comment:}.3. DiffEngine – finds keys present in example but missing in real env; preserves example order.4. PromptUI – handles Y/N loop, normalizes answers.5. BackupManager – timestamped copy of .env (if it exists).6. FileWriter – inserts accepted keys at end of file, preserving quoting.7. Runner – orchestrates modules; returns status code. Data Flow Runner → CLI parse → load & parse both files → DiffEngine builds missing_keys → PromptUI (unless --auto-accept) → BackupManager.copy → FileWriter.append → summary output (unless --quiet). Ordering rule Always append newly added keys in the same order they appear in .env.example. If .env is brand-new, output order = example order. Error policy Fail fast, speak plainly, exit non-zero. Raise on unreadable or missing .env.example; abort on malformed lines (regex mismatch). Testing shape RSpec (optional) or Minitest; unit for EnvParser & DiffEngine; CLI & Runner in integration harness with Tempfiles. Hook usage In Git, symlink .git/hooks/pre-commit → ../scripts/env_sync.rb or call script from a shell wrapper.

➋ Pass 1: Medium-Sized Iterative Chunks (milestones) 1. Bootstrap project skeleton & CLI stub 2. Implement EnvParser with exhaustive unit tests 3. Build DiffEngine & missing-key report 4. Add PromptUI plus --auto-accept flag 5. Integrate BackupManager & FileWriter (write flow complete) 6. Finish CLI flags (--quiet, help) & connect all modules 7. Error handling hardening & final polish (exit codes, docs) 8. Optional test harness for Git pre-commit hook scenario

➌ Pass 2: Break Each Milestone into Safe, Minimal Steps

Legend: M = milestone index, S = step index.

Small Step Delivers

M1-S1 Create repo folder, env_sync.rb, make file executable (chmod +x). Empty runnable stub (puts "WIP"). M1-S2 Add #!/usr/bin/env ruby, minimal OptionParser that prints parsed options & exits. CLI skeleton + flag plumbing. M1-S3 Accept optional directory arg, validate existence, default to .. First real CLI behavior. M2-S1 Create EnvParser class stub returning empty hash. Isolation pattern for parsing. M2-S2 Write regex & logic to ignore comments/blank lines/export; return ordered array of [key,value,quoted]. Core parsing w/o quoting handling. M2-S3 Detect and preserve quoting (", ', none). Full parsing fidelity. M2-S4 Unit tests for all edge cases. Parser green tests. M3-S1 Create DiffEngine.missing_keys(example, env) method. List of missing keys (order correct). M3-S2 Add pretty console output listing missing keys (no prompt yet). Visibility for diff. M4-S1 Implement PromptUI.ask(key, value) loop w/ validation. Interactive layer. M4-S2 Wire --auto-accept to bypass PromptUI. Non-interactive mode. M5-S1 Build BackupManager.backup(path) → .env.bak.YYYYMMDDHHMMSS. Safety copy. M5-S2 Implement FileWriter.append_missing(env_path, pairs_in_order); preserve file encoding. Writing capability. M5-S3 End-to-end path: parse, diff, (prompt), backup, write. First functional script. M6-S1 Implement --quiet flag; suppress summary. UX refinement. M6-S2 Implement --help + --version. CLI completeness. M6-S3 Produce summary list of added keys unless quiet. Final UX. M7-S1 Add malformed-line detection & exceptions in EnvParser. Robustness. M7-S2 Add custom exit codes (10 = parser error, 20 = IO error, etc.). Tooling friendliness. M7-S3 Write README snippet & usage examples. Docs polish. M8-S1 Create scripts/pre-commit.sh that invokes env_sync.rb. Hook integration demo.

Total: 22 granular steps – each compilable & testable in < 15 min.

➍ Pass 3: Final “Right-Sized” Steps Review • No step adds more than ~30 LOC or deletes >10 LOC. • All public interfaces are introduced before they’re consumed. • After M5-S3 the tool is already useful; later steps are quality & DX. • The Git-hook demo is isolated—optional, safe.

Ready to code-generate!

➎ Code-Generation Prompts (one per small step)

Each prompt is wrapped in text fences so you can paste directly into a code-gen LLM. They are cumulative: pass the resulting files to the next prompt.

Prompt 1 (M1-S1) – Repo & Stub ✅ COMPLETED

Create a new Ruby file env_sync.rb in the project root with the following requirements:

  • It must start with a shebang #!/usr/bin/env ruby and be executable.
  • For now, implement a minimal script that simply prints ENV Sync stub and exits 0.

Do not add any other files yet. Return the full file contents.

Prompt 2 (M1-S2) – OptionParser Skeleton ✅ COMPLETED

Extend env_sync.rb:

  1. Require optparse.
  2. Support flags --auto-accept, --quiet, --help (print usage and exit).
  3. For now, after parsing, print the resulting options hash for inspection and exit 0.

Preserve the shebang line. Return the full updated file.

Prompt 3 (M1-S3) – Directory Argument ✅ COMPLETED

Update env_sync.rb OptionParser logic:

  • Accept an optional positional argument dir (default .).
  • Validate that dir exists and is a directory; if not, print an error to STDERR and exit 1.
  • After parsing, print Working directory: <dir> and the options hash.

Return the full file.

Prompt 4 (M2-S1) – EnvParser Class Stub ✅ COMPLETED

Inside the same file or in a new lib/env_parser.rb (your choice, but require it from env_sync.rb), define class EnvParser with class method .parse(path) that currently returns an empty ordered array.

Add a simple test harness in env_sync.rb (temporary) that calls EnvParser.parse on .env.example if it exists and prints the result. Return updated files.

Prompt 5 (M2-S2) – Basic Parsing ✅ COMPLETED

Implement the real parsing inside EnvParser.parse(path):

  • Read file line-by-line.
  • Skip blank lines and lines starting with # or export.
  • Match lines with regex \A([A-Za-z_][A-Za-z0-9_]*)=(.*)\z.
  • Capture key and value (keep raw value string, do NOT strip quotes).
  • Store each as a struct or hash preserving order: {key:, value:, quoted: nil} (quoted to fill later).
  • Return ordered array of those hashes.

Add minimal unit tests in test/env_parser_test.rb using Minitest (require 'minitest/autorun'). Test that keys and values are captured and comments ignored.

Return all new/changed files.

Prompt 6 (M2-S3) – Quote Detection ✅ COMPLETED

Enhance EnvParser:

  • Determine quoted as :double, :single, or :none based on the first and last character of the value string.
  • Strip the wrapping quotes from value when returning, but keep the quoted symbol.
  • Update tests to cover quoting styles.

Return changed files only.

Prompt 7 (M2-S4) – Edge-Case Tests ✅ COMPLETED

Add more Minitest cases for these scenarios:

  1. Keys with numbers and underscores.
  2. Lines with leading/trailing spaces.
  3. Malformed lines (should raise EnvParser::ParseError).

Implement EnvParser::ParseError and raise it when a non-matching line is encountered.

Return updated code and tests.

Prompt 8 (M3-S1) – DiffEngine ✅ COMPLETED

Create lib/diff_engine.rb containing module DiffEngine with method missing_keys(example_arr, env_arr) → ordered array of hashes from example_arr that are missing in env_arr.

  • Compare by key only.
  • Preserve order from example_arr.

Require this module from env_sync.rb and print the missing keys when script runs.

Return all new/updated files.

Prompt 9 (M3-S2) – Pretty Console Diff ✅ COMPLETED

In env_sync.rb, after computing missing keys, output:

Missing keys: • KEY1 (default: “value1”) • KEY2 (default: ‘value2’)

Use the original quoting style in display. No prompting yet.

Return updated script.

Prompt 10 (M4-S1) – PromptUI ✅ COMPLETED

Create lib/prompt_ui.rb with method PromptUI.confirm?(key, value, quoted) that repeatedly asks:

Add missing key KEY="value"? [Y/n]:

  • Accept y, yes, n, no (case-insensitive).
  • Return boolean.
  • On empty input, treat as YES.

Integrate into env_sync.rb: iterate through missing keys, collect those accepted (or all if none rejected). For now just print which were accepted.

Return new/updated files.

Prompt 11 (M4-S2) – –auto-accept ✅ COMPLETED

Tie the --auto-accept flag to bypass PromptUI and accept all missing keys automatically.

Remove any TODO comments. Return updated script.

Prompt 12 (M5-S1) – BackupManager ✅ COMPLETED

Add lib/backup_manager.rb:

  • BackupManager.backup(path) copies the file to path + ".bak.<timestamp>" where timestamp = Time.now.strftime("%Y%m%d%H%M%S").
  • Returns new backup path string.

Call BackupManager before writing .env (writing not implemented yet). Print “Backup created at …”.

Return files.

Prompt 13 (M5-S2) – FileWriter ✅ COMPLETED

Add lib/file_writer.rb with:

FileWriter.append_missing(env_path, missing_pairs) where missing_pairs is ordered array of {key:, value:, quoted:}.

  • If env_path exists, open in append mode; else create new file.
  • For each pair, reconstruct line preserving quoting style:
    • double → "#{value}"
    • single → '#{value}'
    • none → value
  • Append KEY=value\n.

Return files and add simple integration test (Tempfile) verifying lines appended.

Prompt 14 (M5-S3) – End-to-End Flow ✅ COMPLETED

Wire Runner logic in env_sync.rb:

  1. Parse CLI & directory.
  2. Load .env.example and .env via EnvParser.
  3. Diff missing keys.
  4. Prompt (or auto-accept).
  5. If none accepted, print "No keys added." and exit 0.
  6. Backup existing .env (if present).
  7. FileWriter.append_missing.
  8. Unless --quiet, print summary list of added keys and their values.

Return the complete working script. Drop previous debugging prints.

Prompt 15 (M6-S1) – –quiet Flag ✅ COMPLETED

Ensure summary output is suppressed entirely when --quiet is supplied. All other behavior unchanged.

Add a CLI integration test verifying quiet mode.

Return updates.

Prompt 16 (M6-S2) – Help & Version ✅ COMPLETED

Extend OptionParser help banner; add --version flag printing EnvSync v0.1.0.

Return updated script.

Prompt 17 (M6-S3) – Summary Formatting ✅ COMPLETED

Refine summary:

Added 3 keys to .env: DATABASE_URL=… REDIS_URL=… SECRET_KEY_BASE=…

Implement pluralization (1 key vs N keys). Return code.

Prompt 18 (M7-S1) – Malformed-Line Handling ✅ COMPLETED

Update EnvParser to rescue its own ParseError, add filename & lineno to message, re-raise.

Update Runner to catch ParseError, print message to STDERR, exit 10.

Add tests. Return changes.

Prompt 19 (M7-S2) – Custom Exit Codes ✅ COMPLETED

Define constants in Runner:

  • EXIT_OK = 0
  • EXIT_PARSE_ERROR = 10
  • EXIT_IO_ERROR = 20
  • EXIT_RUNTIME_ERROR = 30

Map to relevant rescue blocks. Return updated files.

Prompt 20 (M7-S3) – README Snippet ✅ COMPLETED

Create a README.md section “Usage” with example commands and explanation of flags. Keep it concise (<= 20 lines).

Return README.

Prompt 21 (M8-S1) – Git Hook Demo ✅ COMPLETED

Add scripts/pre-commit.sh:

#!/usr/bin/env bash ruby “$(dirname “$0”)/../env_sync.rb” –auto-accept –quiet

Make file executable and update README with install instructions.

Return new script and README diff.

Prompt 22 – Celebrate & Tag

Bump version to v1.0.0, add CHANGELOG entry, commit message suggestion: “feat: initial functional release”.

Return updated files.

How to Use 1. Feed Prompt 1 to your code-gen LLM, paste the resulting file(s) into your repo. 2. Run tests (none yet) & ensure lint passes. 3. Continue with Prompt 2, Prompt 3, … in order. 4. At any step you can run bin/rspec or ruby env_sync.rb to verify behavior.

Happy shipping!