saga4 misc fixes (#2018)

- **misc fixes [VSC]**
- **misc lefthook**

# Pull Request Checklist

## Description
- [ ] **Description of PR**: Clear and concise description of what this
PR accomplishes
- [ ] **Breaking Changes**: Document any breaking changes (if
applicable)
- [ ] **Related Issues**: Link to any related issues or tickets

## Testing
- [ ] **Test cases Attached**: All relevant test cases have been
added/updated
- [ ] **Manual Testing**: Manual testing completed for the changes

## Monitoring & Debugging
- [ ] **Logging in place**: Appropriate logging has been added for
debugging user issues
- [ ] **Sentry will be able to catch errors**: Error handling ensures
Sentry can capture and report errors
- [ ] **Avoid Dev based/Prisma logging**: No development-only or
Prisma-specific logging in production code

## Configuration
- [ ] **Env variables newly added**: Any new environment variables are
documented in .env.example file or mentioned in description
---

## Additional Notes
<!-- Add any additional context, screenshots, or notes for reviewers
here -->
This commit is contained in:
Sarthak Agarwal 2025-11-14 20:23:58 -08:00 committed by GitHub
parent 2b25fb51ec
commit 761903c2a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 128 additions and 14 deletions

View file

@ -63,6 +63,13 @@ const InitForm = ({ suggestions, errorDetails, pyprojectPath }: Props) => {
});
const renderedOnPurpose = !errorDetails;
// Helper to get directory name from path
const getConfigDirectory = () => {
if (!pyprojectPath) return "workspace root";
const parts = pyprojectPath.split("/");
return parts.length > 1 ? parts.slice(0, -1).join("/") : ".";
};
const setActiveTab = renderedOnPurpose
? useStore((state) => state.setActiveTab)
: undefined;
@ -315,6 +322,20 @@ const InitForm = ({ suggestions, errorDetails, pyprojectPath }: Props) => {
className={styles.form}
>
<h2 className={styles.title}>Project Configuration</h2>
{/* Context info section */}
<div className={styles.contextInfo}>
<div className={styles.contextItem}>
<span className="codicon codicon-file"></span>
<span>
Config file: <code>{pyprojectPath || "pyproject.toml (will be created)"}</code>
</span>
</div>
<div className={styles.helpText}>
💡 All paths below are relative to <code>{getConfigDirectory()}</code>
</div>
</div>
{errorDetails && (
<p
className={styles.bannerError}
@ -332,13 +353,15 @@ style="color: var(--vscode-editorWarning-foreground);"></span>` + errorDetails,
"Module Root",
"Path relative to pyproject.toml where your source code is located.",
true,
"e.g., src, lib, app, or . for current directory"
)}
{renderField(
"tests_root",
"Tests Root",
"Path relative to pyproject.toml where your test files are located",
"Path relative to pyproject.toml where your test files are located (common: tests, test, __tests__)",
true,
"e.g., tests, test, or . for current directory"
)}
{renderField(
@ -374,12 +397,23 @@ style="color: var(--vscode-editorWarning-foreground);"></span>` + errorDetails,
disabled={isLoading}
>
{isLoading ? (
<>
<span className="codicon codicon-loading spin"></span>
{" Saving..."}
</>
) : (
"Save Configurations"
<>
<span className="codicon codicon-save"></span>
{" Save & Apply Configuration"}
</>
)}
</button>
</div>
{!isLoading && (
<div className={styles.actionHelp}>
{pyprojectPath ? "This will update" : "This will create"} your pyproject.toml and reload the extension
</div>
)}
</form>
</div>
);

View file

@ -33,6 +33,57 @@
color: var(--vscode-foreground);
}
.contextInfo {
background: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border);
border-radius: 4px;
padding: 12px;
margin: 8px 0 16px 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.contextItem {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--vscode-descriptionForeground);
}
.contextItem code {
background: var(--vscode-textCodeBlock-background);
padding: 2px 6px;
border-radius: 3px;
font-family: var(--vscode-editor-font-family);
font-size: 11px;
color: var(--vscode-textPreformat-foreground);
}
.helpText {
font-size: 11px;
color: var(--vscode-descriptionForeground);
font-style: italic;
margin-top: 4px;
}
.helpText code {
background: var(--vscode-textCodeBlock-background);
padding: 2px 4px;
border-radius: 3px;
font-family: var(--vscode-editor-font-family);
font-size: 10px;
}
.actionHelp {
font-size: 11px;
color: var(--vscode-descriptionForeground);
text-align: center;
margin-top: 4px;
font-style: italic;
}
.bannerError {
padding: 1rem 0.5rem;
border-radius: 5px;

View file

@ -177,17 +177,38 @@ export class BootCodeflashServerStep extends BaseStep {
};
};
/**When
* Safely register a VS Code command with error handling.
* If a command already exists (e.g., during extension reload), log a warning
* and return a no-op disposable to prevent crashes.
*/
private safeRegisterCommand = (
commandId: string,
callback: (...args: any[]) => any,
): vscode.Disposable => {
try {
return vscode.commands.registerCommand(commandId, callback);
} catch (error) {
// Command already exists - this can happen during extension reload/retry
this.logger.warn(
`Command '${commandId}' already registered. This is expected during extension reload. Error: ${error}`,
);
// Return a no-op disposable to maintain the disposal chain
return { dispose: () => {} };
}
};
private provideCommands = (
sidebarProvider: SidebarProvider,
commentThreadProvider: CommentThreadProvider,
) => {
const optimizeFunctionCommand = vscode.commands.registerCommand(
const optimizeFunctionCommand = this.safeRegisterCommand(
"codeflash.optimizeFunction",
(uri: vscode.Uri, functionName: string) => {
sidebarProvider.addFunctionToQueue(functionName, uri, true);
},
);
const clearAllTasksCommand = vscode.commands.registerCommand(
const clearAllTasksCommand = this.safeRegisterCommand(
"codeflash.clearAllTasks",
() => {
const allTasksIds = this.globalState
@ -199,7 +220,7 @@ export class BootCodeflashServerStep extends BaseStep {
commentThreadProvider.scheduleRefresh();
},
);
const optimizeAllCommand = vscode.commands.registerCommand(
const optimizeAllCommand = this.safeRegisterCommand(
"codeflash.optimizeAll",
async () => {
this.logger.info("Optimizing all functions from command");
@ -207,7 +228,7 @@ export class BootCodeflashServerStep extends BaseStep {
},
);
const viewOptimization = vscode.commands.registerCommand(
const viewOptimization = this.safeRegisterCommand(
"codeflash.viewPatch",
async (
threadOrPayload:
@ -229,7 +250,7 @@ export class BootCodeflashServerStep extends BaseStep {
},
);
const acceptInlineCommentOptimizationCmd = vscode.commands.registerCommand(
const acceptInlineCommentOptimizationCmd = this.safeRegisterCommand(
"codeflash.acceptInlineRefactor",
async (
thread: vscode.CommentThread & {
@ -242,7 +263,7 @@ export class BootCodeflashServerStep extends BaseStep {
},
);
const sendMessage = vscode.commands.registerCommand(
const sendMessage = this.safeRegisterCommand(
"codeflash.sendMessage",
(message: OutgoingWebviewMessage) => {
sidebarProvider.sendMessage(message);

View file

@ -109,13 +109,22 @@ export class InitService extends Disposable {
} catch (error) {
if (maxRetries > 0) {
this.logger.error(
`InitService init error: ${error} (${maxRetries} retries left`,
`InitService init error: ${error} (${maxRetries} retries left)`,
);
// Dispose all resources including LSP clients and command registrations
this.dispose();
this._disposables = [];
// wait for half a second before retrying
await new Promise((resolve) => setTimeout(resolve, 500));
// Wait longer (1 second) to ensure VS Code fully unregisters commands and disposes resources
// This prevents "command already exists" errors during retry
// The delay accounts for:
// 1. Async disposal of LSP clients
// 2. VS Code's internal command registry cleanup
// 3. Event listener teardown
this.logger.info("Waiting for cleanup to complete before retry...");
await new Promise((resolve) => setTimeout(resolve, 1000));
return await this.initWithRetries(maxRetries - 1);
} else {
this.logger.error(`InitService init failed: ${error}`);

View file

@ -2,10 +2,9 @@ pre-commit:
parallel: true
commands:
secret-scan:
runner: "node"
glob: "*"
exclude: "node_modules/**|venv/**|.venv/**|__pycache__/**|dist/**|build/**"
command: "./node_modules/.bin/secretlint {staged_files} --maskSecrets --config ./secretlint.config.js"
run: "./node_modules/.bin/secretlint {staged_files} --maskSecrets --secretlintrc ./secretlint.config.js"
# js-lint:
# runner: "node"