Skip to content

Make Gimlet Configurable#57

Merged
failfmi merged 42 commits intodevelopfrom
feat/configurable
Apr 29, 2026
Merged

Make Gimlet Configurable#57
failfmi merged 42 commits intodevelopfrom
feat/configurable

Conversation

@ERoydev
Copy link
Copy Markdown
Collaborator

@ERoydev ERoydev commented Apr 27, 2026

Summary

Makes Gimlet configurable via .vscode/gimlet.json. Users can now override:

  • tcpPort — debug session port (default 1212)
  • stopOnEntry — pause at program entry (default true)
  • platformToolsVersion — Solana platform-tools version (default 1.54)
  • platformToolsDir — full override of the platform-tools root
  • lldbLibraryPath — direct path to liblldb.{dylib,so} for non-standard layouts
  • depsPath — where compiled programs live (default target/deploy/debug)
  • sbfTraceDir — where SBF traces are written (default target/sbf/trace)

gimlet.json is auto-created on activation with current values and re-read on save. Invalid keys are reported in a single aggregated toast; unknown keys are logged and ignored. Deleting the file resets all overrides to defaults. LLDB library resolution is lazy and re-runs on relevant config changes.

Changes

  • New .vscode/gimlet.json with schema-validated keys and a workspace-relative file watcher
  • globalState is now the single source of truth for config; defaults restored when keys are absent
  • Workspace containment check for path-style overrides (depsPath, sbfTraceDir)
  • Diff-then-write to avoid spurious save events
  • README updated with the options table

Test plan

  • Set/clear each option in .vscode/gimlet.json and confirm the toast + state update
  • Delete gimlet.json and confirm "using defaults" toast + behavior reverts
  • Set platformToolsVersion to a missing version and confirm the LLDB error toast
  • Set depsPath/sbfTraceDir outside the workspace and confirm rejection

ERoydev and others added 15 commits April 21, 2026 16:46
Task 1/6: null-destructure fix
AC: #1
Story: config-hardening
- Add platformToolsDir override to gimlet.json, resolved via
  globalState.getPlatformToolsDir(); all three hardcoded Solana paths
  (LLDB library, LLDB Python site-packages, LLDB scripts dir) now
  derive from it. Users who install platform-tools outside
  ~/.cache/solana/v{version}/ — renamed dirs, Nix, custom toolchains —
  can point Gimlet at their install with one key.
- Add lldbLibraryPath as a finer-grained override that wins over the
  derived LLDB default; useful when the library has a non-conventional
  filename or the liblldb.{ext} symlink is missing.
- Move lldbLibrary to a lazy getter on globalState; require() no
  longer throws when platform-tools is missing, letting the
  litesvm/mollusk gate run cleanly. invalidateLldbLibrary() is called
  on override or version changes.
- debugConfigManager consumes globalState.getPlatformToolsLibDir/BinDir
  for its scripts dir and Python path; the "not found" toast names
  platformToolsDir as an alternative remediation.
- Relocate updates construction in runDebugSessionIteration into the
  existing try/catch so the lazy getter's first-read throw surfaces as
  a Gimlet toast instead of an unhandled exception in the polling loop.
- withLldbConfig and its inject/restore contract are unchanged.

Task 2/6: Changes 1+2+2a (amendment A-1)
AC: #2, #3, #3b, #4, #4b, #5, #11
Story: config-hardening
… toast

Add inline SCHEMA + validateConfig() in globalState.js. setConfig now
runs validation first, applies only keys that pass, and returns
{ errors, unknownKeys } to the caller. Validation checks type,
optional numeric range, and optional regex pattern.

config.js surfaces validation via a single aggregated error toast
listing every offending key with expected-vs-actual type, while
preserving the current in-memory value for each bad key (activation
proceeds with defaults). Unknown keys (typos, user comments-as-keys)
are logged to the Gimlet Output channel instead of rejected.

The success "Gimlet config updated and state refreshed." info toast
is suppressed when errors exist, so the user sees one message, not two.

