Skip to content

feat(debugger): add BAML DAP, Cursor integration, and stepping/hover fixes#3164

Open
antoniosarosi wants to merge 2 commits intocanaryfrom
antonio/cursor-debugger-lsp
Open

feat(debugger): add BAML DAP, Cursor integration, and stepping/hover fixes#3164
antoniosarosi wants to merge 2 commits intocanaryfrom
antonio/cursor-debugger-lsp

Conversation

@antoniosarosi
Copy link
Contributor

@antoniosarosi antoniosarosi commented Feb 23, 2026

Summary

  • add a native BAML Debug Adapter Protocol server to baml-cli (baml-cli dap) with launch, breakpoints, stack trace, scopes, variables, continue/pause/step commands
  • add VM debugger primitives and tests (bex_vm::debugger, breakpoint/step/step-out/pause coverage)
  • wire Cursor extension debug adapter factory and breakpoint contribution updates
  • add debugger_smoke/baml_src/main.baml + VSCode launch entry for quick manual smoke testing
  • improve DAP stepping behavior to avoid same-site breakpoint stickiness and collapse forward same-line sequence-point noise
  • ensure debugger bytecode uses OptLevel::Zero via ProjectDatabase::get_debug_bytecode()
  • fix sequence-point emission so pending statement/terminator sequence points are not clobbered during operand attribution
  • LSP quality-of-life fixes: reduced default log noise, non-blocking project discovery, safer playground behavior when playground env is missing
  • update stow policy to allow baml_cli to depend on bex_vm / bex_vm_types for DAP runtime integration
    --

Notes

Add this to .vscode/settings.json:

"baml.cliPath": "/media/tony/WesternDigitalNvmeSsd/Code/baml/baml_language/target/debug/baml-cli"

Validation

  • cargo test -p bex_vm --test debugger
  • cargo test -p baml_compiler_emit while_loop_unoptimized_keeps_condition_and_body_sequence_points
  • cargo build -p baml_cli
  • pnpm --filter ./app-vscode-ext test:run
  • manual DAP smoke against debugger_smoke/baml_src/main.baml confirms loop stepping sequence (7 -> 5 -> 6 -> 7 ...) and verified breakpoints

Notes

  • local machine-specific .vscode/settings.json (baml.cliPath) was intentionally left out of this PR.

Summary by CodeRabbit

Release Notes

  • New Features

    • VS Code: full BAML debug integration (adapter, launch config, breakpoints, stepping, pause, variable & stack inspection).
    • CLI: built-in Debug Adapter support to run/debug BAML programs.
    • VM: runtime debugger API enabling breakpoints and step controls.
    • Project: compile with selectable optimization levels and a debug build mode.
  • Bug Fixes / Improvements

    • Playground env falls back to local env on timeouts/errors.
  • Tests

    • Added debugger integration tests and a debugger smoke project.

@vercel
Copy link

vercel bot commented Feb 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Feb 23, 2026 6:41pm
promptfiddle Ready Ready Preview, Comment Feb 23, 2026 6:41pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

Implements DAP-based debugging: adds a DAP server to the CLI, VM debugger primitives and stops, preserves debug sequence points in bytecode, VSCode extension wiring to launch the adapter, LSP/playground and project-discovery updates, tests, and supporting configuration and smoke fixtures.

Changes

