Skip to content

Profiler/debug + editor fixes (#61, #62, #65, #68) and a self-contained, test-verified VSIX bundle (#71)#72

Merged
abdushakoor12 merged 8 commits into
mainfrom
bug-fixes-new
Jun 2, 2026
Merged

Profiler/debug + editor fixes (#61, #62, #65, #68) and a self-contained, test-verified VSIX bundle (#71)#72
abdushakoor12 merged 8 commits into
mainfrom
bug-fixes-new

Conversation

@abdushakoor12
Copy link
Copy Markdown
Collaborator

TLDR

Fixes five profiler/debug/editor bugs (macOS external-process profiling #61, Python Processes panel #62, sidebar affordance #65, function-local inlay hints #68) and makes the VS Code bundle self-contained and test-verified — debugpy is now bundled and the e2e suite runs against the real release bundle (#71).

What Was Added?

  • basilisk-profiler-protocol crate — shared wire types (Command/Message/TraceData/FrameData) + newline-delimited-JSON framing for the LSP↔helper Unix socket, so the two binaries can't drift.
  • profiler/helper_client/{mod,wire}.rs — the LSP-side UnixListener: binds the socket before spawning the elevated helper, drives attach→samples→stop, converts samples into the existing SamplerHandle channel (Showstopper: macOS profiling of any non-child process is 100% broken (no socket listener + getcwd elevation failure) #61).
  • profiler/processes.rs + vscode-extension/src/process-explorer.ts — LSP enumeration of attachable Python processes and the "Python Processes" panel; new server command basilisk.profiler.processes (Profiler start UX: no UI button, raw PID input box, fake auto-detect, no process picker #62).
  • Bundled debugpy: debugpy declared as a shipwright asset in shipwright.json; scripts/vendor-debugpy.mjs vendors it at build time; lsp-client.ts passes the bundled dir to the server, which adds it to PYTHONPATH so debugging works without a user-installed debugpy.
  • scripts/stage-runtime.mjs — single source of truth that stages every manifest-declared runtime binary for the platform (used by both test and release packaging).
  • Tests: tests/profiler_helper_socket.rs, src/test/suite/bundle-integrity.test.ts, process-explorer.test.ts, tests/lsp/ws_test_processes.rs, tests/lsp/ws_test_inlay_hints.rs, plus info-panel/activity-panel suites; new debug.rs and helper_client unit tests.

What Was Changed or Deleted?

  • privilege.rs — replaced the broken "elevate, then also attach in-process" flow (which could never read an external macOS process) with create_sampler: in-process py-spy when permitted, elevated helper-over-socket when macOS requires vm_read, denied otherwise. Removed elevate_if_needed, spawn_elevated_helper, create_socket_path.
  • profiler-helper/src/main.rs — now uses the shared protocol crate (−135 lines of duplicated structs/framing).
  • debug.rseffective_python treats a blank interpreter override as absent (no more Command::new("")os error 2); the debugpy check + adapter spawn use the bundled debugpy via PYTHONPATH.
  • commands.rs — debug-session start uses effective_python.
  • inlay_hints.rs + resolver (function_info.rs, function_types.rs) — produce inlay type hints for function-local unannotated (literal-assigned) variables; the local branch was previously dead ([LSP] Function-local variable inlay type hints never produced (dead loop: local_vars only holds annotated vars) #68).
  • info-panel.ts / profiler.ts — actionable vs read-only sidebar rows are now distinguishable with working actions (CRITICAL: Basilisk sidebar — actions vs info indistinguishable, and several Quick Actions are dead (no handler) #65); the raw PID input box is replaced by the process panel (Profiler start UX: no UI button, raw PID input box, fake auto-detect, no process picker #62).
  • Makefile (_test_vsix, _release_vsix) + verify-shipwright.mjs — both packaging paths use stage-runtime + vendor debugpy; verify-shipwright vsix now asserts every bundled component is present (binaries and asset dirs), so a debugger-less bundle fails the build. Deleted the divergent stage-bundled-binary.mjs.

How Do The Automated Tests Prove It Works?

Spec / Doc Changes

  • docs/specs/LSP-PROFILING-SPEC.md — added [PROFILE-HELPER-PROTOCOL] and [PROFILE-HELPER-SOCKET] sections; documented child-vs-external (same-user) macOS process handling.
  • website/src/docs/profiler.md — corrected macOS messaging: only Basilisk-launched child processes profile without elevation; same-user external processes still require it.
  • docs/specs/LSP-ARCHITECTURE-SPEC.md — minor.

Breaking Changes

  • None — new commands and the debugpy asset are additive; the profiler now succeeds for external macOS processes where it previously always failed.

Closes #61, #62, #65, #68, #71.

#68)

The inlay-hints pass looped over `FunctionInfo::local_vars` and skipped any
var with `has_annotation == true`. But `local_vars` is populated solely by
`collect_local_annotated_vars` (only `Stmt::AnnAssign`), so every entry is
annotated and the hint-building body was unreachable — plain `n = 0` locals
were never collected at all. `local_vars` cannot be repurposed because
E0014/E0036/E0047/E0117/E0149 rely on it being annotated-only.

Add `FunctionInfo::local_unannotated_vars`, populated by a new
`collect_local_unannotated_vars` collector (mirrors the annotated one,
reusing `walk_function_stmts` + `assign_infos_from`). The inlay pass consumes
it, and the two near-identical module/local hint loops collapse into one
`push_variable_type_hints` helper.

Adds an e2e regression test for `def f(): n = 0; s = "hi"` asserting the
`: int` / `: str` hints. Spec clarified to note module-level and local.
…m read-only info

Actionable rows (feature toggles, quick actions) in the basilisk.info panel were
visually indistinguishable from read-only Server Info rows: no imperative tooltip
and no inline button affordance. Per [EXTACT-INFO-AFFORDANCE]:

- FeatureItem/ActionItem now carry imperative tooltips and an always-visible
  inline action button. A single basilisk.info.runAction dispatcher forwards the
  button to the row's own command, so button and whole-row click share one source
  of truth.
- Quick Actions derive from a single ActionDef source-of-truth list (kills
  per-call-site drift).
- Read-only Server Info rows keep no command and no inline button (unchanged).

Adds affordance-partition tests and a panel-driven wiring test proving every
actionable row resolves to a registered handler (EXTACT-INFO-ACTION-WIRING) — the
check that would have caught the original "dead action" report. Adds a
BSK_TEST_GREP focus filter for invoking the test runner directly.
Starting a CPU/memory profile no longer demands a hand-typed PID (and
its non-existent "auto-detect"). The LSP now owns process discovery and
the editor renders a picker:

- LSP: new advertised `basilisk.profiler.processes` command backed by
  `profiler/processes.rs` (sysinfo) — enumerates attachable Python
  processes with pid/ppid, interpreter, version, cpu/mem/runtime, owner,
  elevation hint, and interpreter-vs-launcher kind. Enumeration only
  reads the process table, so it never needs elevation. Logs count only.
- VS Code: new "Python Processes" activity-bar panel (process-explorer.ts)
  with sort/group/filter and one-click Profile CPU / Track Memory inline
  actions; package.json view/commands/menus/settings/welcome. profiler.ts
  drops the input box — `profileStart` focuses the panel; panel launches
  reuse the existing session/status-bar state via startProfilingForPid.
- Spec: LSP-PROFILING-SPEC #PROFILE-PROCESSES* family; corrected
  #PROFILE-REQUESTS-START to remove the false auto-detect claim.
- Tests: ws_test_processes e2e (spawns Python, asserts it is listed and
  non-Python is excluded) + process-explorer VSIX component test.
macOS profiling of any non-child process was 100% broken. Three defects:

1. The LSP never bound a UnixListener, so the elevated helper's connect()
   always failed with "No such file or directory (os error 2)" — and after
   "elevating", the LSP still tried an in-process vm_read that can never
   succeed for an external process.
2. `do shell script ... with administrator privileges` inherited an
   inaccessible cwd, failing with `getcwd: cannot access parent directories`.
3. The helper was spawned with `.output().await`, blocking on the exit of a
   process designed to stream samples.

Fix:
- New basilisk-profiler-protocol crate: single source of truth for the wire
  types + newline-JSON framing, shared by the LSP and the helper (removes the
  duplicated message structs).
- profiler/helper_client: bind the UnixListener BEFORE spawning the helper,
  spawn it detached (osascript-elevated in prod, direct for tests), drive
  attach/samples/stop, and forward batches into the same SamplerHandle channel
  the in-process sampler uses.
- build_elevation_script `cd /`s first so the elevated shell cannot fail on an
  inaccessible working directory.
- privilege::create_sampler replaces the broken "elevate then also attach
  in-process" flow: child -> in-process py-spy; external macOS -> elevated
  helper socket; missing -> denied.
- Real-binary e2e tests over a real socket: a cross-platform listener proof
  and cwd/spawn-error checks, plus Linux-gated full-protocol and end-to-end
  sample tests (py-spy can't attach non-elevated on macOS). Docs/spec corrected
  to distinguish child vs external same-user processes.

Also lands the Cargo.lock entries for sysinfo so the lockfile matches the
manifests already on the branch.
Make debugging/profiling actually work on a fresh install, and stop broken
bundles from shipping green.

Debug launch
- effective_python: a blank interpreter override (empty `basilisk.python` /
  interpreterPath) is now treated as absent and falls back to auto-detection,
  instead of spawning `""` -> "No such file or directory (os error 2)".
- The LSP runs the BUNDLED debugpy: the extension passes its vendored debugpy
  directory via BASILISK_DEBUGPY_PATH, and debug.rs prepends it to PYTHONPATH
  for the debugpy check and adapter spawn, so debugging works even when the
  target interpreter has no debugpy.

Bundling integrity (shipwright)
- Declare debugpy as a bundled `asset` component in shipwright.json and vendor
  it at build time (vendor-debugpy.mjs; version read from the manifest), wired
  into `_release_vsix` and the release workflow (with setup-python).
- verify-shipwright `vsix` now asserts EVERY bundled component is present —
  binaries (required AND optional) and asset directories (debugpy) — so a
  declared-but-missing artifact fails the build instead of shipping green.
- Run the VSIX content verification during local packaging too, not only in
  release.yml.
- gitignore the vendored bundled/ build artifact.

Tests: debug.rs effective_python + bundled_debugpy_pythonpath; clippy
-D warnings clean; verify-shipwright now fails a helper-stripped or
debugpy-less VSIX (verified). Follow-up: #71 (run e2e tests against a
real-release VSIX so missing bundled artifacts fail the suite).
The e2e suite loaded the extension from the dev/source layout with only the
`basilisk` binary staged — never the per-platform bundle the release ships. So
debugging/profiling tests passed even though the deployed VSIX was missing the
debugger (debugpy) and the profiler helper: tests validated a different bundle
than what ships.

- New regression test (bundle-integrity.test.ts): inside the e2e harness,
  assert the running extension contains EVERY shipwright-declared bundled
  component for the platform (binaries + asset dirs). Fails if packaging omits
  one (e.g. debugpy) — so a debugger-less bundle can no longer pass tests.
- New stage-runtime.mjs: single source of truth that stages all manifest
  runtime binaries for the platform from a build dir, and hard-fails if a
  declared binary was not built.
- _test_vsix now assembles the REAL bundle via stage-runtime + vendor-debugpy
  (the same vendoring the release uses), instead of staging only basilisk.
- _release_vsix uses the same stage-runtime, collapsing its hand-rolled copy.
- Removed stage-bundled-binary.mjs — the divergent single-binary path that let
  this gap exist.

Red->green verified in the real VS Code e2e harness: the test reported the
missing basilisk-profiler-helper + debugpy before the fix, and passes after.

Closes #71.
Two fixes surfaced by running every CI job locally (ci-prep):

- _test_vsix now builds BOTH bundled binaries (basilisk + profiler-helper)
  and stages from target/ci via stage-runtime, instead of reusing a found
  binary whose directory may lack the helper. The #71 stage-runtime requires
  every bundled binary declared for the platform; on macOS (where the helper
  is bundled) the old reuse path failed with "built binary ... not found".
  This also stops the e2e from running against the slow coverage-instrumented
  binary.

- command-registration.test.ts: add basilisk.profiler.processes to
  SERVER_COMMANDS. #62 added this command to basilisk_common::commands::ALL
  (the server advertises it) but not to the extension's expected-commands
  list, so the "SERVER_COMMANDS matches server capabilities" compliance test
  failed.

Verified locally: VS Code e2e 322 passing, coverage 86% >= 84%.
The #61 socket tests are #[cfg(target_os = "linux")], so they were never
linted on macOS but failed CI's Linux clippy (pedantic, -D warnings):

- unnested_or_patterns (x2): nest the match or-arms
  (`Some(Stopped) | None`, `Ok(None) | Err(_)`).
- too_many_lines (108/100): extract the sample-collection and stop-drain
  loops into collect_sample_batches / drain_until_stopped helpers.

Verified by compiling the Linux paths on macOS via a temporary cfg(unix)
widening: clippy -D warnings is clean.
@abdushakoor12 abdushakoor12 merged commit 7d249a1 into main Jun 2, 2026
12 checks passed
@abdushakoor12 abdushakoor12 deleted the bug-fixes-new branch June 2, 2026 12:31
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.

Showstopper: macOS profiling of any non-child process is 100% broken (no socket listener + getcwd elevation failure)

1 participant