Task 3/6: Change 4
AC: #6
Story: config-hardening
Add depsPath override to gimlet.json. Resolution tiers:
  1. gimlet.json.depsPath (workspace-relative, containment-checked,
     rejects paths that escape the workspace root — same rule as
     sbfTraceDir)
  2. $CARGO_TARGET_DIR/deploy/debug (as-is; may live outside the
     workspace since env vars often do)
  3. workspace/target/deploy/debug default

Drop inputPath entirely. It was vestigial: resolveGimletConfig()
computed and returned it, but no caller destructured or consumed it.
Rather than make a dead key configurable, remove the field, the
resolution block, and the returned-object entry. See amendment A-3.

Harden scanDeployDirectory against a .so-less depsPath. A directory
that exists and lives inside the workspace but contains no compiled
programs directly (e.g. user sets "./target" instead of
"./target/deploy/debug") used to silently pass with an empty
executables map, surfacing as cryptic "Unknown program ID" errors
later. Now counts .so files seen and surfaces a single actionable
toast naming the resolved path and the common mistake. See A-4.

SCHEMA + setConfig extended with depsPath; README documents the new
key; quick-spec updated with AC-7 and AC-7b.

Task 4/6: Change 3 (amendments A-3, A-4)
AC: #7, #7b, #8
Story: config-hardening
ensureGimletConfig previously rewrote gimlet.json on every activation
even when the merged content was byte-for-byte identical to what was
already on disk. Combined with the Cargo.toml watcher that re-runs
activation on every save, this produced:

  - continuous mtime changes (noisy git status)
  - "file changed externally" prompts mid-edit
  - self-write feedback risk with the config watcher — which becomes
    more pronounced once Task 6 lands onDidCreate/onDidDelete coverage

Serialize once into `next`, read current file into `prev` (or null),
only fs.writeFileSync when they differ.

One-time spurious write possible on first run if the file's previous
encoding differs in whitespace — acceptable and self-stabilising.

Task 5/6: Change 6
AC: #9
Story: config-hardening
activateDebugger re-runs on Cargo.toml saves and manual triggers; each
call previously registered a fresh FileSystemWatcher without disposing
the prior one, so one edit to gimlet.json fired N reload toasts.

Store the watcher on the manager and dispose it before re-registering.
Also route onDidCreate and onDidDelete through the same handlers:
atomic-save editors fire delete+create instead of change, and file
deletion now resets all overrides to null with a clear toast.

AC-10, AC-10b.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tasks 1-5 of config-hardening committed code without running the
formatter; running `npm run format` normalized the full tree for
the first time.

