feat: Universal Slash Command Bridge Generation (v5.2.0)#18
Conversation
Auto-generate IDE-native slash command bridges from .agent/workflows/*.md for 5 IDEs: Claude Code, Cursor, OpenCode, VS Code Copilot, and Windsurf. - New lib/command-bridge.js with IDE auto-detection and per-IDE adapters - Provenance-based safe overwrite (never overwrites user commands) - 9 security hardening measures (path traversal, YAML/markdown injection, DoS cap) - Step-builder pattern replaces fragile step counter in kit init - Extracted lib/commands/init.js (bin/kit.js under 800-line limit) - .claude/commands/ git worktree support via gitignore negation pattern - Bridge-sync verification in kit verify - Raised plan validation threshold from 70% to 80% - 982 tests passing (42 new)
Version sync: package.json, manifest.json, README badges, architecture.md, CheatSheet.md, session-context.md
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request updates Devran AI Kit to v5.2.0, introducing universal slash command bridge generation for multiple IDEs (Claude Code, Cursor, OpenCode, VS Code Copilot, and Windsurf). Key changes include a refactored init command using a step-builder pattern, enhanced security measures for bridge generation, and raising the plan validation threshold from 70% to 80%. Review feedback identifies a critical issue where the 128-byte buffer for provenance detection is insufficient for VS Code prompt files with large frontmatter, suggests consolidating duplicate provenance check logic, and recommends more robust regex-based gitignore pattern matching to avoid false positives.
lib/ide-generator.js
Outdated
| const buf = Buffer.alloc(128); | ||
| fs.readSync(fd, buf, 0, 128, 0); | ||
| return buf.toString('utf-8').includes(KIT_BRIDGE_HEADER); |
There was a problem hiding this comment.
The 128-byte buffer is insufficient for detecting the provenance header in VS Code prompt files. As noted in lib/command-bridge.js (line 308), the header is placed after the YAML frontmatter in these files. Since the description field alone can be up to 200 characters, the frontmatter block can easily exceed 128 bytes, causing checkKitProvenance to fail and potentially allowing user files to be overwritten. Increasing this to 512 bytes would be safer.
| const buf = Buffer.alloc(128); | |
| fs.readSync(fd, buf, 0, 128, 0); | |
| return buf.toString('utf-8').includes(KIT_BRIDGE_HEADER); | |
| const buf = Buffer.alloc(512); | |
| const bytesRead = fs.readSync(fd, buf, 0, 512, 0); | |
| return buf.toString('utf-8', 0, bytesRead).includes(KIT_BRIDGE_HEADER); |
lib/command-bridge.js
Outdated
| fs.readSync(fd, buf, 0, 128, 0); | ||
| return buf.toString('utf-8').includes(KIT_BRIDGE_HEADER); | ||
| } finally { |
There was a problem hiding this comment.
Similar to checkKitProvenance in lib/ide-generator.js, the 128-byte limit here will fail to detect the provenance header in VS Code prompt files when the frontmatter is large. Also, this function appears to be a duplicate of checkKitProvenance. Consider consolidating them into a single utility (e.g., in lib/io.js) to improve maintainability.
const buf = Buffer.alloc(512);
const bytesRead = fs.readSync(fd, buf, 0, 512, 0);
return buf.toString('utf-8', 0, bytesRead).includes(KIT_BRIDGE_HEADER);
lib/io.js
Outdated
| const dirIgnored = content.includes(pattern.dir) || content.includes(pattern.dir.replace('/', '/*')); | ||
| const hasNegation = content.includes(`!${pattern.commands}`); |
There was a problem hiding this comment.
The use of String.prototype.includes() for checking gitignore patterns is fragile and prone to false positives. For example, it will match the pattern if it appears in a comment (e.g., # .cursor/ is not ignored). It also fails to match if the trailing slash is missing in the .gitignore file (e.g., .cursor), which is a valid way to ignore a directory. Using a line-anchored regular expression would be more robust.
const escapedDir = pattern.dir.replace(/[.*+?^${}()|[\\\]]/g, '\\$&').replace(/\/$/, '');
const dirIgnored = new RegExp('^' + escapedDir + '/?(\\s*(?:#.*)?)$', 'm').test(content) ||
new RegExp('^' + escapedDir + '/\\*(\\s*(?:#.*)?)$', 'm').test(content);
const hasNegation = new RegExp('^!' + pattern.commands.replace(/[.*+?^${}()|[\\\]]/g, '\\$&').replace(/\/$/, '') + '/?(\\s*(?:#.*)?)$', 'm').test(content);
lib/commands/init.js
Outdated
| try { | ||
| const { generateCommandBridges } = require('../command-bridge'); | ||
| const { writeBridgeConfigs } = require('../ide-generator'); | ||
| const manifest = JSON.parse(fs.readFileSync(path.join(agentPath, 'manifest.json'), 'utf-8')); |
There was a problem hiding this comment.
For consistency and robustness, consider using readJsonSafe() here instead of JSON.parse(fs.readFileSync(...)). This avoids potential crashes if the file is missing or contains invalid JSON, aligning with the usage at line 361.
| const manifest = JSON.parse(fs.readFileSync(path.join(agentPath, 'manifest.json'), 'utf-8')); | |
| const manifest = readJsonSafe(path.join(agentPath, 'manifest.json'), {}); |
extractFrontmatterField regex now matches \r\n (CRLF) in addition to \n (LF), fixing fallback descriptions on Windows where workflow files have Windows line endings.
There was a problem hiding this comment.
Code Review
This pull request introduces a universal slash command bridge generation system, enabling IDE-native slash command support for Claude Code, Cursor, OpenCode, VS Code Copilot, and Windsurf. The changes include a new command bridge generator module, automated IDE detection, provenance-based overwrite protection, and git worktree support. Additionally, the plan validation threshold has been raised to 80% across the framework. I have provided feedback regarding extending the bridge-sync verification to cover all IDEs and improving the robustness of filename parsing.
lib/verify.js
Outdated
| // --- Check 13: Bridge-sync — command bridges match workflows --- | ||
| const workflowItems = manifest.capabilities?.workflows?.items || []; | ||
| const claudeCommandsDir = path.join(path.dirname(agentDir), '.claude', 'commands'); | ||
| if (fs.existsSync(claudeCommandsDir)) { | ||
| const workflowNames = new Set(workflowItems.map((w) => w.name)); | ||
| // Check each workflow has a bridge | ||
| for (const item of workflowItems) { | ||
| const bridgePath = path.join(claudeCommandsDir, `${item.name}.md`); | ||
| const bridgeExists = fs.existsSync(bridgePath); | ||
| results.push({ | ||
| name: `bridge-sync:claude:${item.name}`, | ||
| status: bridgeExists ? 'pass' : 'warn', | ||
| message: bridgeExists | ||
| ? `Bridge exists: .claude/commands/${item.name}.md` | ||
| : `Missing bridge: .claude/commands/${item.name}.md (run kit init to generate)`, | ||
| }); | ||
| } | ||
| // Check for orphaned bridges | ||
| const bridgeFiles = fs.readdirSync(claudeCommandsDir).filter((f) => f.endsWith('.md')); | ||
| for (const f of bridgeFiles) { | ||
| const name = f.endsWith('.md') ? f.slice(0, -3) : f; | ||
| if (!workflowNames.has(name)) { | ||
| results.push({ | ||
| name: `bridge-sync:claude:orphan:${name}`, | ||
| status: 'warn', | ||
| message: `Orphaned bridge: .claude/commands/${f} (no matching workflow)`, | ||
| }); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The new bridge-sync verification is a great addition for ensuring integrity. However, it currently only checks for Claude bridges (.claude/commands/). To fully support the "Universal Slash Command Bridge Generation" feature, this check should be extended to validate bridges for all detected IDEs (e.g., Cursor, OpenCode, etc.).
You could adapt the detectIDEs logic from lib/command-bridge.js to find all relevant command directories and run the sync check against each of them.
lib/verify.js
Outdated
| // Check for orphaned bridges | ||
| const bridgeFiles = fs.readdirSync(claudeCommandsDir).filter((f) => f.endsWith('.md')); | ||
| for (const f of bridgeFiles) { | ||
| const name = f.endsWith('.md') ? f.slice(0, -3) : f; |
There was a problem hiding this comment.
Using f.slice(0, -3) to remove the .md extension is a bit brittle and might not handle edge cases like filenames with multiple dots correctly. A more robust approach would be to use Node's path module.
| const name = f.endsWith('.md') ? f.slice(0, -3) : f; | |
| const name = path.parse(f).name; |
- Increase provenance buffer from 128 to 512 bytes for VS Code prompt files - Consolidate duplicate provenance check into shared checkKitProvenance in io.js - Use regex-based gitignore pattern matching to avoid false positives on comments - Use readJsonSafe instead of JSON.parse in init command bridge generation - Extend bridge-sync verification to all detected IDEs (not just Claude) - Use path.parse(f).name for robust filename parsing in verify.js
Summary
kit updatenever overwrites user-created custom commandskit init.claude/commands/tracked via gitignore negation patternArchitecture Changes
lib/command-bridge.jslib/commands/init.jsdocs/ide-support.mdlib/constants.jslib/ide-generator.jswriteBridgeConfigs()with atomic provenance checklib/io.jsensureClaudeCommandsTracked()+checkBridgeGitignoreWarnings()bin/kit.js--skip-commandsflaglib/updater.jslib/verify.jsSecurity Audit
3 rounds of audit completed (code review + security scan + structural integrity):
Test Plan
tests/unit/command-bridge.test.js— 37 test casestests/unit/gitignore.test.js— 10 new testskit init --forcegenerates bridge files for detected IDEskit verifydetects missing/orphaned bridges