fix: address 10 unresolved PR review threads

- README: add language specifier to fenced code block
- paths.ts: guard npmPackageRootDirectory() against bundle structure drift
- OpenCodeInstaller: resolve bundle from import.meta.url, not process.cwd()
- OpenCodeInstaller: log warnings on AGENTS.md injection failures
- WindsurfHooksInstaller: key registry by full workspace path, not basename
- uninstall.ts: poll health endpoint to wait for worker exit before file deletion
- uninstall.ts: call IDE-specific uninstallers (Gemini, Windsurf, OpenCode, OpenClaw, Codex)
- opencode-plugin: cap session tracking Map at 1000 entries with LRU eviction
- GeminiCliHooksInstaller: document intentional JSON double-escaping

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-04-04 14:45:53 -07:00
parent 21b10b4696
commit c7c68e81f4
7 changed files with 126 additions and 37 deletions
@@ -119,7 +119,10 @@ function buildHookCommand(
throw new Error(`Unknown Gemini CLI event: ${geminiEventName}`);
}
// Escape backslashes for JSON compatibility on Windows
// Double-escape backslashes intentionally: this command string is embedded inside
// a JSON value, so `\\` in the source becomes `\` when the JSON is parsed by the
// IDE. Without double-escaping, Windows paths like C:\Users would lose their
// backslashes and break when the IDE deserializes the hook configuration.
const escapedBunPath = bunPath.replace(/\\/g, '\\\\');
const escapedWorkerPath = workerServicePath.replace(/\\/g, '\\\\');
+35 -12
View File
@@ -16,6 +16,7 @@
import path from 'path';
import { homedir } from 'os';
import { fileURLToPath } from 'url';
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, unlinkSync } from 'fs';
import { logger } from '../../utils/logger.js';
import { CONTEXT_TAG_OPEN, CONTEXT_TAG_CLOSE, injectContextIntoMarkdownFile } from '../../utils/context-injection.js';
@@ -74,8 +75,8 @@ export function findBuiltPluginPath(): string | null {
'plugins', 'marketplaces', 'thedotmack',
'dist', 'opencode-plugin', 'index.js',
),
// Development location (relative to project root)
path.join(process.cwd(), 'dist', 'opencode-plugin', 'index.js'),
// Development location (relative to this module's package root)
path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'dist', 'opencode-plugin', 'index.js'),
];
for (const candidatePath of possiblePaths) {
@@ -171,7 +172,10 @@ export async function syncContextToAgentsMd(
const contextText = await response.text();
if (contextText && contextText.trim()) {
injectContextIntoAgentsMd(contextText);
const injectResult = injectContextIntoAgentsMd(contextText);
if (injectResult !== 0) {
logger.warn('OPENCODE', 'Failed to inject context into AGENTS.md during sync');
}
}
} catch {
// Worker not available — non-critical
@@ -315,22 +319,41 @@ Use claude-mem search tools for manual memory queries.`;
if (contextResponse.ok) {
const realContext = await contextResponse.text();
if (realContext && realContext.trim()) {
injectContextIntoAgentsMd(realContext);
console.log(' Context injected from existing memory');
const injectResult = injectContextIntoAgentsMd(realContext);
if (injectResult !== 0) {
logger.warn('OPENCODE', 'Failed to inject real context into AGENTS.md during install');
} else {
console.log(' Context injected from existing memory');
}
} else {
injectContextIntoAgentsMd(placeholderContext);
console.log(' Placeholder context created (will populate after first session)');
const injectResult = injectContextIntoAgentsMd(placeholderContext);
if (injectResult !== 0) {
logger.warn('OPENCODE', 'Failed to inject placeholder context into AGENTS.md during install');
} else {
console.log(' Placeholder context created (will populate after first session)');
}
}
} else {
injectContextIntoAgentsMd(placeholderContext);
const injectResult = injectContextIntoAgentsMd(placeholderContext);
if (injectResult !== 0) {
logger.warn('OPENCODE', 'Failed to inject placeholder context into AGENTS.md during install');
}
}
} else {
injectContextIntoAgentsMd(placeholderContext);
console.log(' Placeholder context created (worker not running)');
const injectResult = injectContextIntoAgentsMd(placeholderContext);
if (injectResult !== 0) {
logger.warn('OPENCODE', 'Failed to inject placeholder context into AGENTS.md during install');
} else {
console.log(' Placeholder context created (worker not running)');
}
}
} catch {
injectContextIntoAgentsMd(placeholderContext);
console.log(' Placeholder context created (worker not running)');
const injectResult = injectContextIntoAgentsMd(placeholderContext);
if (injectResult !== 0) {
logger.warn('OPENCODE', 'Failed to inject placeholder context into AGENTS.md during install');
} else {
console.log(' Placeholder context created (worker not running)');
}
}
console.log(`
@@ -46,8 +46,7 @@ interface WindsurfHooksJson {
}
interface WindsurfProjectRegistry {
[projectName: string]: {
workspacePath: string;
[workspacePath: string]: {
installedAt: string;
};
}
@@ -104,37 +103,37 @@ export function writeWindsurfRegistry(registry: WindsurfProjectRegistry): void {
}
/**
* Register a project for auto-context updates
* Register a project for auto-context updates.
* Keys by full workspacePath to avoid collisions between directories with the same basename.
*/
export function registerWindsurfProject(projectName: string, workspacePath: string): void {
export function registerWindsurfProject(workspacePath: string): void {
const registry = readWindsurfRegistry();
registry[projectName] = {
workspacePath,
registry[workspacePath] = {
installedAt: new Date().toISOString(),
};
writeWindsurfRegistry(registry);
logger.info('WINDSURF', 'Registered project for auto-context updates', { projectName, workspacePath });
logger.info('WINDSURF', 'Registered project for auto-context updates', { workspacePath });
}
/**
* Unregister a project from auto-context updates
*/
export function unregisterWindsurfProject(projectName: string): void {
export function unregisterWindsurfProject(workspacePath: string): void {
const registry = readWindsurfRegistry();
if (registry[projectName]) {
delete registry[projectName];
if (registry[workspacePath]) {
delete registry[workspacePath];
writeWindsurfRegistry(registry);
logger.info('WINDSURF', 'Unregistered project', { projectName });
logger.info('WINDSURF', 'Unregistered project', { workspacePath });
}
}
/**
* Update Windsurf context files for all registered projects matching this project name.
* Update Windsurf context files for a registered project.
* Called by SDK agents after saving a summary.
*/
export async function updateWindsurfContextForProject(projectName: string, port: number): Promise<void> {
export async function updateWindsurfContextForProject(projectName: string, workspacePath: string, port: number): Promise<void> {
const registry = readWindsurfRegistry();
const entry = registry[projectName];
const entry = registry[workspacePath];
if (!entry) return; // Project doesn't have Windsurf hooks installed
@@ -148,11 +147,11 @@ export async function updateWindsurfContextForProject(projectName: string, port:
const context = await response.text();
if (!context || !context.trim()) return;
writeWindsurfContextFile(entry.workspacePath, context);
logger.debug('WINDSURF', 'Updated context file', { projectName, workspacePath: entry.workspacePath });
writeWindsurfContextFile(workspacePath, context);
logger.debug('WINDSURF', 'Updated context file', { projectName, workspacePath });
} catch (error) {
// Background context update — failure is non-critical
logger.error('WINDSURF', 'Failed to update context file', { projectName }, error as Error);
logger.error('WINDSURF', 'Failed to update context file', { projectName, workspacePath }, error as Error);
}
}
@@ -371,7 +370,7 @@ Use claude-mem's MCP search tools for manual memory queries.
}
// Register project for automatic context updates after summaries
registerWindsurfProject(projectName, workspaceRoot);
registerWindsurfProject(workspaceRoot);
console.log(` Registered for auto-context updates`);
}
@@ -423,8 +422,7 @@ export function uninstallWindsurfHooks(): number {
}
// Unregister project
const projectName = path.basename(workspaceRoot);
unregisterWindsurfProject(projectName);
unregisterWindsurfProject(workspaceRoot);
console.log(` Unregistered from auto-context updates`);
console.log(`\nUninstallation complete!\n`);