No logic changes — quote-style normalization, trailing commas,
arrow-function parens, and line wrapping only. Verified with
`git diff -w` spot-checks across .vscode/launch.json,
src/state/globalState.js, and src/managers/portManager.js.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores .github/* to origin/main to keep PR #57 focused.
Files contained only quote-style and indentation reformatting,
no semantic changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores README.md to origin/main formatting. Re-applies only the
content additions for this PR: 2 new bullets describing the custom
platform-tools install / LLDB library overrides, 3 new gimlet.json
options rows (depsPath, platformToolsDir, lldbLibraryPath), and a
"Which key do I need?" tip paragraph.

No reformatting of existing lines. Diff vs main is content-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores jsconfig.json to origin/main. The branch version had only
cosmetic edits — tab→4-space indent, comma repositioned around a
comment, single-element arrays collapsed to one line — none of which
affect TypeScript/JSDoc behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores eslint.config.mjs to origin/main. Branch version had only
cosmetic edits — quote style, array/object expansion, indent depth,
trailing newline — no rule changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores files to the state at 42daf29 (the commit just before the
prettier sweep in a3075a5) — or to origin/main when prettier was the
only thing that touched them. Keeps every feature/fix commit's logic;
removes the cosmetic noise that was making review hard.

Files identical to main after revert (drop from PR entirely):
- .gitignore
- src/debug.js
- src/logger.js
- src/projectStructure.js
- src/state/sessionState.js
- src/utils.js
- src/managers/vscodeSettingsManager.js

Files restored to 42daf29 (content kept, formatting reverted):
- docs/amendments.md
- docs/quick-config-hardening.md
- src/extension.js
- src/lens/gimletCodeLensProvider.js
- src/managers/portManager.js
- src/managers/sessionManager.js
- src/state/globalState.js

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lost in the bulk prettier-revert of 13f1135 — .claude was a real
content addition on this branch (ignore Claude Code local state),
not formatting churn. Bringing it back.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ERoydev ERoydev changed the base branch from main to develop April 27, 2026 13:16
ERoydev added 3 commits April 27, 2026 16:39
Replace startsWith(workspaceFolder + path.sep) with isInsideWorkspace
helper backed by path.relative. The old check was case-sensitive and
separator-literal, which mis-rejected valid paths on Windows/macOS
when casing differed and could not reliably detect different-drive
paths on Windows. The new helper honors the platform's filesystem
rules (case-insensitive on Windows/macOS) and rejects '..' escapes
plus absolute (different-drive) results.
Wrap the watcher target in vscode.RelativePattern instead of passing
the raw absolute path string. This matches the documented form for
workspace-scoped watchers and makes the intent explicit (anchor +
relative glob) without relying on VS Code's string-as-glob inference.
setConfig({}) now restores tcpPort, stopOnEntry, and platformToolsVersion
to their defaults instead of leaving them at their last value. This makes
the watcher's resetToDefaults handler — invoked when gimlet.json is
deleted — actually match its toast ("using defaults"). Previously, the
path-style overrides cleared via `|| null` but the scalars only assigned
when the key was present, so a deleted config left them stuck.

Also extracts DEFAULT_STOP_ON_ENTRY constant for symmetry with the other
defaults.
@ERoydev ERoydev requested review from Copilot, failfmi and procdump and removed request for failfmi April 27, 2026 14:15
Comment thread .gitignore Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR makes Gimlet’s debugger/session behavior configurable via a workspace .vscode/gimlet.json, including validation, persistence into globalState, and live reloads via a file watcher.

Changes:

  • Add schema-based validation and default-reset behavior for Gimlet config values, with lazy LLDB library resolution.
  • Add .vscode/gimlet.json creation + file watching to reload config on save/delete, plus workspace-containment checks for certain paths.
  • Update debugger launch plumbing and UX messages (e.g., improved LLDB/platform-tools diagnostics; better deploy-dir scanning errors).

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/state/globalState.js Adds schema validation, new override fields, and lazy/cached LLDB library path resolution.
src/config.js Creates/merges gimlet.json, validates + applies config into globalState, adds watcher + containment checks.
src/managers/portManager.js Moves LLDB config resolution inside try/catch so config errors are surfaced cleanly.
src/managers/debugConfigManager.js Derives platform-tools paths from globalState overrides; updates missing-tools error messaging.
src/extension.js Makes deploy-dir scanning resilient to null config; improves error when no .so files are present.
README.md Documents new configuration options and guidance.
.gitignore Ignores .claude.
Comments suppressed due to low confidence (1)

src/config.js:124

  • ensureGimletConfig() only auto-writes tcpPort, platformToolsVersion, and stopOnEntry, but the PR description says gimlet.json is auto-created with the current values for additional configurable keys (depsPath, sbfTraceDir, platformToolsDir, lldbLibraryPath). If the intent is discoverability via the generated file, include these keys (likely as null when unset) so users can see the full set of supported overrides.
        const defaultConfig = {
            tcpPort: globalState.tcpPort,
            platformToolsVersion: globalState.platformToolsVersion,
            stopOnEntry: globalState.stopOnEntry,
        };

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/config.js
Comment thread src/managers/debugConfigManager.js
Comment thread src/extension.js Outdated
Comment thread src/state/globalState.js Outdated
Comment thread src/state/globalState.js
Comment thread src/state/globalState.js Outdated
Comment thread src/config.js Outdated
ERoydev and others added 3 commits April 27, 2026 20:34
The Default column listed `null` for keys whose descriptions
documented concrete fallbacks, contradicting the prose. Replace
those with the actual derived defaults (workspace-relative `$PWD/...`
paths, the platform-tools cache location, and the derived liblldb
path) so the table matches the behavior the descriptions describe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread src/managers/debugConfigManager.js Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

src/config.js:124

  • ensureGimletConfig() only writes tcpPort, platformToolsVersion, and stopOnEntry into the auto-created .vscode/gimlet.json. That means users won’t see (or be able to easily discover) the newly supported options like artifactPath, sbfTracePath, platformToolsPath, and lldbLibraryPath in the generated file, despite the PR description/README advertising them. Consider including all supported keys in defaultConfig (with workspace-relative defaults or null where appropriate).
        const defaultConfig = {
            tcpPort: globalState.tcpPort,
            platformToolsVersion: globalState.platformToolsVersion,
            stopOnEntry: globalState.stopOnEntry,
        };

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/state/configSchema.js
Comment thread README.md Outdated
Comment thread README.md
Comment thread src/state/configSchema.js
Copy link
Copy Markdown
Collaborator

@procdump procdump left a comment

Choose a reason for hiding this comment

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

Suggestions from a separate code review pass. Most are message-wording / cleanup; the inline ones are anchored on diff lines. Two more sit on lines that aren't part of this PR's diff, so I'm leaving them here as text — feel free to take or leave any of them.


src/extension.js:142await the config init so internal throws don't become unhandled rejections.

ensureGimletConfig() is async and contains synchronous fs calls (fs.mkdirSync, fs.writeFileSync) that can throw on permission errors. Without await, any throw becomes an unhandled promise rejection — the user sees nothing. Adding await routes errors through activateDebugger's existing outer try/catch.

-        gimletConfigManager.ensureGimletConfig();
+        await gimletConfigManager.ensureGimletConfig();
         gimletConfigManager.watchGimletConfig(context);

src/managers/debugConfigManager.js:103 — replace silent abort with a thrown error.

if (!session) return false; is the only remaining false-return path in loadProgramModules. portManager catches a thrown error and shows a toast, but a false return triggers stopDebugging() with no explanation. The race is rare (session cleanup vs. debug start) but when it happens the debug session vanishes mysteriously.

-        if (!session) return false;
+        if (!session) {
+            throw new Error(
+                'Gimlet debug session was cleared before program modules could load. ' +
+                    'This usually means the session was stopped concurrently — try debugging again.'
+            );
+        }

If you take this, loadProgramModules no longer needs return true at the bottom, and the matching if (!loaded) branch in portManager.js:143-146 becomes dead code — both can go.

Comment thread README.md Outdated
Comment thread src/config.js Outdated
Comment thread src/config.js Outdated
Comment thread src/managers/debugConfigManager.js Outdated
Comment thread src/managers/debugConfigManager.js Outdated
Comment thread src/state/configSchema.js Outdated
Comment thread src/state/configSchema.js
ERoydev and others added 8 commits April 28, 2026 16:44
Co-authored-by: Boris Astardzhiev <34716852+procdump@users.noreply.github.com>
Co-authored-by: Boris Astardzhiev <34716852+procdump@users.noreply.github.com>
Co-authored-by: Boris Astardzhiev <34716852+procdump@users.noreply.github.com>
Co-authored-by: Boris Astardzhiev <34716852+procdump@users.noreply.github.com>
Co-authored-by: Boris Astardzhiev <34716852+procdump@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/managers/portManager.js
Comment thread src/state/globalState.js
Comment thread src/state/configSchema.js
Comment thread src/config.js Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@ERoydev ERoydev requested a review from procdump April 28, 2026 14:50
Comment thread src/state/globalState.js
@@ -53,30 +155,58 @@ class GimletGeneralState {
setPlatformToolsVersion(version) {
Copy link
Copy Markdown
Collaborator

@procdump procdump Apr 29, 2026

Choose a reason for hiding this comment

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

nit: dead code - remove

@failfmi failfmi merged commit bc98c79 into develop Apr 29, 2026
1 check passed
@failfmi failfmi deleted the feat/configurable branch April 29, 2026 06:13
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.

4 participants