UNPKG

relaycode

Version:

A developer assistant that automates applying code changes from LLMs.

1 lines 15.5 kB
{"version":3,"sources":["../../src/commands/watch.ts"],"names":["getSystemPrompt","projectId","preferredStrategy","patchConfig","header","intro","syntaxAuto","syntaxReplace","syntaxStandardDiff","syntaxSearchReplace","sectionStandardDiff","sectionSearchReplace","otherOps","finalSteps_rules","finalSteps_list","finalSteps","item","index","footer","strategyInfo","preferred","syntax","strategyDetails","watchCommand","options","cwd","clipboardWatcher","configWatcher","debounceTimer","startServices","config","logger","createClipboardWatcher","content","parsedResponse","parseLLMResponse","processPatch","handleConfigChange","newConfig","findConfig","error","initialConfig","loadConfigOrExit","configPath","findConfigPath","path","fs"],"mappings":"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","file":"watch.cjs","sourcesContent":["import { findConfig, loadConfigOrExit, findConfigPath } from '../core/config';\nimport { createClipboardWatcher } from '../core/clipboard';\nimport { parseLLMResponse } from 'relaycode-core';\nimport { processPatch } from '../core/transaction';\nimport { logger } from '../utils/logger';\nimport { type Config } from 'relaycode-core';\nimport fs from 'fs';\nimport path from 'path';\n\nconst getSystemPrompt = (\n projectId: string,\n preferredStrategy: Config['watcher']['preferredStrategy'],\n patchConfig: Config['patch'],\n): string => {\n const header = `\n✅ relaycode is watching for changes.\n\nIMPORTANT: For relaycode to work, you must configure your AI assistant.\nCopy the entire text below and paste it into your LLM's \"System Prompt\"\nor \"Custom Instructions\" section.\n---------------------------------------------------------------------------`;\n\n const intro = `You are an expert AI programmer. To modify a file, you MUST use a code block with a specified patch strategy.`;\n\n const syntaxAuto = `\n**Syntax:**\n\\`\\`\\`typescript // filePath {patchStrategy}\n... content ...\n\\`\\`\\`\n- \\`filePath\\`: The path to the file. **If the path contains spaces, it MUST be enclosed in double quotes.**\n- \\`patchStrategy\\`: (Optional) One of \\`standard-diff\\`, \\`search-replace\\`. If omitted, the entire file is replaced (this is the \\`replace\\` strategy).\n\n**Examples:**\n\\`\\`\\`typescript // src/components/Button.tsx\n...\n\\`\\`\\`\n\\`\\`\\`typescript // \"src/components/My Component.tsx\" standard-diff\n...\n\\`\\`\\``;\n\n const syntaxReplace = `\n**Syntax:**\n\\`\\`\\`typescript // filePath\n... content ...\n\\`\\`\\`\n- \\`filePath\\`: The path to the file. **If the path contains spaces, it MUST be enclosed in double quotes.**\n- Only the \\`replace\\` strategy is enabled. This means you must provide the ENTIRE file content for any change. This is suitable for creating new files or making changes to small files.`;\n\n const syntaxStandardDiff = `\n**Syntax:**\n\\`\\`\\`typescript // filePath standard-diff\n... diff content ...\n\\`\\`\\`\n- \\`filePath\\`: The path to the file. **If the path contains spaces, it MUST be enclosed in double quotes.**\n- You must use the \\`standard-diff\\` patch strategy for all modifications.`;\n\n const syntaxSearchReplace = `\n**Syntax:**\n\\`\\`\\`typescript // filePath search-replace\n... diff content ...\n\\`\\`\\`\n- \\`filePath\\`: The path to the file. **If the path contains spaces, it MUST be enclosed in double quotes.**\n- You must use the \\`search-replace\\` patch strategy for all modifications.`;\n\n const sectionStandardDiff = `---\n\n### Strategy 1: Advanced Unified Diff (\\`standard-diff\\`) - RECOMMENDED\n\nUse for most changes, like refactoring, adding features, and fixing bugs. It's resilient to minor changes in the source file.\n\n**Diff Format:**\n1. **File Headers**: Start with \\`--- {filePath}\\` and \\`+++ {filePath}\\`.\n2. **Hunk Header**: Use \\`@@ ... @@\\`. Exact line numbers are not needed.\n3. **Context Lines**: Include 2-3 unchanged lines before and after your change for context.\n4. **Changes**: Mark additions with \\`+\\` and removals with \\`-\\`. Maintain indentation.\n\n**Example:**\n\\`\\`\\`diff\n--- src/utils.ts\n+++ src/utils.ts\n@@ ... @@\n function calculateTotal(items: number[]): number {\n- return items.reduce((sum, item) => {\n- return sum + item;\n- }, 0);\n+ const total = items.reduce((sum, item) => {\n+ return sum + item * 1.1; // Add 10% markup\n+ }, 0);\n+ return Math.round(total * 100) / 100; // Round to 2 decimal places\n+ }\n\\`\\`\\`\n`;\n\n const sectionSearchReplace = `---\n\n### Strategy 2: Search-Replace (\\`search-replace\\`)\n\nUse for precise, surgical replacements. The \\`SEARCH\\` block must be an exact match of the content in the file.\n\n**Diff Format:**\nRepeat this block for each replacement.\n\\`\\`\\`diff\n<<<<<<< SEARCH\n[exact content to find including whitespace]\n=======\n[new content to replace with]\n>>>>>>> REPLACE\n\\`\\`\\`\n`;\n\n const otherOps = `---\n\n### Other Operations\n\n- **Creating a file**: Use the default \\`replace\\` strategy (omit the strategy name) and provide the full file content.\n- **Deleting a file**:\n \\`\\`\\`typescript // path/to/file.ts\n //TODO: delete this file\n \\`\\`\\`\n \\`\\`\\`typescript // \"path/to/My Old Component.ts\"\n //TODO: delete this file\n \\`\\`\\`\n- **Renaming/Moving a file**:\n \\`\\`\\`json // rename-file\n {\n \"from\": \"src/old/path/to/file.ts\",\n \"to\": \"src/new/path/to/file.ts\"\n }\n \\`\\`\\`\n`;\n\n const finalSteps_rules = [];\n if (patchConfig.minFileChanges > 0) {\n finalSteps_rules.push(`You must modify at least ${patchConfig.minFileChanges} file(s) in this transaction.`);\n }\n if (patchConfig.maxFileChanges) {\n finalSteps_rules.push(`You must not modify more than ${patchConfig.maxFileChanges} file(s) in this transaction.`);\n }\n\n const finalSteps_list = [\n 'Add your step-by-step reasoning in plain text before each code block.',\n ];\n if (finalSteps_rules.length > 0) {\n finalSteps_list.push(`Adhere to file limits: ${finalSteps_rules.join(' ')}`);\n }\n finalSteps_list.push('ALWAYS add the following YAML block at the very end of your response. Use the exact projectId shown here. Generate a new random uuid for each response.');\n\n const finalSteps_list_string = finalSteps_list.map((item, index) => `${index + 1}. ${item}`).join('\\n');\n\n const finalSteps = `---\n\n### Final Steps\n\n${finalSteps_list_string}\n\n \\`\\`\\`yaml\n projectId: ${projectId}\n uuid: (generate a random uuid)\n changeSummary: # A list of key-value pairs for changes\n - edit: src/main.ts\n - new: src/components/Button.tsx\n - delete: src/utils/old-helper.ts\n promptSummary: A brief summary of my request.\n gitCommitMsg: >-\n feat: A concise, imperative git commit message.\n\n Optionally, provide a longer description here.\n \\`\\`\\`\n`;\n \n const footer = `---------------------------------------------------------------------------`;\n\n const strategyInfo = {\n auto: { syntax: syntaxAuto, details: `${sectionStandardDiff}\\n${sectionSearchReplace}` },\n replace: { syntax: syntaxReplace, details: '' },\n 'standard-diff': { syntax: syntaxStandardDiff, details: sectionStandardDiff },\n 'search-replace': { syntax: syntaxSearchReplace, details: sectionSearchReplace },\n };\n\n const preferred = strategyInfo[preferredStrategy] ?? strategyInfo.auto;\n const syntax = preferred.syntax;\n const strategyDetails = preferred.details;\n\n return [header, intro, syntax, strategyDetails, otherOps, finalSteps, footer].filter(Boolean).join('\\n');\n};\n\nexport const watchCommand = async (options: { yes?: boolean } = {}, cwd: string = process.cwd()): Promise<{ stop: () => void }> => {\n let clipboardWatcher: ReturnType<typeof createClipboardWatcher> | null = null;\n let configWatcher: fs.FSWatcher | null = null;\n let debounceTimer: NodeJS.Timeout | null = null;\n\n const startServices = (config: Config) => {\n // Stop existing watcher if it's running\n if (clipboardWatcher) {\n clipboardWatcher.stop();\n }\n\n logger.setLevel(config.core.logLevel);\n logger.debug(`Log level set to: ${config.core.logLevel}`);\n logger.debug(`Preferred strategy set to: ${config.watcher.preferredStrategy}`);\n\n logger.log(getSystemPrompt(config.projectId, config.watcher.preferredStrategy, config.patch));\n\n clipboardWatcher = createClipboardWatcher(config.watcher.clipboardPollInterval, async (content) => {\n logger.info('New clipboard content detected. Attempting to parse...');\n const parsedResponse = parseLLMResponse(content);\n\n if (!parsedResponse) {\n logger.warn('Clipboard content is not a valid relaycode patch. Ignoring.');\n return;\n }\n\n // Check project ID before notifying and processing.\n if (parsedResponse.control.projectId !== config.projectId) {\n logger.debug(`Ignoring patch for different project (expected '${config.projectId}', got '${parsedResponse.control.projectId}').`);\n return;\n }\n\n await processPatch(config, parsedResponse, { cwd, notifyOnStart: true, yes: options.yes });\n logger.info('--------------------------------------------------');\n logger.info('Watching for next patch...');\n });\n };\n\n const handleConfigChange = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(async () => {\n logger.info(`Configuration file change detected. Reloading...`);\n try {\n const newConfig = await findConfig(cwd);\n if (newConfig) {\n logger.success('Configuration reloaded. Restarting services...');\n startServices(newConfig);\n } else {\n logger.error(`Configuration file is invalid or has been deleted. Services paused.`);\n if (clipboardWatcher) {\n clipboardWatcher.stop();\n clipboardWatcher = null;\n }\n }\n } catch (error) {\n logger.error(`Error reloading configuration: ${error instanceof Error ? error.message : String(error)}`);\n }\n }, 250);\n };\n\n // Initial startup\n const initialConfig = await loadConfigOrExit(cwd);\n const configPath = await findConfigPath(cwd);\n logger.success('Configuration loaded. Starting relaycode watch...');\n startServices(initialConfig);\n\n // Watch for changes after initial setup\n if (initialConfig.core.watchConfig && configPath) {\n logger.info(`Configuration file watching is enabled for ${path.basename(configPath)}.`);\n configWatcher = fs.watch(configPath, handleConfigChange);\n } else {\n logger.info('Configuration file watching is disabled. Changes to config will require a restart to take effect.');\n }\n\n const stopAll = () => {\n if (clipboardWatcher) {\n clipboardWatcher.stop();\n }\n if (configWatcher) {\n configWatcher.close();\n logger.info('Configuration file watcher stopped.');\n }\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n }\n };\n return { stop: stopAll };\n};"]}