Implement beamtalk fmt CLI command (BT-978)#1020
Conversation
- Add `beamtalk fmt <path>...` to format .bt files in-place via the unparser - Add `beamtalk fmt-check <path>...` to print unified diffs and exit non-zero for unformatted files - Skip files with parse errors to prevent corruption - Add `similar` crate for unified diff generation - Add Justfile targets: fmt-beamtalk, fmt-check-beamtalk - Update fmt/fmt-check aggregate targets to include Beamtalk formatting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds workspace dependency Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as beamtalk-cli
participant FS as File System
participant Parser as Parser/Unparser
User->>CLI: beamtalk-cli fmt[ --check] [paths]
CLI->>FS: collect .bt files from paths
FS-->>CLI: file list
loop for each file
CLI->>FS: read file content
FS-->>CLI: content
CLI->>Parser: parse(content)
Parser-->>CLI: AST or parse-error
alt parsed
CLI->>Parser: unparse(AST)
Parser-->>CLI: formatted content
CLI->>CLI: compare original vs formatted
alt check mode & differs
CLI->>User: print unified diff
else write mode & differs
CLI->>FS: write formatted content
FS-->>CLI: write result
end
else parse-error
CLI->>User: warn and mark skipped
end
end
alt check mode with diffs or skipped files
CLI->>User: exit non-zero
else
CLI->>User: exit zero
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/beamtalk-cli/Cargo.toml (1)
59-61: Redundanttempfiledev-dependency.
tempfile = "3.14"is already declared as a regular dependency on line 51. The dev-dependency declaration is unnecessary since regular dependencies are available in tests.🔧 Proposed fix
[dev-dependencies] serial_test = "3" -tempfile = "3.14"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/beamtalk-cli/Cargo.toml` around lines 59 - 61, Remove the redundant dev-dependency entry for tempfile by deleting the `tempfile = "3.14"` line from the [dev-dependencies] section in Cargo.toml (the crate already lists tempfile as a normal dependency earlier), leaving only `serial_test = "3"` under [dev-dependencies].
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/beamtalk-cli/Cargo.toml`:
- Around line 59-61: Remove the redundant dev-dependency entry for tempfile by
deleting the `tempfile = "3.14"` line from the [dev-dependencies] section in
Cargo.toml (the crate already lists tempfile as a normal dependency earlier),
leaving only `serial_test = "3"` under [dev-dependencies].
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
Cargo.tomlJustfilecrates/beamtalk-cli/Cargo.tomlcrates/beamtalk-cli/src/commands/fmt.rscrates/beamtalk-cli/src/commands/mod.rscrates/beamtalk-cli/src/main.rs
There was a problem hiding this comment.
Pull request overview
Adds first-party Beamtalk source formatting support to the beamtalk CLI by introducing fmt (in-place) and fmt-check (diff + non-zero exit) commands, and wiring them into repo-wide formatting targets.
Changes:
- Introduce
beamtalk fmt/beamtalk fmt-checksubcommands and dispatch them from the CLI. - Implement formatting + unified-diff checking via parse → unparse round-trip (skipping parse-error files).
- Add
similarfor diff output, plus Justfile targets to include Beamtalk formatting infmt/fmt-check.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/beamtalk-cli/src/main.rs | Adds new CLI subcommands and routes them to the formatter implementation. |
| crates/beamtalk-cli/src/commands/mod.rs | Exposes the new fmt command module. |
| crates/beamtalk-cli/src/commands/fmt.rs | Implements format / format-check logic and adds unit tests for core behaviors. |
| crates/beamtalk-cli/Cargo.toml | Adds dependency needed for unified diff output and updates dev deps. |
| Justfile | Adds fmt targets for Beamtalk and includes them in aggregate fmt/fmt-check. |
| Cargo.toml | Adds similar to workspace dependencies for shared versioning. |
- Remove redundant tempfile dev-dependency (already in [dependencies]) - Use --bin beamtalk in Justfile fmt-beamtalk/fmt-check-beamtalk targets for consistency with all other Justfile targets - Deduplicate source_files when overlapping paths are given, preventing duplicate diff output and incorrect changed-file counts - Exit non-zero in fmt-check when files are skipped due to parse errors (previously could exit 0 while not verifying all files) - Add 2 new tests: deduplication and parse-error behavior Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/beamtalk-cli/src/commands/fmt.rs (1)
37-61: Make file processing order deterministic inrun_fmt.
fmt-checkoutput order can vary across filesystems because collected paths are not sorted. Sorting once after collection makes diffs and summary behavior stable across environments.♻️ Suggested change
for path in paths { let source_path = Utf8PathBuf::from(path); @@ } } + source_files.sort_unstable_by(|a, b| a.as_str().cmp(b.as_str())); + if source_files.is_empty() { miette::bail!("No .bt source files found"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/beamtalk-cli/src/commands/fmt.rs` around lines 37 - 61, The collection of source files in run_fmt is nondeterministic because paths (and files from collect_source_files_from_dir) are pushed in iteration order; after the loop that builds source_files (using variables paths, source_path, seen, collect_source_files_from_dir), sort source_files once before the empty check/return so subsequent fmt-check output is stable across filesystems—e.g., call a stable sort on Utf8PathBuf (by string representation) to make order deterministic before using source_files.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/beamtalk-cli/src/commands/fmt.rs`:
- Around line 37-61: The collection of source files in run_fmt is
nondeterministic because paths (and files from collect_source_files_from_dir)
are pushed in iteration order; after the loop that builds source_files (using
variables paths, source_path, seen, collect_source_files_from_dir), sort
source_files once before the empty check/return so subsequent fmt-check output
is stable across filesystems—e.g., call a stable sort on Utf8PathBuf (by string
representation) to make order deterministic before using source_files.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
Justfilecrates/beamtalk-cli/Cargo.tomlcrates/beamtalk-cli/src/commands/fmt.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/beamtalk-cli/Cargo.toml
Add a hidden --check flag to beamtalk fmt that emits a clear error message directing users to `beamtalk fmt-check` instead of the misleading clap tip about `-- --check`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix comment doubling bug: `// Copyright` was becoming `// // Copyright` because the lexer stored full `// text` in Trivia::LineComment, and the unparser added `// ` again when rendering - Parser now strips `"// "` (with space) or `"//"` from LineComment trivia before constructing Comment objects, matching how collect_doc_comment already handles doc comments - Same fix applied to block comments (strip `/* */` delimiters) - Update all parser snapshots to reflect `content` without the `//` prefix - Add idempotency tests for hanoi.bt, hello.bt, point.bt via include_str! - Remove fmt-beamtalk/fmt-check-beamtalk from Justfile fmt/fmt-check aggregates until the stdlib and tests directories are formatted (BT-981) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
crates/beamtalk-core/src/source_analysis/parser/mod.rs (1)
677-685: Deduplicate line-comment normalization to avoid leading/trailing drift.The same
//-prefix stripping logic is implemented twice. A shared helper will keep behavior consistent as comment rules evolve.♻️ Proposed refactor
+ fn strip_line_comment_prefix(s: &str) -> &str { + s.strip_prefix("// ") + .or_else(|| s.strip_prefix("//")) + .unwrap_or(s) + } + pub(super) fn collect_comment_attachment(&self) -> CommentAttachment { @@ super::Trivia::LineComment(text) => { - let s = text.as_str(); - let content = s - .strip_prefix("// ") - .or_else(|| s.strip_prefix("//")) - .unwrap_or(s); + let content = Self::strip_line_comment_prefix(text.as_str()); leading.push(Comment::line(content, token_span)); } @@ for trivia in last_token.trailing_trivia() { if let super::Trivia::LineComment(text) = trivia { - let s = text.as_str(); - let content = s - .strip_prefix("// ") - .or_else(|| s.strip_prefix("//")) - .unwrap_or(s); + let content = Self::strip_line_comment_prefix(text.as_str()); return Some(Comment::line(content, last_token.span())); } }Also applies to: 724-729
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/beamtalk-core/src/source_analysis/parser/mod.rs` around lines 677 - 685, The line-comment prefix stripping logic is duplicated; create a small helper function (e.g., fn strip_line_comment_prefix(s: &str) -> &str) and replace the inline .strip_prefix("// ").or_else(|| s.strip_prefix("//")).unwrap_or(s) usages with calls to that helper where Comment::line(...) is constructed (the two duplicated sites around the leading.push(Comment::line(...)) occurrences and the other block at the 724-729 region), ensuring the helper returns the original slice when no prefix matches so behavior remains identical to the current collect_doc_comment/Comment::line usage.crates/beamtalk-core/src/unparse/mod.rs (1)
1313-1319: Harden idempotency assertions by requiring clean parses.
assert_idempotentcan still pass when parsing recovers from errors. Add explicit empty-diagnostics checks for source and first-pass output so this suite validates canonical valid formatting behavior.🧪 Proposed test hardening
#[track_caller] fn assert_idempotent(source: &str) { - let pass1 = unparse_module(&parse_source(source)); - let pass2 = unparse_module(&parse_source(&pass1)); + use crate::source_analysis::{lex_with_eof, parse}; + + let (module1, diags1) = parse(lex_with_eof(source)); + assert!( + diags1.is_empty(), + "expected clean parse for idempotency source, got: {diags1:?}" + ); + let pass1 = unparse_module(&module1); + + let (module2, diags2) = parse(lex_with_eof(&pass1)); + assert!( + diags2.is_empty(), + "first-pass output should parse cleanly, got: {diags2:?}" + ); + let pass2 = unparse_module(&module2); assert_eq!( pass1, pass2, "unparser is not idempotent for source:\n{source}\n\npass1:\n{pass1}\n\npass2:\n{pass2}" ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/beamtalk-core/src/unparse/mod.rs` around lines 1313 - 1319, The idempotency helper assert_idempotent should also verify that parsing produced no recoverable errors: after calling parse_source(source) and parse_source(&pass1) (used to create pass1 and pass2 by unparse_module), assert that both parse results have empty diagnostics (e.g., diagnostics.is_empty()) before comparing pass1 and pass2; update assert_idempotent to capture the parse results into local variables (e.g., let parsed1 = parse_source(source); let pass1 = unparse_module(&parsed1); let parsed2 = parse_source(&pass1); let pass2 = unparse_module(&parsed2)) and add assertions that parsed1.diagnostics.is_empty() and parsed2.diagnostics.is_empty() so the test only passes for clean parses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/beamtalk-core/src/source_analysis/parser/mod.rs`:
- Around line 677-685: The line-comment prefix stripping logic is duplicated;
create a small helper function (e.g., fn strip_line_comment_prefix(s: &str) ->
&str) and replace the inline .strip_prefix("// ").or_else(||
s.strip_prefix("//")).unwrap_or(s) usages with calls to that helper where
Comment::line(...) is constructed (the two duplicated sites around the
leading.push(Comment::line(...)) occurrences and the other block at the 724-729
region), ensuring the helper returns the original slice when no prefix matches
so behavior remains identical to the current collect_doc_comment/Comment::line
usage.
In `@crates/beamtalk-core/src/unparse/mod.rs`:
- Around line 1313-1319: The idempotency helper assert_idempotent should also
verify that parsing produced no recoverable errors: after calling
parse_source(source) and parse_source(&pass1) (used to create pass1 and pass2 by
unparse_module), assert that both parse results have empty diagnostics (e.g.,
diagnostics.is_empty()) before comparing pass1 and pass2; update
assert_idempotent to capture the parse results into local variables (e.g., let
parsed1 = parse_source(source); let pass1 = unparse_module(&parsed1); let
parsed2 = parse_source(&pass1); let pass2 = unparse_module(&parsed2)) and add
assertions that parsed1.diagnostics.is_empty() and
parsed2.diagnostics.is_empty() so the test only passes for clean parses.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (75)
test-package-compiler/tests/snapshots/compiler_tests__abstract_class_spawn_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__actor_spawn_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__actor_spawn_with_args_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__actor_state_mutation_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__async_keyword_message_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__async_unary_message_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__async_with_await_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__binary_operators_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__blocks_no_args_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__boundary_deeply_nested_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__boundary_long_identifiers_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__boundary_mixed_errors_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__boundary_unicode_identifiers_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__cascade_complex_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__cascades_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__character_literals_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__class_definition_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__class_methods_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__comment_handling_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__control_flow_mutations_errors_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__control_flow_mutations_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__empty_blocks_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__empty_method_body_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__error_message_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__error_recovery_invalid_syntax_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__error_recovery_malformed_message_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__error_recovery_unterminated_string_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__expect_directive_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__future_pattern_matching_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__future_string_interpolation_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__hello_world_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__intrinsic_keyword_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__map_literals_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__method_lookup_codegen.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__method_lookup_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__multi_keyword_complex_args_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__nested_blocks_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__nested_keyword_messages_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__sealed_class_violation_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__sealed_method_override_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__stdlib_class_dictionary_codegen.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__stdlib_class_dictionary_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__stdlib_class_list_codegen.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__stdlib_class_list_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__stdlib_class_set_codegen.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__stdlib_class_set_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__stdlib_class_tuple_codegen.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__stdlib_class_tuple_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__string_operations_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__typed_class_warnings_codegen.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__typed_class_warnings_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__typed_methods_codegen.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__typed_methods_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__typed_value_type_codegen.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__typed_value_type_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__unary_operators_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__unicode_string_literals_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__value_type_multi_expr_method_codegen.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__value_type_multi_expr_method_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__value_type_param_collision_codegen.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__value_type_param_collision_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__while_true_simple_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__whitespace_handling_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__workspace_binding_cascade_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__workspace_binding_send_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__ws_stdlib_array_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__ws_stdlib_block_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__ws_stdlib_boolean_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__ws_stdlib_dictionary_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__ws_stdlib_integer_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__ws_stdlib_list_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__ws_stdlib_nil_object_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__ws_stdlib_nil_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__ws_stdlib_set_parser.snapis excluded by!**/*.snaptest-package-compiler/tests/snapshots/compiler_tests__ws_stdlib_string_parser.snapis excluded by!**/*.snap
📒 Files selected for processing (3)
Justfilecrates/beamtalk-core/src/source_analysis/parser/mod.rscrates/beamtalk-core/src/unparse/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- Justfile
… BT-978
- Fix unparse_literal for String to emit double-quoted strings ("...")
instead of single-quoted ('...') — single-quoted strings are not
supported by the parser, causing idempotency failures on pass2
- Update two unparse unit tests to expect double-quoted output
- Accept all parser snapshots reflecting correct comment content:
- Comments now stored without delimiters: "Copyright 2026..." not
"// Copyright 2026..." and not " Copyright 2026..."
- The `// ` prefix (including trailing space) is stripped correctly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Implements
beamtalk fmtandbeamtalk fmt-checkas top-level CLI subcommands for formatting Beamtalk source files.beamtalk fmt <path>...— parses.btfiles, runs the AST unparser (from BT-977), and writes formatted output back in-placebeamtalk fmt-check <path>...— prints a unified diff for every file that would change and exits non-zero if any files need reformattingbeamtalk fmt stdlib/ tests/)Justfile targets
fmt-beamtalk/fmt-check-beamtalk— new targets following the established patternfmt/fmt-checkaggregate targets updated to include Beamtalk formattingTests
5 unit tests covering:
fmt(fmt(source)) == fmt(source)Linear: https://linear.app/beamtalk/issue/BT-978
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes / Behavior
Tests
Chores