Cohort / File(s) Summary
DAP CLI
baml_language/crates/baml_cli/src/dap.rs, baml_language/crates/baml_cli/src/lib.rs, baml_language/crates/baml_cli/src/commands.rs, baml_language/crates/baml_cli/Cargo.toml
Adds a full DAP server module, CLI command wiring, and new workspace dependencies; implements message framing, request handlers (initialize, launch, breakpoints, threads, stackTrace, scopes, variables, continue/step/pause, disconnect), and DebugAdapterArgs run entry.
VM Debugger Core
baml_language/crates/bex_vm/src/vm.rs, baml_language/crates/bex_vm/src/debugger.rs, baml_language/crates/bex_vm/src/lib.rs
Introduces debugger types, VM debug state/fields, DebugStop in exec flow, public APIs to set breakpoints and control stepping/pause/continue, stack/locals inspection, and re-exports of debugger types.
Bytecode & Compiler
baml_language/crates/baml_compiler_emit/src/emit.rs, baml_language/crates/baml_compiler_emit/tests/while_loops.rs, baml_language/crates/baml_project/src/db.rs
Preserves pending sequence-point boundaries during emission; adds opt-aware bytecode generation APIs (get_bytecode_with_opt, get_debug_bytecode) and tests ensuring sequence points remain in unoptimized code.
VSCode Extension
typescript2/app-vscode-ext/src/debugger.ts, typescript2/app-vscode-ext/src/extension.ts, typescript2/app-vscode-ext/src/__tests__/debugger.test.ts, typescript2/app-vscode-ext/package.json, typescript2/app-vscode-ext/tsup.config.ts
Adds DebugAdapter factory and command builder, registers debug descriptor for type "baml", updates package.json contributions (debugger, initialConfiguration, breakpoints), tests adapter command, and adjusts bundler externals.
LSP / Playground / Discovery
baml_language/crates/baml_lsp_server/src/lib.rs, baml_language/crates/baml_lsp_server/src/playground_sender.rs, baml_language/crates/baml_lsp_server/src/playground_env.rs, baml_language/crates/bex_project/src/bex_lsp/multi_project/notification.rs
Adds playground_enabled flag and plumbing, local-env fallbacks for playground env requests, guards for misconfigured playground, and async workspace project discovery on LSP initialization.
Runtime & Tooling Adjustments
baml_language/crates/bex_engine/src/lib.rs, baml_language/crates/tools_onionskin/src/compiler.rs, baml_language/crates/bex_vm/tests/debugger.rs, baml_language/crates/bex_tests/src/vm.rs
Handles VmExecState::DebugStop in engine and runner (emit error or mapped behavior), adds comprehensive VM debugger tests, and treats DebugStop as no-op in legacy VM test harness where appropriate.
Config, Smoke Test & Editor
.vscode/launch.json, .vscode/settings.json, debugger_smoke/baml_src/main.baml, baml_language/stow.toml
Adds VSCode launch config and CLI path setting, smoke BAML program for debugging, and updates stow rules to allow baml_cli dependencies for DAP integration.

Sequence Diagram(s)

sequenceDiagram
    participant User as VSCode User
    participant Ext as VSCode Extension
    participant DAP as DAP Server (baml_cli)
    participant DB as ProjectDB
    participant VM as BexVm

    User->>Ext: Start debug (launch config)
    Ext->>DAP: Spawn CLI with "dap" arg
    Ext->>DAP: send initialize
    DAP->>Ext: initialized event
    Ext->>DAP: send launch
    DAP->>DB: load project files & get_debug_bytecode
    DAP->>VM: create VM & load program
    Ext->>DAP: setBreakpoints
    DAP->>VM: debug_set_breakpoints(...)
    Ext->>DAP: configurationDone / continue
    DAP->>VM: start exec()
    VM->>VM: execute instructions (check debug_try_stop)
    alt stop triggered (breakpoint/step/pause)
        VM->>DAP: emit DebugStop
        DAP->>Ext: stopped event
        Ext->>User: show stopped location
        Ext->>DAP: stackTrace / scopes / variables
        DAP->>VM: debug_stack_frames / debug_frame_locals
        Ext->>User: user issues continue/step
        Ext->>DAP: next/stepIn/continue
        DAP->>VM: debug_step_* or debug_continue
        DAP->>VM: resume exec()
    end
    VM->>DAP: execution complete
    DAP->>Ext: terminated event
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding a BAML DAP (Debug Adapter Protocol), Cursor integration, and stepping/hover fixes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch antonio/cursor-debugger-lsp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 23, 2026

Merging this PR will not alter performance

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

✅ 15 untouched benchmarks
⏩ 91 skipped benchmarks1


Comparing antonio/cursor-debugger-lsp (ebf0f85) with canary (6133e3f)

Open in CodSpeed

Footnotes

  1. 91 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions
Copy link

github-actions bot commented Feb 23, 2026

Binary size checks passed

7 passed

