Draft
Conversation
…ag (#49) Both hcl2tojson and jsontohcl2 now accept multiple positional PATH arguments. Output is specified via -o/--output instead of a positional arg. Shell glob expansion (e.g. hcl2tojson *.tf -o out/) works naturally. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rddimon
approved these changes
Mar 16, 2026
Make hcl2tojson and jsontohcl2 agent-friendly by adding structured error handling, distinct exit codes, NDJSON streaming, glob expansion, and convention alignment with hq. Key additions: --ndjson with __file__ provenance, --compact with TTY auto-detection, --only/--exclude block filtering, --fields projection, --diff/--dry-run/--fragment for jsontohcl2, --quiet mode, stdin-first defaults, and shared infrastructure in cli/helpers.py. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…code, path collisions
- Fix --ndjson crash when reading from stdin (open("-") → sys.stdin)
- Make --fragment strip __is_block__ markers so block-structured JSON
becomes flat attributes, differentiating it from --dry-run
- Use dedicated EXIT_DIFF=5 for --diff mode instead of overloading
EXIT_PARTIAL=1 (JSON parse error)
- Preserve relative path structure in _convert_multiple_files to avoid
basename collisions (dir1/main.tf + dir2/main.tf → out/dir1/ + out/dir2/)
- Fix _project_fields list filter to preserve falsy scalars (0, False, "")
- Fix --compact help text (was incorrectly described as alias for --json-indent 0)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Concatenated pretty-printed JSON documents are neither valid JSON nor valid NDJSON. Instead of silently producing unparseable output, require the user to explicitly choose --ndjson (streaming) or -o <dir> (files). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Warnings (bugs): - jsontohcl2 dir/ without -o now errors instead of crashing with RuntimeError - _convert_multiple_files normalizes paths to absolute before commonpath - Non-NDJSON multi-file -s correctly exits 1 on partial failures - _convert_directory uses os.makedirs for nested output paths Info (improvements): - --diff/--dry-run/--fragment are now mutually exclusive in jsontohcl2 - NDJSON mode emits structured JSON errors to stderr - stdout output is buffered when skip=True to prevent partial writes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… gaps - Replace dead `except JSON_SKIPPABLE` with explicit `except UnicodeDecodeError` since JSONDecodeError is already caught above (json_to_hcl.py) - Validate --fragment input is a dict, not an array or scalar - Warn when --ndjson --json-indent are combined (indent is ignored) - Add 8 tests covering: fragment __is_block__ stripping, fragment non-dict rejection, structure error exit code 2, --diff edge cases (nonexistent original, stdin), --ndjson stdin, --ndjson all-fail-with-skip, --exclude with multiple comma-separated types Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix CLAUDE.md and docs claiming `hcl2tojson dir/` works without --ndjson - Route stdin through _convert_single_file so --skip flag is honored - Guard _convert_multiple_files commonpath against identical file paths - Replace os.mkdir with os.makedirs in _convert_directory - Remove redundant sorted() in _collect_files - Reject stdin (-) in multi-file mode with clear error message - Add 7 tests covering stdin+skip, multi-file+stdin, duplicate paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
rddimon
approved these changes
Apr 1, 2026
- Single-file skip now exits 1 (EXIT_PARTIAL) instead of silent 0
- stdin honors -o output path instead of silently ignoring it
- _stream_ndjson tracks IO vs parse errors for correct all-fail exit code
- NDJSON skips empty records after --only/--exclude filtering
- --compact uses separators=(",",":") for truly compact JSON; NDJSON too
- Add _install_sigpipe_handler() for clean pipe-to-head behavior
- Remove dead _convert_stdin (replaced by _convert_single_stream)
- Merge redundant stdin/isfile branches in both CLIs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
50 tests across 12 classes verify end-to-end behavior that unit tests cannot: real exit codes, stdout/stderr separation, stdin piping, pipe composition (hcl2tojson | jsontohcl2 round-trips against golden fixtures), NDJSON structured errors, basename collision handling, stdout buffering with skip, stdin-to-output-file, compact separator correctness, NDJSON IO vs parse error exit codes, and TTY vs pipe default behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ff mode --fields incorrectly preserved leaf lists (e.g. regions, tags) whose keys were not in the field set, defeating the purpose of token-reduction for agent pipelines. Now only recurses into structural containers (dicts and lists-of-dicts for block hierarchy). jsontohcl2 --dry-run and normal mode silently accepted non-dict JSON (arrays, scalars) producing garbled output with exit 0. load_python() now raises TypeError, caught by the existing handler to exit 2. New --semantic-diff ORIGINAL mode compares parsed HCL dict against JSON dict using diff_dicts(), completely ignoring formatting differences (alignment, trailing commas, comments, array layout). --diff-json flag outputs structured JSON for machine consumption. Documented --fragment inner-quote string convention in CLI help, epilog, and docs to prevent the common first-use mistake of passing plain JSON strings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Make
hcl2tojsonandjsontohcl2agent-friendly by applying the conventions established inhq.Shared infrastructure (
cli/helpers.py)_expand_file_args()moved fromhq.py— tool-side glob expansion for all three CLIs_error()structured error formatter (JSON or plain text to stderr)_collect_files()directory walker with configurable extensionsquietparameter on all conversion helpershcl2tojson— new featureshcl2tojson dir/streams NDJSON without requiring-o--ndjson— one JSON object per line with__file__provenance for multi-file--compact— single-line JSON; auto-compact when stdout is not a TTY--only/--exclude— block type filtering (e.g.--only resource,module)--fields— field projection (e.g.--fields cpu,memory)--quiet/-q— suppress stderr progress outputjq)'modules/**/*.tf'works even when quotedjsontohcl2— new features--diff ORIGINAL— unified diff preview against an existing.tffile--semantic-diff ORIGINAL— structural diff that ignores formatting (alignment, commas, comments, array layout); usesdiff_dicts()fromhcl2/query/diff.py--diff-json— machine-readable JSON output for both diff modes--dry-run— convert and print without writing files--fragment— generate attribute snippets from JSON fragments--quiet/-q, stdin-first defaults, glob expansionBug fixes
--fieldslist leak — leaf lists (e.g.regions,tags) whose keys were not in the field set leaked through; now only recurses into structural containers (dicts and lists-of-dicts)jsontohcl2 --dry-runand normal mode silently accepted arrays/scalars with garbled output;load_python()now raisesTypeError(exit 2)--fragmentstring format — documented the inner-quote convention prominently in CLI help, epilog, and docsConvention alignment
--quiet, stdin-firstCLAUDE.mdanddocs/01_getting_started.mdupdatedCaused by #49
Test plan
--fieldsno longer leaks leaf lists (unit + integration tests)--semantic-diffexits 0 for round-trips across all golden fixtures--semantic-diffdetects value changes with exit 5--semantic-diffignores formatting-only differences--diff-jsonproduces valid structured JSON outputhcl2tojson dir/streams NDJSON to stdout with__file__hcl2tojson --ndjson --only resource 'modules/**/*.tf'echo 'x = 1' | hcl2tojsonworks without-jsontohcl2 --semantic-diff original.tf modified.jsonshows only value changes🤖 Generated with Claude Code