Artifact Platform Gzip Baseline Delta Status
bridge_cffi Linux 3.8 MB 4.0 MB -143.8 KB (-3.6%) OK
bridge_cffi-stripped Linux 2.3 MB 2.1 MB +176.4 KB (+8.2%) OK
bridge_cffi macOS 3.1 MB 3.3 MB -149.4 KB (-4.6%) OK
bridge_cffi-stripped macOS 1.9 MB 1.7 MB +144.0 KB (+8.4%) OK
bridge_cffi Windows 3.1 MB 3.3 MB -148.6 KB (-4.6%) OK
bridge_cffi-stripped Windows 1.9 MB 1.8 MB +152.3 KB (+8.6%) OK
bridge_wasm WASM 1.8 MB 1.7 MB +48.7 KB (+2.8%) OK

Generated by cargo size-gate · workflow run

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 21

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
baml_language/crates/baml_lsp_server/src/lib.rs (1)

125-135: 🧹 Nitpick | 🔵 Trivial

Port is bound even when the playground is disabled.

playground_enabled is computed at Line 125, but the port-picking at Line 129 runs unconditionally. When the playground is disabled, the bound TcpListener is handed to playground_server::run, which presumably returns an error mentioning the env vars (caught at Lines 170-173 and logged at info). This works but unnecessarily binds a TCP port.

Consider short-circuiting:

♻️ Optional: skip port binding when playground is disabled
     let playground_enabled = std::env::var("BAML_PLAYGROUND_DEV_PORT").is_ok()
         || std::env::var("BAML_PLAYGROUND_DIR").is_ok();
 
-    let (playground_listener, playground_port): (Option<TcpListener>, u16) =
-        match tokio_runtime.block_on(playground_server::pick_port(3700, 100)) {
-            Ok((listener, port)) => (Some(listener), port),
-            Err(e) => {
-                tracing::error!("Could not find playground port: {e}");
-                (None, 0)
-            }
-        };
+    let (playground_listener, playground_port): (Option<TcpListener>, u16) = if playground_enabled {
+        match tokio_runtime.block_on(playground_server::pick_port(3700, 100)) {
+            Ok((listener, port)) => (Some(listener), port),
+            Err(e) => {
+                tracing::error!("Could not find playground port: {e}");
+                (None, 0)
+            }
+        }
+    } else {
+        tracing::info!("Playground disabled (BAML_PLAYGROUND_DEV_PORT / BAML_PLAYGROUND_DIR not set)");
+        (None, 0)
+    };

Also applies to: 163-178

baml_language/crates/baml_cli/src/commands.rs (1)

115-139: 🧹 Nitpick | 🔵 Trivial

Minor: duplicated error-handling arm.

The DebugAdapter and LanguageServer match arms are identical. Consider extracting a small helper to reduce duplication as more server-like subcommands are added.

Optional refactor
 impl RuntimeCli {
+    fn run_server_command(run: impl FnOnce() -> Result<()>) -> Result<crate::ExitCode> {
+        match run() {
+            Ok(()) => Ok(crate::ExitCode::Success),
+            Err(e) => {
+                #[allow(clippy::print_stderr)]
+                {
+                    eprintln!("Error: {e}");
+                }
+                Ok(crate::ExitCode::Other)
+            }
+        }
+    }
+
     pub fn run(&self) -> Result<crate::ExitCode> {
         match &self.command {
-            Commands::LanguageServer(args) => match args.run() {
-                Ok(()) => Ok(crate::ExitCode::Success),
-                Err(e) => {
-                    #[allow(clippy::print_stderr)]
-                    {
-                        eprintln!("Error: {e}");
-                    }
-                    Ok(crate::ExitCode::Other)
-                }
-            },
-            Commands::DebugAdapter(args) => match args.run() {
-                Ok(()) => Ok(crate::ExitCode::Success),
-                Err(e) => {
-                    #[allow(clippy::print_stderr)]
-                    {
-                        eprintln!("Error: {e}");
-                    }
-                    Ok(crate::ExitCode::Other)
-                }
-            },
+            Commands::LanguageServer(args) => Self::run_server_command(|| args.run()),
+            Commands::DebugAdapter(args) => Self::run_server_command(|| args.run()),
             Commands::Format(args) => args.run(),
         }
     }

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6133e3f and 616ed0c.

⛔ Files ignored due to path filters (1)
  • baml_language/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (26)
  • .vscode/launch.json
  • baml_language/crates/baml_cli/Cargo.toml
  • baml_language/crates/baml_cli/src/commands.rs
  • baml_language/crates/baml_cli/src/dap.rs
  • baml_language/crates/baml_cli/src/lib.rs
  • baml_language/crates/baml_compiler_emit/src/emit.rs
  • baml_language/crates/baml_compiler_emit/tests/while_loops.rs
  • baml_language/crates/baml_lsp_server/src/lib.rs
  • baml_language/crates/baml_lsp_server/src/playground_env.rs
  • baml_language/crates/baml_lsp_server/src/playground_sender.rs
  • baml_language/crates/baml_project/src/db.rs
  • baml_language/crates/baml_tests/src/vm.rs
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/notification.rs
  • baml_language/crates/bex_vm/src/debugger.rs
  • baml_language/crates/bex_vm/src/lib.rs
  • baml_language/crates/bex_vm/src/vm.rs
  • baml_language/crates/bex_vm/tests/debugger.rs
  • baml_language/crates/tools_onionskin/src/compiler.rs
  • baml_language/stow.toml
  • debugger_smoke/baml_src/main.baml
  • typescript2/app-vscode-ext/package.json
  • typescript2/app-vscode-ext/src/__tests__/debugger.test.ts
  • typescript2/app-vscode-ext/src/debugger.ts
  • typescript2/app-vscode-ext/src/extension.ts
  • typescript2/app-vscode-ext/tsup.config.ts

Comment on lines +29 to +51
pub fn run() -> Result<()> {
let stdin = io::stdin();
let stdout = io::stdout();
let mut reader = BufReader::new(stdin.lock());
let writer = BufWriter::new(stdout.lock());
let mut server = DapServer::new(writer);

while let Some(message) = read_dap_message(&mut reader)? {
let value: JsonValue =
serde_json::from_str(&message).context("failed to parse incoming DAP message")?;

if value.get("type").and_then(JsonValue::as_str) != Some("request") {
continue;
}

let keep_running = server.handle_request(&value)?;
if !keep_running {
break;
}
}

Ok(())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Main loop silently ignores non-request DAP messages.

This is correct per the DAP protocol for this server-role implementation (the adapter only needs to respond to requests), but consider logging ignored messages at a trace/debug level for easier protocol-level troubleshooting.

Comment on lines +415 to +439
fn handle_resume_command(
&mut self,
request_seq: i64,
command: &str,
action: ResumeAction,
) -> Result<()> {
if self.session.is_none() {
bail!("no active debug session");
}

let body = if command == "continue" {
Some(json!({ "allThreadsContinued": true }))
} else {
Some(json!({}))
};
self.respond_success(request_seq, command, body)?;
self.send_event(
"continued",
json!({
"threadId": THREAD_ID,
"allThreadsContinued": true,
}),
)?;
self.run_and_emit_or_terminate(action)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Sending continued event for pause request is incorrect per DAP spec.

The pause request means "interrupt a running debuggee." It should produce a stopped event, never a continued event. In this synchronous, single-threaded model the pause request only arrives when the VM is already stopped, making the continuedstopped sequence misleading for DAP clients.

Consider excluding Pause from this shared handler or skipping the continued event for it:

Proposed fix
 fn handle_resume_command(
     &mut self,
     request_seq: i64,
     command: &str,
     action: ResumeAction,
 ) -> Result<()> {
     if self.session.is_none() {
         bail!("no active debug session");
     }

     let body = if command == "continue" {
         Some(json!({ "allThreadsContinued": true }))
     } else {
         Some(json!({}))
     };
     self.respond_success(request_seq, command, body)?;
-    self.send_event(
-        "continued",
-        json!({
-            "threadId": THREAD_ID,
-            "allThreadsContinued": true,
-        }),
-    )?;
+    if !matches!(action, ResumeAction::Pause) {
+        self.send_event(
+            "continued",
+            json!({
+                "threadId": THREAD_ID,
+                "allThreadsContinued": true,
+            }),
+        )?;
+    }
     self.run_and_emit_or_terminate(action)
 }

Comment on lines +577 to +587
struct DebugSession {
db: ProjectDatabase,
vm: BexVm,
stop_on_entry: bool,
file_id_to_path: HashMap<u32, PathBuf>,
path_to_file_id: HashMap<PathBuf, u32>,
sequence_points_by_file: HashMap<u32, Vec<usize>>,
breakpoints_by_file: HashMap<u32, HashSet<usize>>,
handles: HashMap<i64, HandleValue>,
next_handle: i64,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

db field in DebugSession is unused after rebuild_file_maps().

The ProjectDatabase is only queried during construction (via rebuild_file_maps and get_debug_bytecode). After launch, it sits idle in memory holding all project data. If there's no plan to reload/recompile during a debug session, consider dropping it after launch to reduce memory pressure, or document why it's retained.

Comment on lines +971 to +997
fn load_project_files(db: &mut ProjectDatabase, root: &Path) -> Result<()> {
for entry in WalkDir::new(root)
.follow_links(true)
.into_iter()
.filter_map(Result::ok)
{
if !entry.file_type().is_file() {
continue;
}

let path = entry.into_path();
let is_baml = path.extension().and_then(|ext| ext.to_str()) == Some("baml");
let is_baml_jinja = path
.to_string_lossy()
.to_ascii_lowercase()
.ends_with(".baml.jinja");
if !is_baml && !is_baml_jinja {
continue;
}

let content = fs::read_to_string(&path)
.with_context(|| format!("failed to read source file {}", path.display()))?;
db.add_or_update_file(&path, &content);
}

Ok(())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

.baml extension check is case-sensitive, .baml.jinja check is case-insensitive.

Line 982 checks ext == Some("baml") (case-sensitive), while line 985 uses to_ascii_lowercase().ends_with(".baml.jinja") (case-insensitive). This is likely fine since .baml is the canonical extension, but the inconsistency could surprise a maintainer.

Comment on lines +1072 to +1105
fn json_to_vm_value(vm: &mut BexVm, value: &JsonValue) -> Result<Value> {
match value {
JsonValue::Null => Ok(Value::Null),
JsonValue::Bool(value) => Ok(Value::Bool(*value)),
JsonValue::Number(number) => {
if let Some(value) = number.as_i64() {
return Ok(Value::Int(value));
}
if let Some(value) = number.as_u64() {
let value = i64::try_from(value).context("integer is too large for i64")?;
return Ok(Value::Int(value));
}
if let Some(value) = number.as_f64() {
return Ok(Value::Float(value));
}
bail!("unsupported JSON number representation")
}
JsonValue::String(value) => Ok(vm.alloc_string(value.clone())),
JsonValue::Array(values) => {
let values = values
.iter()
.map(|value| json_to_vm_value(vm, value))
.collect::<Result<Vec<_>>>()?;
Ok(vm.alloc_array(values))
}
JsonValue::Object(values) => {
let mut map = indexmap::IndexMap::new();
for (key, value) in values {
map.insert(key.clone(), json_to_vm_value(vm, value)?);
}
Ok(vm.alloc_map(map))
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

No recursion depth limit in json_to_vm_value.

Deeply nested JSON payloads in launch args could cause a stack overflow. Since this processes user-supplied launch configuration, consider adding a depth guard.

Comment on lines +83 to +89
allowed_crates = ["bridge_cffi", "baml_cli"]
reason = "baml_* crates should not depend on bex_* crates (except baml_cli for DAP runtime integration)."

[[namespaces.dependency_rules]]
pattern.select = "bex_vm_types"
allowed_crates = ["baml_compiler_emit", "baml_project"]
reason = "Only compiler_emit and baml_project crates should depend on bex_vm_types (pure data layer)."
allowed_crates = ["baml_compiler_emit", "baml_project", "baml_cli"]
reason = "Only compiler_emit, baml_project, and baml_cli (DAP) should depend on bex_vm_types (pure data layer)."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the stow tool and understand the codebase structure
find . -name "*stow*" -type f | head -20

Repository: BoundaryML/baml

Length of output: 141


🏁 Script executed:

# Search for the stow tool source code
find . -path "*stow*" -name "*.rs" -type f | head -30

Repository: BoundaryML/baml

Length of output: 104


🏁 Script executed:

# Look for the stow.toml file and understand its location relative to the tool
git ls-files | grep -E "(stow|stow\.toml)"

Repository: BoundaryML/baml

Length of output: 266


🏁 Script executed:

# Examine the stow tool main.rs to understand how allowed_crates is processed
wc -l baml_language/crates/tools_stow/src/main.rs

Repository: BoundaryML/baml

Length of output: 107


🏁 Script executed:

# Read the stow main.rs file
cat -n baml_language/crates/tools_stow/src/main.rs | head -200

Repository: BoundaryML/baml

Length of output: 9834


🏁 Script executed:

# Search for allowed_crates and the validation logic
rg -n "allowed_crates" baml_language/crates/tools_stow/src/main.rs -A 5 -B 5

Repository: BoundaryML/baml

Length of output: 804


🏁 Script executed:

# Find the DependencyRule struct definition and validation logic
rg -n "struct DependencyRule\|fn check.*depend\|fn validate.*depend" baml_language/crates/tools_stow/src/main.rs -A 10

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

# Search for DependencyRule definition
rg -n "DependencyRule" baml_language/crates/tools_stow/src/main.rs | head -20

Repository: BoundaryML/baml

Length of output: 317


🏁 Script executed:

# Read lines around the DependencyRule struct definition
sed -n '270,330p' baml_language/crates/tools_stow/src/main.rs

Repository: BoundaryML/baml

Length of output: 1898


🏁 Script executed:

# Find where namespace-scoped rules are applied vs global rules
rg -n "applicable_rules\|namespace.*rules\|check.*violation" baml_language/crates/tools_stow/src/main.rs | head -30

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

# Read the validation logic around line 1112
sed -n '1100,1200p' baml_language/crates/tools_stow/src/main.rs

Repository: BoundaryML/baml

Length of output: 3916


🏁 Script executed:

# Find the get_namespace function to understand how namespace membership is determined
rg -n "fn get_namespace\|get_namespace" baml_language/crates/tools_stow/src/main.rs -A 10 | head -40

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

# Check the actual stow.toml to see baml_cli's namespace assignment
sed -n '1,150p' baml_language/stow.toml

Repository: BoundaryML/baml

Length of output: 5426


🏁 Script executed:

# Find the get_namespace implementation
sed -n '1,2808p' baml_language/crates/tools_stow/src/main.rs | rg -n "fn get_namespace" -A 15

Repository: BoundaryML/baml

Length of output: 1157


🏁 Script executed:

# Also check what name_exceptions does in the code
rg -n "name_exceptions" baml_language/crates/tools_stow/src/main.rs -B 3 -A 3

Repository: BoundaryML/baml

Length of output: 2678


🏁 Script executed:

# Check if surface namespace has any dependency_rules restricting bex_*
sed -n '120,200p' baml_language/stow.toml

Repository: BoundaryML/baml

Length of output: 407


🏁 Script executed:

# Verify which get_namespace function is used in the dependency validation
sed -n '1100,1140p' baml_language/crates/tools_stow/src/main.rs

Repository: BoundaryML/baml

Length of output: 1540


🏁 Script executed:

# Get more context around the validation logic
rg -n "check_dependency_rules\|applicable_rules" baml_language/crates/tools_stow/src/main.rs -B 5 -A 25 | head -60

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

# Confirm: does the validation code use get_namespace or get_namespace_for_crate?
rg -n "applicable_rules\|get_namespace" baml_language/crates/tools_stow/src/main.rs | grep -E "get_namespace|applicable_rules"

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

# Let's check the full function signature and context
rg -n "fn check_dependency_rules" baml_language/crates/tools_stow/src/main.rs -A 50 | head -80

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

# Search for where applicable_rules is populated
rg -n "applicable_rules.extend\|applicable_rules =" baml_language/crates/tools_stow/src/main.rs -B 10

Repository: BoundaryML/baml

Length of output: 41


🏁 Script executed:

# Read the entire validation section more carefully
sed -n '1090,1150p' baml_language/crates/tools_stow/src/main.rs

Repository: BoundaryML/baml

Length of output: 2075


🏁 Script executed:

# Final verification: confirm baml_cli is in surface, not baml namespace
rg -n "baml_cli" baml_language/stow.toml

Repository: BoundaryML/baml

Length of output: 500


baml_cli exceptions in baml namespace rules are semantic no-ops — move restrictions to surface namespace.

The dependency validation code uses get_namespace(), which only checks prefix matching (baml_*, etc.). Since baml_cli doesn't have the baml_ prefix and is registered in the surface namespace via name_exceptions, it will never match the baml namespace rules at lines 83 and 88. The validation therefore never applies these rules to baml_cli, making both allowed_crates entries unreachable.

The actual permission for baml_cli to depend on bex_* and bex_vm_types comes from the surface namespace having no corresponding [[namespaces.dependency_rules]] that restricts them. The reason strings ("controlled exception", "only … and baml_cli (DAP) should depend on …") incorrectly suggest stow enforces this boundary when it's actually unenforced.

To properly control baml_cli's bex_* dependencies, add explicit rules under the surface namespace:

Suggested fix
 [[namespaces]]
 name = "surface"
 name_exceptions = {"baml_cli" = "baml_cli", "baml_playground_wasm" = "baml_playground_wasm" }
+
+[[namespaces.dependency_rules]]
+pattern.select = "bex_*"
+pattern.exclude = ["bex_vm_types"]
+allowed_crates = ["baml_cli"]
+reason = "Only baml_cli (DAP runtime integration) may depend on bex_* internals from the surface namespace."
+
+[[namespaces.dependency_rules]]
+pattern.select = "bex_vm_types"
+allowed_crates = ["baml_cli"]
+reason = "Only baml_cli (DAP) may depend on bex_vm_types from the surface namespace."

Comment on lines +1 to +11
function sum_to(n: int) -> int {
let total = 0;
let i = 1;

while (i <= n) {
total = total + i;
i = i + 1;
}

total
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

sum_to silently returns 0 for negative n

When n < 0, the while (i <= n) condition is false from the start, so total remains 0 and is returned without any error or indication. For a smoke-test file this is harmless, but if sum_to is ever reused in a real DAP scenario with a user-supplied argument, the silent zero could be misleading. Consider a guard if the function moves beyond smoke-test use.

🛡️ Optional guard for negative input
 function sum_to(n: int) -> int {
+  // n must be non-negative; callers passing n < 0 receive 0
   let total = 0;
   let i = 1;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function sum_to(n: int) -> int {
let total = 0;
let i = 1;
while (i <= n) {
total = total + i;
i = i + 1;
}
total
}
function sum_to(n: int) -> int {
// n must be non-negative; callers passing n < 0 receive 0
let total = 0;
let i = 1;
while (i <= n) {
total = total + i;
i = i + 1;
}
total
}

Comment on lines +13 to +17
function main() -> int {
let seed = 8;
let triangle = sum_to(seed);
triangle
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider annotating the expected result for easier manual smoke-test validation

sum_to(8) = 36, but this is nowhere stated in the file. During a manual DAP run you have to mentally compute the expected terminal value. A brief comment makes the session self-documenting.

📝 Proposed annotation
 function main() -> int {
   let seed = 8;
-  let triangle = sum_to(seed);
+  let triangle = sum_to(seed); // expected: 36  (1+2+…+8)
   triangle
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function main() -> int {
let seed = 8;
let triangle = sum_to(seed);
triangle
}
function main() -> int {
let seed = 8;
let triangle = sum_to(seed); // expected: 36 (1+2+…+8)
triangle
}

Comment on lines 16 to 19
"activationEvents": [
"onLanguage:baml"
"onLanguage:baml",
"onDebug"
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🌐 Web query:

VS Code extension activation events onDebug onDebugResolve documentation

💡 Result:

VS Code documents the debug-related extension activation events on the “Activation Events” reference page:

  • onDebug: activates your extension before any debug session starts.

    "activationEvents": ["onDebug"]

    [1]

  • More fine-grained debug activation events (recommended for “heavier” extensions):

    • onDebugInitialConfigurations: fires just before provideDebugConfigurations(...) is called. [1]
    • onDebugResolve:type: fires just before resolveDebugConfiguration(...) (and related resolve methods) are called for a specific debug type. Example:
      "activationEvents": ["onDebugResolve:my-debug-type"]
      [1]

References

Citations:


🌐 Web query:

onDebugAdapterProtocolTracker VS Code activation event

💡 Result:

Use the onDebugAdapterProtocolTracker activation event in your extension’s package.json.

  • To activate only for a specific debug type (recommended), use onDebugAdapterProtocolTracker:<debugType> (for example node). If you omit :<debugType>, VS Code will activate your extension for all debug types. [1]
  • This event fires when a debug session of the relevant type is about to be launched and VS Code may need a Debug Adapter Protocol tracker. [2]

Example:

{
  "activationEvents": [
    "onDebugAdapterProtocolTracker:node"
  ]
}

Sources:
[1] VS Code Updates v1.30 (Debug Adapter Protocol tracker + activation event syntax) (code.visualstudio.com)
[2] VS Code “Activation Events” reference (code.visualstudio.com)

Citations:


🏁 Script executed:

# Let's examine the full context of the package.json file
cd typescript2/app-vscode-ext && cat -n package.json | head -120

Repository: BoundaryML/baml

Length of output: 4168


onDebug activates the extension on every debug session, not just BAML sessions.

Consider using onDebugResolve:baml instead. This is the recommended approach for debug-related activation, as it activates only when resolving debug configurations for BAML, avoiding unnecessary startup overhead when debugging other languages.

Suggested fix
  "activationEvents": [
    "onLanguage:baml",
-   "onDebug"
+   "onDebugResolve:baml"
  ],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"activationEvents": [
"onLanguage:baml"
"onLanguage:baml",
"onDebug"
],
"activationEvents": [
"onLanguage:baml",
"onDebugResolve:baml"
],

Comment on lines +18 to +24
createDebugAdapterDescriptor(
_session: vscode.DebugSession,
_executable: vscode.DebugAdapterExecutable | undefined,
): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
const command = buildBamlDebugAdapterCommand(this.cliPath);
return new vscode.DebugAdapterExecutable(command.command, command.args);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider forwarding environment variables to the DAP subprocess.

vscode.DebugAdapterExecutable accepts an optional options parameter that supports env and cwd. Without it, users cannot configure e.g. RUST_LOG=debug for the DAP server without modifying the extension. This is not blocking, but worth considering for operator/developer ergonomics.

💡 Suggested extension
 createDebugAdapterDescriptor(
   _session: vscode.DebugSession,
   _executable: vscode.DebugAdapterExecutable | undefined,
 ): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
   const command = buildBamlDebugAdapterCommand(this.cliPath);
-  return new vscode.DebugAdapterExecutable(command.command, command.args);
+  return new vscode.DebugAdapterExecutable(command.command, command.args, {
+    env: { ...process.env },
+  });
 }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 616ed0c and ebf0f85.

📒 Files selected for processing (1)
  • .vscode/settings.json

"[baml]": {
"editor.defaultFormatter": "Boundary.baml-extension"
},
"baml.cliPath": "/media/tony/WesternDigitalNvmeSsd/Code/baml/baml_language/target/debug/baml-cli",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove this developer-local path — contradicts PR intent and pollutes shared workspace settings.

The PR objectives explicitly state "Local .vscode/settings.json (baml.cliPath) intentionally omitted from the PR", yet this line was committed. The path /media/tony/WesternDigitalNvmeSsd/Code/baml/baml_language/target/debug/baml-cli is:

  1. Machine-specific: will be missing on every other contributor's machine, likely causing the extension to emit an error or degrade silently.
  2. Information leak: exposes the developer's username, drive label, and internal directory layout.

The fix is to revert this line. If the intent is to document the setting for contributors, add a comment in README or .vscode/extensions.json; if it needs to be tracked locally, add .vscode/settings.json (or a *.local.json variant) to .gitignore.

🗑️ Proposed fix
-  "baml.cliPath": "/media/tony/WesternDigitalNvmeSsd/Code/baml/baml_language/target/debug/baml-cli",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"baml.cliPath": "/media/tony/WesternDigitalNvmeSsd/Code/baml/baml_language/target/debug/baml-cli",

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant