-
Notifications
You must be signed in to change notification settings - Fork 616
feat: implement skills management system with UI and backend integration #1266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Add SkillTools class for handling skill list and control actions. - Extend ConversationsTable to include active_skills field in the database. - Create SkillsSettings.vue component for managing skills in the UI. - Update router to include skills settings route. - Add translations for skills management in English and Chinese. - Implement skills store using Pinia for state management. - Define skill-related types in shared types directory. - Integrate skill installation and uninstallation functionalities.
|
Important Review skippedToo many files! 33 files out of 183 files are above the max files limit of 150. You can disable this status message by setting the 📝 WalkthroughWalkthroughAdds a Skills subsystem: presenter- and renderer-level skill management, SkillSync adapters/converters, agent integration (prompt augmentation and skill tools), file-permission workflow, DB/session persistence for active skills, extensive UI (settings, sync wizards, chat slash UX), tests, i18n, and packaging resources. Changes
Sequence Diagram(s)sequenceDiagram
actor Renderer
participant Presenter
participant SkillPresenter
participant DB
Renderer->>Presenter: installFromFolder/installFromZip/updateSkillFile
Presenter->>SkillPresenter: perform install/update
SkillPresenter->>SkillPresenter: validate frontmatter, write files, update caches
SkillPresenter->>DB: persist metadata/state (active_skills)
SkillPresenter->>Presenter: emit skill installed / metadata-updated
Presenter->>Renderer: forward event -> UI reload
sequenceDiagram
participant Agent
participant Presenter
participant SkillPresenter
participant AgentToolManager
Agent->>Presenter: prepareAgentPrompt(conversationId)
Presenter->>SkillPresenter: getActiveSkills(conversationId)
Presenter->>SkillPresenter: loadSkillContent(...) for active skills
SkillPresenter-->>Presenter: active content + allowedTools
Presenter->>AgentToolManager: merge allowedTools with enabledMcpTools
AgentToolManager-->>Presenter: filtered tool definitions
Presenter->>Agent: final system prompt (with skills metadata/content) and filtered tool list
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Tip For best results, initiate chat on the files or code changes. @deepinfect: Hello! I see you've tagged me. How can I assist you with this pull request? I can help you with various tasks, such as:
Please let me know what you'd like me to do! |
|
@coderabbitai help |
This comment was marked as resolved.
This comment was marked as resolved.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
- Add SkillTools class for handling skill list and control actions. - Extend ConversationsTable to include active_skills field in the database. - Create SkillsSettings.vue component for managing skills in the UI. - Update router to include skills settings route. - Add translations for skills management in English and Chinese. - Implement skills store using Pinia for state management. - Define skill-related types in shared types directory. - Integrate skill installation and uninstallation functionalities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🤖 Fix all issues with AI agents
In @docs/specs/skills-system/tasks.md:
- Around line 212-234: The fenced ASCII diagram block in the docs lacks a
language identifier; update the triple-backtick fence around the diagram in
docs/specs/skills-system/tasks.md to include a language specifier (e.g., "text")
so the block becomes ```text ... ```, or alternatively convert the block into a
Mermaid diagram and mark it as ```mermaid with the corresponding graph
definition; ensure only the fence is changed so linting passes and rendering is
preserved.
In @src/main/presenter/skillPresenter/index.ts:
- Around line 132-145: The code logs a warning when data.name !== dirName but
still returns data.name, causing inconsistent canonical names; change the
returned name to always use dirName when they differ (i.e., set name: dirName if
data.name !== dirName, otherwise data.name) in the return object inside the same
function, and keep description/path/skillRoot/allowedTools logic unchanged so
downstream code like installFromDirectory uses dirName as the canonical skill
name.
- Around line 499-521: uninstallSkill currently uses path.join(this.skillsDir,
name) without validation, allowing path traversal (e.g., "../config") to delete
outside directories; fix by resolving and validating the target directory before
removal: compute resolvedSkillsDir = path.resolve(this.skillsDir) and
resolvedSkillDir = path.resolve(resolvedSkillsDir, name), ensure
resolvedSkillDir === resolvedSkillsDir or
resolvedSkillDir.startsWith(resolvedSkillsDir + path.sep) (or alternatively
reject names containing path separators and allow only a safe whitelist/regex),
and only then proceed to delete with fs.rmSync and clear
metadataCache/contentCache; also use the validated resolvedSkillDir in fs.rmSync
instead of the unvalidated path.join result.
- Around line 564-587: The buildFolderTree function currently recurses without
bounds and follows symlinks; modify buildFolderTree to accept a
depth/currentDepth parameter (or overload) and enforce a MAX_DEPTH constant to
stop recursion when reached, skip entries that are symbolic links by checking
Dirent.isSymbolicLink() (or use fs.lstatSync to detect symlinks) and do not
descend into them, and validate each resolved fullPath stays within the skill
root (use path.resolve and ensure it startsWith the base dir) before recursing
so traversal cannot escape the skill directory.
In @src/renderer/settings/components/SkillsSettings.vue:
- Around line 261-276: The openSkillEditor function currently calls
usePresenter('filePresenter') inside the handler which violates Vue's
composition API rules; move the call to usePresenter to the top-level of the
<script setup> (define a top-level const filePresenter =
usePresenter('filePresenter') alongside other composables) and then remove the
internal call so openSkillEditor uses that top-level filePresenter; also keep
the existing try/catch and consider a null-check or type assertion on
filePresenter if necessary before calling filePresenter.readFile(skill.path).
In @src/renderer/src/i18n/en-US/routes.json:
- Line 17: Add the missing "settings-skills" translation key (as shown in en-US
routes.json) to the routes.json for each remaining locale: da-DK, fa-IR, fr-FR,
he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, and zh-TW; ensure the key name is
exactly "settings-skills" in each file and provide the appropriate localized
string for "Skills" in each locale (matching the style and JSON structure used
by existing route translation files).
In @src/renderer/src/i18n/en-US/settings.json:
- Around line 1042-1073: Add the complete "skills" translation block (keys:
skills.title, skills.description, skills.openFolder, skills.addSkill,
skills.empty, skills.emptyHint,
skills.install.{title,description,fromFolder,selectFolder,success,successMessage,failed},
skills.delete.{title,description,success,successMessage,failed},
skills.edit.{title,placeholder,readFailed,success,failed}) to each missing
locale file (da-DK, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK,
zh-TW) using the existing en-US/zh-CN structure and consistent key names so all
language JSONs contain the same "skills" object.
In @src/renderer/src/i18n/zh-CN/settings.json:
- Around line 1042-1073: The new "skills" translation block (keys: skills.title,
skills.description, skills.openFolder, skills.addSkill, skills.empty,
skills.emptyHint and nested keys
skills.install.{title,description,fromFolder,selectFolder,success,successMessage,failed},
skills.delete.{title,description,success,successMessage,failed},
skills.edit.{title,placeholder,readFailed,success,failed}) was added only for
en-US and zh-CN; add the identical key structure with appropriate localized
strings to every missing locale file (da-DK, fa-IR, fr-FR, he-IL, ja-JP, ko-KR,
pt-BR, ru-RU, zh-HK, zh-TW) so all locale JSONs contain the same "skills" keys
and nested keys for consistency. Ensure translations match the meaning of each
key (e.g., successMessage includes {name} placeholder) and keep key names
identical across files.
🧹 Nitpick comments (13)
docs/specs/skills-system/tasks.md (1)
120-176: Several UI tasks are marked as partially complete or inconsistent with spec.Lines 120, 124, 126, 130-134, 152, 160-163 show tasks marked with
[~]indicating implementation deviations from the original specification:
- Navigation menu icon/position differs from spec
- UI uses list layout instead of card grid
- Missing ZIP/URL installation methods
- Missing footer, header components
- No real-time event monitoring
Consider either updating the specification to match the actual implementation or creating follow-up tasks to align the implementation with the original design.
docs/specs/skills-system/ui-design.md (1)
27-57: Consider adding language identifiers to fenced code blocks.Multiple fenced code blocks in the document lack language identifiers. While this doesn't affect functionality, adding appropriate identifiers improves rendering and clarity:
- ASCII art diagrams: use
```text- Flowcharts: use
```textor```mermaidif converting to proper diagrams- The TypeScript sections already have correct
```typescriptidentifiers ✓Example fix for ASCII art blocks
Change:
``` ┌─────────────────────────────────────────────────────────────────┐ │ Skills Settings │ ├─────────────────────────────────────────────────────────────────┤ ... ```To:
```text ┌─────────────────────────────────────────────────────────────────┐ │ Skills Settings │ ├─────────────────────────────────────────────────────────────────┤ ... ```Also applies to: 61-81, 91-105, 125-171, 183-205, 214-225, 233-247, 251-288, 292-320, 324-342, 348-362
docs/specs/skills-system/research.md (1)
40-47: Add language specifiers to fenced code blocks.Static analysis flagged multiple code blocks missing language specifiers (lines 40, 65, 117, 159, 167, 249, 310, 328, 356, 380). For directory structures and text diagrams, use
textorplaintext.Example fix for line 40
-``` +```text .claude/skills/ ├── skill-name/ │ ├── SKILL.md (必需) # 元数据 + 指令src/main/presenter/index.ts (1)
271-283: Consider addinginitialize()anddestroy()toISkillPresenterinterface.The type assertions
(this.skillPresenter as SkillPresenter).initialize()and(this.skillPresenter as SkillPresenter).destroy()suggest these lifecycle methods should be part of theISkillPresenterinterface rather than requiring casts to the concrete implementation.Suggested interface update in src/shared/types/skill.ts
export interface ISkillPresenter { + // Lifecycle + initialize(): Promise<void> + destroy(): void + // Discovery and listing getSkillsDir(): Promise<string>src/main/presenter/configPresenter/index.ts (1)
840-842: Consider emitting events when skills settings change.Unlike
setLanguage()orsetSyncEnabled()which emit events to notify renderers,setSkillsEnabled()andsetSkillsPath()don't broadcast changes. If the skills system needs to react to runtime configuration changes (e.g., hot-reload when path changes), consider adding event emission.Example pattern from existing code
setSkillsEnabled(enabled: boolean): void { this.setSetting('enableSkills', enabled) + eventBus.sendToRenderer(CONFIG_EVENTS.SKILLS_ENABLED_CHANGED, SendTarget.ALL_WINDOWS, enabled) } setSkillsPath(skillsPath: string): void { this.setSetting('skillsPath', skillsPath) + eventBus.sendToRenderer(CONFIG_EVENTS.SKILLS_PATH_CHANGED, SendTarget.ALL_WINDOWS, skillsPath) }Also applies to: 850-852
src/main/presenter/agentPresenter/acp/agentToolManager.ts (1)
652-657: Consider injectingskillPresenterinstead of accessing globalpresenter.
getSkillTools()accessespresenter.skillPresenterdirectly, creating tight coupling to the global singleton. For consistency with theconfigPresenterpattern already used, consider passingskillPresentervia options.Alternative approach
interface AgentToolManagerOptions { yoBrowserPresenter: IYoBrowserPresenter agentWorkspacePath: string | null configPresenter: IConfigPresenter + skillPresenter: ISkillPresenter commandPermissionHandler?: CommandPermissionService } -private getSkillTools(): SkillTools { - if (!this.skillTools) { - this.skillTools = new SkillTools(presenter.skillPresenter) - } - return this.skillTools -} +private getSkillTools(): SkillTools { + if (!this.skillTools) { + this.skillTools = new SkillTools(this.skillPresenter) + } + return this.skillTools +}src/main/presenter/skillPresenter/skillTools.ts (1)
59-68: Consider reporting which skills were actually affected in deactivate action.For
activate, you validate and report only the valid skills. Fordeactivate,affectedSkillsincludes all requested names even if they weren't active. Consider filtering to only those that were actually removed for consistency.Suggested improvement
} else { - skillNames.forEach((skill) => currentSet.delete(skill)) - newActiveSkills = Array.from(currentSet) - affectedSkills = skillNames + affectedSkills = skillNames.filter((skill) => currentSet.has(skill)) + affectedSkills.forEach((skill) => currentSet.delete(skill)) + newActiveSkills = Array.from(currentSet) }src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts (3)
1-2: Unused type import.
SkillPresenteris imported but only used for type assertions. Sincepresenter.skillPresenteralready implementsISkillPresenter, the type assertion and import may be unnecessary if you rely on the interface.♻️ Consider using the interface directly
import { presenter } from '@/presenter' -import { SkillPresenter } from '../../skillPresenter' +import type { ISkillPresenter } from '@shared/types/skill'Then replace all
presenter.skillPresenter as SkillPresenterwithpresenter.skillPresenter as ISkillPresenteror access the methods directly if the presenter type is properly typed.
30-35: Consider parallel loading of skill content.The sequential
awaitinside the loop may be slow if there are many active skills. Loading content in parallel would improve performance.♻️ Parallel loading suggestion
- const skillContents: string[] = [] - - for (const skillName of activeSkills) { - const skillContent = await skillPresenter.loadSkillContent(skillName) - if (skillContent && skillContent.content) { - skillContents.push(`## Skill: ${skillName}\n\n${skillContent.content}`) - } - } + const loadedContents = await Promise.all( + activeSkills.map((skillName) => skillPresenter.loadSkillContent(skillName)) + ) + + const skillContents = loadedContents + .filter((sc): sc is NonNullable<typeof sc> => sc !== null && !!sc.content) + .map((sc) => `## Skill: ${sc.name}\n\n${sc.content}`)
52-75: Duplicated logic withSkillPresenter.getMetadataPrompt().This function replicates the logic in
SkillPresenter.getMetadataPrompt()(lines 165-176 of skillPresenter/index.ts). Consider delegating to avoid duplication.♻️ Delegate to SkillPresenter
export async function buildSkillsMetadataPrompt(): Promise<string> { try { if (!isSkillsEnabled()) { return '' } const skillPresenter = presenter.skillPresenter as SkillPresenter - const skillsDir = await skillPresenter.getSkillsDir() - const skills = await skillPresenter.getMetadataList() - - const header = '# Available Skills' - const dirLine = `Skills directory: ${skillsDir}` - - if (skills.length === 0) { - return `${header}\n\n${dirLine}\nNo skills are currently installed.` - } - - const lines = skills.map((skill) => `- ${skill.name}: ${skill.description}`) - return `${header}\n\n${dirLine}\nYou can activate these skills using skill_control tool:\n${lines.join('\n')}` + return await skillPresenter.getMetadataPrompt() } catch (error) { console.warn('[SkillsPromptBuilder] Failed to build skills metadata prompt:', error) return '' } }src/main/presenter/skillPresenter/index.ts (3)
83-86: Content cache cleared unnecessarily during skill discovery.
discoverSkillsclearscontentCacheeven though skill content may not have changed. This forces re-loading content from disk on the next access. Consider only clearingcontentCacheentries for skills that are actually removed or changed, or leave content cache management to the file watcher.♻️ Preserve valid content cache entries
async discoverSkills(): Promise<SkillMetadata[]> { - this.metadataCache.clear() - this.contentCache.clear() + const previousMetadata = new Set(this.metadataCache.keys()) + this.metadataCache.clear() if (!fs.existsSync(this.skillsDir)) { + this.contentCache.clear() return [] } const entries = fs.readdirSync(this.skillsDir, { withFileTypes: true }) + const discoveredSkills = new Set<string>() for (const entry of entries) { if (entry.isDirectory()) { const skillPath = path.join(this.skillsDir, entry.name, 'SKILL.md') if (fs.existsSync(skillPath)) { try { const metadata = await this.parseSkillMetadata(skillPath, entry.name) if (metadata) { this.metadataCache.set(entry.name, metadata) + discoveredSkills.add(entry.name) } } catch (error) { console.error(`[SkillPresenter] Failed to parse skill ${entry.name}:`, error) } } } } + + // Clear content cache only for removed skills + for (const skillName of previousMetadata) { + if (!discoveredSkills.has(skillName)) { + this.contentCache.delete(skillName) + } + }
704-747: Missing error handling in watcher callbacks.The
changeandaddevent handlers are async but exceptions aren't caught. IfparseSkillMetadataoreventBus.sendToRendererthrows, the error will be unhandled.♻️ Add try-catch to watcher callbacks
this.watcher.on('change', async (filePath: string) => { + try { if (path.basename(filePath) === 'SKILL.md') { const skillDir = path.dirname(filePath) const skillName = path.basename(skillDir) // Invalidate caches this.contentCache.delete(skillName) // Re-parse metadata const metadata = await this.parseSkillMetadata(filePath, skillName) if (metadata) { this.metadataCache.set(skillName, metadata) eventBus.sendToRenderer(SKILL_EVENTS.METADATA_UPDATED, SendTarget.ALL_WINDOWS, metadata) } } + } catch (error) { + console.error('[SkillPresenter] Error handling file change:', error) + } })Apply similar wrapping to the
addhandler.
766-781: Consider handling symlinks in copyDirectory.Similar to
buildFolderTree,copyDirectorycould follow symlinks that point outside the source directory, potentially copying unintended files.♻️ Skip symlinks during copy
for (const entry of entries) { + if (entry.isSymbolicLink()) { + continue // Skip symlinks for security + } + const srcPath = path.join(src, entry.name) const destPath = path.join(dest, entry.name)
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (29)
CLAUDE.mddocs/specs/skills-system/design.mddocs/specs/skills-system/research.mddocs/specs/skills-system/tasks.mddocs/specs/skills-system/ui-design.mdpackage.jsonsrc/main/events.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/configPresenter/index.tssrc/main/presenter/index.tssrc/main/presenter/sessionPresenter/const.tssrc/main/presenter/sessionPresenter/types.tssrc/main/presenter/skillPresenter/index.tssrc/main/presenter/skillPresenter/skillTools.tssrc/main/presenter/sqlitePresenter/tables/conversations.tssrc/main/presenter/toolPresenter/index.tssrc/renderer/settings/components/SkillsSettings.vuesrc/renderer/settings/main.tssrc/renderer/src/i18n/en-US/routes.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/zh-CN/routes.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/stores/skillsStore.tssrc/shared/types/index.d.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/shared/types/presenters/thread.presenter.d.tssrc/shared/types/skill.ts
🧰 Additional context used
📓 Path-based instructions (21)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use English for logs and comments in TypeScript/JavaScript code
Files:
src/main/presenter/sessionPresenter/types.tssrc/main/events.tssrc/shared/types/presenters/thread.presenter.d.tssrc/shared/types/index.d.tssrc/shared/types/skill.tssrc/main/presenter/sqlitePresenter/tables/conversations.tssrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/toolPresenter/index.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/renderer/src/stores/skillsStore.tssrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/configPresenter/index.tssrc/main/presenter/index.tssrc/renderer/settings/main.tssrc/main/presenter/skillPresenter/skillTools.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/sessionPresenter/const.tssrc/main/presenter/skillPresenter/index.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript with strict type checking enabled
Use OxLint for linting JavaScript and TypeScript files; ensure lint-staged hooks and typecheck pass before commits
Files:
src/main/presenter/sessionPresenter/types.tssrc/main/events.tssrc/shared/types/presenters/thread.presenter.d.tssrc/shared/types/index.d.tssrc/shared/types/skill.tssrc/main/presenter/sqlitePresenter/tables/conversations.tssrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/toolPresenter/index.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/renderer/src/stores/skillsStore.tssrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/configPresenter/index.tssrc/main/presenter/index.tssrc/renderer/settings/main.tssrc/main/presenter/skillPresenter/skillTools.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/sessionPresenter/const.tssrc/main/presenter/skillPresenter/index.ts
src/main/presenter/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/presenter/**/*.ts: Use EventBus to broadcast events from main to renderer viamainWindow.webContents.send()
Implement one presenter per functional domain in the main process
Files:
src/main/presenter/sessionPresenter/types.tssrc/main/presenter/sqlitePresenter/tables/conversations.tssrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/toolPresenter/index.tssrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/configPresenter/index.tssrc/main/presenter/index.tssrc/main/presenter/skillPresenter/skillTools.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/sessionPresenter/const.tssrc/main/presenter/skillPresenter/index.ts
src/main/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/**/*.ts: Use EventBus fromsrc/main/eventbus.tsfor decoupled inter-process communication
Context isolation must be enabled with preload scripts for secure IPC communicationElectron main process code should reside in
src/main/, with presenters organized inpresenter/subdirectory (Window, Tab, Thread, Mcp, Config, LLMProvider), and app events managed viaeventbus.ts
Files:
src/main/presenter/sessionPresenter/types.tssrc/main/events.tssrc/main/presenter/sqlitePresenter/tables/conversations.tssrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/toolPresenter/index.tssrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/configPresenter/index.tssrc/main/presenter/index.tssrc/main/presenter/skillPresenter/skillTools.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/sessionPresenter/const.tssrc/main/presenter/skillPresenter/index.ts
**/*.{js,ts,tsx,jsx,vue,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
All logs and comments must be in English
Files:
src/main/presenter/sessionPresenter/types.tssrc/main/events.tssrc/shared/types/presenters/thread.presenter.d.tssrc/shared/types/index.d.tssrc/shared/types/skill.tssrc/main/presenter/sqlitePresenter/tables/conversations.tssrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/toolPresenter/index.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/renderer/src/stores/skillsStore.tssrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/configPresenter/index.tssrc/main/presenter/index.tssrc/renderer/settings/main.tssrc/main/presenter/skillPresenter/skillTools.tssrc/renderer/settings/components/SkillsSettings.vuesrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/sessionPresenter/const.tssrc/main/presenter/skillPresenter/index.ts
**/*.{js,ts,tsx,jsx,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use OxLint as the linter
Files:
src/main/presenter/sessionPresenter/types.tssrc/main/events.tssrc/shared/types/presenters/thread.presenter.d.tssrc/shared/types/index.d.tssrc/shared/types/skill.tssrc/main/presenter/sqlitePresenter/tables/conversations.tssrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/toolPresenter/index.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/renderer/src/stores/skillsStore.tssrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/configPresenter/index.tssrc/main/presenter/index.tssrc/renderer/settings/main.tssrc/main/presenter/skillPresenter/skillTools.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/sessionPresenter/const.tssrc/main/presenter/skillPresenter/index.ts
**/*.{js,ts,tsx,jsx,vue,json,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use Prettier as the code formatter
Files:
src/main/presenter/sessionPresenter/types.tssrc/main/events.tssrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/routes.jsonsrc/renderer/src/i18n/zh-CN/routes.jsonsrc/shared/types/presenters/thread.presenter.d.tssrc/shared/types/index.d.tspackage.jsonsrc/shared/types/skill.tssrc/main/presenter/sqlitePresenter/tables/conversations.tssrc/renderer/src/i18n/en-US/settings.jsonsrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/toolPresenter/index.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/renderer/src/stores/skillsStore.tssrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/configPresenter/index.tssrc/main/presenter/index.tssrc/renderer/settings/main.tssrc/main/presenter/skillPresenter/skillTools.tssrc/renderer/settings/components/SkillsSettings.vuesrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/sessionPresenter/const.tssrc/main/presenter/skillPresenter/index.ts
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,vue}: Use camelCase for variable and function names; use PascalCase for types and classes; use SCREAMING_SNAKE_CASE for constants
Configure Prettier with single quotes, no semicolons, and line width of 100 characters. Runpnpm run formatafter completing features
Files:
src/main/presenter/sessionPresenter/types.tssrc/main/events.tssrc/shared/types/presenters/thread.presenter.d.tssrc/shared/types/index.d.tssrc/shared/types/skill.tssrc/main/presenter/sqlitePresenter/tables/conversations.tssrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/toolPresenter/index.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/renderer/src/stores/skillsStore.tssrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/configPresenter/index.tssrc/main/presenter/index.tssrc/renderer/settings/main.tssrc/main/presenter/skillPresenter/skillTools.tssrc/renderer/settings/components/SkillsSettings.vuesrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/sessionPresenter/const.tssrc/main/presenter/skillPresenter/index.ts
src/renderer/src/i18n/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
src/renderer/src/i18n/**/*.json: Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/routes.jsonsrc/renderer/src/i18n/zh-CN/routes.jsonsrc/renderer/src/i18n/en-US/settings.json
src/renderer/src/i18n/{zh-CN,zh-HK,zh-TW}/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
For Chinese translations, consider using '智能体' for 'Agents' instead of the English term
Files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/zh-CN/routes.json
src/renderer/src/i18n/en-US/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
Use English (en-US) as the reference for translation accuracy when adding new keys
Files:
src/renderer/src/i18n/en-US/routes.jsonsrc/renderer/src/i18n/en-US/settings.json
src/shared/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/shared/**/*.ts: Shared types between main and renderer processes must be placed insrc/shared/
IPC contract definitions must be placed insrc/shared/Shared TypeScript types and utilities should be placed in
src/shared/
Files:
src/shared/types/presenters/thread.presenter.d.tssrc/shared/types/index.d.tssrc/shared/types/skill.tssrc/shared/types/presenters/legacy.presenters.d.ts
src/renderer/src/**/stores/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Use Pinia for frontend state management
Files:
src/renderer/src/stores/skillsStore.ts
src/renderer/src/**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
usePresenter.tscomposable for renderer-to-main IPC communication via direct presenter method callsEnsure all code comments are in English and all log messages are in English, with no non-English text in code comments or console statements
Use VueUse composables for common utilities like
useLocalStorage,useClipboard,useDebounceFnVue 3 renderer app code should be organized in
src/renderer/srcwith subdirectories forcomponents/,stores/,views/,i18n/, andlib/
Files:
src/renderer/src/stores/skillsStore.ts
src/renderer/src/**/*.{vue,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
src/renderer/src/**/*.{vue,ts,tsx}: Use vue-i18n framework for internationalization located at src/renderer/src/i18n/
All user-facing strings must use i18n keys, not hardcoded text
src/renderer/src/**/*.{vue,ts,tsx}: Usereffor primitives and references,reactivefor objects in Vue 3 Composition API
Prefercomputedproperties over methods for derived state in Vue components
Import Shadcn Vue components from@/shadcn/components/ui/path alias
Use thecn()utility function combining clsx and tailwind-merge for dynamic Tailwind classes
UsedefineAsyncComponent()for lazy loading heavy Vue components
Use TypeScript for all Vue components and composables with explicit type annotations
Define TypeScript interfaces for Vue component props and data structures
UseusePresentercomposable for main process communication instead of direct IPC calls
Files:
src/renderer/src/stores/skillsStore.ts
src/renderer/src/stores/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
src/renderer/src/stores/**/*.ts: Use Setup Store syntax withdefineStorefunction pattern in Pinia stores
Use getters (computed properties) for derived state in Pinia stores
Keep Pinia store actions focused on state mutations and async operations
Files:
src/renderer/src/stores/skillsStore.ts
src/renderer/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
Use class-variance-authority (CVA) for defining component variants with Tailwind classes
Files:
src/renderer/src/stores/skillsStore.ts
src/renderer/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
src/renderer/src/**/*.{ts,tsx}: UseshallowRefandshallowReactivefor optimizing reactivity with large objects
Prefertypeoverinterfacein TypeScript unless using inheritance withextends
Files:
src/renderer/src/stores/skillsStore.ts
src/main/presenter/configPresenter/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Custom prompts are managed independently of MCP through config data source using
configPresenter.getCustomPrompts()
Files:
src/main/presenter/configPresenter/index.ts
src/renderer/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
src/renderer/**/*.vue: Use Vue 3 Composition API for all components
Use Tailwind CSS for styling with scoped styles
All user-facing strings must use i18n keys via vue-i18n
Files:
src/renderer/settings/components/SkillsSettings.vue
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Vue components must be named in PascalCase (e.g.,
ChatInput.vue) and use Vue 3 Composition API with Pinia for state management and Tailwind for styling
Files:
src/renderer/settings/components/SkillsSettings.vue
🧠 Learnings (31)
📚 Learning: 2026-01-05T02:41:13.293Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.293Z
Learning: Applies to src/renderer/src/i18n/{zh-CN,zh-HK,zh-TW}/**/*.json : For Chinese translations, consider using '智能体' for 'Agents' instead of the English term
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/zh-CN/routes.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.293Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.293Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/routes.jsonsrc/renderer/src/i18n/zh-CN/routes.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.293Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.293Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/routes.jsonsrc/renderer/src/i18n/zh-CN/routes.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.293Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.293Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/routes.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.293Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.293Z
Learning: Applies to src/renderer/src/i18n/en-US/**/*.json : Use English (en-US) as the reference for translation accuracy when adding new keys
Applied to files:
src/renderer/src/i18n/en-US/routes.jsonsrc/renderer/src/i18n/zh-CN/routes.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:45.204Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.204Z
Learning: Applies to src/renderer/src/i18n/**/*.ts : All user-facing strings must use vue-i18n keys defined in `src/renderer/src/i18n/`
Applied to files:
src/renderer/src/i18n/zh-CN/routes.json
📚 Learning: 2026-01-05T02:41:45.204Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.204Z
Learning: Applies to src/shared/**/*.ts : Shared TypeScript types and utilities should be placed in `src/shared/`
Applied to files:
src/shared/types/index.d.ts
📚 Learning: 2026-01-05T02:40:52.831Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.831Z
Learning: Applies to src/main/presenter/**/*.ts : Implement one presenter per functional domain in the main process
Applied to files:
src/shared/types/index.d.tssrc/main/presenter/toolPresenter/index.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/main/presenter/index.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/skillPresenter/index.ts
📚 Learning: 2026-01-05T02:40:52.831Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.831Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Use `usePresenter.ts` composable for renderer-to-main IPC communication via direct presenter method calls
Applied to files:
src/shared/types/index.d.tssrc/main/presenter/toolPresenter/index.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/main/presenter/index.tssrc/main/presenter/agentPresenter/acp/agentToolManager.ts
📚 Learning: 2026-01-05T02:40:52.831Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.831Z
Learning: Applies to src/main/presenter/mcpPresenter/inMemoryServers/**/*.ts : MCP tool implementations must be registered in `mcpPresenter/index.ts` after implementation
Applied to files:
src/shared/types/index.d.tssrc/main/presenter/toolPresenter/index.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/index.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/skillPresenter/index.ts
📚 Learning: 2026-01-05T02:40:52.831Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.831Z
Learning: Applies to src/main/presenter/**/*.ts : Use EventBus to broadcast events from main to renderer via `mainWindow.webContents.send()`
Applied to files:
src/shared/types/index.d.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/main/presenter/index.ts
📚 Learning: 2026-01-05T02:41:45.204Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.204Z
Learning: Applies to src/main/**/*.ts : Electron main process code should reside in `src/main/`, with presenters organized in `presenter/` subdirectory (Window, Tab, Thread, Mcp, Config, LLMProvider), and app events managed via `eventbus.ts`
Applied to files:
src/shared/types/index.d.tsCLAUDE.md
📚 Learning: 2026-01-05T02:41:45.204Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.204Z
Learning: Project uses Node.js ≥ 20.19 and pnpm ≥ 10.11 (pnpm only); Windows developers must enable Developer Mode for symlinks
Applied to files:
package.json
📚 Learning: 2026-01-05T02:41:01.036Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/development-setup.mdc:0-0
Timestamp: 2026-01-05T02:41:01.036Z
Learning: Require Node.js >= 20.19.0 and pnpm >= 10.11.0
Applied to files:
package.json
📚 Learning: 2026-01-05T02:41:45.204Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.204Z
Learning: Conventional commits are enforced: format as `type(scope): subject` with subject ≤ 50 characters. Allowed types: feat, fix, docs, dx, style, refactor, perf, test, workflow, build, ci, chore, types, wip, release. Do not include AI co-authoring footers
Applied to files:
CLAUDE.md
📚 Learning: 2026-01-05T02:41:45.204Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.204Z
Learning: PRs must include clear description, link related issues with `Closes #123`, include screenshots/GIFs for UI changes with BEFORE/AFTER ASCII layout blocks, pass lint/typecheck/tests, and keep changes focused
Applied to files:
CLAUDE.md
📚 Learning: 2026-01-05T02:40:52.831Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.831Z
Learning: Applies to src/main/presenter/configPresenter/**/*.ts : Custom prompts are managed independently of MCP through config data source using `configPresenter.getCustomPrompts()`
Applied to files:
src/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/toolPresenter/index.tssrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/configPresenter/index.tssrc/main/presenter/agentPresenter/acp/agentToolManager.ts
📚 Learning: 2026-01-05T02:41:31.619Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.619Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Use `usePresenter` composable for main process communication instead of direct IPC calls
Applied to files:
src/main/presenter/toolPresenter/index.tssrc/shared/types/presenters/legacy.presenters.d.tssrc/main/presenter/index.ts
📚 Learning: 2026-01-05T02:41:31.619Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.619Z
Learning: Applies to src/renderer/src/stores/**/*.ts : Use Setup Store syntax with `defineStore` function pattern in Pinia stores
Applied to files:
src/renderer/src/stores/skillsStore.ts
📚 Learning: 2026-01-05T02:40:52.831Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.831Z
Learning: Applies to src/renderer/src/**/stores/*.ts : Use Pinia for frontend state management
Applied to files:
src/renderer/src/stores/skillsStore.ts
📚 Learning: 2026-01-05T02:41:31.619Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.619Z
Learning: Applies to src/renderer/src/stores/**/*.ts : Keep Pinia store actions focused on state mutations and async operations
Applied to files:
src/renderer/src/stores/skillsStore.ts
📚 Learning: 2026-01-05T02:41:31.619Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.619Z
Learning: Applies to src/renderer/src/stores/**/*.ts : Use getters (computed properties) for derived state in Pinia stores
Applied to files:
src/renderer/src/stores/skillsStore.ts
📚 Learning: 2026-01-05T02:41:31.619Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.619Z
Learning: Applies to src/renderer/src/stores/ : Separate concerns in Pinia stores: use different stores for different domains
Applied to files:
src/renderer/src/stores/skillsStore.ts
📚 Learning: 2026-01-05T02:40:52.831Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.831Z
Learning: Applies to src/main/presenter/llmProviderPresenter/providers/*.ts : LLM provider implementations must follow the standardized event interface with `coreStream` method
Applied to files:
src/main/presenter/index.ts
📚 Learning: 2026-01-05T02:41:45.204Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.204Z
Learning: Use Presenter pattern in main process, EventBus for inter-process events, and two-layer LLM provider architecture (Agent Loop + Provider) with integrated MCP tools
Applied to files:
src/main/presenter/index.ts
📚 Learning: 2026-01-05T02:41:31.619Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.619Z
Learning: Applies to src/renderer/src/router/**/*.ts : Use named routes for easier Vue Router navigation instead of string paths
Applied to files:
src/renderer/settings/main.ts
📚 Learning: 2026-01-05T02:41:31.619Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.619Z
Learning: Applies to src/renderer/src/router/**/*.ts : Use route meta for storing additional route data like title and icon
Applied to files:
src/renderer/settings/main.ts
📚 Learning: 2026-01-05T02:41:31.619Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.619Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Import Shadcn Vue components from `@/shadcn/components/ui/` path alias
Applied to files:
src/renderer/settings/main.ts
📚 Learning: 2026-01-05T02:41:31.619Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.619Z
Learning: Applies to src/renderer/src/router/**/*.ts : Implement lazy loading in Vue Router using dynamic imports for route components
Applied to files:
src/renderer/settings/main.ts
📚 Learning: 2026-01-05T02:40:52.831Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.831Z
Learning: Prefer lightweight spec artifacts under `docs/specs/<feature>/` (spec/plan/tasks) and resolve `[NEEDS CLARIFICATION]` markers before coding
Applied to files:
docs/specs/skills-system/tasks.md
📚 Learning: 2026-01-05T02:41:45.204Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.204Z
Learning: Follow Specification-Driven Development (SDD) methodology; create spec artifacts under `docs/specs/<feature>/` with spec/plan/tasks, resolving any `[NEEDS CLARIFICATION]` items before implementation
Applied to files:
docs/specs/skills-system/tasks.md
🧬 Code graph analysis (8)
src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts (3)
src/main/presenter/agentPresenter/acp/agentToolManager.ts (1)
isSkillsEnabled(648-650)src/main/presenter/index.ts (1)
presenter(321-321)src/main/presenter/skillPresenter/index.ts (1)
SkillPresenter(30-791)
src/shared/types/presenters/legacy.presenters.d.ts (1)
src/shared/types/skill.ts (1)
ISkillPresenter(91-124)
src/renderer/src/stores/skillsStore.ts (2)
src/shared/types/skill.ts (2)
SkillMetadata(13-24)SkillInstallResult(40-44)src/main/presenter/skillPresenter/index.ts (6)
installFromFolder(272-277)uninstallSkill(499-521)getSkillsDir(64-66)openSkillsFolder(592-595)updateSkillFile(526-547)getSkillFolderTree(552-559)
src/main/presenter/agentPresenter/message/messageBuilder.ts (1)
src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts (3)
getSkillsAllowedTools(84-96)buildSkillsMetadataPrompt(52-75)buildSkillsPrompt(15-46)
src/main/presenter/configPresenter/index.ts (3)
test/main/presenter/providerDbModelConfig.test.ts (1)
path(33-35)test/main/presenter/modelConfig.test.ts (1)
path(49-51)test/mocks/electron.ts (1)
app(2-10)
src/main/presenter/index.ts (2)
src/shared/types/skill.ts (1)
ISkillPresenter(91-124)src/main/presenter/skillPresenter/index.ts (1)
SkillPresenter(30-791)
src/main/presenter/skillPresenter/skillTools.ts (1)
src/shared/types/skill.ts (3)
ISkillPresenter(91-124)SkillListItem(77-81)SkillControlAction(86-86)
src/main/presenter/skillPresenter/index.ts (3)
src/shared/types/skill.ts (5)
SkillMetadata(13-24)SkillContent(30-35)SkillInstallOptions(49-51)SkillInstallResult(40-44)SkillFolderNode(56-61)src/main/eventbus.ts (1)
eventBus(151-151)src/main/events.ts (1)
SKILL_EVENTS(265-272)
🪛 markdownlint-cli2 (0.18.1)
docs/specs/skills-system/ui-design.md
27-27: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
61-61: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
91-91: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
125-125: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
183-183: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
214-214: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
233-233: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
251-251: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
292-292: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
324-324: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
348-348: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
CLAUDE.md
18-18: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
73-73: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
105-105: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
114-114: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
docs/specs/skills-system/research.md
40-40: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
65-65: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
117-117: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
159-159: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
167-167: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
249-249: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
310-310: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
328-328: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
356-356: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
380-380: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
docs/specs/skills-system/tasks.md
212-212: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
| ``` | ||
| Phase 1 (基础设施) | ||
| │ | ||
| ├── 1.1-1.3 类型/数据库/配置 ─┐ | ||
| │ │ | ||
| ├── 1.4 SkillPresenter ──────┼── Phase 2 (Agent Loop) | ||
| │ │ │ | ||
| ├── 1.5 安装/卸载 ───────────┤ ├── 2.1-2.2 工具/Context | ||
| │ │ │ | ||
| ├── 1.6 热加载 ──────────────┤ └── 2.3-2.4 工具合并/事件 | ||
| │ │ | ||
| └── 1.7 会话状态 ────────────┘ | ||
| │ | ||
| ▼ | ||
| Phase 3 (UI) | ||
| │ | ||
| ├── 3.1-3.2 路由/Store | ||
| │ | ||
| └── 3.3-3.9 组件 | ||
| │ | ||
| ▼ | ||
| Phase 4 (完善) | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add language specification to fenced code block.
The fenced code block containing the ASCII dependency diagram should specify a language identifier to satisfy markdown linting rules. Use text or mermaid (if converting to a Mermaid diagram) as the language specifier.
📝 Proposed fix
-```
+```text
Phase 1 (基础设施)
│
├── 1.1-1.3 类型/数据库/配置 ─┐Alternatively, consider converting to a Mermaid diagram for better rendering:
-```
+```mermaid
+graph TD
+ A[Phase 1: 基础设施] --> B[Phase 2: Agent Loop]
+ B --> C[Phase 3: UI]
+ C --> D[Phase 4: 完善]🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
212-212: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In @docs/specs/skills-system/tasks.md around lines 212 - 234, The fenced ASCII
diagram block in the docs lacks a language identifier; update the
triple-backtick fence around the diagram in docs/specs/skills-system/tasks.md to
include a language specifier (e.g., "text") so the block becomes ```text ...
```, or alternatively convert the block into a Mermaid diagram and mark it as
```mermaid with the corresponding graph definition; ensure only the fence is
changed so linting passes and rendering is preserved.
| // Ensure name matches directory name | ||
| if (data.name !== dirName) { | ||
| console.warn( | ||
| `[SkillPresenter] Skill name "${data.name}" doesn't match directory "${dirName}"` | ||
| ) | ||
| } | ||
|
|
||
| return { | ||
| name: data.name || dirName, | ||
| description: data.description || '', | ||
| path: skillPath, | ||
| skillRoot: path.dirname(skillPath), | ||
| allowedTools: Array.isArray(data.allowedTools) ? data.allowedTools : undefined | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name mismatch handling is inconsistent.
When data.name !== dirName, a warning is logged but data.name is still used (line 140). This could cause issues since later code (e.g., installFromDirectory) uses the name to construct paths. Consider enforcing that name matches dirName or always using dirName as the canonical name.
🐛 Use dirName as canonical name
// Ensure name matches directory name
if (data.name !== dirName) {
console.warn(
`[SkillPresenter] Skill name "${data.name}" doesn't match directory "${dirName}"`
)
}
return {
- name: data.name || dirName,
+ name: dirName,
description: data.description || '',
path: skillPath,
skillRoot: path.dirname(skillPath),
allowedTools: Array.isArray(data.allowedTools) ? data.allowedTools : undefined
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Ensure name matches directory name | |
| if (data.name !== dirName) { | |
| console.warn( | |
| `[SkillPresenter] Skill name "${data.name}" doesn't match directory "${dirName}"` | |
| ) | |
| } | |
| return { | |
| name: data.name || dirName, | |
| description: data.description || '', | |
| path: skillPath, | |
| skillRoot: path.dirname(skillPath), | |
| allowedTools: Array.isArray(data.allowedTools) ? data.allowedTools : undefined | |
| } | |
| // Ensure name matches directory name | |
| if (data.name !== dirName) { | |
| console.warn( | |
| `[SkillPresenter] Skill name "${data.name}" doesn't match directory "${dirName}"` | |
| ) | |
| } | |
| return { | |
| name: dirName, | |
| description: data.description || '', | |
| path: skillPath, | |
| skillRoot: path.dirname(skillPath), | |
| allowedTools: Array.isArray(data.allowedTools) ? data.allowedTools : undefined | |
| } |
🤖 Prompt for AI Agents
In @src/main/presenter/skillPresenter/index.ts around lines 132 - 145, The code
logs a warning when data.name !== dirName but still returns data.name, causing
inconsistent canonical names; change the returned name to always use dirName
when they differ (i.e., set name: dirName if data.name !== dirName, otherwise
data.name) in the return object inside the same function, and keep
description/path/skillRoot/allowedTools logic unchanged so downstream code like
installFromDirectory uses dirName as the canonical skill name.
| async uninstallSkill(name: string): Promise<SkillInstallResult> { | ||
| try { | ||
| const skillDir = path.join(this.skillsDir, name) | ||
|
|
||
| if (!fs.existsSync(skillDir)) { | ||
| return { success: false, error: `Skill "${name}" not found` } | ||
| } | ||
|
|
||
| // Remove from caches | ||
| this.metadataCache.delete(name) | ||
| this.contentCache.delete(name) | ||
|
|
||
| // Delete the directory | ||
| fs.rmSync(skillDir, { recursive: true, force: true }) | ||
|
|
||
| eventBus.sendToRenderer(SKILL_EVENTS.UNINSTALLED, SendTarget.ALL_WINDOWS, { name }) | ||
|
|
||
| return { success: true, skillName: name } | ||
| } catch (error) { | ||
| const errorMsg = error instanceof Error ? error.message : String(error) | ||
| return { success: false, error: errorMsg } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing path traversal validation in uninstallSkill.
The name parameter is directly used to construct a path without validation. A malicious input like "../config" could delete directories outside the skills directory.
🔒 Add path validation
async uninstallSkill(name: string): Promise<SkillInstallResult> {
try {
+ // Validate skill name to prevent path traversal
+ if (!name || name.includes('/') || name.includes('\\') || name === '..' || name === '.') {
+ return { success: false, error: 'Invalid skill name' }
+ }
+
const skillDir = path.join(this.skillsDir, name)
+ const resolved = path.resolve(skillDir)
+
+ // Ensure the resolved path is within skillsDir
+ if (!resolved.startsWith(path.resolve(this.skillsDir) + path.sep)) {
+ return { success: false, error: 'Invalid skill name' }
+ }
if (!fs.existsSync(skillDir)) {
return { success: false, error: `Skill "${name}" not found` }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async uninstallSkill(name: string): Promise<SkillInstallResult> { | |
| try { | |
| const skillDir = path.join(this.skillsDir, name) | |
| if (!fs.existsSync(skillDir)) { | |
| return { success: false, error: `Skill "${name}" not found` } | |
| } | |
| // Remove from caches | |
| this.metadataCache.delete(name) | |
| this.contentCache.delete(name) | |
| // Delete the directory | |
| fs.rmSync(skillDir, { recursive: true, force: true }) | |
| eventBus.sendToRenderer(SKILL_EVENTS.UNINSTALLED, SendTarget.ALL_WINDOWS, { name }) | |
| return { success: true, skillName: name } | |
| } catch (error) { | |
| const errorMsg = error instanceof Error ? error.message : String(error) | |
| return { success: false, error: errorMsg } | |
| } | |
| } | |
| async uninstallSkill(name: string): Promise<SkillInstallResult> { | |
| try { | |
| // Validate skill name to prevent path traversal | |
| if (!name || name.includes('/') || name.includes('\\') || name === '..' || name === '.') { | |
| return { success: false, error: 'Invalid skill name' } | |
| } | |
| const skillDir = path.join(this.skillsDir, name) | |
| const resolved = path.resolve(skillDir) | |
| // Ensure the resolved path is within skillsDir | |
| if (!resolved.startsWith(path.resolve(this.skillsDir) + path.sep)) { | |
| return { success: false, error: 'Invalid skill name' } | |
| } | |
| if (!fs.existsSync(skillDir)) { | |
| return { success: false, error: `Skill "${name}" not found` } | |
| } | |
| // Remove from caches | |
| this.metadataCache.delete(name) | |
| this.contentCache.delete(name) | |
| // Delete the directory | |
| fs.rmSync(skillDir, { recursive: true, force: true }) | |
| eventBus.sendToRenderer(SKILL_EVENTS.UNINSTALLED, SendTarget.ALL_WINDOWS, { name }) | |
| return { success: true, skillName: name } | |
| } catch (error) { | |
| const errorMsg = error instanceof Error ? error.message : String(error) | |
| return { success: false, error: errorMsg } | |
| } | |
| } |
🤖 Prompt for AI Agents
In @src/main/presenter/skillPresenter/index.ts around lines 499 - 521,
uninstallSkill currently uses path.join(this.skillsDir, name) without
validation, allowing path traversal (e.g., "../config") to delete outside
directories; fix by resolving and validating the target directory before
removal: compute resolvedSkillsDir = path.resolve(this.skillsDir) and
resolvedSkillDir = path.resolve(resolvedSkillsDir, name), ensure
resolvedSkillDir === resolvedSkillsDir or
resolvedSkillDir.startsWith(resolvedSkillsDir + path.sep) (or alternatively
reject names containing path separators and allow only a safe whitelist/regex),
and only then proceed to delete with fs.rmSync and clear
metadataCache/contentCache; also use the validated resolvedSkillDir in fs.rmSync
instead of the unvalidated path.join result.
| const openSkillEditor = async (skill: SkillMetadata) => { | ||
| editingSkill.value = skill | ||
| try { | ||
| const filePresenter = usePresenter('filePresenter') | ||
| const content = await filePresenter.readFile(skill.path) | ||
| editingContent.value = content | ||
| editorOpen.value = true | ||
| } catch (error) { | ||
| console.error('Failed to read skill file:', error) | ||
| toast({ | ||
| title: t('settings.skills.edit.readFailed'), | ||
| description: String(error), | ||
| variant: 'destructive' | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move usePresenter call to setup scope.
Vue composables should be called at the top level of <script setup>, not inside event handlers. While this may work at runtime, it violates Vue composition API conventions and could cause issues with HMR or reactivity.
Suggested fix
const devicePresenter = usePresenter('devicePresenter')
+const filePresenter = usePresenter('filePresenter')
// ... later in openSkillEditor ...
const openSkillEditor = async (skill: SkillMetadata) => {
editingSkill.value = skill
try {
- const filePresenter = usePresenter('filePresenter')
const content = await filePresenter.readFile(skill.path)Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/renderer/settings/components/SkillsSettings.vue around lines 261 - 276,
The openSkillEditor function currently calls usePresenter('filePresenter')
inside the handler which violates Vue's composition API rules; move the call to
usePresenter to the top-level of the <script setup> (define a top-level const
filePresenter = usePresenter('filePresenter') alongside other composables) and
then remove the internal call so openSkillEditor uses that top-level
filePresenter; also keep the existing try/catch and consider a null-check or
type assertion on filePresenter if necessary before calling
filePresenter.readFile(skill.path).
- Added `selectFiles` method to `IDevicePresenter` interface for file selection with options for filters and multiple selections. - Created comprehensive tests for `SkillPresenter`, covering skill discovery, installation, activation, and deactivation. - Introduced tests for `SkillTools` to validate skill handling, including edge cases and input validation. - Mocked necessary dependencies and ensured proper integration with the event bus for skill events.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🤖 Fix all issues with AI agents
In @resources/skills/git-commit/SKILL.md:
- Around line 24-36: Update the “Types” list in SKILL.md so it matches the
repo's enforced conventional-commit types: add the missing types (dx, workflow,
types, wip, release) alongside the existing ones and adjust the docs label text
to "docs: Documentation-only changes"; ensure each bullet remains the same
format (e.g., "- **dx**: ...") and preserve the heading "## Types" and
surrounding structure so the skill will suggest only valid commit prefixes.
In @src/renderer/settings/components/skills/SkillEditorSheet.vue:
- Around line 178-201: The frontmatter in buildSkillContent() is being
hand-constructed and can produce invalid YAML when editName.value,
editDescription.value, or entries from editAllowedTools.value contain special
characters; replace the manual string building with a proper object and
serialize it with yaml.dump (from the existing yaml package). Specifically,
inside buildSkillContent() construct an object like { name: editName.value,
description: editDescription.value, allowedTools: [...] } where allowedTools is
produced by splitting/trimming editAllowedTools.value, then call
yaml.dump(frontmatterObj) and concatenate a YAML document separator and
editContent.value as needed; ensure you import yaml and remove the current
frontmatter string assembly logic so all values are safely escaped.
In @src/renderer/settings/components/skills/SkillInstallDialog.vue:
- Around line 278-302: The install result handling in handleInstallResult should
not substring-match result.error; update it to expect a structured response
(e.g., result: { success: boolean; code?: string; skillName?: string; error?:
string }) and branch on result.code === 'CONFLICT' (use result.skillName
directly) instead of result.error?.includes('already exists') and regex
extraction; adjust the producer (skillsStore install method) to return that
structured object and update uses of conflictSkillName, pendingInstallAction,
conflictDialogOpen, success toast/emit/isOpen handling to consume the new
fields.
- Around line 310-317: In handleConflictOverwrite wrap the await
pendingInstallAction.value() call in try/catch/finally so any thrown error is
passed to showError(error) and the UI state always resets; in try call await
pendingInstallAction.value(), in catch call showError(err), and in finally set
pendingInstallAction.value = null, conflictDialogOpen.value = false, and
conflictSkillName.value = '' to ensure the dialog/state is cleared regardless of
success or failure.
In @src/renderer/src/i18n/fa-IR/settings.json:
- Around line 1043-1102: The "skills" section in fa-IR/settings.json is still in
English; translate every user-facing string under the "skills" key (e.g., title,
description, openFolder, addSkill, empty, emptyHint, noResults, search, count,
the entire install subtree including
tabFolder/tabZip/tabUrl/fromFolder/selectFolder/folderHint/folderTip/zipHint/urlPlaceholder/urlHint/installButton/installing/success/successMessage/failed/dragNotSupported,
delete subtree, edit subtree, conflict subtree) into Persian (فارسی), keeping
technical tokens like "ZIP", "URL", and "Skill" in English only if that matches
your locale convention; then replicate the same keys with consistent
translations across all 12 locale files (da-DK, en-US, fa-IR, fr-FR, he-IL,
ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) to ensure parity.
In @src/renderer/src/i18n/fr-FR/settings.json:
- Around line 1043-1101: The fr-FR "skills" block contains English strings;
update all values under the "skills" object (e.g., keys like "title",
"description", "openFolder", "install.title", "install.successMessage",
"delete.description", "edit.placeholder", "conflict.description", etc.) to
proper French translations, or if English is intentional, explicitly document
that by adding a clear fallback reference to en-US (or a comment) so the locale
file no longer appears untranslated. Ensure every nested key in "skills" is
either translated into French or linked to the en-US fallback consistently.
In @src/renderer/src/i18n/he-IL/settings.json:
- Around line 1043-1101: The he-IL locale file contains English strings under
the "skills" object (e.g., "skills.title", "skills.description",
"skills.openFolder", "skills.install.*", "skills.delete.*", "skills.edit.*",
"skills.conflict.*"); translate these values into Hebrew (or explicitly confirm
that English fallback is intended) and update each key's value accordingly so
the Hebrew locale provides proper localized text for the Skills UI (ensure
placeholders like "{count}" and "{name}" are preserved and messages such as
"Installing..."/"Installation Successful"/"Deletion Successful" keep their
semantics).
In @src/renderer/src/i18n/ja-JP/settings.json:
- Around line 1043-1101: The "skills" localization block in ja-JP currently
contains English text; replace every string in the "skills" object (including
nested objects install, delete, edit, conflict and keys like title, description,
openFolder, addSkill, empty, emptyHint, noResults, search, count, install.* ,
delete.* , edit.* , conflict.*) with appropriate Japanese translations,
preserving placeholders like {count} and {name} and escaped quotes (\"{name}\"),
and keeping punctuation/formatting consistent with other locales.
In @src/renderer/src/i18n/ko-KR/settings.json:
- Around line 1043-1101: The keys under "skills" (including nested objects like
"install", "delete", "edit", "conflict" and properties such as "title",
"description", "openFolder", "addSkill", "emptyHint", "search", "count",
"install.title", "install.description", "install.tabFolder", "install.tabZip",
"install.tabUrl", "install.fromFolder", "install.selectFolder",
"install.folderHint", "install.folderTip", "install.zipHint",
"install.urlPlaceholder", "install.urlHint", "install.installButton",
"install.installing", "install.success", "install.successMessage",
"install.failed", "install.dragNotSupported", "delete.title",
"delete.description", "delete.success", "delete.successMessage",
"delete.failed", "edit.title", "edit.placeholder", "edit.readFailed",
"edit.success", "edit.failed", "edit.name", "edit.namePlaceholder",
"edit.description", "edit.descriptionPlaceholder", "edit.allowedTools",
"edit.allowedToolsPlaceholder", "edit.allowedToolsHint", "edit.content",
"edit.files", "edit.noFiles", "conflict.title", "conflict.description",
"conflict.overwrite") are still in English; translate each string value into
Korean (preserving placeholders like {name} and {count}) or, if the project uses
an explicit fallback strategy instead of translating, confirm that strategy in
the PR and update the locale file to either contain Korean translations or a
comment indicating intentional fallback; ensure punctuation and escape sequences
(e.g., \"{name}\") remain correct.
In @src/renderer/src/stores/skillsStore.ts:
- Around line 113-115: getSkillFolderTree lacks an explicit return type; update
its signature to return Promise<SkillFolderNode[]> and import the
SkillFolderNode type from @shared/types/skill, i.e., change the function
declaration for getSkillFolderTree to include the Promise<SkillFolderNode[]>
return type and ensure an import for SkillFolderNode is added at the top of the
file so the type is available for the annotation.
In @test/main/presenter/skillPresenter/skillTools.test.ts:
- Around line 1-4: The test imports "Mock" as a runtime symbol but it's only
used for TypeScript casting; change the import to be type-only (or better,
replace uses with the function-specific type) so no runtime import occurs:
update the import list in skillTools.test.ts to import Mock as a type-only
symbol (e.g., "import type { Mock } from 'vitest'") or, preferably, cast vi.fn()
returns to the recommended function mock type (MockedFunction) from 'vitest'
instead of Mock; locate uses of vi.fn() and casts to Mock and switch them to
MockedFunction (or MockInstance when mocking constructors) while keeping imports
type-only. Note: for vi.fn() in this repo's TS/verbatimModuleSyntax setup the
recommended type for mocked functions is MockedFunction.
🟡 Minor comments (6)
src/main/presenter/devicePresenter/index.ts-479-496 (1)
479-496: Comments must be in English per coding guidelines.The implementation is correct and properly typed, but the JSDoc comments are in Chinese (
选择文件,返回所选文件的路径), violating the requirement that all comments in TypeScript files must be in English. Please update the comments to English:/** * Select files * @param options File selection options * @returns Returns the paths of selected files; empty array if user cancels */Note: The existing
selectDirectorymethod has the same issue with Chinese comments.docs/specs/skills-system/tasks.md-214-236 (1)
214-236: Fix MD040: add a language to the fenced block.Markdownlint flagged Line 214 because the fence has no language; please add one (e.g.,
text).Proposed patch
-``` +```text Phase 1 (基础设施) │ ├── 1.1-1.3 类型/数据库/配置 ─┐ │ │ ├── 1.4 SkillPresenter ──────┼── Phase 2 (Agent Loop) │ │ │ ├── 1.5 安装/卸载 ───────────┤ ├── 2.1-2.2 工具/Context │ │ │ ├── 1.6 热加载 ──────────────┤ └── 2.3-2.4 工具合并/事件 │ │ └── 1.7 会话状态 ────────────┘ │ ▼ Phase 3 (UI) │ ├── 3.1-3.2 路由/Store │ └── 3.3-3.9 组件 │ ▼ Phase 4 (完善)docs/specs/skills-system/code-review.md-223-253 (1)
223-253: Update YAML serializer recommendation to use existingyamldependency instead of introducingjs-yaml.The repo already standardizes on
yaml(v2.8.2), notjs-yaml. Revise the code example to import fromyaml:Corrected example
import { dump } from 'yaml' const buildSkillContent = (): string => { const frontmatter = { name: editName.value, description: editDescription.value, ...(tools.length > 0 && { allowedTools: tools }) } return `---\n${dump(frontmatter)}---\n\n${editContent.value}` }src/renderer/settings/components/skills/SkillsSettings.vue-137-162 (1)
137-162: Guard against unmount duringawait loadSkills()before registering IPC listeners.
If the component unmounts while Line 138 is in-flight,setupEventListeners()still runs and the cleanup won’t execute.Proposed guard
+const disposed = ref(false) + onMounted(async () => { await skillsStore.loadSkills() - setupEventListeners() + if (!disposed.value) setupEventListeners() }) onUnmounted(() => { + disposed.value = true if (eventCleanup.value) { eventCleanup.value() } }) @@ const handleSkillEvent = () => { - skillsStore.loadSkills() + void skillsStore.loadSkills() }src/renderer/settings/components/skills/SkillEditorSheet.vue-135-150 (1)
135-150: Surface read/save failures to the user (not only console).Skill file read failures are currently only logged; save failures toast correctly. Since
settings.skills.edit.readFailedexists, consider toasting on read failures too.Also applies to: 224-229
src/renderer/settings/components/skills/SkillEditorSheet.vue-134-176 (1)
134-176: Guard against stale state + async races in thewatch(props.skill)loader.
- When
props.skillbecomesnull, the form keeps the previous skill’s values.- Rapidly switching skills can cause an earlier
readFileto resolve later and overwrite the newer selection.Consider clearing fields on
nulland using a request token (oronInvalidate) to ignore stale reads.Proposed fix
watch( () => props.skill, async (skill) => { + let cancelled = false + // If a new skill arrives before read finishes, ignore the old result. + // Vue passes an invalidation callback via the 3rd arg. + // (Shown below as onInvalidate usage.) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const onInvalidate = (cb: () => void) => cb() + onInvalidate(() => { + cancelled = true + }) + if (skill) { editName.value = skill.name editDescription.value = skill.description editAllowedTools.value = skill.allowedTools?.join(', ') || '' try { const content = await filePresenter.readFile(skill.path) + if (cancelled) return // Parse content to extract body (after frontmatter) const parsed = parseSkillContent(content) editContent.value = parsed.body } catch (error) { console.error('Failed to read skill file:', error) editContent.value = '' } + } else { + editName.value = '' + editDescription.value = '' + editAllowedTools.value = '' + editContent.value = '' } }, { immediate: true } )Committable suggestion skipped: line range outside the PR's diff.
🧹 Nitpick comments (14)
resources/skills/code-review/SKILL.md (1)
49-54: Consider adding more specific activation examples.The usage section defines when to activate the skill but could benefit from concrete examples of user queries or contexts that would trigger this skill (e.g., "User provides code snippet", "User says 'review this function'", etc.).
📝 Example enhancement
## Usage Activate this skill when: - User asks for code review - User wants feedback on their implementation - User requests security audit of code +- User provides a code snippet or file path for analysis +- User mentions performance concerns with their code +- User wants to ensure OWASP compliancesrc/renderer/src/i18n/fr-FR/routes.json (1)
17-17: Translations for "settings-skills" are complete across all required locales.The French translation "Compétences" is appropriate. Verification confirms that the "settings-skills" key has been added to all 12 language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names.
The file uses hyphen-separated keys (e.g., "settings-skills") while coding guidelines recommend dot-separated hierarchical structure (e.g., "settings.skills"). However, this naming pattern is consistent across all route keys in the file and would require renaming across all 12 locales to fully align with guidelines.
docs/specs/skills-system/tasks.md (1)
119-127: Clarify the Pinia store file plan (skills.ts vs skillsStore.ts) and avoid nonstandard[~]task markers.Line 124 reads like a conflict (“create
skills.ts” but “skillsStore.tsalready exists”). Pick one canonical path/name in the task list to prevent “both exist” drift. Also[~](Line 120) won’t render as a checkbox—consider- [ ]plus a short “(spec mismatch)” note instead. Based on learnings.resources/skills/git-commit/SKILL.md (1)
39-53: Add the repo rule about not including AI co-authoring footers.Since the repo enforces “Do not include AI co-authoring footers”, it’s worth making this explicit in the Footer guidance so the skill doesn’t suggest them. Based on learnings.
docs/specs/skills-system/code-review.md (1)
63-66: Make “Status” lines into real GitHub task list items (actionable tracking).
**Status**: [ ] Not Fixedwon’t render as a checkbox. Consider replacing with- [ ] Fixed/- [ ] Not fixed(or a single task per issue) so progress is trackable in GitHub UI.Also applies to: 86-87, 119-120
src/renderer/settings/components/skills/SkillFolderTree.vue (1)
33-55: Consider debouncing rapid skillName changes.The current implementation reloads the tree whenever
skillNamechanges. If the prop changes rapidly (e.g., from a dropdown or search), multiple API calls could be in flight simultaneously.💡 Optional: Add debouncing
Consider adding a debounce to the watch callback:
+import { ref, watch, onMounted } from 'vue' +import { watchDebounced } from '@vueuse/core' // if available + const props = defineProps<{ skillName: string }>() -watch( +watchDebounced( () => props.skillName, () => { loadTree() - } + }, + { debounce: 300 } )This is optional and depends on how the component is used. If
skillNameonly changes on deliberate user actions (not rapid input), the current implementation is fine.src/renderer/settings/components/skills/SkillFolderTreeNode.vue (1)
3-28: Avoid toggling “expanded” for file nodes; consider basic keyboard semantics for directories.
Right now Line 6 toggles even whennode.type !== 'directory'(no visual impact, but surprising behavior). Also, a clickable<div>without keyboard handling makes the tree hard to use without a mouse.Proposed fix (limit toggle to directories)
- @click="toggleExpand" + @click="node.type === 'directory' && toggleExpand()"src/renderer/settings/components/skills/SkillCard.vue (1)
2-49: The hover state is redundant; prefer CSS-only visibility (+ focus-within).
You already havegroup-hoveron Line 34; thehoveringref + mouseenter/mouseleave can be removed, and addinggroup-focus-withinhelps keyboard users.Proposed simplification
<template> <div - class="border rounded-lg p-4 bg-card hover:bg-accent/50 transition-colors group" - @mouseenter="hovering = true" - @mouseleave="hovering = false" + class="border rounded-lg p-4 bg-card hover:bg-accent/50 transition-colors group" > @@ <div class="flex items-center gap-1 ml-2 shrink-0 transition-opacity" - :class="{ 'opacity-0 group-hover:opacity-100': !hovering }" + class="opacity-0 group-hover:opacity-100 group-focus-within:opacity-100" > @@ </template> <script setup lang="ts"> -import { ref } from 'vue' import { Icon } from '@iconify/vue' import { Button } from '@shadcn/components/ui/button' import type { SkillMetadata } from '@shared/types/skill' @@ -const hovering = ref(false) </script>Also applies to: 52-68
src/renderer/settings/components/skills/SkillsSettings.vue (1)
178-199: Ensure delete error toast has a safe fallback message.
Ifresult.errorcan be empty/undefined, the destructive toast becomes unhelpful.src/renderer/src/i18n/zh-CN/settings.json (1)
1043-1102: Looks good: zh-CNsettings.skillsis translated and complete.
Optional: consider aligning “AI 助手” wording with the repo’s preferred “智能体” terminology if that’s the established convention.src/renderer/settings/components/skills/SkillInstallDialog.vue (1)
27-53: Dropzones advertise drag/drop, but drag/drop is “not supported”.Given the explicit limitation, consider either:
- removing
@drop/@dragoverhandlers (so it’s just a click-to-select panel), or- clearly labeling the UI as “click to select (drag/drop not supported)” and avoid hover/drop affordances.
This prevents a misleading UX.
Also applies to: 55-79, 200-212, 238-247
test/main/presenter/skillPresenter/skillPresenter.test.ts (1)
579-596: Consider simplifying mock implementation.The callCount-based mock implementation works but adds complexity. Consider extracting this pattern to a helper function if used multiple times, or using separate mock setups for the discovery call versus tree-building calls.
♻️ Alternative approach
it('should return folder tree for existing skill', async () => { - // Reset readdirSync to return files for the skill folder - let callCount = 0 - ;(fs.readdirSync as Mock).mockImplementation(() => { - callCount++ - if (callCount === 1) { - // First call is for discovering skills - return [{ name: 'test-skill', isDirectory: () => true }] - } - // Subsequent calls are for building tree - return empty to prevent recursion - return [{ name: 'SKILL.md', isDirectory: () => false }] - }) + // Setup mock for discovery + ;(fs.readdirSync as Mock) + .mockReturnValueOnce([{ name: 'test-skill', isDirectory: () => true }]) + .mockReturnValue([{ name: 'SKILL.md', isDirectory: () => false }]) const tree = await skillPresenter.getSkillFolderTree('test-skill') expect(Array.isArray(tree)).toBe(true) expect(tree.length).toBeGreaterThanOrEqual(0) })src/renderer/src/stores/skillsStore.ts (2)
31-90: Consider managing loading and error state in mutating actions.The install/uninstall actions successfully reload skills but don't manage the store's
loadinganderrorstate during their operations. This means:
- The
loadingflag won't betrueduring install/uninstall operations (only duringloadSkills)- Previous error messages remain in
erroruntil the nextloadSkillscallWhile the current implementation works since these actions return their own results, managing store-level state would provide better UI feedback.
♻️ Suggested pattern
const installFromFolder = async ( folderPath: string, options?: { overwrite?: boolean } ): Promise<SkillInstallResult> => { + loading.value = true + error.value = null try { const result = await skillPresenter.installFromFolder(folderPath, options) if (result.success) { await loadSkills() + } else { + error.value = result.error || 'Installation failed' } return result } catch (e) { const errorMsg = e instanceof Error ? e.message : String(e) + error.value = errorMsg return { success: false, error: errorMsg } + } finally { + loading.value = false } }Apply similar pattern to other mutating actions.
31-111: Code duplication: Extract common async action pattern.The install/uninstall/update actions share identical error handling and reload logic. Consider extracting this pattern to reduce duplication.
♻️ Helper function approach
const withSkillMutation = async <T>( operation: () => Promise<T>, shouldReload: (result: T) => boolean ): Promise<T> => { loading.value = true error.value = null try { const result = await operation() if (shouldReload(result)) { await loadSkills() } return result } catch (e) { const errorMsg = e instanceof Error ? e.message : String(e) error.value = errorMsg throw e } finally { loading.value = false } } const installFromFolder = async ( folderPath: string, options?: { overwrite?: boolean } ): Promise<SkillInstallResult> => { return withSkillMutation( () => skillPresenter.installFromFolder(folderPath, options), (result) => result.success ) }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (42)
docs/specs/skills-system/code-review.mddocs/specs/skills-system/tasks.mdelectron-builder.ymlpackage.jsonresources/skills/code-review/SKILL.mdresources/skills/git-commit/SKILL.mdsrc/main/presenter/agentPresenter/message/messageBuilder.tssrc/main/presenter/devicePresenter/index.tssrc/renderer/settings/components/skills/SkillCard.vuesrc/renderer/settings/components/skills/SkillEditorSheet.vuesrc/renderer/settings/components/skills/SkillFolderTree.vuesrc/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/renderer/settings/components/skills/SkillsHeader.vuesrc/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/settings/main.tssrc/renderer/src/i18n/da-DK/routes.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/fa-IR/routes.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/fr-FR/routes.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/routes.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/pt-BR/routes.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/ru-RU/routes.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/zh-HK/routes.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/zh-TW/routes.jsonsrc/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/stores/skillsStore.tssrc/shared/types/presenters/legacy.presenters.d.tstest/main/presenter/skillPresenter/skillPresenter.test.tstest/main/presenter/skillPresenter/skillTools.test.ts
✅ Files skipped from review due to trivial changes (1)
- src/renderer/src/i18n/fa-IR/routes.json
🚧 Files skipped from review as they are similar to previous changes (4)
- package.json
- src/renderer/settings/main.ts
- src/main/presenter/agentPresenter/message/messageBuilder.ts
- src/shared/types/presenters/legacy.presenters.d.ts
🧰 Additional context used
📓 Path-based instructions (22)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use English for logs and comments in TypeScript/JavaScript code
Files:
src/main/presenter/devicePresenter/index.tstest/main/presenter/skillPresenter/skillTools.test.tssrc/renderer/src/stores/skillsStore.tstest/main/presenter/skillPresenter/skillPresenter.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript with strict type checking enabled
Use OxLint for linting JavaScript and TypeScript files; ensure lint-staged hooks and typecheck pass before commits
Files:
src/main/presenter/devicePresenter/index.tstest/main/presenter/skillPresenter/skillTools.test.tssrc/renderer/src/stores/skillsStore.tstest/main/presenter/skillPresenter/skillPresenter.test.ts
src/main/presenter/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/presenter/**/*.ts: Use EventBus to broadcast events from main to renderer viamainWindow.webContents.send()
Implement one presenter per functional domain in the main process
Files:
src/main/presenter/devicePresenter/index.ts
src/main/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/**/*.ts: Use EventBus fromsrc/main/eventbus.tsfor decoupled inter-process communication
Context isolation must be enabled with preload scripts for secure IPC communicationElectron main process code should reside in
src/main/, with presenters organized inpresenter/subdirectory (Window, Tab, Thread, Mcp, Config, LLMProvider), and app events managed viaeventbus.ts
Files:
src/main/presenter/devicePresenter/index.ts
**/*.{js,ts,tsx,jsx,vue,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
All logs and comments must be in English
Files:
src/main/presenter/devicePresenter/index.tssrc/renderer/settings/components/skills/SkillFolderTree.vuesrc/renderer/settings/components/skills/SkillCard.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/renderer/settings/components/skills/SkillEditorSheet.vuesrc/renderer/settings/components/skills/SkillFolderTreeNode.vuetest/main/presenter/skillPresenter/skillTools.test.tssrc/renderer/src/stores/skillsStore.tstest/main/presenter/skillPresenter/skillPresenter.test.tssrc/renderer/settings/components/skills/SkillsHeader.vuesrc/renderer/settings/components/skills/SkillsSettings.vue
**/*.{js,ts,tsx,jsx,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use OxLint as the linter
Files:
src/main/presenter/devicePresenter/index.tstest/main/presenter/skillPresenter/skillTools.test.tssrc/renderer/src/stores/skillsStore.tstest/main/presenter/skillPresenter/skillPresenter.test.ts
**/*.{js,ts,tsx,jsx,vue,json,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use Prettier as the code formatter
Files:
src/main/presenter/devicePresenter/index.tssrc/renderer/src/i18n/pt-BR/routes.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/settings/components/skills/SkillFolderTree.vuesrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/ru-RU/routes.jsonsrc/renderer/settings/components/skills/SkillCard.vuesrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/fr-FR/routes.jsonsrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/settings/components/skills/SkillEditorSheet.vuesrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/da-DK/routes.jsontest/main/presenter/skillPresenter/skillTools.test.tssrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/routes.jsonsrc/renderer/src/stores/skillsStore.tssrc/renderer/src/i18n/he-IL/settings.jsontest/main/presenter/skillPresenter/skillPresenter.test.tssrc/renderer/settings/components/skills/SkillsHeader.vuesrc/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/src/i18n/zh-TW/routes.jsonsrc/renderer/src/i18n/ko-KR/routes.json
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,vue}: Use camelCase for variable and function names; use PascalCase for types and classes; use SCREAMING_SNAKE_CASE for constants
Configure Prettier with single quotes, no semicolons, and line width of 100 characters. Runpnpm run formatafter completing features
Files:
src/main/presenter/devicePresenter/index.tssrc/renderer/settings/components/skills/SkillFolderTree.vuesrc/renderer/settings/components/skills/SkillCard.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/renderer/settings/components/skills/SkillEditorSheet.vuesrc/renderer/settings/components/skills/SkillFolderTreeNode.vuetest/main/presenter/skillPresenter/skillTools.test.tssrc/renderer/src/stores/skillsStore.tstest/main/presenter/skillPresenter/skillPresenter.test.tssrc/renderer/settings/components/skills/SkillsHeader.vuesrc/renderer/settings/components/skills/SkillsSettings.vue
src/renderer/src/i18n/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
src/renderer/src/i18n/**/*.json: Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Files:
src/renderer/src/i18n/pt-BR/routes.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/ru-RU/routes.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/fr-FR/routes.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/da-DK/routes.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/routes.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/zh-TW/routes.jsonsrc/renderer/src/i18n/ko-KR/routes.json
src/renderer/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
src/renderer/**/*.vue: Use Vue 3 Composition API for all components
Use Tailwind CSS for styling with scoped styles
All user-facing strings must use i18n keys via vue-i18n
Files:
src/renderer/settings/components/skills/SkillFolderTree.vuesrc/renderer/settings/components/skills/SkillCard.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/renderer/settings/components/skills/SkillEditorSheet.vuesrc/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/settings/components/skills/SkillsHeader.vuesrc/renderer/settings/components/skills/SkillsSettings.vue
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Vue components must be named in PascalCase (e.g.,
ChatInput.vue) and use Vue 3 Composition API with Pinia for state management and Tailwind for styling
Files:
src/renderer/settings/components/skills/SkillFolderTree.vuesrc/renderer/settings/components/skills/SkillCard.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/renderer/settings/components/skills/SkillEditorSheet.vuesrc/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/settings/components/skills/SkillsHeader.vuesrc/renderer/settings/components/skills/SkillsSettings.vue
src/renderer/src/i18n/en-US/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
Use English (en-US) as the reference for translation accuracy when adding new keys
Files:
src/renderer/src/i18n/en-US/settings.json
src/renderer/src/i18n/{zh-CN,zh-HK,zh-TW}/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
For Chinese translations, consider using '智能体' for 'Agents' instead of the English term
Files:
src/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/routes.jsonsrc/renderer/src/i18n/zh-TW/routes.json
test/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Place test files in
test/directory with corresponding structure to source files
Files:
test/main/presenter/skillPresenter/skillTools.test.tstest/main/presenter/skillPresenter/skillPresenter.test.ts
test/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Vitest as the testing framework for unit and integration tests
Files:
test/main/presenter/skillPresenter/skillTools.test.tstest/main/presenter/skillPresenter/skillPresenter.test.ts
test/**/*.test.ts
📄 CodeRabbit inference engine (AGENTS.md)
Vitest test suites should be organized in
test/main/**andtest/renderer/**mirroring source structure, with file names following*.test.tsor*.spec.tspattern
Files:
test/main/presenter/skillPresenter/skillTools.test.tstest/main/presenter/skillPresenter/skillPresenter.test.ts
src/renderer/src/**/stores/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Use Pinia for frontend state management
Files:
src/renderer/src/stores/skillsStore.ts
src/renderer/src/**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
usePresenter.tscomposable for renderer-to-main IPC communication via direct presenter method callsEnsure all code comments are in English and all log messages are in English, with no non-English text in code comments or console statements
Use VueUse composables for common utilities like
useLocalStorage,useClipboard,useDebounceFnVue 3 renderer app code should be organized in
src/renderer/srcwith subdirectories forcomponents/,stores/,views/,i18n/, andlib/
Files:
src/renderer/src/stores/skillsStore.ts
src/renderer/src/**/*.{vue,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
src/renderer/src/**/*.{vue,ts,tsx}: Use vue-i18n framework for internationalization located at src/renderer/src/i18n/
All user-facing strings must use i18n keys, not hardcoded text
src/renderer/src/**/*.{vue,ts,tsx}: Usereffor primitives and references,reactivefor objects in Vue 3 Composition API
Prefercomputedproperties over methods for derived state in Vue components
Import Shadcn Vue components from@/shadcn/components/ui/path alias
Use thecn()utility function combining clsx and tailwind-merge for dynamic Tailwind classes
UsedefineAsyncComponent()for lazy loading heavy Vue components
Use TypeScript for all Vue components and composables with explicit type annotations
Define TypeScript interfaces for Vue component props and data structures
UseusePresentercomposable for main process communication instead of direct IPC calls
Files:
src/renderer/src/stores/skillsStore.ts
src/renderer/src/stores/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
src/renderer/src/stores/**/*.ts: Use Setup Store syntax withdefineStorefunction pattern in Pinia stores
Use getters (computed properties) for derived state in Pinia stores
Keep Pinia store actions focused on state mutations and async operations
Files:
src/renderer/src/stores/skillsStore.ts
src/renderer/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
Use class-variance-authority (CVA) for defining component variants with Tailwind classes
Files:
src/renderer/src/stores/skillsStore.ts
src/renderer/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
src/renderer/src/**/*.{ts,tsx}: UseshallowRefandshallowReactivefor optimizing reactivity with large objects
Prefertypeoverinterfacein TypeScript unless using inheritance withextends
Files:
src/renderer/src/stores/skillsStore.ts
🧠 Learnings (29)
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Applied to files:
src/renderer/src/i18n/pt-BR/routes.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/ru-RU/routes.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/fr-FR/routes.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/da-DK/routes.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/routes.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/zh-TW/routes.jsonsrc/renderer/src/i18n/ko-KR/routes.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/en-US/**/*.json : Use English (en-US) as the reference for translation accuracy when adding new keys
Applied to files:
src/renderer/src/i18n/pt-BR/routes.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/ru-RU/routes.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/da-DK/routes.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/routes.jsonsrc/renderer/src/i18n/ko-KR/routes.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Applied to files:
src/renderer/src/i18n/pt-BR/routes.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/ru-RU/routes.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/da-DK/routes.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/zh-TW/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Applied to files:
src/renderer/src/i18n/pt-BR/routes.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/ru-RU/routes.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/fr-FR/routes.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/da-DK/routes.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/routes.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/zh-TW/routes.jsonsrc/renderer/src/i18n/ko-KR/routes.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/{zh-CN,zh-HK,zh-TW}/**/*.json : For Chinese translations, consider using '智能体' for 'Agents' instead of the English term
Applied to files:
src/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/routes.jsonsrc/renderer/src/i18n/zh-TW/routes.json
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Vue 3 renderer app code should be organized in `src/renderer/src` with subdirectories for `components/`, `stores/`, `views/`, `i18n/`, and `lib/`
Applied to files:
src/renderer/settings/components/skills/SkillFolderTree.vuesrc/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/settings/components/skills/SkillsSettings.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/router/**/*.ts : Implement lazy loading in Vue Router using dynamic imports for route components
Applied to files:
src/renderer/settings/components/skills/SkillFolderTree.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.vue : Use `<script setup>` syntax for concise Vue 3 component definitions with Composition API
Applied to files:
src/renderer/settings/components/skills/SkillFolderTree.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/renderer/settings/components/skills/SkillsSettings.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Define TypeScript interfaces for Vue component props and data structures
Applied to files:
src/renderer/settings/components/skills/SkillFolderTree.vuesrc/renderer/settings/components/skills/SkillCard.vuesrc/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/settings/components/skills/SkillsSettings.vue
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Use vue-i18n framework for internationalization located at src/renderer/src/i18n/
Applied to files:
src/renderer/settings/components/skills/SkillFolderTree.vuesrc/renderer/settings/components/skills/SkillsHeader.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Import Shadcn Vue components from `@/shadcn/components/ui/` path alias
Applied to files:
src/renderer/settings/components/skills/SkillFolderTree.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Use `defineAsyncComponent()` for lazy loading heavy Vue components
Applied to files:
src/renderer/settings/components/skills/SkillFolderTree.vue
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Ensure all code comments are in English and all log messages are in English, with no non-English text in code comments or console statements
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/zh-CN/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Run 'pnpm run i18n' to check translation completeness using zh-CN as source, or 'pnpm run i18n:en' to check using en-US as source
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/zh-CN/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Use the i18n-code-reviewer agent to validate translation quality, check for missing translations, review proper noun handling, and ensure consistency across languages
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/zh-CN/settings.json
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Applies to src/renderer/src/i18n/**/*.ts : All user-facing strings must use vue-i18n keys defined in `src/renderer/src/i18n/`
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/ko-KR/routes.json
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.vue : Define props and emits explicitly in Vue components using `defineProps` and `defineEmits` with TypeScript interfaces
Applied to files:
src/renderer/settings/components/skills/SkillCard.vuesrc/renderer/settings/components/skills/SkillsHeader.vue
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/renderer/**/*.vue : All user-facing strings must use i18n keys via vue-i18n
Applied to files:
src/renderer/src/i18n/fr-FR/routes.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : All user-facing strings must use i18n keys, not hardcoded text
Applied to files:
src/renderer/src/i18n/zh-CN/settings.json
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/renderer/src/**/stores/*.ts : Use Pinia for frontend state management
Applied to files:
src/renderer/src/stores/skillsStore.ts
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/stores/**/*.ts : Use Setup Store syntax with `defineStore` function pattern in Pinia stores
Applied to files:
src/renderer/src/stores/skillsStore.ts
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/stores/**/*.ts : Keep Pinia store actions focused on state mutations and async operations
Applied to files:
src/renderer/src/stores/skillsStore.ts
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/stores/**/*.ts : Use getters (computed properties) for derived state in Pinia stores
Applied to files:
src/renderer/src/stores/skillsStore.ts
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/stores/ : Separate concerns in Pinia stores: use different stores for different domains
Applied to files:
src/renderer/src/stores/skillsStore.ts
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/main/presenter/**/*.ts : Implement one presenter per functional domain in the main process
Applied to files:
test/main/presenter/skillPresenter/skillPresenter.test.ts
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Applies to test/**/*.test.ts : Vitest test suites should be organized in `test/main/**` and `test/renderer/**` mirroring source structure, with file names following `*.test.ts` or `*.spec.ts` pattern
Applied to files:
test/main/presenter/skillPresenter/skillPresenter.test.ts
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Prefer lightweight spec artifacts under `docs/specs/<feature>/` (spec/plan/tasks) and resolve `[NEEDS CLARIFICATION]` markers before coding
Applied to files:
docs/specs/skills-system/tasks.md
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Create ASCII diagrams to show current and proposed UI/layout changes before implementation, using BEFORE/AFTER format
Applied to files:
docs/specs/skills-system/tasks.md
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Conventional commits are enforced: format as `type(scope): subject` with subject ≤ 50 characters. Allowed types: feat, fix, docs, dx, style, refactor, perf, test, workflow, build, ci, chore, types, wip, release. Do not include AI co-authoring footers
Applied to files:
resources/skills/git-commit/SKILL.md
🧬 Code graph analysis (3)
src/main/presenter/devicePresenter/index.ts (1)
test/mocks/electron.ts (1)
dialog(29-32)
test/main/presenter/skillPresenter/skillTools.test.ts (2)
src/main/presenter/skillPresenter/skillTools.ts (1)
SkillTools(3-79)src/shared/types/skill.ts (2)
ISkillPresenter(91-124)SkillMetadata(13-24)
test/main/presenter/skillPresenter/skillPresenter.test.ts (5)
src/main/presenter/skillPresenter/index.ts (1)
SkillPresenter(30-791)src/shared/types/presenters/legacy.presenters.d.ts (1)
IConfigPresenter(462-694)src/main/presenter/index.ts (1)
presenter(321-321)src/main/eventbus.ts (1)
eventBus(151-151)src/main/events.ts (1)
SKILL_EVENTS(265-272)
🪛 LanguageTool
docs/specs/skills-system/code-review.md
[style] ~368-~368: To elevate your writing, try using a synonym here.
Context: ...is could create a very long tree that's hard to navigate. ```typescript const expan...
(HARD_TO)
resources/skills/git-commit/SKILL.md
[grammar] ~28-~28: Use a hyphen to join words.
Context: ...x**: A bug fix - docs: Documentation only changes - style: Changes that d...
(QB_NEW_EN_HYPHEN)
🪛 markdownlint-cli2 (0.18.1)
docs/specs/skills-system/tasks.md
214-214: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
| // Common result handling | ||
| const handleInstallResult = ( | ||
| result: { success: boolean; error?: string; skillName?: string }, | ||
| retryWithOverwrite: () => Promise<void> | ||
| ) => { | ||
| if (result.success) { | ||
| toast({ | ||
| title: t('settings.skills.install.success'), | ||
| description: t('settings.skills.install.successMessage', { name: result.skillName }) | ||
| }) | ||
| emit('installed') | ||
| isOpen.value = false | ||
| } else if (result.error?.includes('already exists')) { | ||
| const skillName = result.error.match(/"([^"]+)"/)?.[1] || '' | ||
| conflictSkillName.value = skillName | ||
| pendingInstallAction.value = retryWithOverwrite | ||
| conflictDialogOpen.value = true | ||
| } else { | ||
| toast({ | ||
| title: t('settings.skills.install.failed'), | ||
| description: result.error, | ||
| variant: 'destructive' | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t infer conflicts by substring-matching result.error.
result.error?.includes('already exists') + regex parsing is brittle and will break if the error message changes. Prefer a structured result (e.g., { code: 'CONFLICT', skillName }) from skillsStore.
🤖 Prompt for AI Agents
In @src/renderer/settings/components/skills/SkillInstallDialog.vue around lines
278 - 302, The install result handling in handleInstallResult should not
substring-match result.error; update it to expect a structured response (e.g.,
result: { success: boolean; code?: string; skillName?: string; error?: string })
and branch on result.code === 'CONFLICT' (use result.skillName directly) instead
of result.error?.includes('already exists') and regex extraction; adjust the
producer (skillsStore install method) to return that structured object and
update uses of conflictSkillName, pendingInstallAction, conflictDialogOpen,
success toast/emit/isOpen handling to consume the new fields.
| const handleConflictOverwrite = async () => { | ||
| conflictDialogOpen.value = false | ||
| if (pendingInstallAction.value) { | ||
| await pendingInstallAction.value() | ||
| pendingInstallAction.value = null | ||
| } | ||
| conflictSkillName.value = '' | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle overwrite retry errors and always reset conflict state.
If pendingInstallAction.value() throws, there’s no toast and state reset is partial. Wrap in try/catch/finally and route errors through showError.
Proposed fix
const handleConflictOverwrite = async () => {
conflictDialogOpen.value = false
- if (pendingInstallAction.value) {
- await pendingInstallAction.value()
- pendingInstallAction.value = null
- }
- conflictSkillName.value = ''
+ try {
+ await pendingInstallAction.value?.()
+ } catch (error) {
+ showError(error)
+ } finally {
+ pendingInstallAction.value = null
+ conflictSkillName.value = ''
+ }
}🤖 Prompt for AI Agents
In @src/renderer/settings/components/skills/SkillInstallDialog.vue around lines
310 - 317, In handleConflictOverwrite wrap the await
pendingInstallAction.value() call in try/catch/finally so any thrown error is
passed to showError(error) and the UI state always resets; in try call await
pendingInstallAction.value(), in catch call showError(err), and in finally set
pendingInstallAction.value = null, conflictDialogOpen.value = false, and
conflictSkillName.value = '' to ensure the dialog/state is cleared regardless of
success or failure.
| "skills": { | ||
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files" | ||
| }, | ||
| "conflict": { | ||
| "title": "Skill Already Exists", | ||
| "description": "A skill named \"{name}\" already exists. Do you want to overwrite it?", | ||
| "overwrite": "Overwrite" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CRITICAL: English content in Persian translation file
The entire skills section contains English text instead of Persian (فارسی) translations. Since this is the fa-IR/settings.json file, all user-facing strings must be translated to Persian.
For example:
- Line 1044: "Skills" should be translated to Persian
- Line 1045: "Manage and configure AI assistant skills" should be translated to Persian
- All other strings (lines 1046-1101) need Persian translations
As per coding guidelines, please also ensure that:
- Translations are added to ALL 12 language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names
- Technical terms like "ZIP", "URL", and "Skill" can remain in English if that's the established convention in Persian locale, but descriptive text must be translated
Based on coding guidelines.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/fa-IR/settings.json around lines 1043 - 1102, The
"skills" section in fa-IR/settings.json is still in English; translate every
user-facing string under the "skills" key (e.g., title, description, openFolder,
addSkill, empty, emptyHint, noResults, search, count, the entire install subtree
including
tabFolder/tabZip/tabUrl/fromFolder/selectFolder/folderHint/folderTip/zipHint/urlPlaceholder/urlHint/installButton/installing/success/successMessage/failed/dragNotSupported,
delete subtree, edit subtree, conflict subtree) into Persian (فارسی), keeping
technical tokens like "ZIP", "URL", and "Skill" in English only if that matches
your locale convention; then replicate the same keys with consistent
translations across all 12 locale files (da-DK, en-US, fa-IR, fr-FR, he-IL,
ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) to ensure parity.
| const getSkillFolderTree = async (name: string) => { | ||
| return await skillPresenter.getSkillFolderTree(name) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add return type annotation for type safety.
The getSkillFolderTree action is missing a return type annotation. Based on the SkillPresenter interface, this should return Promise<SkillFolderNode[]>.
♻️ Add return type
- const getSkillFolderTree = async (name: string) => {
+ const getSkillFolderTree = async (name: string): Promise<SkillFolderNode[]> => {
return await skillPresenter.getSkillFolderTree(name)
}You may need to import the SkillFolderNode type from @shared/types/skill.
As per coding guidelines: "Use TypeScript for all Vue components and composables with explicit type annotations."
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/renderer/src/stores/skillsStore.ts around lines 113 - 115,
getSkillFolderTree lacks an explicit return type; update its signature to return
Promise<SkillFolderNode[]> and import the SkillFolderNode type from
@shared/types/skill, i.e., change the function declaration for
getSkillFolderTree to include the Promise<SkillFolderNode[]> return type and
ensure an import for SkillFolderNode is added at the top of the file so the type
is available for the annotation.
| import { describe, it, expect, beforeEach, vi, Mock } from 'vitest' | ||
| import { SkillTools } from '../../../../src/main/presenter/skillPresenter/skillTools' | ||
| import type { ISkillPresenter, SkillMetadata } from '../../../../src/shared/types/skill' | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import Mock as a type-only symbol (avoid runtime import pitfalls).
Mock is used only for TypeScript casting; importing it as a value can cause issues depending on TS module settings.
Proposed fix
-import { describe, it, expect, beforeEach, vi, Mock } from 'vitest'
+import { describe, it, expect, beforeEach, vi } from 'vitest'
+import type { Mock } from 'vitest'In the Vitest version used by this repo, what is the recommended type for casting mocked functions returned by `vi.fn()` (e.g., `Mock`, `MockInstance`, `MockedFunction`) when using TypeScript with `verbatimModuleSyntax`?
🤖 Prompt for AI Agents
In @test/main/presenter/skillPresenter/skillTools.test.ts around lines 1 - 4,
The test imports "Mock" as a runtime symbol but it's only used for TypeScript
casting; change the import to be type-only (or better, replace uses with the
function-specific type) so no runtime import occurs: update the import list in
skillTools.test.ts to import Mock as a type-only symbol (e.g., "import type {
Mock } from 'vitest'") or, preferably, cast vi.fn() returns to the recommended
function mock type (MockedFunction) from 'vitest' instead of Mock; locate uses
of vi.fn() and casts to Mock and switch them to MockedFunction (or MockInstance
when mocking constructors) while keeping imports type-only. Note: for vi.fn() in
this repo's TS/verbatimModuleSyntax setup the recommended type for mocked
functions is MockedFunction.
…ll management - Delegated skills metadata prompt construction to skillPresenter.getMetadataPrompt() to reduce code duplication. - Initialized activeSkills in default conversation settings to maintain state. - Introduced SKILL_CONFIG constants for better configuration management, including limits on file sizes and download timeouts. - Implemented checks for file sizes and content types during skill downloads to prevent memory exhaustion and ensure valid files. - Enhanced folder tree building to limit depth and skip symbolic links, preventing infinite recursion. - Updated SkillEditorSheet.vue to use YAML serialization for SKILL.md content, improving format consistency. - Simplified drag-and-drop functionality in SkillInstallDialog.vue by removing unsupported features and adding URL validation. - Added user feedback for invalid URL formats in skill installation. - Updated translations for skill name hints in both English and Chinese.
…pchat into feat/skills
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/presenter/sessionPresenter/managers/conversationManager.ts (1)
177-229: EnsureactiveSkillsalways defaults to an array (avoidundefinedleaking into persisted settings).Right now it’s only explicitly reset when inheriting from
latestConversation.settings(Line 188). IfDEFAULT_SETTINGSdoesn’t include it, a new conversation can end up withactiveSkillsunset.Proposed diff
Object.assign(mergedSettings, sanitizedSettings) + // Normalize optional arrays to avoid leaking `undefined` into persisted settings + mergedSettings.activeSkills ??= [] + if (mergedSettings.temperature === undefined || mergedSettings.temperature === null) { mergedSettings.temperature = defaultModelsSettings?.temperature ?? 0.7 } const conversationId = await this.sqlitePresenter.createConversation(title, mergedSettings)
🤖 Fix all issues with AI agents
In @docs/specs/skills-system/skills-syncing-claude-code.md:
- Around line 17-32: The fenced directory-tree code block currently lacks a
language identifier; update the opening fence for the block that begins with ```
and the directory listing (~/.claude/skills/ ├── code-review/ ...) to include a
language token such as text or tree (e.g., change ``` to ```text) so
markdownlint MD040 is resolved and the block is explicitly marked as plain text.
In @docs/specs/skills-system/skills-syncing-cursor.md:
- Around line 17-24: Update the fenced code blocks in skills-syncing-cursor.md
to include a language specifier to satisfy markdownlint MD040: change the
triple-backtick blocks around the directory tree and the list of commands (the
blocks around the lines showing ".cursor/commands/" and the block showing the
"/code-review" style list) to use "```text" instead of "```"; ensure both
occurrences mentioned (around the directory tree and the second block at the
region covering the > /code-review, > /refactor, > /lint-suite lines) are
updated so all fenced blocks declare the language.
In @docs/specs/skills-system/skills-syncing-kiro.md:
- Around line 17-25: Several fenced code blocks in skills-syncing-kiro.md lack a
language indicator causing markdownlint MD040; update each triple-backtick block
shown (the directory tree block, the short notes block, and the ASCII art block)
to use a language tag such as "text" (i.e., replace ``` with ```text), and apply
the same change to the other problematic ranges mentioned (lines ~126-130 and
~262-278) so all fenced blocks include a language specifier.
In @src/renderer/settings/components/skills/SkillInstallDialog.vue:
- Around line 28-45: The component SkillInstallDialog.vue uses hardcoded user
strings ("Invalid URL format…", "ZIP Files") and non-keyboard-accessible
clickable divs; replace those literal strings with i18n keys and use the
existing translation helper (e.g. t('settings.skills.install.invalidUrl') and
t('settings.skills.install.zipFiles')) and add matching entries across all
locale files, and make the dropzone elements keyboard-accessible by either
converting the interactive <div> to a semantic <button> or adding role="button"
tabindex="0" and a keydown handler that calls the same handler as click (e.g.
@keydown.enter.space="selectFolder") so selectFolder and selectZip remain
operable by keyboard.
In @src/renderer/src/i18n/zh-CN/settings.json:
- Around line 1043-1102: Update the zh-CN translation for the skills namespace:
change edit.allowedTools value from "额外工具" to a semantically equivalent phrase
like "允许的工具" or "可用工具" so it aligns with the en-US key "Allowed Tools"; edit the
string under the "skills" -> "edit" -> "allowedTools" entry to the chosen
translation.
🧹 Nitpick comments (9)
docs/specs/skills-system/skills-syncing-copilot.md (1)
17-23: Add language identifier to fenced code block.Line 17 is missing a language specifier. For consistency with other code blocks in the document and to enable proper syntax highlighting, add a language identifier (e.g.,
txtorplaintext).-``` +```plaintext .github/prompts/ ├── code-review.prompt.md ├── refactor.prompt.md ├── generate-tests.prompt.md └── explain-code.prompt.md -``` +```docs/specs/skills-system/skills-syncing-windsurf.md (1)
17-23: Add language identifier to fenced code blocks for consistency.Lines 17-23 and similar code blocks (lines 71, 90) in this file lack language specifiers. While this doesn't impair readability, adding language identifiers (e.g., ```bash, ```markdown) improves markdown rendering and syntax highlighting in documentation viewers.
As per markdownlint rule MD040, specify language for all fenced code blocks. For example:
♻️ Example fix for directory structure block
-``` +```bash .windsurf/workflows/ ├── code-review.md ├── deploy.md ├── pr-review.md └── run-tests.md -``` +```docs/specs/skills-system/skills-syncing-antigravity.md (2)
17-23: Add language identifiers to fenced code blocks.Similar to the Windsurf document, lines 17-23 and 57-74 lack language specifiers. Add ```bash, ```typescript, ```javascript, etc. as appropriate for better rendering and syntax highlighting.
Also applies to: 57-74
192-200: Step-structure detection regex may not match all valid formats.The
hasStepsStructure()function checks for patterns like### \d+\.and### Step \d+, but workflows might use alternative numbering or formatting:
### Step One(spelled-out numbers)### 1)(parentheses instead of period)- Inconsistent spacing:
###1.or### 1 .Consider making the regex more flexible or documenting the exact format expected. Alternatively, use a robust Markdown/YAML parser to extract structure programmatically.
docs/specs/skills-system/code-review.md (1)
1-30: Clarify the status and intent of this code review documentation.All 26 issues in this code review are marked "[ ] Not Fixed" with no indication of whether they are:
- Blocking issues that must be fixed before merge
- Documented findings for future work/follow-up PRs
- Known limitations that are intentional design choices
Given the PR summary states that commit messages reference "documented additional issues and recommendations from a skills system code review," this appears to be a post-implementation review document rather than a pre-merge blocker. However, some issues (especially Medium severity ones) warrant clarification.
Suggest adding an executive summary section at the top that states:
- The review's purpose and scope
- Which issues are critical for this PR vs. future work
- Whether this PR addresses any of the identified issues
- Links to any corresponding tickets/issues if being tracked separately
src/main/presenter/skillPresenter/index.ts (1)
94-101: Consider adding error handling for initialization failures.If any step in the initialization sequence fails (builtin installation, discovery, or watcher setup), the
initializedflag is set totrueanyway. This could lead to inconsistent state if, for example, discovery fails but the system believes it's initialized.🛡️ Recommended enhancement for initialization error handling
async initialize(): Promise<void> { if (this.initialized) return + try { await this.installBuiltinSkills() await this.discoverSkills() this.watchSkillFiles() this.initialized = true + } catch (error) { + console.error('[SkillPresenter] Initialization failed:', error) + // Clean up partial initialization + this.stopWatching() + this.metadataCache.clear() + this.contentCache.clear() + throw error + } }src/renderer/src/i18n/en-US/settings.json (1)
1043-1102: Looks good; consider pluralization for{count} skillsIf you support ICU messages, consider pluralization (e.g., “1 skill” vs “{count} skills”) for
skills.count. Otherwise this is fine as a baseline.src/renderer/settings/components/skills/SkillFolderTreeNode.vue (1)
3-28: Make expand toggle directory-only + fix clickable affordance/a11yCurrently, the row is clickable for files too (toggles
expandedbut has no effect), and the cursor iscursor-defaultdespite click behavior. Consider restricting toggle to directories and making it keyboard-accessible.Proposed change
- <div - class="flex items-center gap-1.5 py-0.5 px-1 rounded hover:bg-muted/50 cursor-default" + <div + class="flex items-center gap-1.5 py-0.5 px-1 rounded hover:bg-muted/50" + :class="node.type === 'directory' ? 'cursor-pointer' : 'cursor-default'" :style="{ paddingLeft: `${depth * 12 + 4}px` }" - @click="toggleExpand" + role="button" + :tabindex="node.type === 'directory' ? 0 : -1" + @click="toggleExpand" + @keydown.enter.prevent="toggleExpand" + @keydown.space.prevent="toggleExpand" >const toggleExpand = () => { + if (props.node.type !== 'directory') return expanded.value = !expanded.value }Also applies to: 42-47
src/renderer/settings/components/skills/SkillInstallDialog.vue (1)
271-295: Conflict handling is brittle (string parsing); prefer structured result
result.error?.includes('already exists')plus regex extraction of the skill name from quotes creates a fragile contract between UI and backend/store. Prefer returning something like:
result.code: 'SKILL_ALREADY_EXISTS'result.skillName: string(already present in the type)- maybe
result.messagefor displayAlso consider guarding the error toast against
undefineddescriptions.Example of a more robust UI-side fallback
- } else if (result.error?.includes('already exists')) { - const skillName = result.error.match(/"([^"]+)"/)?.[1] || '' - conflictSkillName.value = skillName + } else if (result.skillName && result.error?.includes('already exists')) { + conflictSkillName.value = result.skillName pendingInstallAction.value = retryWithOverwrite conflictDialogOpen.value = true } else { toast({ title: t('settings.skills.install.failed'), - description: result.error, + description: result.error ?? t('common.operationFailed'), variant: 'destructive' }) }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
docs/specs/skills-system/code-review.mddocs/specs/skills-system/skills-syncing-antigravity.mddocs/specs/skills-system/skills-syncing-claude-code.mddocs/specs/skills-system/skills-syncing-copilot.mddocs/specs/skills-system/skills-syncing-cursor.mddocs/specs/skills-system/skills-syncing-kiro.mddocs/specs/skills-system/skills-syncing-windsurf.mddocs/specs/skills-system/skills-syncing.mdpackage.jsonsrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/sessionPresenter/managers/conversationManager.tssrc/main/presenter/skillPresenter/index.tssrc/renderer/settings/components/skills/SkillEditorSheet.vuesrc/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/shared/types/presenters/legacy.presenters.d.tssrc/shared/types/presenters/thread.presenter.d.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- package.json
- src/shared/types/presenters/legacy.presenters.d.ts
- src/renderer/settings/components/skills/SkillEditorSheet.vue
🧰 Additional context used
📓 Path-based instructions (14)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use English for logs and comments in TypeScript/JavaScript code
Files:
src/shared/types/presenters/thread.presenter.d.tssrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/sessionPresenter/managers/conversationManager.tssrc/main/presenter/skillPresenter/index.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript with strict type checking enabled
Use OxLint for linting JavaScript and TypeScript files; ensure lint-staged hooks and typecheck pass before commits
Files:
src/shared/types/presenters/thread.presenter.d.tssrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/sessionPresenter/managers/conversationManager.tssrc/main/presenter/skillPresenter/index.ts
src/shared/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/shared/**/*.ts: Shared types between main and renderer processes must be placed insrc/shared/
IPC contract definitions must be placed insrc/shared/Shared TypeScript types and utilities should be placed in
src/shared/
Files:
src/shared/types/presenters/thread.presenter.d.ts
**/*.{js,ts,tsx,jsx,vue,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
All logs and comments must be in English
Files:
src/shared/types/presenters/thread.presenter.d.tssrc/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/sessionPresenter/managers/conversationManager.tssrc/main/presenter/skillPresenter/index.ts
**/*.{js,ts,tsx,jsx,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use OxLint as the linter
Files:
src/shared/types/presenters/thread.presenter.d.tssrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/sessionPresenter/managers/conversationManager.tssrc/main/presenter/skillPresenter/index.ts
**/*.{js,ts,tsx,jsx,vue,json,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use Prettier as the code formatter
Files:
src/shared/types/presenters/thread.presenter.d.tssrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/renderer/src/i18n/en-US/settings.jsonsrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/sessionPresenter/managers/conversationManager.tssrc/main/presenter/skillPresenter/index.ts
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,vue}: Use camelCase for variable and function names; use PascalCase for types and classes; use SCREAMING_SNAKE_CASE for constants
Configure Prettier with single quotes, no semicolons, and line width of 100 characters. Runpnpm run formatafter completing features
Files:
src/shared/types/presenters/thread.presenter.d.tssrc/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/sessionPresenter/managers/conversationManager.tssrc/main/presenter/skillPresenter/index.ts
src/renderer/src/i18n/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
src/renderer/src/i18n/**/*.json: Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/settings.json
src/renderer/src/i18n/{zh-CN,zh-HK,zh-TW}/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
For Chinese translations, consider using '智能体' for 'Agents' instead of the English term
Files:
src/renderer/src/i18n/zh-CN/settings.json
src/renderer/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
src/renderer/**/*.vue: Use Vue 3 Composition API for all components
Use Tailwind CSS for styling with scoped styles
All user-facing strings must use i18n keys via vue-i18n
Files:
src/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vue
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Vue components must be named in PascalCase (e.g.,
ChatInput.vue) and use Vue 3 Composition API with Pinia for state management and Tailwind for styling
Files:
src/renderer/settings/components/skills/SkillFolderTreeNode.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vue
src/renderer/src/i18n/en-US/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
Use English (en-US) as the reference for translation accuracy when adding new keys
Files:
src/renderer/src/i18n/en-US/settings.json
src/main/presenter/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/presenter/**/*.ts: Use EventBus to broadcast events from main to renderer viamainWindow.webContents.send()
Implement one presenter per functional domain in the main process
Files:
src/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/sessionPresenter/managers/conversationManager.tssrc/main/presenter/skillPresenter/index.ts
src/main/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/**/*.ts: Use EventBus fromsrc/main/eventbus.tsfor decoupled inter-process communication
Context isolation must be enabled with preload scripts for secure IPC communicationElectron main process code should reside in
src/main/, with presenters organized inpresenter/subdirectory (Window, Tab, Thread, Mcp, Config, LLMProvider), and app events managed viaeventbus.ts
Files:
src/main/presenter/agentPresenter/message/skillsPromptBuilder.tssrc/main/presenter/sessionPresenter/managers/conversationManager.tssrc/main/presenter/skillPresenter/index.ts
🧠 Learnings (15)
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Applied to files:
docs/specs/skills-system/skills-syncing.mdsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/{zh-CN,zh-HK,zh-TW}/**/*.json : For Chinese translations, consider using '智能体' for 'Agents' instead of the English term
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/en-US/**/*.json : Use English (en-US) as the reference for translation accuracy when adding new keys
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Ensure all code comments are in English and all log messages are in English, with no non-English text in code comments or console statements
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Run 'pnpm run i18n' to check translation completeness using zh-CN as source, or 'pnpm run i18n:en' to check using en-US as source
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Applies to src/renderer/src/i18n/**/*.ts : All user-facing strings must use vue-i18n keys defined in `src/renderer/src/i18n/`
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Use the i18n-code-reviewer agent to validate translation quality, check for missing translations, review proper noun handling, and ensure consistency across languages
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : All user-facing strings must use i18n keys, not hardcoded text
Applied to files:
src/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/en-US/settings.json
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Vue 3 renderer app code should be organized in `src/renderer/src` with subdirectories for `components/`, `stores/`, `views/`, `i18n/`, and `lib/`
Applied to files:
src/renderer/settings/components/skills/SkillFolderTreeNode.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.vue : Use `<script setup>` syntax for concise Vue 3 component definitions with Composition API
Applied to files:
src/renderer/settings/components/skills/SkillInstallDialog.vue
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/main/presenter/configPresenter/**/*.ts : Custom prompts are managed independently of MCP through config data source using `configPresenter.getCustomPrompts()`
Applied to files:
src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/main/presenter/**/*.ts : Implement one presenter per functional domain in the main process
Applied to files:
src/main/presenter/skillPresenter/index.ts
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/main/presenter/mcpPresenter/inMemoryServers/**/*.ts : MCP tool implementations must be registered in `mcpPresenter/index.ts` after implementation
Applied to files:
src/main/presenter/skillPresenter/index.ts
🧬 Code graph analysis (1)
src/main/presenter/skillPresenter/index.ts (3)
src/shared/types/skill.ts (6)
ISkillPresenter(91-124)SkillMetadata(13-24)SkillContent(30-35)SkillInstallOptions(49-51)SkillInstallResult(40-44)SkillFolderNode(56-61)src/main/eventbus.ts (1)
eventBus(151-151)src/main/events.ts (1)
SKILL_EVENTS(265-272)
🪛 LanguageTool
docs/specs/skills-system/code-review.md
[style] ~368-~368: To elevate your writing, try using a synonym here.
Context: ...is could create a very long tree that's hard to navigate. ```typescript const expan...
(HARD_TO)
🪛 markdownlint-cli2 (0.18.1)
docs/specs/skills-system/skills-syncing-antigravity.md
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
74-74: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
docs/specs/skills-system/skills-syncing.md
201-201: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
308-308: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
323-323: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
345-345: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
366-366: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
368-368: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
393-393: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
395-395: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
420-420: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
422-422: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
453-453: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
455-455: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
479-479: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
481-481: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
502-502: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
504-504: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
533-533: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
563-563: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
595-595: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
docs/specs/skills-system/skills-syncing-cursor.md
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
78-78: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
docs/specs/skills-system/skills-syncing-copilot.md
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
docs/specs/skills-system/skills-syncing-claude-code.md
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
docs/specs/skills-system/skills-syncing-kiro.md
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
126-126: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
262-262: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
docs/specs/skills-system/skills-syncing-windsurf.md
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
71-71: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
90-90: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (20)
docs/specs/skills-system/skills-syncing-windsurf.md (1)
150-187: Verify conversion logic handles edge cases in YAML and Markdown parsing.The
convertFromWindsurf()function extracts the title and description by searching for line markers (#,## Steps). This approach is fragile if:
- A workflow has no title (code assumes one exists)
- The description section contains multiple paragraphs (code breaks on first non-empty line)
- Step content contains markdown that could be misinterpreted
Consider using a robust Markdown frontmatter parser (as mentioned in the broader code-review.md document) to extract structured metadata consistently across all workflow formats.
docs/specs/skills-system/skills-syncing-antigravity.md (1)
125-142: Verify gray-matter integration aligns with backend implementation.The conversion functions use
grayMatterfor YAML frontmatter parsing (line 127) and assume a specific structure (data.description). Confirm that:
- The
gray-matterversion in package.json (^4.0.3) is compatible with this usage- The backend SkillPresenter also uses
gray-matterfor consistent parsing- Edge cases (empty description, malformed YAML) are handled gracefully
Also applies to: 147-167
docs/specs/skills-system/code-review.md (3)
224-254: Request verification: Critical security and data integrity issues (Issues 8, 9, 14).This review identifies three critical issues that should be verified as either fixed or explicitly deferred:
Issue 8 (YAML Injection, Medium severity, lines 224-254): Unsanitized user input in YAML frontmatter could corrupt skill files. The documentation recommends using
js-yamlfor serialization.Issue 14 (Symlink Handling, Medium severity, lines 383-412): Symlinks could cause infinite recursion and stack overflow. The documentation recommends checking
entry.isSymbolicLink().Issue 9 (Skill Name Change Handling, Medium severity, lines 258-281): Renaming a skill in the editor doesn't update the directory, causing a mismatch.
Verify that the actual implementation in
src/renderer/settings/components/skills/SkillEditorSheet.vueandsrc/main/presenter/skillPresenter/index.tshas addressed these issues or confirm they are tracked separately.Also applies to: 258-281
123-163: Issue 4 (URL Download Safety) and Issue 21 (Content-Type Validation) are closely related.Both issues address the safety of downloading skill ZIPs from URLs. The documentation recommends adding:
- Timeout and size limits (AbortController, Content-Length validation)
- Content-Type header validation
These should be implemented together in a single, robust download handler. Verify that
src/main/presenter/skillPresenter/index.ts:downloadSkillZip()includes both safeguards.
775-820: Approve comprehensive documentation quality and prioritization.The code review document is exceptionally well-structured with:
- Clear location references and code examples for each issue
- Concrete recommendations with code snippets
- Organized severity levels and priority tiers
- A helpful test coverage gaps section
- Realistic assessment of trade-offs (e.g., "implementation is solid overall with good security practices")
The Priority 1-3 breakdown at the end (lines 806-820) provides actionable guidance. If this review represents the current state of the codebase, the prioritization is sound: YAML injection and symlink handling are legitimately critical, while many Low-severity items are defensive improvements rather than bugs.
src/shared/types/presenters/thread.presenter.d.ts (1)
8-28: Good addition; ensure callers normalizeactiveSkillsto[]when absent.src/main/presenter/skillPresenter/index.ts (13)
23-39: LGTM! Well-designed security configuration.The security-focused configuration constants with documented rationale (preventing memory exhaustion, ZIP bombs, stack overflow) demonstrate defensive programming. The limits appear reasonable for the skill system's use case.
60-89: LGTM! Clean constructor and path resolution.Dependency injection of
IConfigPresenterfollows good design patterns. The path resolution logic properly handles both configured and default paths, with safe directory creation usingrecursive: true.
106-136: LGTM! Robust skill discovery implementation.The discovery logic correctly handles errors for individual skills without failing the entire operation (Line 126), ensuring that one malformed skill doesn't prevent others from loading. The event emission (Line 133) keeps the renderer synchronized with discovered skills.
141-175: LGTM! Thorough metadata parsing with good validation.The metadata parsing includes proper validation of required fields (Lines 150-153), helpful warnings for name mismatches (Lines 156-160), and defensive type filtering for the
allowedToolsarray (Lines 167-169). The fallback todirName(Line 163) ensures robustness.
181-191: LGTM! Excellent race condition protection pattern.The
discoveryPromisepattern (Lines 183-187) elegantly prevents concurrent discovery calls, ensuring thread-safe lazy initialization. Thefinallyblock guarantees cleanup even on errors.
212-254: LGTM! Well-implemented lazy loading with security checks.The two-level caching strategy (metadata + content) optimizes memory usage. The file size check (Lines 230-237) before reading prevents memory exhaustion attacks, aligning with the documented security approach. Path variable replacement (Line 241) provides useful templating capability.
364-439: LGTM! Excellent security-aware installation implementation.The installation logic includes multiple critical security checks:
- Path traversal protection (Lines 405-411)
- Skill name validation preventing directory traversal (Lines 394-396)
- Source/target equality check preventing self-installation (Lines 401-403)
- Backup functionality preventing data loss (Lines 413-420)
The cache management and event emission maintain system consistency.
454-511: LGTM! Exemplary security implementation for ZIP extraction.This implementation demonstrates excellent security practices:
- Pre-extraction size validation (Lines 456-459) prevents ZIP bomb attacks
- Absolute path rejection (Lines 476-478) for both Windows and Unix
- Manual segment parsing with
..rejection (Lines 480-490)- Double validation with
path.relative(Lines 497-501) ensures extracted files stay within boundsThis defense-in-depth approach significantly reduces attack surface.
533-575: LGTM! Robust download implementation with multiple safeguards.The download logic implements layered security:
- Request timeout via
AbortController(Lines 534-535)- Pre-download size check using
Content-Length(Lines 543-549)- Content-Type validation (Lines 552-560)
- Post-download size verification (Lines 565-569)
The
finallyblock ensures proper cleanup of the timeout. This defense-in-depth approach prevents both hanging connections and resource exhaustion.
645-686: LGTM! Well-protected recursive directory traversal.The implementation includes critical safeguards:
- Depth limiting (Lines 650-652) prevents stack overflow
- Symlink skipping (Lines 660-662) prevents infinite recursion
- Graceful error handling (Lines 682-685)
The recursive depth tracking (Line 670) correctly prevents unbounded traversal.
699-754: LGTM! Well-designed active skills management.The implementation demonstrates good design:
getActiveSkillsauto-corrects invalid skills (Lines 705-709), improving UXsetActiveSkillscalculates diffs and emits granular events (Lines 734-749), enabling selective UI updates- Different error handling strategies: safe defaults for reads, exceptions for writes
The integration with
sessionPresenterprovides proper persistence.
789-853: LGTM! Robust hot-reload implementation with proper cache management.The file watcher includes thoughtful configuration:
- Single-watcher guard (Lines 790-792)
awaitWriteFinishprevents partial-write race conditions (Lines 797-800)- Depth limiting and
ignoreInitialoptimize performance- Proper cache invalidation on all events (Lines 809, 840-841)
- Event emission keeps UI synchronized
The error handler (Lines 848-850) logs without crashing the watcher.
858-901: LGTM! Complete resource cleanup and lifecycle management.The utility methods demonstrate proper resource management:
stopWatchingincludes null checks and logging (Lines 859-863)copyDirectoryskips symlinks consistently (Lines 876-878) with other traversal methodsdestroythoroughly cleans up all resources (Lines 895-899)The lifecycle methods ensure clean shutdown without resource leaks.
src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts (1)
1-2: The type assertions in this file are actually safe and do not present the claimed risk.The
presenter.skillPresenteris typed asISkillPresenter(not optional) and is always initialized in the presenter's constructor asnew SkillPresenter(this.configPresenter). WhileisSkillsEnabled()controls whether skills are active, it does not affect whether the presenter object itself is instantiated—the presenter is always created. Additionally, all three usages are guarded byisSkillsEnabled()checks and wrapped in try-catch blocks that return safe defaults, providing defensive safeguards even if skills are disabled.The direct import of
SkillPresenteron line 2 is unused for runtime behavior and the type assertions are safe given the non-optional typing of the property.
| ``` | ||
| ~/.claude/skills/ | ||
| ├── code-review/ | ||
| │ ├── SKILL.md # 必需:元数据 + 指令 | ||
| │ ├── references/ # 可选:参考文档(按需加载) | ||
| │ │ ├── style-guide.md | ||
| │ │ └── checklist.md | ||
| │ ├── scripts/ # 可选:可执行脚本 | ||
| │ │ └── lint.sh | ||
| │ └── assets/ # 可选:输出模板/资源 | ||
| │ └── report-template.md | ||
| ├── refactor/ | ||
| │ └── SKILL.md | ||
| └── my-skill/ | ||
| └── SKILL.md | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add language identifier to directory structure code block.
The fenced code block is missing a language identifier. Specify text or tree to resolve the markdownlint warning (MD040).
Fix the code block fence
-```
+```text
~/.claude/skills/
├── code-review/🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In @docs/specs/skills-system/skills-syncing-claude-code.md around lines 17 - 32,
The fenced directory-tree code block currently lacks a language identifier;
update the opening fence for the block that begins with ``` and the directory
listing (~/.claude/skills/ ├── code-review/ ...) to include a language token
such as text or tree (e.g., change ``` to ```text) so markdownlint MD040 is
resolved and the block is explicitly marked as plain text.
| ``` | ||
| .cursor/commands/ | ||
| ├── code-review.md | ||
| ├── refactor.md | ||
| ├── lint-suite.md | ||
| ├── create-pr.md | ||
| └── optimize-performance.md | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add languages to fenced blocks to satisfy markdownlint (MD040).
Proposed diff
-```
+```text
.cursor/commands/
├── code-review.md
├── refactor.md
├── lint-suite.md
├── create-pr.md
└── optimize-performance.md@@
- +text
/code-review
/refactor
/lint-suite
Also applies to: 78-83
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In @docs/specs/skills-system/skills-syncing-cursor.md around lines 17 - 24,
Update the fenced code blocks in skills-syncing-cursor.md to include a language
specifier to satisfy markdownlint MD040: change the triple-backtick blocks
around the directory tree and the list of commands (the blocks around the lines
showing ".cursor/commands/" and the block showing the "/code-review" style list)
to use "```text" instead of "```"; ensure both occurrences mentioned (around the
directory tree and the second block at the region covering the > /code-review, >
/refactor, > /lint-suite lines) are updated so all fenced blocks declare the
language.
| ``` | ||
| .kiro/steering/ | ||
| ├── product.md # 产品概述(always included) | ||
| ├── tech.md # 技术栈(always included) | ||
| ├── structure.md # 项目结构(always included) | ||
| ├── react-components.md # 条件包含 | ||
| ├── api-patterns.md # 按需引用 | ||
| └── testing-guidelines.md # 按需引用 | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add languages to fenced blocks to satisfy markdownlint (MD040).
Proposed diff
-```
+```text
.kiro/steering/
├── product.md # 产品概述(always included)
├── tech.md # 技术栈(always included)
├── structure.md # 项目结构(always included)
├── react-components.md # 条件包含
├── api-patterns.md # 按需引用
└── testing-guidelines.md # 按需引用@@
- +text
Check #api-patterns for the REST conventions
Follow #react-components guidelines
@@
-```
+```text
┌────────────────────────────────────────────────────────────┐
│ Kiro 导出选项 │
@@
└────────────────────────────────────────────────────────────┘
</details>
Also applies to: 126-130, 262-278
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.18.1)</summary>
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
In @docs/specs/skills-system/skills-syncing-kiro.md around lines 17 - 25,
Several fenced code blocks in skills-syncing-kiro.md lack a language indicator
causing markdownlint MD040; update each triple-backtick block shown (the
directory tree block, the short notes block, and the ASCII art block) to use a
language tag such as "text" (i.e., replace withtext), and apply the same
change to the other problematic ranges mentioned (lines ~126-130 and ~262-278)
so all fenced blocks include a language specifier.
</details>
<!-- This is an auto-generated comment by CodeRabbit -->
| ``` | ||
| ┌─────────────────────────────────────────────────────────────────────┐ | ||
| │ SkillSyncPresenter │ | ||
| │ 统一的同步协调器,管理导入/导出流程 │ | ||
| ├─────────────────────────────────────────────────────────────────────┤ | ||
| │ │ | ||
| │ ┌───────────────────┐ ┌───────────────────┐ │ | ||
| │ │ ToolScanner │ │ FormatConverter │ │ | ||
| │ │ 扫描外部工具目录 │ │ 格式转换引擎 │ │ | ||
| │ └─────────┬─────────┘ └─────────┬─────────┘ │ | ||
| │ │ │ │ | ||
| │ ▼ ▼ │ | ||
| │ ┌─────────────────────────────────────────────────────────────┐ │ | ||
| │ │ Format Adapters (插件化) │ │ | ||
| │ │ 每个外部工具一个 Adapter,负责解析和序列化该工具的格式 │ │ | ||
| │ └─────────────────────────────────────────────────────────────┘ │ | ||
| │ │ | ||
| └─────────────────────────────────────────────────────────────────────┘ | ||
| │ | ||
| │ 调用 | ||
| ▼ | ||
| ┌─────────────────────────────────────────────────────────────────────┐ | ||
| │ SkillPresenter │ | ||
| │ 现有的 Skill 管理器(安装/卸载/读写) │ | ||
| └─────────────────────────────────────────────────────────────────────┘ | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix markdownlint issues: add fence languages (MD040) and convert “Step X” emphasis to headings (MD036).
Proposed diff (apply same pattern across the file)
-```
+```text
┌─────────────────────────────────────────────────────────────────────┐
│ SkillSyncPresenter │
@@
└─────────────────────────────────────────────────────────────────────┘
│
│ 调用
▼
┌─────────────────────────────────────────────────────────────────────┐
│ SkillPresenter │
│ 现有的 Skill 管理器(安装/卸载/读写) │
└─────────────────────────────────────────────────────────────────────┘@@
-Step 1: 选择来源工具
+#### Step 1: 选择来源工具
- +text
┌──────────────────────────────────────────────────────────────────┐
│ 从其他工具导入 Skills ✕ │
@@
└──────────────────────────────────────────────────────────────────┘
@@
-**Step 2: 选择 Skills**
+#### Step 2: 选择 Skills
@@
-**Step 3: 预览与冲突处理**
+#### Step 3: 预览与冲突处理
Also applies to: 308-335, 345-391, 366-418, 420-449, 453-529, 533-555, 563-576, 595-620
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
201-201: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
| <div | ||
| class="border-2 border-dashed rounded-lg p-8 text-center hover:border-primary/50 transition-colors cursor-pointer" | ||
| @click="selectFolder" | ||
| > | ||
| <Icon | ||
| v-if="!installing" | ||
| icon="lucide:folder-open" | ||
| class="w-10 h-10 mx-auto text-muted-foreground mb-2" | ||
| /> | ||
| <Icon | ||
| v-else | ||
| icon="lucide:loader-2" | ||
| class="w-10 h-10 mx-auto text-muted-foreground mb-2 animate-spin" | ||
| /> | ||
| <p class="text-sm text-muted-foreground"> | ||
| {{ t('settings.skills.install.folderHint') }} | ||
| </p> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace hardcoded user-facing strings with i18n + make clickable areas keyboard-accessible
description: 'Invalid URL format…'(Line 250) and the file picker filter name'ZIP Files'(Line 214) are user-facing strings and should be i18n-backed (per guidelines/learnings).- The folder/zip “dropzone” divs are clickable but not keyboard accessible; suggest
role="button" tabindex="0"+@keydown.enter.space, or use actual<button>/<Button>.
Proposed i18n + a11y changes (component)
- <div
+ <div
class="border-2 border-dashed rounded-lg p-8 text-center hover:border-primary/50 transition-colors cursor-pointer"
@click="selectFolder"
+ role="button"
+ tabindex="0"
+ @keydown.enter.prevent="selectFolder"
+ @keydown.space.prevent="selectFolder"
>
...
- <div
+ <div
class="border-2 border-dashed rounded-lg p-8 text-center hover:border-primary/50 transition-colors cursor-pointer"
@click="selectZip"
+ role="button"
+ tabindex="0"
+ @keydown.enter.prevent="selectZip"
+ @keydown.space.prevent="selectZip"
> const result = await devicePresenter.selectFiles({
- filters: [{ name: 'ZIP Files', extensions: ['zip'] }]
+ filters: [{ name: t('settings.skills.install.zipFileFilterName'), extensions: ['zip'] }]
}) toast({
title: t('settings.skills.install.failed'),
- description: 'Invalid URL format. Please enter a valid HTTP or HTTPS URL.',
+ description: t('settings.skills.install.invalidUrl'),
variant: 'destructive'
})Proposed i18n additions (en-US + zh-CN shown; please add to all locales)
--- a/src/renderer/src/i18n/en-US/settings.json
+++ b/src/renderer/src/i18n/en-US/settings.json
@@
"install": {
@@
"dragNotSupported": "Drag not supported, please click to select"
+ ,"invalidUrl": "Invalid URL format. Please enter a valid HTTP or HTTPS URL."
+ ,"zipFileFilterName": "ZIP Files"
},--- a/src/renderer/src/i18n/zh-CN/settings.json
+++ b/src/renderer/src/i18n/zh-CN/settings.json
@@
"install": {
@@
"dragNotSupported": "暂不支持拖拽,请点击选择"
+ ,"invalidUrl": "URL 格式无效,请输入有效的 HTTP 或 HTTPS 地址。"
+ ,"zipFileFilterName": "ZIP 文件"
},Also applies to: 52-69, 213-216, 245-253
🤖 Prompt for AI Agents
In @src/renderer/settings/components/skills/SkillInstallDialog.vue around lines
28 - 45, The component SkillInstallDialog.vue uses hardcoded user strings
("Invalid URL format…", "ZIP Files") and non-keyboard-accessible clickable divs;
replace those literal strings with i18n keys and use the existing translation
helper (e.g. t('settings.skills.install.invalidUrl') and
t('settings.skills.install.zipFiles')) and add matching entries across all
locale files, and make the dropzone elements keyboard-accessible by either
converting the interactive <div> to a semantic <button> or adding role="button"
tabindex="0" and a keydown handler that calls the same handler as click (e.g.
@keydown.enter.space="selectFolder") so selectFolder and selectZip remain
operable by keyboard.
- Implement comprehensive unit tests for the security module, covering path safety, file size limits, permissions, and input validation. - Introduce unit tests for the ToolScanner class, validating external tool configurations, path expansion, and tool availability. - Mock filesystem interactions to isolate tests and ensure reliability. - Validate filename safety and prevent path traversal in directory scanning. - Ensure proper handling of oversized files during tool scanning.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🤖 Fix all issues with AI agents
In @src/main/presenter/skillSyncPresenter/adapters/antigravityAdapter.ts:
- Around line 77-105: The detect() method incorrectly requires YAML frontmatter;
update detect(content: string) to accept files without frontmatter by returning
true when hasStepsStructure(content) is true even if no frontmatter is present,
and when frontmatter exists keep validating that data.description is a string
(allow other fields to be absent) plus hasStepsStructure(body). Reuse the
existing private hasStepsStructure(...) helper instead of duplicating regex
checks. Also replace the Windows-unsafe split('/') logic in extractName() with
path.basename and path.extname to reliably derive the filename without extension
on all platforms.
In @src/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.ts:
- Around line 31-56: The adapter incorrectly advertises supportsModel as false
while parse()/serialize() accept and emit a model and tags without type checks;
update getCapabilities() to set supportsModel: true, add validation in parse()
so that data.model is assigned only if typeof data.model === 'string' and
data.tags is assigned only if Array.isArray(data.tags) && data.tags.every(t =>
typeof t === 'string'), and mirror those checks in serialize() when writing
frontmatter; also replace any manual path splitting (e.g.,
filePath.split('/').pop()) with path.basename(context.filePath) to ensure
cross-platform path handling (references: parse(), serialize(),
getCapabilities(), CanonicalSkill.model, CanonicalSkill.tags, and usage of
context.filePath).
In @src/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts:
- Around line 122-134: extractName currently uses context.filePath.split('/')
and string replace to get a filename and strip ".md", which breaks on Windows
and is extension-fragile; update extractName to use the Node path utilities
(import from 'node:path') and call path.basename or path.parse on
context.filePath to obtain the base name and remove the extension safely (e.g.,
path.parse(context.filePath).name), keeping the existing titleToName logic and
ParseContext type untouched.
In @src/main/presenter/skillSyncPresenter/adapters/index.ts:
- Around line 19-37: The adapters Map is silently overridden when a duplicate
adapter.id is registered; update registerBuiltinAdapters() and registerAdapter()
to guard against duplicates by checking adapters.has(adapter.id) before calling
adapters.set(...); on duplicate either throw a descriptive Error (including
adapter.id and source like "builtin" vs "runtime") or log a clear warning and
skip registration so insertion order / detection is not silently changed; ensure
the check happens for each adapter in the builtinAdapters loop and in the
single-adapter registerAdapter() path.
In @src/main/presenter/skillSyncPresenter/adapters/kiroAdapter.ts:
- Around line 104-128: The detect() method fails to recognize "inclusion:
on-demand" — update its inclusion checks to accept 'on-demand' in addition to
'always' and 'conditional' (i.e., include === 'on-demand' or match those three
values); change removeKiroMetaComments() regex from \w+ to allow hyphens (e.g.,
use character class including -) so comments like <!-- Kiro inclusion: on-demand
--> are removed; change extractName() to use path.basename(...) instead of
split('/') to support Windows paths; and update serializeFrontmatter() to escape
quotes/backslashes in array values by calling the existing escapeYamlString()
for each array element before joining/serializing.
In @src/main/presenter/skillSyncPresenter/index.ts:
- Around line 326-347: Conflict detection and rename logic incorrectly operate
on the file basename for subfolder formats (e.g., */SKILL.md), causing missed
conflicts and wrong renames; update the preview generation in the block that
builds previews (where getExportTargetPath(skillName, targetDir, tool) is called
and existingFiles is checked) to detect conflicts using the top-level folder
name (skillName) when format is a subfolder-style targetToolId, and when a
conflict is found call a folder-unique generator (not generateUniqueFilePath
which renames files) to produce a unique skill folder name, recompute targetPath
via getExportTargetPath with the new folder name, and set the
preview.conflict.existingPath/strategy accordingly; apply the same fix to the
other occurrence around lines referenced (719-733) so both preview paths and
conflict strategies handle subfolder formats correctly.
- Around line 266-364: The previewExport method (and similar blocks around lines
369-496, 566-602) is emitting sensitive/verbose console logs
(console.log/console.warn/console.error) that leak skill names, file paths and
full lists; replace these with structured logger calls at appropriate levels
(e.g. this.logger.debug for debug-only details and this.logger.warn/error for
warnings/errors), and gate debug-level messages behind a runtime debug flag
(e.g. this.syncContext.debug or this.syncContext.verbose) so they are not
emitted by default. Specifically, change console.log/console.warn/console.error
in previewExport, the resolveSkillsDir usage, getExportTargetPath calls, and
conversion logging (including convertedContent.length and full skill lists) to
conditional this.logger.debug(...) when debug is enabled; avoid printing raw
filesystem paths and full arrays—mask or redact path segments and list lengths
instead (e.g. show "<redacted-path>" or only counts), and keep error logs but
include sanitized error.message rather than full objects.
- Around line 197-214: The temp folder created by
createTempSkillFolder(preview.skill) can leak if installFromFolder or later code
throws; refactor to declare tempDir before the install and wrap the install and
subsequent operations in a try/finally so cleanupTempFolder(tempDir) always
runs; keep the existing logic that may update preview.skill.name when strategy
=== ConflictStrategy.RENAME (generateUniqueName) but ensure tempDir is cleaned
in the finally block even on errors from skillPresenter.installFromFolder or
other failures.
- Around line 607-634: In createTempSkillFolder validate and sanitize all
user-provided names (skill.name, ref.name, script.name) before using them with
path.join: reject or sanitize names containing path separators or traversal
sequences (../), use a filename-safe check (e.g., isFilenameSafe or
strip/replace path chars) and fallback to a safe generated filename; after
building each target path verify containment by checking path.relative(tempDir,
targetPath) does not start with '..' (or resolve and compare prefixes) and throw
or skip writing if the check fails to ensure files cannot escape tempDir.
In @src/main/presenter/skillSyncPresenter/security.ts:
- Around line 189-235: The traversal swallows all non-"Size limit exceeded"
errors causing validateFolderSize to undercount and possibly return valid:true;
modify validateFolderSize/calculateSize to track a failure flag (e.g., hadError)
or capture the first non-synthetic error encountered during recursion, stop or
continue traversal as appropriate, and ensure the final return sets valid:false
and includes an explanatory error message when any filesystem error occurred
(permission/stat/readdir), while preserving the early throw for the 'Size limit
exceeded' case; reference the calculateSize inner function, the totalSize
variable, and the final catch branch to implement this change.
In @src/renderer/settings/components/skills/SkillSyncDialog/ExportWizard.vue:
- Around line 438-464: loadTools currently swallows errors so handleNext always
advances; modify loadTools to return a boolean (true on success, false on
failure) or rethrow the error, and in handleNext call const ok = await
loadTools() and only emit('update:step', 2) when ok is true; keep the existing
catch/toast in loadTools but ensure it returns false after handling the error
(or let the error propagate and handle it in handleNext) so step 2 is not
emitted on failure (refer to functions loadTools and handleNext).
In @src/renderer/settings/components/skills/SkillSyncDialog/SkillSelector.vue:
- Around line 74-123: toggleAll currently flips local skillCheckedState but
never emits the updated selection, causing parent/child desync; update toggleAll
to compute the new selected array from props.skills (e.g., map names when
selecting all, or empty array when deselecting), assign skillCheckedState.value
accordingly, and call emit('update:selectedSkills', newSelected) so the parent
receives the change and updateSkillChecked no longer reads stale
props.selectedSkills.
- Around line 56-60: The Shadcn component imports in SkillSelector.vue use the
alias '@shadcn/components/ui/...' which is inconsistent with the repo
convention; update the import specifiers for the Vue UI components (Button,
Badge, Checkbox, ScrollArea) to use the repo alias '@/shadcn/components/ui/...'
so they resolve correctly (leave the existing import for the shared type
ExternalSkillInfo unchanged if it already uses @shared). Ensure each import line
is updated to the '@/shadcn/...' path.
In @src/renderer/src/i18n/ru-RU/common.json:
- Around line 17-18: The locale keys "next" and "back" violate the dot-separated
hierarchical naming convention; rename them to "navigation.next" and
"navigation.back" (and update any references in code/templates that use "next"
or "back") across the locale files so they match the other 12 locales and keep
translations unchanged, ensuring all lookups use the new keys (e.g., replace
occurrences of next -> navigation.next and back -> navigation.back, and update
any i18n key usage in components or tests).
🟡 Minor comments (9)
docs/specs/skills-system/create-skill-prompt.md-64-76 (1)
64-76: Add language specifications to code blocks for markdown compliance.Several code blocks displaying directory structures lack language specifications, which triggers MD040 linting errors. Directory trees are conventionally shown using
bashortextlanguage tags.🔧 Proposed fix: Add language specifications
Apply language tags to the directory structure code blocks:
- ``` + ```bash skill-name/ ├── SKILL.md (required) │ ├── YAML frontmatter (required) │ │ ├── name: (required) │ │ └── description: (required) │ └── Markdown instructions (required) └── Supporting Files (optional, any organization) ├── *.md - Reference documentation ├── *.sh / *.py - Executable scripts └── any-folder/ - Organize as needed - ``` + ```Repeat similarly for the three other directory structure blocks (Pattern A, Pattern B, and Pattern C at lines 99-106, 109-116, and 120-127 respectively).
Also applies to: 99-106, 109-116, 120-127
src/renderer/settings/components/skills/SkillsHeader.vue-18-23 (1)
18-23: Add aria-label to search input for accessibility.The search input lacks an explicit label and relies only on a placeholder, which is insufficient for screen readers.
♿ Proposed accessibility fix
<Input :model-value="searchQuery" @update:model-value="$emit('update:searchQuery', String($event))" :placeholder="t('settings.skills.search')" + :aria-label="t('settings.skills.search')" class="pl-8 h-8 w-48" />docs/specs/skills-system/create-skill-spec.md-11-13 (1)
11-13: Minor grammar refinement needed in core principle.Line 13 uses "无需专门工具" (no special tools required); consider rephrasing to improve clarity and grammatical flow. The intent is clear, but the phrasing could be more natural (e.g., "无需专门工具支持" or similar).
As per static analysis hints, there is a grammar concern with the phrase structure.
src/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts-106-116 (1)
106-116: Capabilities vs behavior: clarifysupportsReferencessemantics.
serialize()can inline references, butgetCapabilities()reportssupportsReferences: false. If the UI/workflow uses this capability flag to decide whether references are preserved, this mismatch can cause surprises.Also applies to: 71-78
src/renderer/settings/components/skills/SkillSyncDialog/ImportWizard.vue-239-279 (1)
239-279: Import progress is not being updated during execution.Lines 54-64 display
importProgress.current,importProgress.total, andimportProgress.currentSkill, but these values are only initialized at line 241 and never updated during the actual import operation. The comment at line 302 mentions "Listen to import progress events" but no event listener is implemented.Either:
- Implement the event listener to update
importProgressas the import proceeds, or- Remove the progress display and show a simple loading state
Suggested event listener implementation
If the presenter emits progress events, add this after line 114:
const skillSyncPresenter = usePresenter('skillSyncPresenter') + +// Listen to import progress +skillSyncPresenter.on?.('import:progress', (progress) => { + importProgress.value = progress +})Alternatively, if progress tracking isn't supported yet, simplify the importing state display at lines 54-64 to just show a loading spinner without detailed progress.
Committable suggestion skipped: line range outside the PR's diff.
src/renderer/settings/components/skills/SkillSyncDialog/ExportWizard.vue-84-106 (1)
84-106: Make tool selection keyboard-accessible (avoid clickabledivonly).At minimum add
role="button",tabindex="0", and@keydown.enter/@keydown.spacehandlers; or use a real<button>element for the card.src/main/presenter/index.ts-254-256 (1)
254-256: Changed TS comments should be English (repo guideline).Please translate the newly added Skills comments around initialization to English to match
**/*.{js,ts,tsx,jsx,vue}guidance.Also applies to: 277-289
src/main/presenter/skillSyncPresenter/security.ts-256-288 (1)
256-288:checkWritePermission()doesn’t check the filesystem root directory case.If
targetPath(or its parents) resolve to the root immediately, the loop doesn’t run and returnsfalseeven if root is writable (edge case, but the function claims “nearest existing ancestor”). Easiest fix: checkrootonce before returning.src/main/presenter/skillSyncPresenter/formatConverter.ts-171-180 (1)
171-180: False “flattened subfolder” warning when references/scripts are empty arrays.
(skill.references || skill.scripts)is truthy for[], so you can warn even when there’s nothing to flatten.Proposed fix
- if (capabilities.supportsSubfolders === false && (skill.references || skill.scripts)) { + const hasRefs = (skill.references?.length ?? 0) > 0 + const hasScripts = (skill.scripts?.length ?? 0) > 0 + if (capabilities.supportsSubfolders === false && (hasRefs || hasScripts)) { if (!warnings.some((w) => w.field === 'references' || w.field === 'scripts')) { warnings.push({ type: 'format_change', message: `Subfolder structure will be flattened (${targetTool.name} uses single-file format)`, suggestion: 'All content will be in a single file' }) } }
🧹 Nitpick comments (24)
src/renderer/settings/components/skills/SkillsHeader.vue (1)
20-20: Consider removing unnecessary String() coercion.The
String()wrapper is likely unnecessary since the ShadcnInputcomponent already emits string values for text inputs.♻️ Simplification
- @update:model-value="$emit('update:searchQuery', String($event))" + @update:model-value="$emit('update:searchQuery', $event)"src/renderer/src/i18n/zh-CN/settings.json (1)
1107-1162: Inconsistent terminology: "Skills" vs "技能".The sync section uses the English term "Skills" in multiple places (e.g., "从其他工具导入 Skills", "已成功导入 {count} 个 Skills"), while the rest of the skills UI consistently uses the Chinese translation "技能" (lines 1044-1052). This creates a terminology inconsistency within the same feature.
For consistency with the established Chinese term "技能" used throughout the skills management interface, consider replacing all occurrences of "Skills" with "技能" in the sync-related strings.
Suggested terminology fixes
- "importTitle": "从其他工具导入 Skills", + "importTitle": "从其他工具导入技能", - "exportTitle": "导出 Skills 到其他工具", + "exportTitle": "导出技能到其他工具", - "importDescription": "从 Claude Code、Cursor 等工具导入 Skills", + "importDescription": "从 Claude Code、Cursor 等工具导入技能", - "exportDescription": "将 Skills 导出到其他 AI 助手工具", + "exportDescription": "将技能导出到其他 AI 助手工具", - "step2Title": "选择要导入的 Skills", + "step2Title": "选择要导入的技能", - "exportStep1Title": "选择要导出的 Skills", + "exportStep1Title": "选择要导出的技能", - "skillCount": "已检测到 {count} 个 Skills", + "skillCount": "已检测到 {count} 个技能", - "readyToExport": "准备导出 {count} 个 Skills", + "readyToExport": "准备导出 {count} 个技能", - "importSuccessMessage": "已成功导入 {count} 个 Skills", + "importSuccessMessage": "已成功导入 {count} 个技能", - "exportSuccessMessage": "已成功导出 {count} 个 Skills", + "exportSuccessMessage": "已成功导出 {count} 个技能",src/shared/types/index.d.ts (1)
8-9: Preferexport type *here if these modules are types-only.This file is a
.d.tsbarrel and already usesexport type *for other entries; usingexport type * from './skill'/./skillSync'keeps the public surface clearly type-only (helpful under strict TS / verbatimModuleSyntax setups). If either module exports runtime values, keepexport *as-is.src/renderer/settings/components/skills/SkillSyncDialog/SkillSelector.vue (1)
112-114:allSelectedshould check membership, not just array lengths.Length equality can be wrong with duplicates or if
selectedSkillscontains names not inskills.Possible adjustment
const allSelected = computed(() => { - return props.skills.length > 0 && props.selectedSkills.length === props.skills.length + if (props.skills.length === 0) return false + const selected = new Set(props.selectedSkills) + return props.skills.every((s) => selected.has(s.name)) })src/main/events.ts (1)
264-284: Nice additive event grouping; naming looks consistent.Optional: if consumers rely on literal types, consider
as conston these objects (and ideally the whole events file) for stronger typing.test/main/presenter/skillSyncPresenter/adapters/index.test.ts (1)
224-242: Document detection order ambiguity between Copilot and Antigravity formats.The test correctly documents that Copilot's detect() may match Antigravity format first due to detection order. This is a known limitation where both formats share similar frontmatter structures (description-only frontmatter), making them ambiguous.
Consider enhancing the detection logic to be more specific for each format, possibly by checking for distinctive markers beyond just frontmatter structure (e.g., Antigravity's required "## Steps" section structure).
src/renderer/settings/components/skills/SkillSyncDialog/ConflictResolver.vue (1)
37-37: Use enum constant for default strategy value.The fallback value
'skip'is a string literal, butConflictStrategy.SKIPis used elsewhere for consistency. Using the enum constant ensures type safety and makes refactoring easier.♻️ Proposed fix
<RadioGroup - :model-value="strategies[conflict.skillName] || 'skip'" + :model-value="strategies[conflict.skillName] || ConflictStrategy.SKIP" @update:model-value="updateStrategy(conflict.skillName, $event as ConflictStrategy)" class="flex gap-4" >docs/specs/skills-system/skills-syncing-tasks.md (2)
239-239: Add language specifier to fenced code block.The fenced code block at line 239 is missing a language identifier, which affects syntax highlighting and tooling support.
♻️ Proposed fix
-``` +```text SkillSyncDialog/ ├── SkillSyncDialog.vue # 同步向导主组件 ├── ImportWizard.vue # 导入向导
366-366: Add language specifier to fenced code block.The ASCII art dependency diagram at line 366 is missing a language identifier. Consider adding
textormermaid(if converting to a diagram format) for better rendering.♻️ Proposed fix
-``` +```text Phase 1 (类型定义) │ └── 1.1 共享类型 ───────────────────────────────────────────────┐src/main/presenter/skillSyncPresenter/adapters/windsurfAdapter.ts (2)
79-95: Clarify detection logic comment.The comment at line 80 states "Must have ## Steps section" but the return statement uses OR logic (
hasStepsSection || hasNumberedSteps), meaning either condition is sufficient. If the OR logic is intentional (for lenient detection), update the comment to reflect this. Otherwise, consider AND logic if both conditions are truly required.📝 Suggested comment clarification
detect(content: string): boolean { - // Windsurf format: pure Markdown with ## Steps section and numbered steps + // Windsurf format: pure Markdown with ## Steps section or numbered steps // Must NOT start with --- (frontmatter) if (content.trim().startsWith('---')) {
187-204: Consider extracting common name conversion utilities.The
titleToNameandnameToTitlemethods are duplicated across multiple adapters (CursorAdapter has identical implementations). Extracting these to a shared utility module would reduce duplication and ensure consistent behavior.Consider creating a shared utility file such as
src/main/presenter/skillSyncPresenter/adapters/utils.ts:export function titleToName(title: string): string { return title .toLowerCase() .replace(/[^a-z0-9\s-]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') .replace(/^-|-$/g, '') } export function nameToTitle(name: string): string { return name .split('-') .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' ') }Then import and use these functions in all adapters.
src/renderer/settings/components/skills/SkillSyncDialog/ToolSelector.vue (1)
72-94: Consider extracting icon mappings to a shared constant.The
getToolIconandgetToolIconBgfunctions contain hardcoded mappings for tool IDs. If these mappings are used elsewhere in the codebase (e.g., in other Skill Sync components), consider extracting them to a shared constant file to maintain consistency and avoid duplication.src/main/presenter/skillSyncPresenter/adapters/copilotAdapter.ts (2)
223-228: Skill root reference regex may match unintended patterns.The regex
/\$\{SKILL_ROOT\}\/references\/([^\s]+)/guses[^\s]+to capture paths, which will stop at any whitespace. This could break paths with spaces. Consider using a more specific pattern or boundary detection.Suggested improvement
private processSkillRootReferences(content: string): string { - // Match ${SKILL_ROOT}/references/... patterns - return content.replace(/\$\{SKILL_ROOT\}\/references\/([^\s]+)/g, (_, path) => { + // Match ${SKILL_ROOT}/references/path patterns (up to quote, paren, or newline) + return content.replace(/\$\{SKILL_ROOT\}\/references\/([^\s'")\n]+)/g, (_, path) => { return `#file:'${path}'` }) }
263-273: YAML quoting logic may miss edge cases.The
needsQuotingfunction checks for several special characters, but YAML has additional edge cases:
- Leading/trailing quotes or special indicators like
@,`,|,>,&,*,!,[,],{,}- Reserved words like
true,false,null,yes,no- Numeric-looking strings
Consider using a YAML library like
js-yamlfor safer serialization, or expand the quoting rules to cover these cases. Sincegray-matteris already a dependency, you might leverage its stringify capabilities.src/shared/types/skillSync.ts (1)
170-179: Consider adding documentation for MERGE strategy.The
ConflictStrategyenum includes aMERGEoption with the comment "only for specific scenarios". Consider adding JSDoc comments explaining whenMERGEis applicable and what it means in the context of skill synchronization to improve developer understanding.Suggested documentation enhancement
/** * Conflict handling strategy */ export enum ConflictStrategy { /** Skip the conflicting item */ SKIP = 'skip', /** Overwrite the existing item */ OVERWRITE = 'overwrite', /** Rename with suffix */ RENAME = 'rename', - /** Merge (only for specific scenarios) */ + /** + * Merge (only for specific scenarios, e.g., combining tool lists or references). + * Not all formats support merge operations. + */ MERGE = 'merge' }src/main/presenter/skillSyncPresenter/types.ts (1)
1-63: Looks good for internal data shapes; considertypealiases if you’re standardizing away frominterface.src/main/presenter/skillSyncPresenter/adapters/antigravityAdapter.ts (1)
134-157: Avoid rolling your own YAML quoting rules if possible.
formatYamlValue()will miss edge cases (tabs, unicode, multiline semantics). Ifgray-matterstringify (or a YAML lib already in the repo) is acceptable, prefer delegating YAML escaping/serialization to it.src/main/presenter/index.ts (1)
58-60: Avoid concrete casts for lifecycle (initialize()/destroy()); put lifecycle on the interface or encapsulate it.Right now the call sites rely on
as SkillPresenter/SkillSyncPresenter, which defeats the point of typingskillPresenter: ISkillPresenterand can drift.Also applies to: 187-192, 254-289, 320-322
src/main/presenter/skillSyncPresenter/adapters/index.ts (2)
66-73: Detection order is implicitly “registration order”; consider making it explicit.Since
detectAdapter()iteratesMap.values(), any future custom registration will affect detection precedence. If multiple adapters can match, you probably want an explicit priority (or “first-match” documented + stable ordering rules).
39-41: Module-load side effects are fine, but keep tests in mind.If tests ever need a clean registry, consider exporting a test-only reset hook (or lazily registering on first access) to avoid cross-test coupling.
src/main/presenter/skillSyncPresenter/toolScanner.ts (3)
127-132:expandPath()only supports~/(not~\on Windows).If Windows is a target, also handle
~\to avoid surprising behavior.
318-332: Consider replacingconsole.*with your main-process logger.These are library-like utilities;
console.error()can be noisy and hard to route. A structured logger (or at least consistent prefixes + log levels) makes this easier to operate.Also applies to: 377-380, 422-425, 468-471
490-518: Frontmatter “description” extraction via regex is brittle (known trade-off).Given the comment “quick extraction”, this is acceptable, but expect false negatives/positives for multiline YAML, quoting, indentation, etc. If the UX depends on it, consider reusing the same frontmatter parser your adapters use.
src/main/presenter/skillSyncPresenter/formatConverter.ts (1)
204-214: Deduplicate adapter detection logic (registry already hasdetectAdapter).
autoDetectAndParse()re-implements the registry iteration. Consider callingdetectAdapter()from./adaptersto keep behavior consistent and centralized.
| detect(content: string): boolean { | ||
| // Antigravity format: optional frontmatter with description only | ||
| // and ## Steps structure | ||
| const hasFrontmatter = content.trim().startsWith('---') | ||
|
|
||
| if (hasFrontmatter) { | ||
| try { | ||
| const { data, content: body } = matter(content) | ||
|
|
||
| // Antigravity frontmatter only has description (no name, tools, etc.) | ||
| const hasDescriptionOnly = | ||
| typeof data.description === 'string' && | ||
| !data.name && | ||
| !data.tools && | ||
| !data.title && | ||
| !data.inclusion | ||
|
|
||
| // Should have steps structure | ||
| const hasStepsStructure = | ||
| body.includes('## Steps') || /^### \d+\./m.test(body) || /^### Step \d+/im.test(body) | ||
|
|
||
| return hasDescriptionOnly && hasStepsStructure | ||
| } catch { | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| return false | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
fd -t f "antigravityAdapter" --type fileRepository: ThinkInAIXYZ/deepchat
Length of output: 208
🏁 Script executed:
cat -n src/main/presenter/skillSyncPresenter/adapters/antigravityAdapter.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 5343
🏁 Script executed:
fd -t f "antigravity" -i | head -20Repository: ThinkInAIXYZ/deepchat
Length of output: 263
🏁 Script executed:
rg -i "antigravity" --type ts --type json | head -30Repository: ThinkInAIXYZ/deepchat
Length of output: 3372
🏁 Script executed:
cat -n docs/specs/skills-system/skills-syncing-antigravity.mdRepository: ThinkInAIXYZ/deepchat
Length of output: 6397
🏁 Script executed:
cat -n test/main/presenter/skillSyncPresenter/adapters/antigravityAdapter.test.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 13628
Fix format detection: frontmatter is optional per specification, but detect() currently requires it.
Antigravity workflows without YAML frontmatter are valid (spec line 80: "可选,仅支持 description" = Optional) and will fail detection. The current implementation returns false immediately when no frontmatter is found (line 104), even though a file with valid ## Steps structure should be recognized.
Additionally, extractName() (lines 126-129) uses split('/') which breaks on Windows paths; use path.basename() and path.extname() instead.
Note: hasStepsStructure() already exists as a private method (lines 134-137), so the proposed refactoring can reuse it:
Suggested approach
+import path from 'path'
import matter from 'gray-matter'
detect(content: string): boolean {
// Antigravity format: optional frontmatter with description only
// and ## Steps structure
- const hasFrontmatter = content.trim().startsWith('---')
+ const trimmed = content.trimStart()
+ const hasFrontmatter = trimmed.startsWith('---')
if (hasFrontmatter) {
try {
- const { data, content: body } = matter(content)
+ const { data, content: body } = matter(trimmed)
const hasDescriptionOnly =
typeof data.description === 'string' &&
!data.name &&
!data.tools &&
!data.title &&
!data.inclusion
- const hasStepsStructure =
- body.includes('## Steps') || /^### \d+\./m.test(body) || /^### Step \d+/im.test(body)
-
return hasDescriptionOnly && this.hasStepsStructure(body)
} catch {
return false
}
}
- return false
+ // No frontmatter: check if content has steps structure (optional frontmatter case)
+ return this.hasStepsStructure(trimmed)
}
private extractName(context: ParseContext): string {
- const filename = context.filePath.split('/').pop() || ''
- return filename.replace('.md', '')
+ const filename = path.basename(context.filePath)
+ return filename.replace(path.extname(filename), '')
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/main/presenter/skillSyncPresenter/adapters/antigravityAdapter.ts around
lines 77 - 105, The detect() method incorrectly requires YAML frontmatter;
update detect(content: string) to accept files without frontmatter by returning
true when hasStepsStructure(content) is true even if no frontmatter is present,
and when frontmatter exists keep validating that data.description is a string
(allow other fields to be absent) plus hasStepsStructure(body). Reuse the
existing private hasStepsStructure(...) helper instead of duplicating regex
checks. Also replace the Windows-unsafe split('/') logic in extractName() with
path.basename and path.extname to reliably derive the filename without extension
on all platforms.
| parse(content: string, context: ParseContext): CanonicalSkill { | ||
| const { data, content: body } = matter(content) | ||
|
|
||
| // Extract name from frontmatter or fallback to directory name | ||
| const name = this.extractName(data, context) | ||
|
|
||
| // Extract description | ||
| const description = this.extractDescription(data) | ||
|
|
||
| // Extract allowed tools - handle both string and array formats | ||
| const allowedTools = this.parseAllowedTools(data['allowed-tools']) | ||
|
|
||
| return { | ||
| name, | ||
| description, | ||
| instructions: body.trim(), | ||
| allowedTools, | ||
| model: data.model, | ||
| tags: data.tags, | ||
| source: { | ||
| tool: this.id, | ||
| originalPath: context.filePath, | ||
| originalFormat: 'yaml-frontmatter-markdown' | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find and examine the claudeCodeAdapter.ts file
find . -name "claudeCodeAdapter.ts" -type fRepository: ThinkInAIXYZ/deepchat
Length of output: 186
🏁 Script executed:
# Read the full file to understand the complete implementation
cat -n src/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 8477
🏁 Script executed:
# Check the shared types to understand CanonicalSkill and FormatCapabilities
find . -path "*shared/types*" -name "*.ts" | head -20Repository: ThinkInAIXYZ/deepchat
Length of output: 931
🏁 Script executed:
# Search for other adapters to understand the pattern
find . -path "*adapters*" -name "*Adapter.ts" -type fRepository: ThinkInAIXYZ/deepchat
Length of output: 470
🏁 Script executed:
# Find and examine the CanonicalSkill type definition
find . -path "*shared/types*" -name "*.ts" -exec grep -l "CanonicalSkill" {} \;Repository: ThinkInAIXYZ/deepchat
Length of output: 96
🏁 Script executed:
# Look for skillSync type definitions
find . -name "*skillSync*" -type f | head -20Repository: ThinkInAIXYZ/deepchat
Length of output: 96
🏁 Script executed:
# Search for CanonicalSkill definition
rg "interface CanonicalSkill|type CanonicalSkill" -A 20Repository: ThinkInAIXYZ/deepchat
Length of output: 2571
🏁 Script executed:
# Read the full skillSync.ts file to see type definitions
cat -n src/shared/types/skillSync.ts | head -100Repository: ThinkInAIXYZ/deepchat
Length of output: 3280
🏁 Script executed:
# Check if path module is already imported anywhere in the adapter
rg "import.*path" src/main/presenter/skillSyncPresenter/adapters/Repository: ThinkInAIXYZ/deepchat
Length of output: 47
🏁 Script executed:
# Check how other adapters handle similar situations
grep -n "extractName\|folderPath" src/main/presenter/skillSyncPresenter/adapters/*.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 1828
🏁 Script executed:
# Check how antigravityAdapter handles extractName
sed -n '126,140p' src/main/presenter/skillSyncPresenter/adapters/antigravityAdapter.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 501
🏁 Script executed:
# Check copilotAdapter's extractName implementation
sed -n '169,185p' src/main/presenter/skillSyncPresenter/adapters/copilotAdapter.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 580
🏁 Script executed:
# Check if any adapter imports path module
grep -n "import.*path" src/main/presenter/skillSyncPresenter/adapters/*.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 47
🏁 Script executed:
# Check the complete FormatCapabilities interface to see all supported fields
sed -n '89,110p' src/shared/types/skillSync.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 722
Align getCapabilities() with actual behavior (model is parsed/serialized but marked unsupported).
The parse() method assigns model and tags directly from frontmatter without validating their types (lines 48–49). Meanwhile, serialize() conditionally writes these fields if present (lines 78–84). However, getCapabilities() declares supportsModel: false (line 124), creating an inconsistency that can mislead consumers of this adapter. Additionally, both fields lack type validation—if frontmatter contains a non-string model or non-array tags, the values violate the CanonicalSkill contract.
Fix by:
- Validating
modelis a string andtagsis a string array before assignment - Updating
supportsModel: trueto match actual behavior - Using
path.basename()instead ofsplit('/')for cross-platform path handling
Proposed fix (validate + capability alignment)
import matter from 'gray-matter'
+import path from 'path'
import type {
IFormatAdapter,
CanonicalSkill,
ParseContext,
FormatCapabilities
} from '@shared/types/skillSync'
@@
parse(content: string, context: ParseContext): CanonicalSkill {
const { data, content: body } = matter(content)
@@
- return {
+ const model = typeof data.model === 'string' ? data.model : undefined
+ const tags = Array.isArray(data.tags)
+ ? data.tags.filter((t): t is string => typeof t === 'string' && t.trim().length > 0)
+ : undefined
+
+ return {
name,
description,
instructions: body.trim(),
allowedTools,
- model: data.model,
- tags: data.tags,
+ model,
+ tags,
source: {
tool: this.id,
originalPath: context.filePath,
originalFormat: 'yaml-frontmatter-markdown'
}
}
}
@@
getCapabilities(): FormatCapabilities {
return {
hasFrontmatter: true,
supportsName: true,
supportsDescription: true,
supportsTools: true,
- supportsModel: false, // Claude Code doesn't officially support model field
+ supportsModel: true, // If we round-trip it, we support it
supportsSubfolders: true,
supportsReferences: true,
supportsScripts: true
}
}
@@
private extractName(data: Record<string, unknown>, context: ParseContext): string {
@@
if (context.folderPath) {
- const parts = context.folderPath.split('/')
- return parts[parts.length - 1] || 'unnamed-skill'
+ return path.basename(context.folderPath) || 'unnamed-skill'
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.ts around
lines 31 - 56, The adapter incorrectly advertises supportsModel as false while
parse()/serialize() accept and emit a model and tags without type checks; update
getCapabilities() to set supportsModel: true, add validation in parse() so that
data.model is assigned only if typeof data.model === 'string' and data.tags is
assigned only if Array.isArray(data.tags) && data.tags.every(t => typeof t ===
'string'), and mirror those checks in serialize() when writing frontmatter; also
replace any manual path splitting (e.g., filePath.split('/').pop()) with
path.basename(context.filePath) to ensure cross-platform path handling
(references: parse(), serialize(), getCapabilities(), CanonicalSkill.model,
CanonicalSkill.tags, and usage of context.filePath).
src/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
Outdated
Show resolved
Hide resolved
src/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
Outdated
Show resolved
Hide resolved
| const adapters: Map<string, IFormatAdapter> = new Map() | ||
|
|
||
| /** | ||
| * Register all built-in adapters | ||
| */ | ||
| function registerBuiltinAdapters(): void { | ||
| const builtinAdapters: IFormatAdapter[] = [ | ||
| new ClaudeCodeAdapter(), | ||
| new CursorAdapter(), | ||
| new WindsurfAdapter(), | ||
| new CopilotAdapter(), | ||
| new KiroAdapter(), | ||
| new AntigravityAdapter() | ||
| ] | ||
|
|
||
| for (const adapter of builtinAdapters) { | ||
| adapters.set(adapter.id, adapter) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against duplicate adapter IDs (silent override today).
adapters.set(adapter.id, adapter) in both builtin registration and registerAdapter() will silently replace an existing adapter, which can cause non-obvious behavior changes (especially since detection order depends on insertion order).
Proposed fix
const adapters: Map<string, IFormatAdapter> = new Map()
+function assertUniqueAdapterId(adapter: IFormatAdapter): void {
+ if (adapters.has(adapter.id)) {
+ throw new Error(`Duplicate adapter id: ${adapter.id}`)
+ }
+}
+
function registerBuiltinAdapters(): void {
const builtinAdapters: IFormatAdapter[] = [
new ClaudeCodeAdapter(),
new CursorAdapter(),
new WindsurfAdapter(),
new CopilotAdapter(),
new KiroAdapter(),
new AntigravityAdapter()
]
for (const adapter of builtinAdapters) {
+ assertUniqueAdapterId(adapter)
adapters.set(adapter.id, adapter)
}
}
export function registerAdapter(adapter: IFormatAdapter): void {
- adapters.set(adapter.id, adapter)
+ assertUniqueAdapterId(adapter)
+ adapters.set(adapter.id, adapter)
}Also applies to: 59-61
🤖 Prompt for AI Agents
In @src/main/presenter/skillSyncPresenter/adapters/index.ts around lines 19 -
37, The adapters Map is silently overridden when a duplicate adapter.id is
registered; update registerBuiltinAdapters() and registerAdapter() to guard
against duplicates by checking adapters.has(adapter.id) before calling
adapters.set(...); on duplicate either throw a descriptive Error (including
adapter.id and source like "builtin" vs "runtime") or log a clear warning and
skip registration so insertion order / detection is not silently changed; ensure
the check happens for each adapter in the builtinAdapters loop and in the
single-adapter registerAdapter() path.
| export async function validateFolderSize( | ||
| folderPath: string, | ||
| maxSize: number = MAX_SKILL_FOLDER_SIZE | ||
| ): Promise<{ valid: boolean; totalSize: number; error?: string }> { | ||
| let totalSize = 0 | ||
|
|
||
| async function calculateSize(dirPath: string): Promise<void> { | ||
| try { | ||
| const entries = await fs.promises.readdir(dirPath, { withFileTypes: true }) | ||
|
|
||
| for (const entry of entries) { | ||
| const fullPath = path.join(dirPath, entry.name) | ||
|
|
||
| if (entry.isFile()) { | ||
| const stats = await fs.promises.stat(fullPath) | ||
| totalSize += stats.size | ||
|
|
||
| // Early exit if already over limit | ||
| if (totalSize > maxSize) { | ||
| throw new Error('Size limit exceeded') | ||
| } | ||
| } else if (entry.isDirectory()) { | ||
| await calculateSize(fullPath) | ||
| } | ||
| } | ||
| } catch (error) { | ||
| if (error instanceof Error && error.message === 'Size limit exceeded') { | ||
| throw error | ||
| } | ||
| // Ignore other errors (permission issues, etc.) | ||
| } | ||
| } | ||
|
|
||
| try { | ||
| await calculateSize(folderPath) | ||
| return { valid: true, totalSize } | ||
| } catch (error) { | ||
| if (error instanceof Error && error.message === 'Size limit exceeded') { | ||
| return { | ||
| valid: false, | ||
| totalSize, | ||
| error: `Folder too large: exceeds limit of ${maxSize} bytes` | ||
| } | ||
| } | ||
| return { valid: true, totalSize } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
validateFolderSize() can undercount and return valid: true on errors.
calculateSize() swallows all errors except the synthetic “Size limit exceeded”. That means permission/stat errors can cause partial traversal and a false “valid” result, undermining the resource-exhaustion guard.
Consider tracking an hadError flag and returning { valid: false, error: 'Failed to fully scan folder...' } (or at least a warning surfaced to callers).
🤖 Prompt for AI Agents
In @src/main/presenter/skillSyncPresenter/security.ts around lines 189 - 235,
The traversal swallows all non-"Size limit exceeded" errors causing
validateFolderSize to undercount and possibly return valid:true; modify
validateFolderSize/calculateSize to track a failure flag (e.g., hadError) or
capture the first non-synthetic error encountered during recursion, stop or
continue traversal as appropriate, and ensure the final return sets valid:false
and includes an explanatory error message when any filesystem error occurred
(permission/stat/readdir), while preserving the early throw for the 'Size limit
exceeded' case; reference the calculateSize inner function, the totalSize
variable, and the final catch branch to implement this change.
| const handleNext = async () => { | ||
| if (props.currentStep === 1) { | ||
| await loadTools() | ||
| emit('update:step', 2) | ||
| } else if (props.currentStep === 2) { | ||
| await previewExport() | ||
| emit('update:step', 3) | ||
| } else if (props.currentStep === 3) { | ||
| await executeExport() | ||
| } | ||
| } | ||
| const loadTools = async () => { | ||
| scanningTools.value = true | ||
| try { | ||
| availableTools.value = await skillSyncPresenter.getRegisteredTools() | ||
| } catch (error) { | ||
| console.error('Load tools error:', error) | ||
| toast({ | ||
| title: t('settings.skills.sync.loadToolsError'), | ||
| description: String(error), | ||
| variant: 'destructive' | ||
| }) | ||
| } finally { | ||
| scanningTools.value = false | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t advance to Step 2 if loadTools() fails.
Right now loadTools() swallows errors and handleNext() still emits step 2.
Proposed fix
const handleNext = async () => {
if (props.currentStep === 1) {
- await loadTools()
- emit('update:step', 2)
+ const ok = await loadTools()
+ if (ok) emit('update:step', 2)
} else if (props.currentStep === 2) {
await previewExport()
emit('update:step', 3)
} else if (props.currentStep === 3) {
await executeExport()
}
}
-const loadTools = async () => {
+const loadTools = async (): Promise<boolean> => {
scanningTools.value = true
try {
availableTools.value = await skillSyncPresenter.getRegisteredTools()
+ return true
} catch (error) {
console.error('Load tools error:', error)
toast({
title: t('settings.skills.sync.loadToolsError'),
description: String(error),
variant: 'destructive'
})
+ return false
} finally {
scanningTools.value = false
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleNext = async () => { | |
| if (props.currentStep === 1) { | |
| await loadTools() | |
| emit('update:step', 2) | |
| } else if (props.currentStep === 2) { | |
| await previewExport() | |
| emit('update:step', 3) | |
| } else if (props.currentStep === 3) { | |
| await executeExport() | |
| } | |
| } | |
| const loadTools = async () => { | |
| scanningTools.value = true | |
| try { | |
| availableTools.value = await skillSyncPresenter.getRegisteredTools() | |
| } catch (error) { | |
| console.error('Load tools error:', error) | |
| toast({ | |
| title: t('settings.skills.sync.loadToolsError'), | |
| description: String(error), | |
| variant: 'destructive' | |
| }) | |
| } finally { | |
| scanningTools.value = false | |
| } | |
| } | |
| const handleNext = async () => { | |
| if (props.currentStep === 1) { | |
| const ok = await loadTools() | |
| if (ok) emit('update:step', 2) | |
| } else if (props.currentStep === 2) { | |
| await previewExport() | |
| emit('update:step', 3) | |
| } else if (props.currentStep === 3) { | |
| await executeExport() | |
| } | |
| } | |
| const loadTools = async (): Promise<boolean> => { | |
| scanningTools.value = true | |
| try { | |
| availableTools.value = await skillSyncPresenter.getRegisteredTools() | |
| return true | |
| } catch (error) { | |
| console.error('Load tools error:', error) | |
| toast({ | |
| title: t('settings.skills.sync.loadToolsError'), | |
| description: String(error), | |
| variant: 'destructive' | |
| }) | |
| return false | |
| } finally { | |
| scanningTools.value = false | |
| } | |
| } |
🤖 Prompt for AI Agents
In @src/renderer/settings/components/skills/SkillSyncDialog/ExportWizard.vue
around lines 438 - 464, loadTools currently swallows errors so handleNext always
advances; modify loadTools to return a boolean (true on success, false on
failure) or rethrow the error, and in handleNext call const ok = await
loadTools() and only emit('update:step', 2) when ok is true; keep the existing
catch/toast in loadTools but ensure it returns false after handling the error
(or let the error propagate and handle it in handleNext) so step 2 is not
emitted on failure (refer to functions loadTools and handleNext).
| import { Button } from '@shadcn/components/ui/button' | ||
| import { Badge } from '@shadcn/components/ui/badge' | ||
| import { Checkbox } from '@shadcn/components/ui/checkbox' | ||
| import { ScrollArea } from '@shadcn/components/ui/scroll-area' | ||
| import type { ExternalSkillInfo } from '@shared/types/skillSync' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shadcn import path likely inconsistent with repo convention.
Repo learnings indicate importing Shadcn Vue components via @/shadcn/components/ui/...; this file uses @shadcn/components/ui/... which may not resolve depending on configured aliases.
🤖 Prompt for AI Agents
In @src/renderer/settings/components/skills/SkillSyncDialog/SkillSelector.vue
around lines 56 - 60, The Shadcn component imports in SkillSelector.vue use the
alias '@shadcn/components/ui/...' which is inconsistent with the repo
convention; update the import specifiers for the Vue UI components (Button,
Badge, Checkbox, ScrollArea) to use the repo alias '@/shadcn/components/ui/...'
so they resolve correctly (leave the existing import for the shared type
ExternalSkillInfo unchanged if it already uses @shared). Ensure each import line
is updated to the '@/shadcn/...' path.
| // Local state for checkbox bindings | ||
| const skillCheckedState = ref<Record<string, boolean>>({}) | ||
| // Initialize from props | ||
| watch( | ||
| () => props.selectedSkills, | ||
| (selected) => { | ||
| const newState: Record<string, boolean> = {} | ||
| for (const skill of props.skills) { | ||
| newState[skill.name] = selected.includes(skill.name) | ||
| } | ||
| skillCheckedState.value = newState | ||
| }, | ||
| { immediate: true } | ||
| ) | ||
| // Also reinitialize when skills change | ||
| watch( | ||
| () => props.skills, | ||
| (skills) => { | ||
| const newState: Record<string, boolean> = {} | ||
| for (const skill of skills) { | ||
| newState[skill.name] = props.selectedSkills.includes(skill.name) | ||
| } | ||
| skillCheckedState.value = newState | ||
| }, | ||
| { immediate: true } | ||
| ) | ||
| const updateSkillChecked = (skillName: string, checked: boolean) => { | ||
| skillCheckedState.value[skillName] = checked | ||
| // Directly emit updated selections | ||
| const newSelected = checked | ||
| ? [...props.selectedSkills.filter((n) => n !== skillName), skillName] | ||
| : props.selectedSkills.filter((n) => n !== skillName) | ||
| emit('update:selectedSkills', newSelected) | ||
| } | ||
| const allSelected = computed(() => { | ||
| return props.skills.length > 0 && props.selectedSkills.length === props.skills.length | ||
| }) | ||
| const toggleAll = () => { | ||
| const newState: Record<string, boolean> = {} | ||
| const selectAll = !allSelected.value | ||
| for (const skill of props.skills) { | ||
| newState[skill.name] = selectAll | ||
| } | ||
| skillCheckedState.value = newState | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“Select all” is broken: toggleAll() updates local state but never emits update:selectedSkills.
This will desync UI from parent state and can generate incorrect selections on subsequent clicks (because updateSkillChecked derives from stale props.selectedSkills).
Proposed fix (emit in toggleAll and derive from skills list)
const toggleAll = () => {
const newState: Record<string, boolean> = {}
const selectAll = !allSelected.value
+ const newSelected: string[] = []
for (const skill of props.skills) {
newState[skill.name] = selectAll
+ if (selectAll) newSelected.push(skill.name)
}
skillCheckedState.value = newState
+ emit('update:selectedSkills', newSelected)
}🤖 Prompt for AI Agents
In @src/renderer/settings/components/skills/SkillSyncDialog/SkillSelector.vue
around lines 74 - 123, toggleAll currently flips local skillCheckedState but
never emits the updated selection, causing parent/child desync; update toggleAll
to compute the new selected array from props.skills (e.g., map names when
selecting all, or empty array when deselecting), assign skillCheckedState.value
accordingly, and call emit('update:selectedSkills', newSelected) so the parent
receives the change and updateSkillChecked no longer reads stale
props.selectedSkills.
| "next": "Далее", | ||
| "back": "Назад", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Step 1: Check the structure and content of ru-RU/common.json around the added keys
echo "=== Checking ru-RU/common.json structure ==="
cat -n src/renderer/src/i18n/ru-RU/common.json | head -30
echo ""
echo "=== Checking file size ==="
wc -l src/renderer/src/i18n/ru-RU/common.jsonRepository: ThinkInAIXYZ/deepchat
Length of output: 1545
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Step 2: Verify all locale files have "next" and "back" keys
echo "=== Checking for 'next' and 'back' keys in all locale common.json files ==="
python3 <<'PY'
import json
from pathlib import Path
root = Path("src/renderer/src/i18n")
locales = sorted([d.name for d in root.iterdir() if d.is_dir() and (d / "common.json").exists()])
print(f"Found {len(locales)} locales:")
for locale in locales:
print(f" - {locale}")
print("\n=== Verifying 'next' and 'back' keys ===")
all_ok = True
for locale in locales:
common_file = root / locale / "common.json"
try:
data = json.loads(common_file.read_text(encoding="utf-8"))
has_next = "next" in data
has_back = "back" in data
status = "✓" if (has_next and has_back) else "✗"
print(f"{status} {locale}: next={has_next}, back={has_back}")
if not (has_next and has_back):
all_ok = False
except Exception as e:
print(f"✗ {locale}: ERROR - {e}")
all_ok = False
print(f"\nAll locales updated: {all_ok}")
PYRepository: ThinkInAIXYZ/deepchat
Length of output: 151
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Step 3: Check naming conventions in existing i18n files
echo "=== Checking existing key naming patterns in common.json files ==="
# Show structure of a couple common.json files to understand conventions
python3 <<'PY'
import json
from pathlib import Path
# Check en-US as reference
en_file = Path("src/renderer/src/i18n/en-US/common.json")
with open(en_file) as f:
en_data = json.load(f)
print("=== Top-level keys in en-US/common.json ===")
top_keys = [k for k in en_data.keys() if not k.startswith("_")]
for key in sorted(top_keys):
val = en_data[key]
if isinstance(val, dict):
print(f" {key}: {{{', '.join(list(val.keys())[:3])}{'...' if len(val) > 3 else ''}}}")
else:
print(f" {key}: {val} (string)")
PYRepository: ThinkInAIXYZ/deepchat
Length of output: 151
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Use jq to quickly check all locale files for "next" and "back" keys
echo "=== Checking 'next' and 'back' keys in all locale common.json files ==="
cd src/renderer/src/i18n
for locale in */; do
locale_name="${locale%/}"
if [ -f "$locale/common.json" ]; then
has_next=$(jq 'has("next")' "$locale/common.json" 2>/dev/null || echo "error")
has_back=$(jq 'has("back")' "$locale/common.json" 2>/dev/null || echo "error")
echo "$locale_name: next=$has_next, back=$has_back"
fi
doneRepository: ThinkInAIXYZ/deepchat
Length of output: 472
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check the naming convention by sampling keys from en-US/common.json
echo "=== Naming convention in en-US/common.json ==="
jq 'keys[]' src/renderer/src/i18n/en-US/common.json | head -25Repository: ThinkInAIXYZ/deepchat
Length of output: 414
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check if there's a browser.back key to assess collision concern
echo "=== Checking for existing 'back' related keys in en-US/common.json ==="
jq 'paths(scalars) | select(contains(["back"]))' src/renderer/src/i18n/en-US/common.jsonRepository: ThinkInAIXYZ/deepchat
Length of output: 174
Rename next and back keys to follow dot-separated hierarchical naming convention; however, all locales are properly updated.
The new keys follow the existing codebase pattern of root-level string keys, but they violate the coding guideline requirement for dot-separated hierarchical naming grouped by feature/context. Rename them to navigation.next and navigation.back (or similar contextual grouping) to align with the specified convention.
Verification confirms all 12 locale files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) consistently include both keys with identical naming.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/ru-RU/common.json around lines 17 - 18, The locale
keys "next" and "back" violate the dot-separated hierarchical naming convention;
rename them to "navigation.next" and "navigation.back" (and update any
references in code/templates that use "next" or "back") across the locale files
so they match the other 12 locales and keep translations unchanged, ensuring all
lookups use the new keys (e.g., replace occurrences of next -> navigation.next
and back -> navigation.back, and update any i18n key usage in components or
tests).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/main/presenter/sqlitePresenter/tables/conversations.ts (2)
158-225: Bug: avoid inserting the string'NULL'intoenabled_mcp_tools.Line 207 currently inserts
'NULL'(a literal string), which will later fail JSON.parse and silently degrade settings.Proposed patch
- settings.enabledMcpTools ? JSON.stringify(settings.enabledMcpTools) : 'NULL', + settings.enabledMcpTools ? JSON.stringify(settings.enabledMcpTools) : null,
34-41: ValidateactiveSkillsJSON shape (ensure it’s always an array) + keep TS comments English-only.
getJsonField(result.active_skills, [])can returnnull/object if the stored JSON is unexpected; downstream likely expectsstring[].- Line 34 comment is non-English (guidelines require English comments in TS).
Proposed patch (minimal helper + comment)
-// 解析 JSON 字段 +// Parse JSON fields function getJsonField<T>(val: string | null | undefined, fallback: T): T { try { return val ? JSON.parse(val) : fallback } catch { return fallback } } + +function getJsonArrayField<T>(val: string | null | undefined, fallback: T[]): T[] { + const parsed = getJsonField<unknown>(val, fallback) + return Array.isArray(parsed) ? (parsed as T[]) : fallback +}Then replace the activeSkills mappings:
- activeSkills: getJsonField(result.active_skills, []), + activeSkills: getJsonArrayField<string>(result.active_skills, []),(and similarly in
list,listByParentConversationId,listByParentMessageIds).Also applies to: 227-312, 448-537, 539-617, 619-702
src/shared/types/presenters/thread.presenter.d.ts (1)
8-28: Good API extension; ensureactiveSkillsis normalized to[]at boundaries.Since this is optional, older persisted conversations / IPC payloads may omit it—make sure consumers don’t assume it’s always an array (e.g., default to
[]when reading settings).src/main/presenter/sessionPresenter/const.ts (1)
3-13: Use a factory function instead of exporting a shared mutable constant forDEFAULT_SETTINGS.The current pattern creates a real risk: when
latestConversationis absent,defaultSettings = DEFAULT_SETTINGSfollowed by shallow copies (e.g.,{ ...defaultSettings }) meansactiveSkillsandacpWorkdirMapshare references with the exported constant. Any in-place mutation (e.g.,push()) on these nested arrays/objects will corrupt the shared default across future conversations.Consider:
-export const DEFAULT_SETTINGS: CONVERSATION_SETTINGS = { +export const createDefaultSettings = (): CONVERSATION_SETTINGS => ({ systemPrompt: '', temperature: 0.7, contextLength: 12800, maxTokens: 8192, providerId: 'deepseek', modelId: 'deepseek-chat', artifacts: 0, acpWorkdirMap: {}, activeSkills: [] -} +})This guarantees each call site receives a fresh, independent object tree.
src/main/events.ts (1)
1-9: Replace Chinese comments with English.The file header contains Chinese comments, which violates the coding guidelines requiring all comments in TypeScript files to be in English.
As per coding guidelines, all code comments must be in English.
Proposed English translation
/** - * 事件系统常量定义 + * Event system constant definitions * - * 按功能领域分类事件名,采用统一的命名规范: - * - 使用冒号分隔域和具体事件 - * - 使用小写并用连字符连接多个单词 + * Event names are categorized by functional domain using a unified naming convention: + * - Use colons to separate domain and specific event + * - Use lowercase and connect multiple words with hyphens * - * 看似这里和 renderer/events.ts 重复了,其实不然,这里只包含了main->renderer 和 main->main 的事件 + * Although this appears to duplicate renderer/events.ts, it only contains main->renderer and main->main events */
🤖 Fix all issues with AI agents
In @src/main/presenter/skillSyncPresenter/adapters/kiroAdapter.ts:
- Around line 54-58: The current code prepends Kiro metadata using
skill.instructions = kiroMeta + instructions.trim() after skill.instructions was
already set to instructions.trim(), causing the metadata to be combined with the
wrong value; fix by prepending the meta to the original instructions before
assigning to the skill or by updating the existing field (e.g., compute
finalInstructions = kiroMeta + instructions.trim() when data.inclusion or
data.file_patterns are present, then set skill.instructions =
finalInstructions), using buildKiroMetaComment, data.inclusion,
data.file_patterns and instructions.trim() to locate the relevant logic.
In @src/renderer/src/i18n/da-DK/settings.json:
- Around line 1080-1096: Add the missing i18n key skills.edit.nameHint in the
da-DK settings JSON by inserting "nameHint": "Færdighedsnavn kan ikke ændres
efter oprettelse" into the existing "edit" object (e.g., directly after
"namePlaceholder" or next to "name") so the key matches other locale files and
restores i18n consistency.
In @src/renderer/src/i18n/pt-BR/settings.json:
- Around line 1042-1102: The "skills" namespace in the pt-BR locale (the
"skills" JSON object and all nested keys: title, description, openFolder,
addSkill, empty, emptyHint, noResults, search, count, install.*, delete.*,
edit.*, conflict.*) is still in English—translate every string into Brazilian
Portuguese while preserving placeholders like {name} and {count} and keeping key
names unchanged; update all nested keys (install.tabFolder/tabZip/tabUrl,
install.fromFolder/selectFolder/folderHint/folderTip/zipHint/urlPlaceholder/urlHint/installButton/installing/success/successMessage/failed/dragNotSupported,
delete.title/description/success/successMessage/failed,
edit.title/placeholder/readFailed/success/failed/name/namePlaceholder/description/descriptionPlaceholder/allowedTools/allowedToolsPlaceholder/allowedToolsHint/content/files/noFiles,
conflict.title/description/overwrite) and ensure the same localized texts (or
appropriate equivalents) are applied/added to other locale JSON files per
guidelines for consistency.
In @src/renderer/src/i18n/zh-TW/settings.json:
- Around line 1043-1102: The "skills" locale block is still in English;
translate every user-facing string under the "skills" object into Traditional
Chinese (繁體中文) while keeping all keys and placeholders intact (e.g., "skills",
"install", "delete", "edit", "conflict" and nested keys like "count": "{count}
skills", "successMessage": "Skill {name} has been installed successfully",
"description", "openFolder", "addSkill", etc.); preserve interpolation tokens
({count}, {name}), escaped quotes, and punctuation, and then mirror the same
translated keys across the other locale files as required by the i18n
guidelines.
🟠 Major comments (18)
src/main/presenter/skillSyncPresenter/adapters/antigravityAdapter.ts-31-50 (1)
31-50: Fix filename extraction for Windows paths (currently split on/).
context.filePath.split('/')will fail on Windows (\). Usenode:path.Proposed patch
import matter from 'gray-matter' +import path from 'node:path' ... private extractName(context: ParseContext): string { - const filename = context.filePath.split('/').pop() || '' - return filename.replace('.md', '') + return path.basename(context.filePath, '.md') }Also applies to: 126-129
src/main/presenter/skillSyncPresenter/adapters/antigravityAdapter.ts-77-105 (1)
77-105:detect()is too strict: allow Antigravity files without frontmatter (it’s documented as optional).Right now, any workflow without
---frontmatter will never be detected, even if it clearly has the## Stepsstructure.Proposed patch (reuse `hasStepsStructure` + allow empty frontmatter)
detect(content: string): boolean { - const hasFrontmatter = content.trim().startsWith('---') - - if (hasFrontmatter) { - try { - const { data, content: body } = matter(content) - - // Antigravity frontmatter only has description (no name, tools, etc.) - const hasDescriptionOnly = - typeof data.description === 'string' && - !data.name && - !data.tools && - !data.title && - !data.inclusion - - // Should have steps structure - const hasStepsStructure = - body.includes('## Steps') || /^### \d+\./m.test(body) || /^### Step \d+/im.test(body) - - return hasDescriptionOnly && hasStepsStructure - } catch { - return false - } - } - - return false + try { + const { data, content: body } = matter(content) + const keys = Object.keys(data ?? {}) + + const hasOnlyDescription = + keys.length === 0 || (keys.length === 1 && typeof (data as any).description === 'string') + + return hasOnlyDescription && this.hasStepsStructure(body) + } catch { + return false + } }Also applies to: 134-137
test/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.test.ts-21-34 (1)
21-34: Capabilities mismatch:supportsModeldeclares false but adapter actively parses and serializes model.
ClaudeCodeAdapter.getCapabilities()reportssupportsModel: false, yet theparse()method extracts the model field (line 48) andserialize()includes it when present (lines 78-80). Tests confirm both operations work (lines 156-168 and 307-318). This inconsistency can mislead any consumer code that gates the model field based on capabilities.Decide: either set
supportsModel: trueif the field is intentionally supported, or remove model handling from parse/serialize if it should not be supported.test/main/presenter/skillSyncPresenter/adapters/index.test.ts-158-242 (1)
158-242: Antigravity content is being incorrectly detected as Copilot due to overly broad detection logic.The Copilot adapter matches any frontmatter with description-only fields (line 142 in
copilotAdapter.ts), but Antigravity uses the same pattern. Since Copilot is registered and checked before Antigravity (lines 29-31 inindex.ts), Antigravity-formatted files will be consistently misidentified.Fix by narrowing Copilot's detection to require specific fields like
agentortools, or reordering adapter detection priority so more specific formats (Antigravity's steps requirement) are checked first.test/main/presenter/skillSyncPresenter/adapters/index.test.ts-91-156 (1)
91-156: Add cleanup for custom adapter registered in first test to avoid registry pollution.The first test registers
'custom-test-adapter'without cleanup, leaving it in the global registry for subsequent tests. The second test demonstrates the correct pattern (save original → override → restore). Apply the same approach to the first test or add anafterEachhook to clean up any custom adapters:Example fix:
describe('registerAdapter', () => { const CUSTOM_ADAPTER_ID = 'custom-test-adapter' afterEach(() => { // Remove custom adapter if it was registered if (getAdapter(CUSTOM_ADAPTER_ID)) { // Option 1: expose a removeAdapter function, or // Option 2: reset via vi.resetModules() + dynamic imports } }) it('should register a custom adapter', () => { // ... test code ... }) })src/renderer/settings/components/skills/SkillCard.vue-1-49 (1)
1-49: Add accessible labels and keyboard support to icon-only edit/delete buttons.Icon-only buttons currently lack accessible names for screen readers, and the action buttons are hidden until hover—making them inaccessible to keyboard-only users. This fails WCAG Level AA keyboard accessibility requirements.
Add SR-only labels using i18n keys and reveal actions on keyboard focus:
- Add
aria-labelor<span class="sr-only">{{ t('...') }}</span>with descriptive text for each button- Replace the hover-only visibility with
group-focus-within:opacity-100in addition togroup-hover:opacity-100to show buttons on keyboard focus- Remove the
hoveringref and simplify to CSS-based state management- Ensure i18n keys exist for
settings.skills.card.editandsettings.skills.card.deleteProposed diff (accessibility + simplify hover state)
<template> <div class="border rounded-lg p-4 bg-card hover:bg-accent/50 transition-colors group" - @mouseenter="hovering = true" - @mouseleave="hovering = false" > <div class="flex items-start justify-between"> <div class="flex-1 min-w-0"> @@ </div> <div - class="flex items-center gap-1 ml-2 shrink-0 transition-opacity" - :class="{ 'opacity-0 group-hover:opacity-100': !hovering }" + class="flex items-center gap-1 ml-2 shrink-0 transition-opacity opacity-0 group-hover:opacity-100 group-focus-within:opacity-100" > - <Button variant="ghost" size="sm" class="h-8 w-8 p-0" @click="$emit('edit')"> + <Button variant="ghost" size="sm" class="h-8 w-8 p-0" type="button" @click="$emit('edit')"> <Icon icon="lucide:edit" class="w-4 h-4" /> + <span class="sr-only">{{ t('settings.skills.card.edit') }}</span> </Button> <Button variant="ghost" size="sm" class="h-8 w-8 p-0 text-destructive" + type="button" @click="$emit('delete')" > <Icon icon="lucide:trash-2" class="w-4 h-4" /> + <span class="sr-only">{{ t('settings.skills.card.delete') }}</span> </Button> </div> </div> </div> </template> <script setup lang="ts"> -import { ref } from 'vue' +import { useI18n } from 'vue-i18n' import { Icon } from '@iconify/vue' import { Button } from '@shadcn/components/ui/button' import type { SkillMetadata } from '@shared/types/skill' @@ -const hovering = ref(false) +const { t } = useI18n() </script>Committable suggestion skipped: line range outside the PR's diff.
src/renderer/settings/components/skills/SkillsHeader.vue-1-52 (1)
1-52: Fix RTL layout for the search input (icon positioning and padding).The search icon uses
left-2.5and the input usespl-8, which are directional utilities that don't automatically flip whendir="rtl"is applied to individual elements. Thedirattribute is only on the title section (line 4), not the outer container. RTL users will see the icon and padding on the wrong side.Move
:dir="languageStore.dir"to the outer container and use conditional:classbindings for the icon position and input padding:Proposed diff
<template> - <div class="shrink-0 px-4 pt-4"> + <div class="shrink-0 px-4 pt-4" :dir="languageStore.dir"> <div class="flex items-center justify-between"> - <div :dir="languageStore.dir" class="flex-1"> + <div class="flex-1"> <div class="font-medium"> {{ t('settings.skills.title') }} </div> @@ -17,13 +17,15 @@ <div class="relative"> <Icon icon="lucide:search" - class="absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" + class="absolute top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" + :class="languageStore.dir === 'rtl' ? 'right-2.5' : 'left-2.5'" /> <Input :model-value="searchQuery" @update:model-value="$emit('update:searchQuery', String($event))" :placeholder="t('settings.skills.search')" - class="pl-8 h-8 w-48" + class="h-8 w-48" + :class="languageStore.dir === 'rtl' ? 'pr-8' : 'pl-8'" /> </div> <script setup lang="ts">src/renderer/src/i18n/fr-FR/settings.json-1043-1102 (1)
1043-1102: Skills section contains English text instead of French translations.The entire
skillssection in the French locale file uses English strings. This should be translated to French to provide a proper localized experience for French-speaking users. As per coding guidelines, translations must be added to ALL language files with proper translations.🌐 Example French translations (partial)
"skills": { "title": "Compétences", "description": "Gérer et configurer les compétences de l'assistant IA", "openFolder": "Ouvrir le dossier", "addSkill": "Ajouter une compétence", "empty": "Aucune compétence", "emptyHint": "Cliquez sur \"Ajouter une compétence\" pour installer une nouvelle compétence", "noResults": "Aucune compétence correspondante trouvée", "search": "Rechercher des compétences...", "count": "{count} compétences", "install": { "title": "Installer une compétence", "description": "Choisir la méthode d'installation de la compétence", ... } }src/renderer/settings/components/skills/SkillSyncDialog/SkillSelector.vue-116-123 (1)
116-123:toggleAlldoesn't emit the selection change.The function updates local state but never emits
update:selectedSkills, so the parent component won't be notified when the user clicks "Select All" or "Deselect All".🐛 Proposed fix
const toggleAll = () => { const newState: Record<string, boolean> = {} const selectAll = !allSelected.value for (const skill of props.skills) { newState[skill.name] = selectAll } skillCheckedState.value = newState + const newSelected = selectAll ? props.skills.map((s) => s.name) : [] + emit('update:selectedSkills', newSelected) }src/renderer/src/i18n/ko-KR/settings.json-1043-1102 (1)
1043-1102: Skills section contains English text instead of Korean translations.The entire
skillssection in the Korean locale file uses English strings. This should be translated to Korean to provide a proper localized experience for Korean-speaking users. As per coding guidelines, translations must be added to ALL language files with proper translations.🌐 Example Korean translations (partial)
"skills": { "title": "스킬", "description": "AI 어시스턴트 스킬 관리 및 구성", "openFolder": "폴더 열기", "addSkill": "스킬 추가", "empty": "스킬이 없습니다", "emptyHint": "\"스킬 추가\"를 클릭하여 새 스킬을 설치하세요", "noResults": "일치하는 스킬을 찾을 수 없습니다", "search": "스킬 검색...", "count": "{count}개 스킬", "install": { "title": "스킬 설치", "description": "스킬 설치 방법 선택", ... } }src/renderer/settings/components/skills/SkillSyncDialog/ImportWizard.vue-197-206 (1)
197-206: Step advances even when preview fails.When
previewImport()fails, the catch block shows a toast but the step still advances to 3 viaemit('update:step', 3). This leaves the user on the conflict resolution step with no data.🐛 Proposed fix
const handleNext = async () => { if (props.currentStep === 1) { emit('update:step', 2) } else if (props.currentStep === 2) { - await previewImport() - emit('update:step', 3) + const success = await previewImport() + if (success) { + emit('update:step', 3) + } } else if (props.currentStep === 3) { await executeImport() } }Also update
previewImportto return a boolean:-const previewImport = async () => { +const previewImport = async (): Promise<boolean> => { if (!selectedToolId.value) return + if (!selectedToolId.value) return false loading.value = true try { // ... existing code ... + return true } catch (error) { // ... existing error handling ... + return false } finally { loading.value = false } }Committable suggestion skipped: line range outside the PR's diff.
src/renderer/src/i18n/zh-HK/settings.json-1043-1101 (1)
1043-1101: zh-HK and zh-TW locales are using English strings instead of Traditional Chinese translationsThe
skillssection in both zh-HK and zh-TW locales contains English text, violating i18n guidelines requiring all user-facing strings to be properly translated. In contrast, zh-CN correctly uses Simplified Chinese translations for the same keys. Both files need Traditional Chinese translations applied.Consider using zh-CN as a reference for key naming structure, then provide appropriate Traditional Chinese (zh-HK/zh-TW) translations. For terminology, '技能' aligns with zh-CN usage for "Skills."
test/main/presenter/skillSyncPresenter/adapters/kiroAdapter.test.ts-25-38 (1)
25-38:supportsDescriptionshould betrue: Kiro format fully implements description parsing and serialization via blockquoteThe implementation actively extracts descriptions from blockquotes in the
parse()method (lines 162–205) and serializes them back as blockquotes in theserialize()method (lines 88–90). Tests explicitly validate this behavior in lines 161–175 (extraction) and 264–274 (serialization). The capability flag declaringsupportsDescription: falsecontradicts the implemented functionality. If capabilities inform UI or export workflows, this mismatch will cause incorrect behavior. The description support is unconventional (blockquote-based rather than a YAML field), but it is fully supported and should be reflected in the capabilities.Suggested fix
getCapabilities(): FormatCapabilities { return { hasFrontmatter: true, supportsName: true, - supportsDescription: false, // Can be embedded as blockquote + supportsDescription: true, // Via blockquote at document start supportsTools: false, supportsModel: false, supportsSubfolders: false, supportsReferences: false, supportsScripts: false } }Committable suggestion skipped: line range outside the PR's diff.
src/renderer/settings/components/skills/SkillSyncDialog/ToolSelector.vue-13-44 (1)
13-44: Make tool rows keyboard-accessible (currently click-only divs).Use a
<button>(preferred) or addrole="button",tabindex="0", and@keydown.enter/@keydown.spaceso users can select tools via keyboard.src/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.ts-48-55 (1)
48-55: Fix capability mismatch: adapter reads and writesmodelbut reportssupportsModel: false.ClaudeCodeAdapter reads
modelfrom frontmatter during parse (line 48) and conditionally writes it during serialize (lines 78–79), yet getCapabilities() reportssupportsModel: false. This is inconsistent with every other adapter: Copilot setssupportsModel: trueand reads/writes model; Cursor, Kiro, Windsurf, and Antigravity setsupportsModel: falseand ignore model entirely. This mismatch can cause UI or conversion logic to incorrectly treat model as unsupported when the adapter actually preserves it.Either preserve model support and set
supportsModel: true, or stop reading and writing model fields entirely.Option 1: Enable model support (recommended if Claude Code SKILL.md supports model field)
getCapabilities(): FormatCapabilities { return { hasFrontmatter: true, supportsName: true, supportsDescription: true, supportsTools: true, - supportsModel: false, // Claude Code doesn't officially support model field + supportsModel: true, supportsSubfolders: true, supportsReferences: true, supportsScripts: true } }Also applies to: lines 77–84 (serialize), 118–129 (detect method incorrectly uses
startsWith('---')without trimming leading whitespace)src/shared/types/skillSync.ts-136-147 (1)
136-147:ExternalSkillInfo.lastModifiedwill serialize to string across IPC, causing runtime type mismatches.When
scanExternalTools()or otherISkillSyncPresentermethods are called from the renderer (via the genericpresenter:callIPC handler), thelastModified: Datefields will serialize to ISO strings through Electron's structured clone. This creates a silent type mismatch—the value is a string but the type signature declaresDate, breaking code that calls Date methods or usesinstanceof Datechecks. UselastModifiedMs: numberor update the type toDate | stringinstead.src/renderer/src/i18n/en-US/settings.json-1103-1173 (1)
1103-1173: Thesyncsub-section is missing from fa-IR locale.The en-US file includes a comprehensive
skills.syncsection (lines 1103-1173) with ~70 keys for import/export functionality. This entire section is missing from the fa-IR settings.json file, which will cause missing translation warnings or fallback behavior.src/renderer/src/i18n/fa-IR/settings.json-1043-1101 (1)
1043-1101: Skills translations are in English instead of Persian (fa-IR).All strings in the new
skillsnamespace are in English, but this is the Persian (fa-IR) locale file. These should be translated to Persian like the rest of the file. For example, "Skills" should be translated similarly to how "Settings" is translated as "تنظیمات".Based on learnings, translations must be added to ALL language files with proper localization for each locale.
🟡 Minor comments (16)
docs/specs/skills-system/skills-syncing.md-366-366 (1)
366-366: Use headings instead of bold “Step …” labels (MD036).This improves navigation and removes markdownlint noise.
Proposed patch (pattern)
-**Step 1: 选择来源工具** +#### Step 1: 选择来源工具Also applies to: 393-393, 420-420, 453-453, 479-479, 502-502
docs/specs/skills-system/skills-syncing.md-201-226 (1)
201-226: Add languages to fenced blocks to satisfy markdownlint (MD040).Most of these are diagrams / flows / file trees; mark them as
textfor consistency.Proposed patch (apply similarly to other unlabeled fences)
-``` +```text ┌─────────────────────────────────────────────────────────────────────┐ │ SkillSyncPresenter │ ... └─────────────────────────────────────────────────────────────────────┘ -``` +```Also applies to: 308-335, 345-362, 368-391, 395-418, 422-449, 455-477, 481-500, 504-529, 533-555, 563-620, 595-620
docs/specs/skills-system/skills-syncing-cursor.md-17-24 (1)
17-24: Add languages to fenced blocks (MD040).Proposed patch
-``` +```text .cursor/commands/ ├── code-review.md ... -``` +``` -``` +```text > /code-review > /refactor > /lint-suite -``` +```Also applies to: 78-82
src/renderer/src/i18n/he-IL/settings.json-1043-1045 (1)
1043-1045: Translate "Skills" to Hebrew.The title value "Skills" on line 1044 is in English within a Hebrew translation file. According to coding guidelines, proper nouns and technical terms (like "DeepChat", "MCP") should remain untranslated, but "Skills" is a general term that should be translated to Hebrew for consistency.
🌐 Suggested translation
Consider using the Hebrew translation:
"skills": { - "title": "Skills", + "title": "מיומנויות", "description": "Manage and configure AI assistant skills",docs/specs/skills-system/skills-syncing-windsurf.md-17-23 (1)
17-23: Fix markdownlint MD040: add languages to fenced code blocks.This file trips MD040 (missing fenced code block language). Add
text(or a more specific language) to the relevant fences.Proposed diff
-``` +```text .windsurf/workflows/ ├── code-review.md ├── deploy.md ├── pr-review.md └── run-tests.md@@
-+text/code-review
/deploy
/pr-reviewAlso applies to: 86-91
docs/specs/skills-system/ui-design.md-27-57 (1)
27-57: Fix markdownlint MD040 by adding languages to fenced code blocks.Use e.g.
textfor ASCII UI diagrams,typescriptfor TS snippets, andjsonfor i18n examples.Also applies to: 61-81, 91-119, 125-171, 183-205, 214-225, 233-247, 251-288, 292-342, 348-362, 368-412, 419-467, 473-485, 493-521, 529-545
test/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.test.ts-36-99 (1)
36-99: Document the intentional contract split betweendetect()andparse().
detect()requires bothnameanddescription(strict by design for auto-detection), whileparse()uses folder path fallback when name is missing. This split is intentional—autoDetectAndParse()gatesparse()withdetect(), butparseExternal()callsparse()directly with explicit toolId. Consider adding a comment explaining that the lenient fallback inparse()is for explicit adapter selection flows, not implicit auto-detection.src/renderer/settings/components/skills/SkillInstallDialog.vue-247-254 (1)
247-254: Hardcoded English string violates i18n requirements.The error message for invalid URL format is hardcoded in English. Per coding guidelines, all user-facing strings must use i18n keys via vue-i18n.
🔧 Proposed fix
if (!isValidUrl(installUrl.value)) { toast({ title: t('settings.skills.install.failed'), - description: 'Invalid URL format. Please enter a valid HTTP or HTTPS URL.', + description: t('settings.skills.install.invalidUrl'), variant: 'destructive' }) return }Add the corresponding key to all i18n locale files:
"install": { ... "invalidUrl": "Invalid URL format. Please enter a valid HTTP or HTTPS URL." }Committable suggestion skipped: line range outside the PR's diff.
test/main/presenter/skillSyncPresenter/adapters/kiroAdapter.test.ts-343-354 (1)
343-354: Misleading test name/comment: “quote title with special characters” doesn’t exercise special charsEither update the fixture to include characters that require YAML quoting (e.g.,
:/#/ leading*), or rename the test.src/shared/types/presenters/legacy.presenters.d.ts-1075-1076 (1)
1075-1076: EnsureactiveSkillsis normalized to[]at consumption pointsOptional type is fine for migration compatibility, but all consumers must normalize
undefinedto[]to prevent potential scattered nullish checks. Verify that code accessingactiveSkillsfollows the established pattern:conversation?.settings?.activeSkills || []or equivalent defaults on retrieval.src/shared/types/skillSync.ts-203-223 (1)
203-223: Minor naming consistency:targetToolvstargetToolId.Consider aligning to
targetToolId(to match other APIs likepreviewExport(..., targetToolId, ...)) to reduce glue-code churn.src/renderer/settings/components/skills/SkillSyncDialog/ToolSelector.vue-36-41 (1)
36-41: UI allows “0 skills” display but blocks selection—add a disabled state/message.Right now
tool.available && tool.skills.length > 0gates selection, but the list still looks selectable and showscount: 0. Consider a distinct badge (e.g., “No skills found”) + disabled styling/cursor and prevent “selected” styling for non-selectable items.Also applies to: 66-70
src/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.ts-100-113 (1)
100-113: Detection should usetrimStart()to tolerate leading whitespace.
content.startsWith('---')will fail if the file has a leading newline/BOM/whitespace.src/main/presenter/skillSyncPresenter/adapters/copilotAdapter.ts-128-148 (1)
128-148: Detection heuristic is non-unique; requires additional discriminator to avoid collisions.The condition
(typeof data.description === 'string' && !data.name)matches any description-only frontmatter without a name field, which causes collisions with other formats (notably Antigravity). This is documented in the test suite—when content has description-only frontmatter with a Steps structure, Copilot incorrectly matches due to detection order.However, the proposed fix using
typeof data.model === 'string'conflicts with the documented format spec, which lists model as "Optional". Additionally, it would break the existing test at line 59-66 that expects description-only frontmatter (without model, agent, or tools) to be detected as Copilot.Instead, tighten detection by requiring at least one Copilot-specific field. A better fix would be to check for the presence of any discriminator:
const hasCopilotFields = data.agent === 'agent' || Array.isArray(data.tools) || typeof data.model === 'string'Or, keep the description-only case but add an additional check (e.g., verify it's NOT pure Antigravity by rejecting the Antigravity-specific pattern).
Committable suggestion skipped: line range outside the PR's diff.
src/renderer/settings/components/skills/SkillSyncDialog/ExportWizard.vue-498-545 (1)
498-545: Progress tracking is initialized but never updated.Lines 500-501 initialize
exportProgress, but the values are never updated during the export operation. The user sees "Exporting 0/N" throughout the entire process.If
skillSyncPresenter.executeExport()doesn't provide progress callbacks, consider either:
- Removing the progress display and showing a simple spinner
- Adding progress callback support to the presenter method
Option 1: Simplify the UI
- <div v-else-if="exporting" class="flex flex-col items-center justify-center py-8"> - <Icon icon="lucide:loader-2" class="w-8 h-8 animate-spin text-primary mb-2" /> - <span class="text-sm text-muted-foreground"> - {{ - t('settings.skills.sync.exporting', { - current: exportProgress.current, - total: exportProgress.total - }) - }} - </span> - <span class="text-xs text-muted-foreground mt-1">{{ exportProgress.currentSkill }}</span> - </div> + <div v-else-if="exporting" class="flex flex-col items-center justify-center py-8"> + <Icon icon="lucide:loader-2" class="w-8 h-8 animate-spin text-primary mb-2" /> + <span class="text-sm text-muted-foreground">{{ t('settings.skills.sync.exporting') }}</span> + </div>Committable suggestion skipped: line range outside the PR's diff.
src/main/presenter/skillSyncPresenter/adapters/kiroAdapter.ts-104-128 (1)
104-128: Incomplete detection logic for inclusion modes.According to the file header comment (line 9), Kiro supports three inclusion modes:
always,conditional, andon-demand. However, the detection logic at lines 116-117 only checks foralwaysandconditional, missing theon-demandmode.🔧 Proposed fix
// Check for Kiro-specific fields const hasKiroFields = typeof data.title === 'string' || data.inclusion === 'always' || data.inclusion === 'conditional' || + data.inclusion === 'on-demand' || Array.isArray(data.file_patterns)
| // Embed Kiro-specific info in instructions as comments if present | ||
| if (data.inclusion || data.file_patterns) { | ||
| const kiroMeta = this.buildKiroMetaComment(data) | ||
| skill.instructions = kiroMeta + instructions.trim() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix instruction concatenation logic.
Line 57 overwrites skill.instructions with kiroMeta + instructions.trim(), but skill.instructions was already set to instructions.trim() at line 46. This means the Kiro metadata comment is prepended to the wrong string. The logic should prepend to the original instructions before assignment.
🐛 Proposed fix
- // Embed Kiro-specific info in instructions as comments if present
- if (data.inclusion || data.file_patterns) {
- const kiroMeta = this.buildKiroMetaComment(data)
- skill.instructions = kiroMeta + instructions.trim()
- }
+ // Embed Kiro-specific info in instructions as comments if present
+ let finalInstructions = instructions.trim()
+ if (data.inclusion || data.file_patterns) {
+ const kiroMeta = this.buildKiroMetaComment(data)
+ finalInstructions = kiroMeta + finalInstructions
+ }
+
+ skill.instructions = finalInstructionsAlternatively, move the metadata embedding before the skill object creation:
// Kiro has no description field, try to extract from blockquote at start
const { description, instructions } = this.extractDescriptionAndInstructions(body)
+ // Embed Kiro-specific info in instructions as comments if present
+ let finalInstructions = instructions.trim()
+ if (data.inclusion || data.file_patterns) {
+ const kiroMeta = this.buildKiroMetaComment(data)
+ finalInstructions = kiroMeta + finalInstructions
+ }
+
// Store Kiro-specific metadata in source
const skill: CanonicalSkill = {
name,
description,
- instructions: instructions.trim(),
+ instructions: finalInstructions,
source: {
tool: this.id,
originalPath: context.filePath,
originalFormat: 'kiro-steering'
}
}
-
- // Embed Kiro-specific info in instructions as comments if present
- if (data.inclusion || data.file_patterns) {
- const kiroMeta = this.buildKiroMetaComment(data)
- skill.instructions = kiroMeta + instructions.trim()
- }
return skill🤖 Prompt for AI Agents
In @src/main/presenter/skillSyncPresenter/adapters/kiroAdapter.ts around lines
54 - 58, The current code prepends Kiro metadata using skill.instructions =
kiroMeta + instructions.trim() after skill.instructions was already set to
instructions.trim(), causing the metadata to be combined with the wrong value;
fix by prepending the meta to the original instructions before assigning to the
skill or by updating the existing field (e.g., compute finalInstructions =
kiroMeta + instructions.trim() when data.inclusion or data.file_patterns are
present, then set skill.instructions = finalInstructions), using
buildKiroMetaComment, data.inclusion, data.file_patterns and instructions.trim()
to locate the relevant logic.
| "skills": { | ||
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files" | ||
| }, | ||
| "conflict": { | ||
| "title": "Skill Already Exists", | ||
| "description": "A skill named \"{name}\" already exists. Do you want to overwrite it?", | ||
| "overwrite": "Overwrite" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Untranslated English content in Traditional Chinese locale file.
The entire skills section (lines 1043-1102) contains English text instead of Traditional Chinese translations. This violates the i18n guidelines and will display English UI strings to Traditional Chinese users.
All keys must be translated to Traditional Chinese (繁體中文).
✍️ Translation guidance
Example translations needed:
- "Skills" → "技能"
- "Manage and configure AI assistant skills" → "管理和配置 AI 助手技能"
- "Open Folder" → "開啟資料夾"
- "Add Skill" → "新增技能"
- "No skills yet" → "尚無技能"
- "Install Skill" → "安裝技能"
- etc.
Based on coding guidelines: new translations must be added to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales.
As per coding guidelines, all user-facing strings must use properly translated i18n keys in each locale file.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/zh-TW/settings.json around lines 1043 - 1102, The
"skills" locale block is still in English; translate every user-facing string
under the "skills" object into Traditional Chinese (繁體中文) while keeping all keys
and placeholders intact (e.g., "skills", "install", "delete", "edit", "conflict"
and nested keys like "count": "{count} skills", "successMessage": "Skill {name}
has been installed successfully", "description", "openFolder", "addSkill",
etc.); preserve interpolation tokens ({count}, {name}), escaped quotes, and
punctuation, and then mirror the same translated keys across the other locale
files as required by the i18n guidelines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🤖 Fix all issues with AI agents
In @src/main/presenter/agentPresenter/acp/agentToolManager.ts:
- Around line 825-830: The lazily-initialized skillTools (returned by
getSkillTools) is never cleared, unlike fileSystemHandler and bashHandler in
getAllToolDefinitions; add cleanup to reset this.skillTools = undefined/null
when skills are disabled or when workspace/manager lifecycle changes (e.g., in
getAllToolDefinitions where workspace path changes or in an explicit
dispose/reset method) so SkillTools is reconstructed consistently and mirrors
the existing cleanup behavior.
In @src/renderer/src/i18n/he-IL/settings.json:
- Around line 1044-1102: The skills localization block contains English strings
that must be translated for the missing locales; update the "skills" object keys
(e.g., "title", "description", "openFolder", "addSkill", "install" subkeys like
"title"/"description"/"tabFolder"/"tabZip"/"tabUrl"/"fromFolder"/"selectFolder"/"zipHint"/"urlPlaceholder"/"installButton"/"successMessage",
"delete" messages, "edit" messages including "nameHint", and "conflict"
messages) in fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, and zh-TW
so they are fully localized (use the same keys; replace the English values with
correct translations for each locale); ensure placeholders like {name} and
{count} and any quoted text remain intact.
In @src/renderer/src/i18n/ko-KR/settings.json:
- Around line 1106-1107: The string value for the key conflictDescription
contains an incorrect escape sequence (\\\") that renders a literal backslash;
update the value for conflictDescription to use the correct JSON escape for a
double quote (\"\")—i.e., replace the sequence \\\"{name}\\\" with \"{name}\"—so
the rendered text shows quotes around the name correctly.
- Around line 1043-1102: Translate all English strings under the "skills"
section to Korean: update keys such as "skills.title", "skills.description",
"skills.openFolder", "skills.addSkill", "skills.empty", "skills.emptyHint",
"skills.noResults", "skills.search", "skills.count", and all nested objects
"skills.install.*" (title, description, tabFolder, tabZip, tabUrl, fromFolder,
selectFolder, folderHint, folderTip, zipHint, urlPlaceholder, urlHint,
installButton, installing, success, successMessage, failed, dragNotSupported),
"skills.delete.*" (title, description, success, successMessage, failed),
"skills.edit.*" (title, placeholder, readFailed, success, failed, name,
namePlaceholder, description, descriptionPlaceholder, allowedTools,
allowedToolsPlaceholder, allowedToolsHint, content, files, noFiles, nameHint),
and "skills.conflict.*" (title, description, overwrite) with proper Korean
translations consistent with the rest of the ko-KR file.
In @src/renderer/src/i18n/ru-RU/settings.json:
- Around line 1103-1173: The Russian translations under the "sync" block use
inconsistent casing and some incorrect terms (e.g., "покрытие"/"полное покрытие"
for overwrite, "неудачный проект" for failedItems, many lowercase titles).
Update the values for keys in the sync object (e.g., "overwrite",
"overwriteAll", "failedItems", "title", "batchAction", "exported", "failed",
"noConflicts", "selectedCount", etc.) to idiomatic Russian and consistent UI
capitalization: use "Перезаписать" / "Перезаписать все" for overwrite,
"Неудачные элементы" or "Сбой" for failed/failedItems, capitalize titles and
labels (e.g., "Синхронизация", "Пакетная обработка"), and fix other awkward
phrases (e.g., "Импортируется" vs "Импортируется..." kept consistent). Ensure
pluralization placeholders remain ({count}, {success}, {skipped}, {failed}) and
preserve existing keys unchanged.
- Around line 1043-1102: The skills.* block contains many English strings;
translate all keys under skills (e.g., skills.title, skills.description,
skills.openFolder, skills.addSkill, skills.empty, skills.emptyHint,
skills.noResults, skills.search, skills.count, the entire skills.install subkeys
like
skills.install.title/tabFolder/tabZip/tabUrl/fromFolder/selectFolder/folderHint/folderTip/zipHint/urlPlaceholder/urlHint/installButton/installing/success/successMessage/failed/dragNotSupported,
the skills.delete and skills.edit subkeys except tool names, and
skills.conflict) into Russian while preserving proper nouns and tool names
(DeepChat, Claude Code, Cursor, Read/Grep/Bash) and keeping the existing Russian
phrase in skills.edit.nameHint; ensure phrasing is natural and consistent with
other ru-RU locale strings and update successMessage/delete descriptions to use
localized placeholders like {name}.
In @src/renderer/src/i18n/zh-HK/settings.json:
- Around line 1042-1174: The zh-HK locale's skills section contains many
untranslated English strings (e.g., keys like skills.title, skills.description,
skills.openFolder, skills.addSkill, skills.install.*, skills.delete.*,
skills.edit.*, skills.conflict.*, and skills.sync.*); translate every English
value into Traditional Chinese (Hong Kong) while keeping the JSON keys
unchanged, ensure placeholders like {count}, {name}, {current}/{total} remain
intact, and maintain consistent terminology (e.g., use "技能" for "Skill") across
entries before committing the updated settings.json.
In @src/renderer/src/i18n/zh-TW/settings.json:
- Around line 1043-1096: Several keys under the "skills" section (e.g., "title",
"description", "openFolder", "addSkill", "empty", "emptyHint", "noResults",
"search", "count", and all nested keys under "install", "delete", "edit", and
"conflict") are still in English; replace each English string with proper
Traditional Chinese translations (consistent with existing zh-TW style such as
using "技能" for "Skills", "開啟資料夾" for "Open Folder", etc.), ensuring keys like
install.title/install.description/install.tabFolder/install.tabZip/install.tabUrl/install.fromFolder/install.selectFolder/install.folderHint/install.folderTip/install.zipHint/install.urlPlaceholder/install.urlHint/install.installButton/install.installing/install.success/install.successMessage/install.failed/install.dragNotSupported,
delete.title/delete.description/delete.success/delete.successMessage/delete.failed,
edit.title/edit.placeholder/edit.readFailed/edit.success/edit.failed/edit.name/edit.namePlaceholder/edit.description/edit.descriptionPlaceholder/edit.allowedTools/edit.allowedToolsPlaceholder/edit.allowedToolsHint/edit.content/edit.files/edit.noFiles/edit.nameHint,
and the conflict subsection are fully localized; after updating zh-TW, mirror
the same localized content pattern across all other locale files as required by
the coding guidelines.
🧹 Nitpick comments (4)
resources/skills/infographic-syntax-creator/SKILL.md (1)
19-19: Capitalize "Markdown" as a proper noun.Per standard usage, "Markdown" should be capitalized as it's the name of the markup language.
✏️ Proposed fix
- - Output is a single `infographic` markdown code block; no extra text. + - Output is a single `infographic` Markdown code block; no extra text.src/main/presenter/permission/filePermissionService.ts (1)
27-34: Unused_rememberparameter.The
_rememberparameter is accepted but not used. If persistence is planned for the future, consider adding a TODO comment. Otherwise, remove the parameter to avoid confusion.💡 Option: Document future intent or remove
- approve(conversationId: string, paths: string[], _remember: boolean): void { + approve(conversationId: string, paths: string[]): void { + // TODO: Add persistence support if _remember is needed in the futureOr document the intent:
+ // Note: _remember parameter reserved for future persistent storage support approve(conversationId: string, paths: string[], _remember: boolean): void {src/main/presenter/agentPresenter/acp/agentToolManager.ts (1)
596-616: Dynamic filesystem handler creation per call.A new
AgentFileSystemHandleris created on eachcallFileSystemToolinvocation with dynamically computed allowed directories. While correct, consider caching if performance becomes a concern with frequent tool calls.src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts (1)
207-248: Read operations bypass allowlist validation—confirm this security posture is intentional.The
enforceAllowed: falseparameter allows reads to access any file outsideallowedDirectories(including system files like/etc/passwdor~/.ssh/). This appears intentional based on the code comment at line 1083 ("Preserve matches without allowlist filtering for read operations") and is consistent acrossreadFile,listDirectory,grepSearch,directoryTree,getFileInfo, andglobSearch.Write operations enforce the allowlist strictly via both the default
enforceAllowed: trueand runtime permission checks inassertWritePermission. The asymmetry is deliberate: writes are constrained toallowedDirectories, reads are not.Since there are no application-level path exclusions, agents can read sensitive files. Confirm this trade-off (convenience vs. security boundary) aligns with threat model and intended usage constraints.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
resources/skills/infographic-syntax-creator/SKILL.mdresources/skills/infographic-syntax-creator/references/prompt.mdsrc/main/presenter/agentPresenter/acp/agentFileSystemHandler.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/agentPresenter/permission/permissionHandler.tssrc/main/presenter/index.tssrc/main/presenter/permission/filePermissionService.tssrc/main/presenter/permission/index.tssrc/main/presenter/sessionPresenter/index.tssrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/zh-TW/settings.json
🚧 Files skipped from review as they are similar to previous changes (4)
- src/renderer/src/i18n/pt-BR/settings.json
- src/renderer/src/i18n/da-DK/settings.json
- src/renderer/src/i18n/fa-IR/settings.json
- src/renderer/src/i18n/fr-FR/settings.json
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,ts,tsx,jsx,vue,json,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use Prettier as the code formatter
Files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/main/presenter/sessionPresenter/index.tssrc/main/presenter/permission/filePermissionService.tssrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/main/presenter/permission/index.tssrc/main/presenter/agentPresenter/acp/agentFileSystemHandler.tssrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/main/presenter/agentPresenter/permission/permissionHandler.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/index.ts
src/renderer/src/i18n/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
src/renderer/src/i18n/**/*.json: Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.json
src/renderer/src/i18n/{zh-CN,zh-HK,zh-TW}/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
For Chinese translations, consider using '智能体' for 'Agents' instead of the English term
Files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.json
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use English for logs and comments in TypeScript/JavaScript code
Files:
src/main/presenter/sessionPresenter/index.tssrc/main/presenter/permission/filePermissionService.tssrc/main/presenter/permission/index.tssrc/main/presenter/agentPresenter/acp/agentFileSystemHandler.tssrc/main/presenter/agentPresenter/permission/permissionHandler.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/index.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript with strict type checking enabled
Use OxLint for linting JavaScript and TypeScript files; ensure lint-staged hooks and typecheck pass before commits
Files:
src/main/presenter/sessionPresenter/index.tssrc/main/presenter/permission/filePermissionService.tssrc/main/presenter/permission/index.tssrc/main/presenter/agentPresenter/acp/agentFileSystemHandler.tssrc/main/presenter/agentPresenter/permission/permissionHandler.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/index.ts
src/main/presenter/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/presenter/**/*.ts: Use EventBus to broadcast events from main to renderer viamainWindow.webContents.send()
Implement one presenter per functional domain in the main process
Files:
src/main/presenter/sessionPresenter/index.tssrc/main/presenter/permission/filePermissionService.tssrc/main/presenter/permission/index.tssrc/main/presenter/agentPresenter/acp/agentFileSystemHandler.tssrc/main/presenter/agentPresenter/permission/permissionHandler.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/index.ts
src/main/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/**/*.ts: Use EventBus fromsrc/main/eventbus.tsfor decoupled inter-process communication
Context isolation must be enabled with preload scripts for secure IPC communicationElectron main process code should reside in
src/main/, with presenters organized inpresenter/subdirectory (Window, Tab, Thread, Mcp, Config, LLMProvider), and app events managed viaeventbus.ts
Files:
src/main/presenter/sessionPresenter/index.tssrc/main/presenter/permission/filePermissionService.tssrc/main/presenter/permission/index.tssrc/main/presenter/agentPresenter/acp/agentFileSystemHandler.tssrc/main/presenter/agentPresenter/permission/permissionHandler.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/index.ts
**/*.{js,ts,tsx,jsx,vue,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
All logs and comments must be in English
Files:
src/main/presenter/sessionPresenter/index.tssrc/main/presenter/permission/filePermissionService.tssrc/main/presenter/permission/index.tssrc/main/presenter/agentPresenter/acp/agentFileSystemHandler.tssrc/main/presenter/agentPresenter/permission/permissionHandler.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/index.ts
**/*.{js,ts,tsx,jsx,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use OxLint as the linter
Files:
src/main/presenter/sessionPresenter/index.tssrc/main/presenter/permission/filePermissionService.tssrc/main/presenter/permission/index.tssrc/main/presenter/agentPresenter/acp/agentFileSystemHandler.tssrc/main/presenter/agentPresenter/permission/permissionHandler.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/index.ts
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,vue}: Use camelCase for variable and function names; use PascalCase for types and classes; use SCREAMING_SNAKE_CASE for constants
Configure Prettier with single quotes, no semicolons, and line width of 100 characters. Runpnpm run formatafter completing features
Files:
src/main/presenter/sessionPresenter/index.tssrc/main/presenter/permission/filePermissionService.tssrc/main/presenter/permission/index.tssrc/main/presenter/agentPresenter/acp/agentFileSystemHandler.tssrc/main/presenter/agentPresenter/permission/permissionHandler.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/index.ts
🧠 Learnings (21)
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/{zh-CN,zh-HK,zh-TW}/**/*.json : For Chinese translations, consider using '智能体' for 'Agents' instead of the English term
Applied to files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Applied to files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Applied to files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Applied to files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/en-US/**/*.json : Use English (en-US) as the reference for translation accuracy when adding new keys
Applied to files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Ensure all code comments are in English and all log messages are in English, with no non-English text in code comments or console statements
Applied to files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Run 'pnpm run i18n' to check translation completeness using zh-CN as source, or 'pnpm run i18n:en' to check using en-US as source
Applied to files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.json
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Applies to src/renderer/src/i18n/**/*.ts : All user-facing strings must use vue-i18n keys defined in `src/renderer/src/i18n/`
Applied to files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : All user-facing strings must use i18n keys, not hardcoded text
Applied to files:
src/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.json
📚 Learning: 2025-06-21T15:48:29.950Z
Learnt from: neoragex2002
Repo: ThinkInAIXYZ/deepchat PR: 550
File: src/main/presenter/mcpPresenter/inMemoryServers/meetingServer.ts:250-252
Timestamp: 2025-06-21T15:48:29.950Z
Learning: In the meeting server implementation (src/main/presenter/mcpPresenter/inMemoryServers/meetingServer.ts), when multiple tabs have the same title, the user prefers to let the code silently select the first match without adding warnings or additional ambiguity handling.
Applied to files:
src/main/presenter/sessionPresenter/index.ts
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/main/presenter/**/*.ts : Implement one presenter per functional domain in the main process
Applied to files:
src/main/presenter/sessionPresenter/index.tssrc/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/index.ts
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use English for logs and comments in TypeScript/JavaScript code
Applied to files:
src/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/he-IL/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Use the i18n-code-reviewer agent to validate translation quality, check for missing translations, review proper noun handling, and ensure consistency across languages
Applied to files:
src/renderer/src/i18n/ko-KR/settings.json
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/renderer/**/*.vue : All user-facing strings must use i18n keys via vue-i18n
Applied to files:
src/renderer/src/i18n/ko-KR/settings.json
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/main/presenter/mcpPresenter/inMemoryServers/**/*.ts : MCP tool implementations must be registered in `mcpPresenter/index.ts` after implementation
Applied to files:
src/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/index.ts
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/main/presenter/configPresenter/**/*.ts : Custom prompts are managed independently of MCP through config data source using `configPresenter.getCustomPrompts()`
Applied to files:
src/main/presenter/agentPresenter/acp/agentToolManager.ts
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Use `usePresenter.ts` composable for renderer-to-main IPC communication via direct presenter method calls
Applied to files:
src/main/presenter/agentPresenter/acp/agentToolManager.tssrc/main/presenter/index.ts
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Use `usePresenter` composable for main process communication instead of direct IPC calls
Applied to files:
src/main/presenter/index.ts
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/main/presenter/**/*.ts : Use EventBus to broadcast events from main to renderer via `mainWindow.webContents.send()`
Applied to files:
src/main/presenter/index.ts
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/main/presenter/llmProviderPresenter/providers/*.ts : LLM provider implementations must follow the standardized event interface with `coreStream` method
Applied to files:
src/main/presenter/index.ts
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Use Presenter pattern in main process, EventBus for inter-process events, and two-layer LLM provider architecture (Agent Loop + Provider) with integrated MCP tools
Applied to files:
src/main/presenter/index.ts
🧬 Code graph analysis (4)
src/main/presenter/sessionPresenter/index.ts (1)
src/main/presenter/index.ts (1)
presenter(330-330)
src/main/presenter/permission/filePermissionService.ts (1)
src/main/presenter/permission/index.ts (2)
FilePermissionRequiredError(3-3)FilePermissionService(3-3)
src/main/presenter/agentPresenter/permission/permissionHandler.ts (2)
src/main/presenter/index.ts (1)
presenter(330-330)build/generate-version-files.mjs (1)
key(29-29)
src/main/presenter/index.ts (4)
src/shared/types/skillSync.ts (1)
ISkillSyncPresenter(317-371)src/main/presenter/permission/filePermissionService.ts (1)
FilePermissionService(24-53)src/main/presenter/skillPresenter/index.ts (1)
SkillPresenter(51-901)src/main/presenter/skillSyncPresenter/index.ts (1)
SkillSyncPresenter(37-762)
🪛 LanguageTool
resources/skills/infographic-syntax-creator/SKILL.md
[uncategorized] ~19-~19: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...: - Output is a single infographic markdown code block; no extra text. - First l...
(MARKDOWN_NNP)
resources/skills/infographic-syntax-creator/references/prompt.md
[uncategorized] ~147-~147: 您的意思是“"不"全”?
Context: ...- #f97316 ``` ## 常见问题与最佳实践 - 信息不足时,可合理补全,但避免编造与主题无关内容 - value 为数值类型,若无明确数值可省略...
(BU)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-check (x64)
🔇 Additional comments (23)
resources/skills/infographic-syntax-creator/references/prompt.md (1)
1-150: Comprehensive and well-structured reference guide.This reference document provides clear guidelines for generating AntV Infographic syntax. The structure is logical with explicit syntax rules, comprehensive template listings, a practical generation workflow, and concrete examples. The document appropriately guides downstream systems on output constraints (plain text only, no JSON/Markdown/explanatory text).
Regarding the static analysis hint at line 147: LanguageTool suggests replacing "不足" with "不全". This appears to be a false positive—"不足" (insufficient) is the correct word choice for this context, expressing a lack of information. The sentence is grammatically sound as-is.
src/renderer/src/i18n/ja-JP/settings.json (2)
1103-1173: Properly translated sync section.The sync subsection is correctly translated to Japanese with appropriate handling of technical terms and consistent formatting. This section follows the established translation patterns and provides a good user experience for Japanese speakers.
1042-1174: All required locale files contain the skills namespace with consistent structure.Verification confirms that the
skillsnamespace has been successfully added to all 12 required language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names and structure across all locales.src/main/presenter/agentPresenter/permission/permissionHandler.ts (2)
173-183: LGTM! Clear filesystem permission handling flow.The new branch cleanly handles filesystem permission grants:
- Validates paths before approval
- Falls back to denial flow when paths are missing
- Uses optional chaining for safe service access
870-877: Well-implemented defensive helper.The helper correctly handles edge cases: null sources, non-array values, and filters out empty/whitespace-only strings.
src/main/presenter/sessionPresenter/index.ts (3)
64-64: Consistent permission cleanup on tab close.File permission cleanup correctly mirrors the existing command permission cleanup pattern.
281-304: Comprehensive permission lifecycle management.The additions ensure file permissions are cleared alongside command permissions across all conversation lifecycle methods. The optional chaining guards against service unavailability.
432-435: Proper cleanup before conversation deletion.File permissions are cleared before the conversation is deleted, preventing orphaned permission state.
src/main/presenter/permission/index.ts (1)
3-3: LGTM!Clean barrel export following the existing pattern for CommandPermissionService.
src/main/presenter/permission/filePermissionService.ts (1)
49-52: Path normalization looks correct.The implementation properly uses
path.resolvefollowed bypath.normalize, and applies case-insensitive comparison on Windows. This matches typical filesystem behavior.src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts (3)
155-157: Clear options interface for path validation.The
PathValidationOptionsinterface provides a clean way to control validation behavior without modifying the method signature excessively.
194-205: Useful public path resolution utilities.These methods enable external callers (like
AgentToolManager) to resolve and check paths before invoking operations, supporting the new permission flow.
1083-1088: Simplified glob search validation.The comment and implementation now preserve matches without allowlist filtering for read operations. This aligns with the relaxed read validation approach.
src/main/presenter/index.ts (4)
106-108: Public API surface correctly extended.The new presenters and service are properly exposed following the existing pattern for other presenters.
189-193: Skill presenters correctly wired with dependency injection.SkillPresenter receives configPresenter, and SkillSyncPresenter receives skillPresenter, establishing the proper dependency chain.
279-291: Config-gated skill initialization with proper error handling.The initialization respects the
enableSkillsconfig flag and gracefully handles errors without crashing the application startup.
322-323: Type assertions in destroy are safe here.Since the properties are initialized with these concrete types in the constructor, the casts are valid. Consider using a type guard pattern in the future if the implementation becomes more polymorphic.
src/main/presenter/agentPresenter/acp/agentToolManager.ts (5)
188-203: Skill schemas with Zod refinement.The
skill_controlschema uses.refine()to ensure eitherskill_nameorskillsis provided. This validation runs at parse time.
264-268: Skill tools correctly gated by config.Tool definitions are only included when both in agent mode and skills are enabled, preventing exposure of skill tools when the feature is disabled.
716-742: Well-structured allowed directories builder.The
buildAllowedDirectoriesmethod uses deduplication via Set and maintains order priority. It correctly includes:
- Workspace path
- Agent workspace path
- Skills path
- User home/.deepchat
- Temp directory
- Approved paths from FilePermissionService
744-773: Write permission assertion with clear error propagation.The
assertWritePermissionmethod checks each target path against the allowed directories and throwsFilePermissionRequiredErrorwith the denied paths. This enables the permission UI to prompt for specific paths.
878-913: Skill tool execution with proper validation.The
callSkillToolmethod:
- Checks skills are enabled before execution
- Validates arguments with Zod schema
- Properly handles both
skill_nameandskillsarray inputs- Returns structured JSON responses
src/renderer/src/i18n/ru-RU/settings.json (1)
1043-1174: No action required. Allsettings.skills.*keys are properly synchronized across all 12 locales (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent 120 keys matching the en-US reference.
| private getSkillTools(): SkillTools { | ||
| if (!this.skillTools) { | ||
| this.skillTools = new SkillTools(presenter.skillPresenter) | ||
| } | ||
| return this.skillTools | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if AgentToolManager has a destroy method
rg -n "destroy\s*\(\)" src/main/presenter/agentPresenter/acp/agentToolManager.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Get the full content of the agentToolManager.ts file to understand class structure and lifecycle
wc -l src/main/presenter/agentPresenter/acp/agentToolManager.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 126
🏁 Script executed:
#!/bin/bash
# Check for cleanup/dispose patterns in the class
rg -n "cleanup|dispose|teardown|reset" src/main/presenter/agentPresenter/acp/agentToolManager.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Read the class definition and early lines to understand structure
head -100 src/main/presenter/agentPresenter/acp/agentToolManager.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 3575
🏁 Script executed:
#!/bin/bash
# Read the specific area around lines 825-830 with context
sed -n '800,860p' src/main/presenter/agentPresenter/acp/agentToolManager.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 1830
🏁 Script executed:
#!/bin/bash
# Search for skillTools usage throughout the file
rg -n "skillTools" src/main/presenter/agentPresenter/acp/agentToolManager.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 529
🏁 Script executed:
#!/bin/bash
# Read the constructor and method signatures
sed -n '100,200p' src/main/presenter/agentPresenter/acp/agentToolManager.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 3761
🏁 Script executed:
#!/bin/bash
# Check the SkillTools class definition
cat -n src/main/presenter/skillPresenter/skillTools.ts | head -100Repository: ThinkInAIXYZ/deepchat
Length of output: 2851
🏁 Script executed:
#!/bin/bash
# Search for constructor in agentToolManager
rg -n "constructor" src/main/presenter/agentPresenter/acp/agentToolManager.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 118
🏁 Script executed:
#!/bin/bash
# Check if other presenter classes have cleanup/destroy patterns
fd "Presenter.ts" src/main/presenter --exec grep -l "destroy\|cleanup\|dispose" {} \;Repository: ThinkInAIXYZ/deepchat
Length of output: 419
🏁 Script executed:
#!/bin/bash
# Check what cleanup pattern is used in one of the presenters
rg -A 10 "destroy\|cleanup\|dispose" src/main/presenter/tabPresenter.ts | head -30Repository: ThinkInAIXYZ/deepchat
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Read the constructor of AgentToolManager to understand initialization
sed -n '205,250p' src/main/presenter/agentPresenter/acp/agentToolManager.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 1708
🏁 Script executed:
#!/bin/bash
# Check if AgentToolManager extends any base class or implements interface
sed -n '46,70p' src/main/presenter/agentPresenter/acp/agentToolManager.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 1071
🏁 Script executed:
#!/bin/bash
# Search for how agentToolManager is instantiated and used
rg -n "agentToolManager\|AgentToolManager" src/main/presenter --type ts | head -20Repository: ThinkInAIXYZ/deepchat
Length of output: 47
Consider cleanup for lazily-initialized members.
The fileSystemHandler and bashHandler are explicitly cleaned up when the workspace path changes in getAllToolDefinitions(), but skillTools is initialized lazily and never cleaned up. While SkillTools itself doesn't hold resources requiring cleanup, consider whether it should be reset (set to null) when skills are disabled or when the manager's lifecycle ends, for consistency with other lazy-initialized members.
🤖 Prompt for AI Agents
In @src/main/presenter/agentPresenter/acp/agentToolManager.ts around lines 825 -
830, The lazily-initialized skillTools (returned by getSkillTools) is never
cleared, unlike fileSystemHandler and bashHandler in getAllToolDefinitions; add
cleanup to reset this.skillTools = undefined/null when skills are disabled or
when workspace/manager lifecycle changes (e.g., in getAllToolDefinitions where
workspace path changes or in an explicit dispose/reset method) so SkillTools is
reconstructed consistently and mirrors the existing cleanup behavior.
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", | ||
| "nameHint": "לא ניתן לשנות את שם הכישור לאחר יצירתו" | ||
| }, | ||
| "conflict": { | ||
| "title": "Skill Already Exists", | ||
| "description": "A skill named \"{name}\" already exists. Do you want to overwrite it?", | ||
| "overwrite": "Overwrite" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if Hebrew translations for skills were properly added and verify other language files
# Check if skills section exists in all required locale files
echo "=== Checking skills section in all locale files ==="
for locale in da-DK en-US fa-IR fr-FR he-IL ja-JP ko-KR pt-BR ru-RU zh-CN zh-HK zh-TW; do
file="src/renderer/src/i18n/${locale}/settings.json"
if [ -f "$file" ]; then
if jq -e '.promptSetting.skills' "$file" > /dev/null 2>&1; then
count=$(jq '.promptSetting.skills | .. | strings' "$file" 2>/dev/null | wc -l)
echo "✓ ${locale}: skills section exists (${count} string values)"
else
echo "✗ ${locale}: skills section MISSING"
fi
else
echo "✗ ${locale}: file not found"
fi
done
echo ""
echo "=== Sample of he-IL skills translations (first 10 values) ==="
jq '.promptSetting.skills | .. | strings' src/renderer/src/i18n/he-IL/settings.json 2>/dev/null | head -20
echo ""
echo "=== Checking if he-IL has English vs Hebrew text ==="
echo "he-IL title field:"
jq '.promptSetting.skills.title' src/renderer/src/i18n/he-IL/settings.json
echo ""
echo "he-IL install.title field:"
jq '.promptSetting.skills.install.title' src/renderer/src/i18n/he-IL/settings.json
echo ""
echo "he-IL sync.title field (should be Hebrew):"
jq '.promptSetting.skills.sync.title' src/renderer/src/i18n/he-IL/settings.jsonRepository: ThinkInAIXYZ/deepchat
Length of output: 723
🏁 Script executed:
#!/bin/bash
# Check file existence and actual structure
echo "=== Checking if file exists and basic info ==="
if [ -f "src/renderer/src/i18n/he-IL/settings.json" ]; then
lines=$(wc -l < src/renderer/src/i18n/he-IL/settings.json)
echo "✓ File exists with $lines lines"
echo ""
echo "=== Top-level keys in he-IL/settings.json ==="
jq 'keys' src/renderer/src/i18n/he-IL/settings.json
echo ""
echo "=== Searching for 'skills' string anywhere in the file ==="
grep -i "skills" src/renderer/src/i18n/he-IL/settings.json | head -5
echo ""
echo "=== Lines around 1044-1102 ==="
sed -n '1040,1110p' src/renderer/src/i18n/he-IL/settings.json | head -50
else
echo "✗ File does not exist"
fiRepository: ThinkInAIXYZ/deepchat
Length of output: 2659
🏁 Script executed:
#!/bin/bash
# Check skills section content across all locales
echo "=== Checking if skills section is properly translated in all locales ==="
echo ""
for locale in da-DK en-US fa-IR fr-FR he-IL ja-JP ko-KR pt-BR ru-RU zh-CN zh-HK zh-TW; do
file="src/renderer/src/i18n/${locale}/settings.json"
if [ -f "$file" ]; then
if jq -e '.skills' "$file" > /dev/null 2>&1; then
# Get a sample of the first 3 string values
sample=$(jq '.skills | .. | strings' "$file" 2>/dev/null | head -3 | tr '\n' ' ')
echo "${locale}:"
echo " Sample content: $sample"
else
echo "${locale}: skills section MISSING"
fi
fi
done
echo ""
echo "=== Checking if en-US (reference) has skills ==="
jq '.skills.title, .skills.install.title, .skills.sync' src/renderer/src/i18n/en-US/settings.json 2>/dev/nullRepository: ThinkInAIXYZ/deepchat
Length of output: 4219
Critical: Skills section missing translations in 9 locales.
The newly added skills section contains English text in fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, and zh-TW. Only da-DK and zh-CN have proper translations. All user-facing strings in the skills management feature must be translated to each target language per the coding guidelines.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/he-IL/settings.json around lines 1044 - 1102, The
skills localization block contains English strings that must be translated for
the missing locales; update the "skills" object keys (e.g., "title",
"description", "openFolder", "addSkill", "install" subkeys like
"title"/"description"/"tabFolder"/"tabZip"/"tabUrl"/"fromFolder"/"selectFolder"/"zipHint"/"urlPlaceholder"/"installButton"/"successMessage",
"delete" messages, "edit" messages including "nameHint", and "conflict"
messages) in fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, and zh-TW
so they are fully localized (use the same keys; replace the English values with
correct translations for each locale); ensure placeholders like {name} and
{count} and any quoted text remain intact.
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", | ||
| "nameHint": "スキル作成後、名称は変更できません。" | ||
| }, | ||
| "conflict": { | ||
| "title": "Skill Already Exists", | ||
| "description": "A skill named \"{name}\" already exists. Do you want to overwrite it?", | ||
| "overwrite": "Overwrite" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete translation: English strings in ja-JP locale file.
Lines 1044-1102 contain English strings instead of Japanese translations for the Skills feature UI. This will result in a mixed-language interface for Japanese users, significantly degrading the user experience.
Examples of untranslated strings:
- Line 1044: "Skills" → should be "スキル"
- Line 1045: "Manage and configure AI assistant skills" → needs Japanese translation
- Line 1047: "Open Folder" → "フォルダを開く"
- Line 1054: "Install Skill" → "スキルをインストール"
Note: Line 1096 contains a Japanese string ("スキル作成後、名称は変更できません。") which is inconsistent with the surrounding English content and suggests partial translation attempt.
Please complete the Japanese translations for all strings in this section to match the pattern used in the sync subsection (lines 1103-1173).
Based on learnings, all language files should have consistent translations added when new features are introduced.
| "skills": { | ||
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", | ||
| "nameHint": "스킬 생성 후 이름은 변경할 수 없습니다." | ||
| }, | ||
| "conflict": { | ||
| "title": "Skill Already Exists", | ||
| "description": "A skill named \"{name}\" already exists. Do you want to overwrite it?", | ||
| "overwrite": "Overwrite" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing Korean translations in the skills section.
The skills.title, skills.description, skills.install.*, skills.delete.*, skills.edit.*, and skills.conflict.* keys are in English, while this is the Korean (ko-KR) locale file. The sync subsection is properly translated to Korean, but the other subsections are not.
Based on the coding guidelines, all user-facing strings in locale files should be translated to the target language. Please translate these English strings to Korean for consistency with the rest of the file and the sync section.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/ko-KR/settings.json around lines 1043 - 1102,
Translate all English strings under the "skills" section to Korean: update keys
such as "skills.title", "skills.description", "skills.openFolder",
"skills.addSkill", "skills.empty", "skills.emptyHint", "skills.noResults",
"skills.search", "skills.count", and all nested objects "skills.install.*"
(title, description, tabFolder, tabZip, tabUrl, fromFolder, selectFolder,
folderHint, folderTip, zipHint, urlPlaceholder, urlHint, installButton,
installing, success, successMessage, failed, dragNotSupported),
"skills.delete.*" (title, description, success, successMessage, failed),
"skills.edit.*" (title, placeholder, readFailed, success, failed, name,
namePlaceholder, description, descriptionPlaceholder, allowedTools,
allowedToolsPlaceholder, allowedToolsHint, content, files, noFiles, nameHint),
and "skills.conflict.*" (title, description, overwrite) with proper Korean
translations consistent with the rest of the ko-KR file.
| "conflictDescription": "동일한 이름의 스킬 \\\"{name}\\\"이 이미 존재합니다.", | ||
| "deselectAll": "전체 선택 취소", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect quote escaping in conflictDescription.
The \\\" escaping produces a literal backslash before the quotes in the rendered string. Based on the pattern used elsewhere in this file (e.g., line 29), this should use \" instead.
Proposed fix
- "conflictDescription": "동일한 이름의 스킬 \\\"{name}\\\"이 이미 존재합니다.",
+ "conflictDescription": "동일한 이름의 스킬 \"{name}\"이 이미 존재합니다.",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "conflictDescription": "동일한 이름의 스킬 \\\"{name}\\\"이 이미 존재합니다.", | |
| "deselectAll": "전체 선택 취소", | |
| "conflictDescription": "동일한 이름의 스킬 \"{name}\"이 이미 존재합니다.", | |
| "deselectAll": "전체 선택 취소", |
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/ko-KR/settings.json around lines 1106 - 1107, The
string value for the key conflictDescription contains an incorrect escape
sequence (\\\") that renders a literal backslash; update the value for
conflictDescription to use the correct JSON escape for a double quote
(\"\")—i.e., replace the sequence \\\"{name}\\\" with \"{name}\"—so the rendered
text shows quotes around the name correctly.
| }, | ||
| "skills": { | ||
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", | ||
| "nameHint": "技能創建後名稱不可更改" | ||
| }, | ||
| "conflict": { | ||
| "title": "Skill Already Exists", | ||
| "description": "A skill named \"{name}\" already exists. Do you want to overwrite it?", | ||
| "overwrite": "Overwrite" | ||
| }, | ||
| "sync": { | ||
| "batchAction": "批量處理", | ||
| "conflict": "衝突", | ||
| "conflictDescription": "已存在同名技能「{name}」", | ||
| "deselectAll": "取消全選", | ||
| "export": "匯出到其他工具...", | ||
| "exportButton": "匯出", | ||
| "exportDescription": "將 Skills 匯出到其他 AI 助手工具", | ||
| "exportError": "匯出失敗", | ||
| "exportPartial": "部分匯出成功", | ||
| "exportPartialMessage": "成功 {success} 個,跳過 {skipped} 個,失敗 {failed} 個", | ||
| "exportStep1Title": "選擇要匯出的 Skills", | ||
| "exportStep2Title": "選擇目標工具", | ||
| "exportStep3Title": "確認匯出", | ||
| "exportSuccess": "匯出成功", | ||
| "exportSuccessMessage": "已成功導出 {count} 個 Skills", | ||
| "exportTitle": "匯出 Skills 到其他工具", | ||
| "exportWarnings": "匯出警告", | ||
| "exported": "已匯出", | ||
| "exporting": "正在匯出 ({current}/{total})...", | ||
| "failed": "失敗", | ||
| "failedItems": "失敗項目", | ||
| "import": "從其他工具匯入...", | ||
| "importButton": "導入", | ||
| "importDescription": "從 Claude Code、Cursor 等工具匯入 Skills", | ||
| "importError": "匯入失敗", | ||
| "importPartial": "部分導入成功", | ||
| "importPartialMessage": "成功 {success} 個,跳過 {skipped} 個,失敗 {failed} 個", | ||
| "importSuccess": "匯入成功", | ||
| "importSuccessMessage": "已成功導入 {count} 個 Skills", | ||
| "importTitle": "從其他工具匯入 Skills", | ||
| "imported": "已匯入", | ||
| "kiroAlways": "始終引入", | ||
| "kiroAlwaysDesc": "每次對話都自動包含", | ||
| "kiroConditional": "條件引入", | ||
| "kiroConditionalDesc": "根據檔案匹配模式自動引入", | ||
| "kiroFilePatterns": "檔案匹配模式", | ||
| "kiroFilePatternsHint": "逗號分隔的 glob 模式", | ||
| "kiroFilePatternsPlaceholder": "例如: **/*.ts, src/**/*.vue", | ||
| "kiroInclusion": "引入模式", | ||
| "kiroOnDemand": "按需引入", | ||
| "kiroOnDemandDesc": "僅在用戶明確請求時使用", | ||
| "kiroOptions": "Kiro 匯出選項", | ||
| "loadToolsError": "載入工具列表失敗", | ||
| "noConflicts": "無衝突", | ||
| "noToolsFound": "未檢測到已安裝的工具", | ||
| "notInstalled": "未安裝", | ||
| "overwrite": "覆蓋", | ||
| "overwriteAll": "全部覆蓋", | ||
| "previewError": "預覽失敗", | ||
| "previewing": "正在預覽...", | ||
| "readyToExport": "準備匯出 {count} 個 Skills", | ||
| "rename": "重新命名", | ||
| "renameAll": "全部重命名", | ||
| "resultPartial": "部分成功", | ||
| "resultSuccess": "操作成功", | ||
| "scanError": "掃描失敗", | ||
| "scanning": "正在掃描...", | ||
| "selectAll": "全選", | ||
| "selectedCount": "已選擇 {count} 個", | ||
| "skillCount": "已檢測到 {count} 個 Skills", | ||
| "skip": "跳過", | ||
| "skipAll": "全部跳過", | ||
| "skipped": "已跳過", | ||
| "step1Title": "選擇來源工具", | ||
| "step2Title": "選擇要匯入的 Skills", | ||
| "step3Title": "確認匯入", | ||
| "title": "同步", | ||
| "warnings": "轉換警告", | ||
| "importing": "正在匯入 ({current}/{total})..." | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete translations - most entries remain in English instead of Traditional Chinese (Hong Kong).
The zh-HK locale file should contain Traditional Chinese (Hong Kong) translations throughout, but the majority of the new skills section remains in English. For example:
- Line 1044:
"title": "Skills"should be"title": "技能"or similar - Line 1045:
"description": "Manage and configure AI assistant skills"needs Chinese translation - Lines 1046-1052, 1054-1095, 1098-1101, and many entries in the sync section are all in English
Only a few entries have been translated to Chinese (e.g., line 1096: "技能創建後名稱不可更改"), proving translation is both possible and expected.
This incomplete translation will result in zh-HK users seeing English UI text instead of Traditional Chinese, creating an inconsistent and poor user experience.
Based on coding guidelines, all language files must receive complete translations with consistent key names across locales.
Please translate all English entries to Traditional Chinese (Hong Kong). If you need assistance generating the translations, I can help provide suggested translations for the entire section.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/zh-HK/settings.json around lines 1042 - 1174, The
zh-HK locale's skills section contains many untranslated English strings (e.g.,
keys like skills.title, skills.description, skills.openFolder, skills.addSkill,
skills.install.*, skills.delete.*, skills.edit.*, skills.conflict.*, and
skills.sync.*); translate every English value into Traditional Chinese (Hong
Kong) while keeping the JSON keys unchanged, ensure placeholders like {count},
{name}, {current}/{total} remain intact, and maintain consistent terminology
(e.g., use "技能" for "Skill") across entries before committing the updated
settings.json.
| "skills": { | ||
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", | ||
| "nameHint": "技能創建後名稱不可更改" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: English strings in Traditional Chinese locale file.
Lines 1043-1096 contain English text instead of Traditional Chinese translations. This will display English UI to zh-TW users, breaking the localized experience.
For example:
- Line 1044:
"title": "Skills"should be"title": "技能" - Line 1045:
"description": "Manage and configure AI assistant skills"should be translated to Traditional Chinese - Line 1046:
"openFolder": "Open Folder"should be"openFolder": "開啟資料夾"
Notice that lines 1097-1173 (the sync subsection) are correctly translated to Traditional Chinese, demonstrating the expected pattern. The same translation quality should be applied to lines 1043-1096.
As per coding guidelines, all new translations must be added to ALL language files with properly localized content for each locale.
📝 Translation reference
Based on the correctly translated sync subsection and common zh-TW conventions, here are suggested translations for the untranslated keys:
- "title": "Skills",
- "description": "Manage and configure AI assistant skills",
- "openFolder": "Open Folder",
- "addSkill": "Add Skill",
- "empty": "No skills yet",
- "emptyHint": "Click \"Add Skill\" to install a new skill",
- "noResults": "No matching skills found",
- "search": "Search skills...",
- "count": "{count} skills",
+ "title": "技能",
+ "description": "管理和配置 AI 助手技能",
+ "openFolder": "開啟資料夾",
+ "addSkill": "新增技能",
+ "empty": "尚無技能",
+ "emptyHint": "點擊「新增技能」以安裝新技能",
+ "noResults": "未找到符合的技能",
+ "search": "搜尋技能...",
+ "count": "{count} 個技能",Please translate all remaining English strings in the install, delete, edit, and conflict subsections as well.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/zh-TW/settings.json around lines 1043 - 1096, Several
keys under the "skills" section (e.g., "title", "description", "openFolder",
"addSkill", "empty", "emptyHint", "noResults", "search", "count", and all nested
keys under "install", "delete", "edit", and "conflict") are still in English;
replace each English string with proper Traditional Chinese translations
(consistent with existing zh-TW style such as using "技能" for "Skills", "開啟資料夾"
for "Open Folder", etc.), ensuring keys like
install.title/install.description/install.tabFolder/install.tabZip/install.tabUrl/install.fromFolder/install.selectFolder/install.folderHint/install.folderTip/install.zipHint/install.urlPlaceholder/install.urlHint/install.installButton/install.installing/install.success/install.successMessage/install.failed/install.dragNotSupported,
delete.title/delete.description/delete.success/delete.successMessage/delete.failed,
edit.title/edit.placeholder/edit.readFailed/edit.success/edit.failed/edit.name/edit.namePlaceholder/edit.description/edit.descriptionPlaceholder/edit.allowedTools/edit.allowedToolsPlaceholder/edit.allowedToolsHint/edit.content/edit.files/edit.noFiles/edit.nameHint,
and the conflict subsection are fully localized; after updating zh-TW, mirror
the same localized content pattern across all other locale files as required by
the coding guidelines.
…o Code, and OpenCode; update Cursor adapter to use SKILL.md format
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.ts (1)
18-26: Consider consolidating duplicate parse logic across adapters.This parse method implementation is duplicated identically across all five new adapters (GooseAdapter, CopilotUserAdapter, OpenCodeAdapter, KiloCodeAdapter, CodexAdapter). Each adapter only differs in the
idandnameproperties.Consider refactoring to reduce duplication:
- Option 1: Add a protected method in
ClaudeCodeAdapterthat accepts an adapter ID and sets the source, then call it from each child adapter.- Option 2: Override parse in the base class to automatically set source based on
this.idif all these adapters follow the same pattern.- Option 3: Extract the hard-coded
'yaml-frontmatter-markdown'to a constant in the base class.♻️ Example refactor (Option 1)
In
ClaudeCodeAdapter:protected setSourceMetadata(skill: CanonicalSkill, context: ParseContext): void { skill.source = { tool: this.id, originalPath: context.filePath, originalFormat: 'yaml-frontmatter-markdown' } }Then in each adapter:
parse(content: string, context: ParseContext): CanonicalSkill { const skill = super.parse(content, context) - skill.source = { - tool: this.id, - originalPath: context.filePath, - originalFormat: 'yaml-frontmatter-markdown' - } + this.setSourceMetadata(skill, context) return skill }src/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts (1)
14-26: Avoid mutatingsuper.parse()result; return a new object instead.This keeps adapters side-effect-free and reduces surprises if the base implementation later reuses/caches objects.
Proposed diff
export class CursorAdapter extends ClaudeCodeAdapter { readonly id = 'cursor' readonly name = 'Cursor' parse(content: string, context: ParseContext): CanonicalSkill { const skill = super.parse(content, context) - skill.source = { - tool: this.id, - originalPath: context.filePath, - originalFormat: 'yaml-frontmatter-markdown' - } - return skill + return { + ...skill, + source: { + ...skill.source, + tool: this.id, + originalPath: context.filePath, + originalFormat: 'yaml-frontmatter-markdown' + } + } } }src/main/presenter/skillSyncPresenter/toolScanner.ts (1)
376-390: Consider parallel scanning for improved performance.The sequential
awaitin the loop works correctly but may be slow when scanning many tools. Parallel scanning could improve responsiveness.♻️ Optional parallel implementation
async scanExternalTools(projectRoot?: string): Promise<ScanResult[]> { - const results: ScanResult[] = [] - - for (const tool of this.toolRegistry.values()) { - // Skip project-level tools if no project root provided - if (tool.isProjectLevel && !projectRoot) { - continue - } - - const result = await this.scanTool(tool.id, projectRoot) - results.push(result) - } - - return results + const tools = Array.from(this.toolRegistry.values()).filter( + (tool) => !tool.isProjectLevel || projectRoot + ) + + return Promise.all(tools.map((tool) => this.scanTool(tool.id, projectRoot))) }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
src/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/codexAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/cursorAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/gooseAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/index.tssrc/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.tssrc/main/presenter/skillSyncPresenter/toolScanner.tstest/main/presenter/skillSyncPresenter/adapters/cursorAdapter.test.tstest/main/presenter/skillSyncPresenter/adapters/index.test.tstest/main/presenter/skillSyncPresenter/index.test.tstest/main/presenter/skillSyncPresenter/toolScanner.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use English for logs and comments in TypeScript/JavaScript code
Files:
src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.tssrc/main/presenter/skillSyncPresenter/toolScanner.tssrc/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/codexAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/index.tssrc/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript with strict type checking enabled
Use OxLint for linting JavaScript and TypeScript files; ensure lint-staged hooks and typecheck pass before commits
Files:
src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.tssrc/main/presenter/skillSyncPresenter/toolScanner.tssrc/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/codexAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/index.tssrc/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
src/main/presenter/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/presenter/**/*.ts: Use EventBus to broadcast events from main to renderer viamainWindow.webContents.send()
Implement one presenter per functional domain in the main process
Files:
src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.tssrc/main/presenter/skillSyncPresenter/toolScanner.tssrc/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/codexAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/index.tssrc/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
src/main/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/main/**/*.ts: Use EventBus fromsrc/main/eventbus.tsfor decoupled inter-process communication
Context isolation must be enabled with preload scripts for secure IPC communicationElectron main process code should reside in
src/main/, with presenters organized inpresenter/subdirectory (Window, Tab, Thread, Mcp, Config, LLMProvider), and app events managed viaeventbus.ts
Files:
src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.tssrc/main/presenter/skillSyncPresenter/toolScanner.tssrc/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/codexAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/index.tssrc/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
**/*.{js,ts,tsx,jsx,vue,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
All logs and comments must be in English
Files:
src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.tssrc/main/presenter/skillSyncPresenter/toolScanner.tssrc/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/codexAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/index.tssrc/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
**/*.{js,ts,tsx,jsx,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use OxLint as the linter
Files:
src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.tssrc/main/presenter/skillSyncPresenter/toolScanner.tssrc/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/codexAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/index.tssrc/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
**/*.{js,ts,tsx,jsx,vue,json,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use Prettier as the code formatter
Files:
src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.tssrc/main/presenter/skillSyncPresenter/toolScanner.tssrc/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/codexAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/index.tssrc/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,vue}: Use camelCase for variable and function names; use PascalCase for types and classes; use SCREAMING_SNAKE_CASE for constants
Configure Prettier with single quotes, no semicolons, and line width of 100 characters. Runpnpm run formatafter completing features
Files:
src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.tssrc/main/presenter/skillSyncPresenter/toolScanner.tssrc/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/codexAdapter.tssrc/main/presenter/skillSyncPresenter/adapters/index.tssrc/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
🧠 Learnings (2)
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/main/presenter/mcpPresenter/inMemoryServers/**/*.ts : MCP tool implementations must be registered in `mcpPresenter/index.ts` after implementation
Applied to files:
src/main/presenter/skillSyncPresenter/toolScanner.tssrc/main/presenter/skillSyncPresenter/adapters/index.ts
📚 Learning: 2025-06-21T15:48:29.950Z
Learnt from: neoragex2002
Repo: ThinkInAIXYZ/deepchat PR: 550
File: src/main/presenter/mcpPresenter/inMemoryServers/meetingServer.ts:250-252
Timestamp: 2025-06-21T15:48:29.950Z
Learning: In the meeting server implementation (src/main/presenter/mcpPresenter/inMemoryServers/meetingServer.ts), when multiple tabs have the same title, the user prefers to let the code silently select the first match without adding warnings or additional ambiguity handling.
Applied to files:
src/main/presenter/skillSyncPresenter/adapters/index.ts
🧬 Code graph analysis (6)
src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.ts (1)
src/shared/types/skillSync.ts (2)
ParseContext(270-277)CanonicalSkill(52-80)
src/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.ts (1)
src/shared/types/skillSync.ts (2)
ParseContext(270-277)CanonicalSkill(52-80)
src/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.ts (2)
src/main/presenter/skillSyncPresenter/adapters/index.ts (2)
OpenCodeAdapter(93-93)ClaudeCodeAdapter(86-86)src/shared/types/skillSync.ts (2)
ParseContext(270-277)CanonicalSkill(52-80)
src/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.ts (1)
src/shared/types/skillSync.ts (2)
ParseContext(270-277)CanonicalSkill(52-80)
src/main/presenter/skillSyncPresenter/adapters/codexAdapter.ts (1)
src/shared/types/skillSync.ts (2)
ParseContext(270-277)CanonicalSkill(52-80)
src/main/presenter/skillSyncPresenter/adapters/index.ts (3)
src/shared/types/skillSync.ts (1)
IFormatAdapter(282-307)src/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.ts (1)
ClaudeCodeAdapter(24-252)src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.ts (1)
GooseAdapter(14-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-check (x64)
🔇 Additional comments (16)
src/main/presenter/skillSyncPresenter/adapters/codexAdapter.ts (1)
1-43: Well-documented adapter implementation.The comprehensive file header (lines 1-12) and method-level JSDoc comments (lines 28-31, 35) make this adapter more maintainable than the other format adapters in this batch. The documentation clearly explains the format characteristics and the relationship to ClaudeCodeAdapter.
src/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts (1)
1-6: Adapter wiring is straightforward and consistent with other tool adapters.Also applies to: 8-17
src/main/presenter/skillSyncPresenter/adapters/index.ts (1)
49-64: Public API + re-exports look clean and easy to consume.Also applies to: 85-97
src/main/presenter/skillSyncPresenter/toolScanner.ts (13)
1-19: LGTM!The imports are well-organized and appropriate for the module's functionality. Good use of
fs.promisesfor async file operations throughout the class.
21-51: LGTM!The capability presets are well-structured and provide good reusability for the tool configurations below.
56-201: LGTM!The tool registry is comprehensive and well-structured. The distinction between user-level (
~/) and project-level (relative paths withisProjectLevel: true) tools is clear and consistently applied.
225-242: LGTM!The path safety validation is well-implemented with a defense-in-depth approach: symlink-aware resolution for existing paths, and string-based normalization for non-existent paths. The trailing separator handling (line 238-240) correctly prevents prefix-matching attacks.
266-288: LGTM!The class constructor efficiently populates the registry from the static array, and the getter methods are straightforward.
293-306: LGTM!The availability check properly handles the case where the tool doesn't exist or the directory isn't accessible, returning
falsegracefully.
311-371: LGTM!The
scanToolmethod has comprehensive error handling with clear distinction between:
- Unknown tools (error message)
- Missing project root for project-level tools (error message)
- Path exists but isn't a directory (error message)
- Directory doesn't exist (no error, just unavailable)
395-415: LGTM!The method appropriately dispatches to subdirectory or file scanning based on the pattern structure. Error handling is resilient, logging issues without failing the entire scan.
420-465: LGTM!The subdirectory scanning is well-implemented with proper security validations for both filename safety and path traversal prevention before constructing paths.
470-510: LGTM!The file scanning method mirrors the subdirectory scanning approach with consistent security validations. Pattern matching and extraction work correctly.
515-555: LGTM!The skill info extraction is well-structured with proper size validation before reading. The name and path determination based on pattern type is correct.
573-601: LGTM!The description extraction handles both YAML frontmatter and first-paragraph fallback gracefully. The 200-character truncation with ellipsis is a sensible choice for preview text.
627-630: LGTM!The singleton export pattern is appropriate for this scanner utility, while still allowing direct class instantiation for testing purposes.
| function registerBuiltinAdapters(): void { | ||
| const builtinAdapters: IFormatAdapter[] = [ | ||
| new ClaudeCodeAdapter(), | ||
| new CodexAdapter(), | ||
| new CursorAdapter(), | ||
| new WindsurfAdapter(), | ||
| new CopilotAdapter(), | ||
| new KiroAdapter(), | ||
| new AntigravityAdapter(), | ||
| new OpenCodeAdapter(), | ||
| new GooseAdapter(), | ||
| new KiloCodeAdapter(), | ||
| new CopilotUserAdapter() | ||
| ] | ||
|
|
||
| for (const adapter of builtinAdapters) { | ||
| adapters.set(adapter.id, adapter) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Define duplicate-id + detect precedence semantics (and implement them).
Right now duplicates overwrite silently, and overriding an existing id won’t move it later in detection order (Map insertion order stays the same).
Proposed diff (guard built-ins, make registerAdapter update detection order)
function registerBuiltinAdapters(): void {
const builtinAdapters: IFormatAdapter[] = [
new ClaudeCodeAdapter(),
new CodexAdapter(),
new CursorAdapter(),
new WindsurfAdapter(),
new CopilotAdapter(),
new KiroAdapter(),
new AntigravityAdapter(),
new OpenCodeAdapter(),
new GooseAdapter(),
new KiloCodeAdapter(),
new CopilotUserAdapter()
]
for (const adapter of builtinAdapters) {
+ if (adapters.has(adapter.id)) {
+ throw new Error(`Duplicate built-in format adapter id: ${adapter.id}`)
+ }
adapters.set(adapter.id, adapter)
}
}
export function registerAdapter(adapter: IFormatAdapter): void {
- adapters.set(adapter.id, adapter)
+ // Ensure latest registration wins for detectAdapter() ordering.
+ if (adapters.has(adapter.id)) adapters.delete(adapter.id)
+ adapters.set(adapter.id, adapter)
}Also applies to: 69-71, 76-83
…tences", "כישורים", "技能", "Habilidades", "Навыки" with "Skills" in various languages for consistency across the application.
- Introduced SkillsPanel.vue to display and manage user skills. - Implemented useSkillsData composable for managing skills state and interactions. - Added useSlashMentionData composable to handle slash mention functionality for skills, prompts, and tools. - Updated MentionList.vue to support new categorization and display logic for skills and prompts. - Created SlashMention extension for TipTap editor to handle slash-triggered mentions. - Enhanced slashSuggestion.ts to manage slash mention data and filtering. - Updated i18n files for English and Chinese to include new skills-related translations. - Added new events for skill activation and deactivation. - Removed unused watchers and code related to tools and prompts from useMentionData.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/renderer/src/components/editor/mention/MentionList.vue (1)
163-176: Unguarded JSON.parse may throw on malformed content.
JSON.parse(selectedPrompt.value.content)can throw ifcontentis malformed. Sincecontentis user or external data, consider wrapping in try-catch.🛡️ Suggested defensive handling
const handlePromptParams = (values: Record<string, string>) => { if (selectedPrompt.value) { - const promptContent = JSON.parse(selectedPrompt.value.content) + let promptContent + try { + promptContent = JSON.parse(selectedPrompt.value.content) + } catch (e) { + console.error('Failed to parse prompt content:', e) + showParamsDialog.value = false + selectedPrompt.value = null + return + } if (!promptContent.arguments) { promptContent.arguments = {} }
🤖 Fix all issues with AI agents
In @package.json:
- Line 76: The dependency update to "chokidar": "^5.0.0" may be incompatible due
to chokidar v5 being ESM-only and requiring Node >=20; verify and either (A)
confirm the project runs on Node 20+ and is ESM-compatible (convert require()
usages of chokidar to import, ensure "type":"module" or .mjs modules, and update
CI/workstation Node versions), or (B) pin to a CommonJS-compatible older version
(e.g., "^4.4.0") in package.json and keep require() calls; also update
package.json "engines" to reflect the minimum Node version if you adopt v5.0.0
so tooling/CI enforce it.
In @src/renderer/settings/components/skills/SyncStatusSection.vue:
- Line 88: The syncingTools Set is never updated so SyncStatusCard never shows
activity; update the handleSync logic to add the toolId to syncingTools when a
sync starts and remove it when the sync completes or fails (ensure removals
happen in both success and catch/finally paths), or if parent should own state,
remove syncingTools and forward a prop instead; target the syncingTools ref and
the handleSync handler (and any emit calls that indicate start/finish) to
implement adding/removing toolId so the SyncStatusCard receives the correct
syncing state.
In @src/renderer/src/components/chat-input/composables/useSlashMentionData.ts:
- Around line 58-67: The label construction in the items.push loop can include
"undefined" because tool.server.icons may be undefined; update the label
expression inside the for (const tool of tools.value) loop (the items.push call)
to coalesce tool.server.icons to an empty string (e.g., use tool.server.icons ??
'') and ensure the separating space is only added when an icon exists so you
don't render a leading/extra space before tool.function.name.
In @src/renderer/src/components/chat-input/SkillsPanel.vue:
- Around line 30-31: Replace the hardcoded "Tools: " prefix in SkillsPanel.vue
with the i18n translation key (use the existing i18n helper, e.g., $t or t
depending on project conventions) when rendering the allowedTools list for a
skill (the <p v-if="skill.allowedTools?.length"> block referencing
skill.allowedTools.join). Also add the "panel.tools": "Tools" entry to the
chat.json locale files (under the existing "panel" object) so the UI uses the
translated string.
In @src/renderer/src/components/editor/mention/MentionList.vue:
- Line 44: Replace the hardcoded "No result" text in the MentionList.vue
template with an i18n key by calling the translator (e.g.,
t('mention.noResult')), and ensure you import useI18n in the <script setup> and
destructure t from it (const { t } = useI18n()). Update the template's fallback
div to use the t(...) call and add the new i18n key "mention.noResult" to your
locale files; keep the key name consistent with other mention-related keys used
in this component.
In @src/renderer/src/components/NewThread.vue:
- Around line 564-573: The catch block for applying pending skills (around
threadId, pendingSkills, and skillPresenter.setActiveSkills) currently only logs
to console; update it to present a user-facing error notification so users know
skill application failed—invoke the app's existing notification/toast mechanism
(for example this.$toast.error, a global notify utility, or emit a UI error
event) with a clear message like "Failed to apply skills to thread" and include
brief error details, then still log the error and ensure
chatInput?.consumePendingSkills?() is not called on failure.
In @src/renderer/src/events.ts:
- Line 165: The comment "Skills 相关事件" is in Chinese and must be replaced with an
English comment; update that comment in src/renderer/src/events.ts to
"Skills-related events" (or "Events related to skills") so all comments in the
renderer TypeScript files are in English.
In @src/renderer/src/i18n/en-US/settings.json:
- Around line 1043-1194: The en-US file adds two new top-level objects
syncStatus and syncPrompt that are missing from 10 locale files; update each
incomplete locale (da-DK, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU,
zh-HK, zh-TW) by adding the same keys syncStatus and syncPrompt with the
corresponding subkeys (title, description,
scanning/noToolsFound/skillCount/notInstalled/notAvailable/noSkills/import/syncing/showMore/showLess
for syncStatus; title, description, dontShowAgain/skip/importSelected for
syncPrompt) using either existing translations if available or copying the
en-US/zh-CN strings as placeholders so all locale files share the identical key
structure.
In @src/renderer/src/i18n/fr-FR/settings.json:
- Around line 1043-1102: The French i18n block "skills" currently contains
English text; translate every string under the "skills" key (including nested
keys "install", "delete", "edit", "conflict" and their fields such as
"description", "openFolder", "addSkill", "empty", "noResults", "search",
"count", "install.title", "install.description", "install.tabFolder",
"install.tabZip", "install.tabUrl", "install.fromFolder",
"install.selectFolder", "install.folderHint", "install.folderTip",
"install.zipHint", "install.urlPlaceholder", "install.urlHint",
"install.installButton", "install.installing", "install.success",
"install.successMessage", "install.failed", "install.dragNotSupported",
"delete.title", "delete.description", "delete.success", "delete.successMessage",
"delete.failed", "edit.title", "edit.placeholder", "edit.readFailed",
"edit.success", "edit.failed", "edit.name", "edit.namePlaceholder",
"edit.description", "edit.descriptionPlaceholder", "edit.allowedTools",
"edit.allowedToolsPlaceholder", "edit.allowedToolsHint", "edit.content",
"edit.files", "edit.noFiles", and "conflict.title/description/overwrite") into
proper French, preserving the exact JSON keys and placeholders (e.g., {count},
{name}) and matching phrasing used in other locale files for consistency; update
the fr-FR/settings.json "skills" block with those French strings so all locales
remain consistent.
In @src/renderer/src/i18n/ja-JP/routes.json:
- Line 17: Replace the English value for the "settings-skills" key in
routes.json with the Japanese transliteration; update "settings-skills":
"Skills" to use the katakana form (e.g., "スキル") or, to match other settings keys
that include "設定", use "スキル設定" so the Japanese locale is consistent.
In @src/renderer/src/i18n/ja-JP/settings.json:
- Around line 1043-1096: The skills i18n block contains English strings instead
of Japanese; update every key under the "skills" object (including nested
objects "install", "delete", "edit" and keys like "title", "description",
"openFolder", "addSkill", "emptyHint", "search", "count", "install.title",
"install.description", "install.tabFolder", "install.tabZip", "install.tabUrl",
"install.fromFolder", "install.selectFolder", "install.folderHint",
"install.folderTip", "install.zipHint", "install.urlPlaceholder",
"install.urlHint", "install.installButton", "install.installing",
"install.success", "install.successMessage", "install.failed",
"install.dragNotSupported", "delete.title", "delete.description",
"delete.success", "delete.successMessage", "delete.failed", "edit.title",
"edit.placeholder", "edit.readFailed", "edit.success", "edit.failed",
"edit.name", "edit.namePlaceholder", "edit.description",
"edit.descriptionPlaceholder", "edit.allowedTools",
"edit.allowedToolsPlaceholder", "edit.allowedToolsHint", "edit.content",
"edit.files", "edit.noFiles", "nameHint") with correct Japanese translations (or
pull the corresponding strings from en-US as source and translate them),
ensuring plural/placeholders like "{count}" and "{name}" are preserved and
existing Japanese text (e.g., "nameHint") is kept consistent.
In @src/renderer/src/i18n/ko-KR/routes.json:
- Line 17: The "settings-skills" localization entry currently uses the English
string "Skills"; update its value to a Korean translation to match the ko-KR
locale (change the value for the "settings-skills" key to a Korean term such as
"스킬" or "기술"). Ensure you only replace the right-hand string value for the
"settings-skills" key in the routes.json so formatting and key names remain
unchanged.
In @src/renderer/src/i18n/ko-KR/settings.json:
- Around line 1043-1095: The skills localization block ("skills" object and its
nested keys like "install", "delete", "edit") contains English strings (e.g.,
"title", "description", "openFolder", "addSkill", "name", "content", etc.);
replace those English values with Korean translations (or copy corresponding
values from en-US into ko-KR and then translate) so every user-facing string in
the "skills" section is translated; ensure you update all nested keys shown
(install.tabFolder, install.tabZip, install.tabUrl, install.fromFolder,
install.selectFolder, install.folderHint, install.folderTip, install.zipHint,
install.urlPlaceholder, install.urlHint,
install.installButton/installing/success/successMessage/failed/dragNotSupported,
delete.title/description/success/successMessage/failed,
edit.title/placeholder/readFailed/success/failed/name/namePlaceholder/description/descriptionPlaceholder/allowedTools/allowedToolsPlaceholder/allowedToolsHint/content/files/noFiles)
to Korean to satisfy i18n completeness.
🧹 Nitpick comments (7)
docs/specs/skills-ux-redesign/analysis.md (1)
47-53: Consider adding language specifiers to code blocks for better rendering.The markdown code blocks (ASCII diagrams and example output) would benefit from explicit language identifiers for proper syntax highlighting and rendering. For ASCII art diagrams, use ```text, and for example outputs, use appropriate identifiers like ```plaintext or ```console.
📝 Suggested improvements
Example for lines 47-53:
-``` +```text ┌──────────────────────────────────────────────────────────────────┐ │ [Mode ▾] [📁] [📎] [🌐] [MCP ▾] [✨ 2] [Model ▾] [⚙️] [↑]│Apply similar changes to code blocks at lines 97, 114, and 129.
Based on static analysis hints.
Also applies to: 97-110, 114-125, 129-137
src/renderer/src/components/chat-input/composables/useSlashMentionData.ts (2)
82-106: Redundant initial updates from multiple watchers.All three watchers have
immediate: true, causingupdateSlashMentionData()to be called three times during initialization. Consider consolidating the watchers or removingimmediate: truefrom two of them, keeping it only on the first one.♻️ Suggested refactor using a single consolidated watcher
- // Watch skills changes - watch( - () => skillsStore.skills, - () => { - updateSlashMentionData() - }, - { immediate: true, deep: true } - ) - - // Watch prompts changes - watch( - () => prompts.value, - () => { - updateSlashMentionData() - }, - { immediate: true } - ) - - // Watch tools changes - watch( - () => tools.value, - () => { - updateSlashMentionData() - }, - { immediate: true } - ) + // Watch all data sources and update slash mention data + watch( + [() => skillsStore.skills, prompts, tools], + () => { + updateSlashMentionData() + }, + { immediate: true, deep: true } + )
109-118: Consider checking loading state before triggering loadSkills.The condition only checks if skills array is empty, but doesn't account for whether skills are already being loaded. This could trigger duplicate load calls.
♻️ Suggested improvement
watch( () => conversationId.value, () => { // Ensure skills are loaded - if (skillsStore.skills.length === 0) { + if (skillsStore.skills.length === 0 && !skillsStore.loading) { skillsStore.loadSkills() } }, { immediate: true } )src/renderer/settings/components/skills/SyncStatusCard.vue (1)
98-132: Consider extracting tool icon/background mappings to a shared utility.These icon and background mappings may be useful in other components that display tool information. If they're used elsewhere in the codebase, consider extracting them to a shared utility file.
src/renderer/settings/components/skills/SyncPromptDialog.vue (1)
150-189: Consider extracting shared scanning logic.The scanning logic in
onMounted(lines 158-170) andcheckAndShow(lines 179-187) is nearly identical. Consider extracting to a shared helper function to reduce duplication.♻️ Suggested refactor
+const scanAndShowDialog = async () => { + const results = await skillSyncPresenter.scanExternalTools() + const availableTools = results.filter( + (tool) => tool.available && tool.skills.length > 0 && !tool.toolId.includes('project') + ) + if (availableTools.length > 0) { + detectedTools.value = availableTools + selectedTools.value = new Set(availableTools.map((t) => t.toolId)) + isOpen.value = true + } + return availableTools.length > 0 +} + onMounted(async () => { try { const shown = await configPresenter.getSetting('skills.syncPromptShown') if (shown) return - - const results = await skillSyncPresenter.scanExternalTools() - const availableTools = results.filter( - (tool) => tool.available && tool.skills.length > 0 && !tool.toolId.includes('project') - ) - - if (availableTools.length > 0) { - detectedTools.value = availableTools - selectedTools.value = new Set(availableTools.map((t) => t.toolId)) - isOpen.value = true - } + await scanAndShowDialog() } catch (error) { console.error('Failed to check for external tools:', error) } }) defineExpose({ - checkAndShow: async () => { - const results = await skillSyncPresenter.scanExternalTools() - const availableTools = results.filter( - (tool) => tool.available && tool.skills.length > 0 && !tool.toolId.includes('project') - ) - if (availableTools.length > 0) { - detectedTools.value = availableTools - selectedTools.value = new Set(availableTools.map((t) => t.toolId)) - isOpen.value = true - } - } + checkAndShow: scanAndShowDialog })src/renderer/src/components/editor/mention/MentionList.vue (1)
196-200: Redundant JSON.parse handling pattern.The mcpEntry parsing pattern (check if string, then parse) is repeated in both the params dialog path (lines 196-200) and the content assignment (lines 207-210, 219-224). Consider extracting this to a helper function to reduce duplication.
♻️ Optional helper extraction
// Add helper before selectItem const parseMcpEntry = (entry: unknown) => { if (!entry) return null if (typeof entry === 'string') { try { return JSON.parse(entry) } catch { return null } } return entry }src/renderer/src/components/editor/mention/slashSuggestion.ts (1)
67-69: Consider adding explicit type annotations.The
componentandpopupvariables lack type annotations. While TypeScript can infer types in some cases, explicit types improve code readability and catch potential issues early. As per coding guidelines, TypeScript files should use explicit type annotations.📝 Suggested typing
render: () => { - let component - let popup + let component: VueRenderer | null = null + let popup: ReturnType<typeof tippy> | null = null
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (46)
.gitignoredocs/specs/skills-ux-redesign/analysis.mddocs/specs/skills-ux-redesign/spec.mddocs/specs/skills-ux-redesign/tasks.mdpackage.jsonsrc/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/settings/components/skills/SyncPromptDialog.vuesrc/renderer/settings/components/skills/SyncStatusCard.vuesrc/renderer/settings/components/skills/SyncStatusSection.vuesrc/renderer/src/components/NewThread.vuesrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/src/components/chat-input/SkillsPanel.vuesrc/renderer/src/components/chat-input/composables/useMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/editor/mention/MentionList.vuesrc/renderer/src/components/editor/mention/slashMention.tssrc/renderer/src/components/editor/mention/slashSuggestion.tssrc/renderer/src/components/editor/mention/suggestion.tssrc/renderer/src/events.tssrc/renderer/src/i18n/da-DK/routes.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/en-US/chat.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/fa-IR/routes.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/fr-FR/routes.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/routes.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/pt-BR/routes.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/ru-RU/routes.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/zh-CN/chat.jsonsrc/renderer/src/i18n/zh-CN/routes.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/zh-HK/routes.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/zh-TW/routes.jsonsrc/renderer/src/i18n/zh-TW/settings.json
✅ Files skipped from review due to trivial changes (1)
- .gitignore
🚧 Files skipped from review as they are similar to previous changes (5)
- src/renderer/src/i18n/pt-BR/routes.json
- src/renderer/src/i18n/da-DK/routes.json
- src/renderer/src/i18n/fa-IR/routes.json
- src/renderer/src/i18n/fa-IR/settings.json
- src/renderer/src/i18n/fr-FR/routes.json
🧰 Additional context used
📓 Path-based instructions (16)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use English for logs and comments in TypeScript/JavaScript code
Files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/editor/mention/slashMention.tssrc/renderer/src/components/chat-input/composables/useMentionData.tssrc/renderer/src/events.tssrc/renderer/src/components/editor/mention/slashSuggestion.tssrc/renderer/src/components/editor/mention/suggestion.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript with strict type checking enabled
Use OxLint for linting JavaScript and TypeScript files; ensure lint-staged hooks and typecheck pass before commits
Files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/editor/mention/slashMention.tssrc/renderer/src/components/chat-input/composables/useMentionData.tssrc/renderer/src/events.tssrc/renderer/src/components/editor/mention/slashSuggestion.tssrc/renderer/src/components/editor/mention/suggestion.ts
src/renderer/src/**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
usePresenter.tscomposable for renderer-to-main IPC communication via direct presenter method callsEnsure all code comments are in English and all log messages are in English, with no non-English text in code comments or console statements
Use VueUse composables for common utilities like
useLocalStorage,useClipboard,useDebounceFnVue 3 renderer app code should be organized in
src/renderer/srcwith subdirectories forcomponents/,stores/,views/,i18n/, andlib/
Files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/src/components/NewThread.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/src/components/chat-input/SkillsPanel.vuesrc/renderer/src/components/editor/mention/slashMention.tssrc/renderer/src/components/chat-input/composables/useMentionData.tssrc/renderer/src/events.tssrc/renderer/src/components/editor/mention/slashSuggestion.tssrc/renderer/src/components/editor/mention/MentionList.vuesrc/renderer/src/components/editor/mention/suggestion.ts
**/*.{js,ts,tsx,jsx,vue,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
All logs and comments must be in English
Files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/settings/components/skills/SyncStatusCard.vuesrc/renderer/src/components/NewThread.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/settings/components/skills/SyncPromptDialog.vuesrc/renderer/src/components/chat-input/SkillsPanel.vuesrc/renderer/src/components/editor/mention/slashMention.tssrc/renderer/src/components/chat-input/composables/useMentionData.tssrc/renderer/src/events.tssrc/renderer/settings/components/skills/SyncStatusSection.vuesrc/renderer/src/components/editor/mention/slashSuggestion.tssrc/renderer/src/components/editor/mention/MentionList.vuesrc/renderer/src/components/editor/mention/suggestion.ts
**/*.{js,ts,tsx,jsx,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use OxLint as the linter
Files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/editor/mention/slashMention.tssrc/renderer/src/components/chat-input/composables/useMentionData.tssrc/renderer/src/events.tssrc/renderer/src/components/editor/mention/slashSuggestion.tssrc/renderer/src/components/editor/mention/suggestion.ts
**/*.{js,ts,tsx,jsx,vue,json,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use Prettier as the code formatter
Files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/settings/components/skills/SyncStatusCard.vuesrc/renderer/src/components/NewThread.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/settings/components/skills/SyncPromptDialog.vuesrc/renderer/src/components/chat-input/SkillsPanel.vuesrc/renderer/src/components/editor/mention/slashMention.tssrc/renderer/src/i18n/en-US/chat.jsonsrc/renderer/src/components/chat-input/composables/useMentionData.tssrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/events.tssrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/settings/components/skills/SyncStatusSection.vuesrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/components/editor/mention/slashSuggestion.tssrc/renderer/src/i18n/ko-KR/routes.jsonsrc/renderer/src/components/editor/mention/MentionList.vuesrc/renderer/src/i18n/ja-JP/settings.jsonpackage.jsonsrc/renderer/src/components/editor/mention/suggestion.ts
src/renderer/src/**/*.{vue,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
src/renderer/src/**/*.{vue,ts,tsx}: Use vue-i18n framework for internationalization located at src/renderer/src/i18n/
All user-facing strings must use i18n keys, not hardcoded text
src/renderer/src/**/*.{vue,ts,tsx}: Usereffor primitives and references,reactivefor objects in Vue 3 Composition API
Prefercomputedproperties over methods for derived state in Vue components
Import Shadcn Vue components from@/shadcn/components/ui/path alias
Use thecn()utility function combining clsx and tailwind-merge for dynamic Tailwind classes
UsedefineAsyncComponent()for lazy loading heavy Vue components
Use TypeScript for all Vue components and composables with explicit type annotations
Define TypeScript interfaces for Vue component props and data structures
UseusePresentercomposable for main process communication instead of direct IPC calls
Files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/src/components/NewThread.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/src/components/chat-input/SkillsPanel.vuesrc/renderer/src/components/editor/mention/slashMention.tssrc/renderer/src/components/chat-input/composables/useMentionData.tssrc/renderer/src/events.tssrc/renderer/src/components/editor/mention/slashSuggestion.tssrc/renderer/src/components/editor/mention/MentionList.vuesrc/renderer/src/components/editor/mention/suggestion.ts
src/renderer/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
Use class-variance-authority (CVA) for defining component variants with Tailwind classes
Files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/editor/mention/slashMention.tssrc/renderer/src/components/chat-input/composables/useMentionData.tssrc/renderer/src/events.tssrc/renderer/src/components/editor/mention/slashSuggestion.tssrc/renderer/src/components/editor/mention/suggestion.ts
src/renderer/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
src/renderer/src/**/*.{ts,tsx}: UseshallowRefandshallowReactivefor optimizing reactivity with large objects
Prefertypeoverinterfacein TypeScript unless using inheritance withextends
Files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/editor/mention/slashMention.tssrc/renderer/src/components/chat-input/composables/useMentionData.tssrc/renderer/src/events.tssrc/renderer/src/components/editor/mention/slashSuggestion.tssrc/renderer/src/components/editor/mention/suggestion.ts
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,vue}: Use camelCase for variable and function names; use PascalCase for types and classes; use SCREAMING_SNAKE_CASE for constants
Configure Prettier with single quotes, no semicolons, and line width of 100 characters. Runpnpm run formatafter completing features
Files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/settings/components/skills/SyncStatusCard.vuesrc/renderer/src/components/NewThread.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/settings/components/skills/SyncPromptDialog.vuesrc/renderer/src/components/chat-input/SkillsPanel.vuesrc/renderer/src/components/editor/mention/slashMention.tssrc/renderer/src/components/chat-input/composables/useMentionData.tssrc/renderer/src/events.tssrc/renderer/settings/components/skills/SyncStatusSection.vuesrc/renderer/src/components/editor/mention/slashSuggestion.tssrc/renderer/src/components/editor/mention/MentionList.vuesrc/renderer/src/components/editor/mention/suggestion.ts
src/renderer/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
src/renderer/**/*.vue: Use Vue 3 Composition API for all components
Use Tailwind CSS for styling with scoped styles
All user-facing strings must use i18n keys via vue-i18n
Files:
src/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/settings/components/skills/SyncStatusCard.vuesrc/renderer/src/components/NewThread.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/settings/components/skills/SyncPromptDialog.vuesrc/renderer/src/components/chat-input/SkillsPanel.vuesrc/renderer/settings/components/skills/SyncStatusSection.vuesrc/renderer/src/components/editor/mention/MentionList.vue
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Vue components must be named in PascalCase (e.g.,
ChatInput.vue) and use Vue 3 Composition API with Pinia for state management and Tailwind for styling
Files:
src/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/settings/components/skills/SyncStatusCard.vuesrc/renderer/src/components/NewThread.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/settings/components/skills/SyncPromptDialog.vuesrc/renderer/src/components/chat-input/SkillsPanel.vuesrc/renderer/settings/components/skills/SyncStatusSection.vuesrc/renderer/src/components/editor/mention/MentionList.vue
src/renderer/src/**/*.vue
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
Import useI18n from vue-i18n in Vue components to access translation functions t and locale
src/renderer/src/**/*.vue: Use<script setup>syntax for concise Vue 3 component definitions with Composition API
Define props and emits explicitly in Vue components usingdefinePropsanddefineEmitswith TypeScript interfaces
Useprovide/injectfor dependency injection in Vue components instead of prop drilling
Use Tailwind CSS for all styling instead of writing scoped CSS files
Use mobile-first responsive design approach with Tailwind breakpoints
Use Iconify Vue with lucide icons as primary choice, following patternlucide:{icon-name}
Usev-memodirective for memoizing expensive computations in templates
Usev-oncedirective for rendering static content without reactivity updates
Use virtual scrolling withRecycleScrollercomponent for rendering long lists
Subscribe to events usingrendererEvents.on()and unsubscribe inonUnmountedlifecycle hook
Files:
src/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/src/components/NewThread.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/src/components/chat-input/SkillsPanel.vuesrc/renderer/src/components/editor/mention/MentionList.vue
src/renderer/src/components/**/*.vue
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
Name Vue components using PascalCase (e.g.,
ChatInput.vue,MessageItemUser.vue)
Files:
src/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/src/components/NewThread.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/src/components/chat-input/SkillsPanel.vuesrc/renderer/src/components/editor/mention/MentionList.vue
src/renderer/src/i18n/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
src/renderer/src/i18n/**/*.json: Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Files:
src/renderer/src/i18n/en-US/chat.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ko-KR/routes.jsonsrc/renderer/src/i18n/ja-JP/settings.json
src/renderer/src/i18n/en-US/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
Use English (en-US) as the reference for translation accuracy when adding new keys
Files:
src/renderer/src/i18n/en-US/chat.jsonsrc/renderer/src/i18n/en-US/settings.json
🧠 Learnings (30)
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Use VueUse composables for common utilities like `useLocalStorage`, `useClipboard`, `useDebounceFn`
Applied to files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/chat-input/ChatInput.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/composables/**/*.ts : Name composables using camelCase with `use` prefix (e.g., `useChatState.ts`, `useMessageList.ts`)
Applied to files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/src/components/chat-input/composables/useMentionData.ts
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Use `usePresenter` composable for main process communication instead of direct IPC calls
Applied to files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/src/components/NewThread.vue
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Use `usePresenter.ts` composable for renderer-to-main IPC communication via direct presenter method calls
Applied to files:
src/renderer/src/components/chat-input/composables/useSlashMentionData.tssrc/renderer/src/components/chat-input/composables/useSkillsData.tssrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/src/components/NewThread.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.vue : Use `<script setup>` syntax for concise Vue 3 component definitions with Composition API
Applied to files:
src/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/settings/components/skills/SyncStatusCard.vuesrc/renderer/settings/components/skills/SyncStatusSection.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Define TypeScript interfaces for Vue component props and data structures
Applied to files:
src/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/src/components/chat-input/SkillsPanel.vue
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Vue 3 renderer app code should be organized in `src/renderer/src` with subdirectories for `components/`, `stores/`, `views/`, `i18n/`, and `lib/`
Applied to files:
src/renderer/settings/components/skills/SkillsSettings.vue
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Use vue-i18n framework for internationalization located at src/renderer/src/i18n/
Applied to files:
src/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/settings/components/skills/SyncStatusSection.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.vue : Define props and emits explicitly in Vue components using `defineProps` and `defineEmits` with TypeScript interfaces
Applied to files:
src/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/src/components/chat-input/SkillsPanel.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.vue : Use Iconify Vue with lucide icons as primary choice, following pattern `lucide:{icon-name}`
Applied to files:
src/renderer/src/components/chat-input/ChatInput.vue
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/main/presenter/configPresenter/**/*.ts : Custom prompts are managed independently of MCP through config data source using `configPresenter.getCustomPrompts()`
Applied to files:
src/renderer/src/components/NewThread.vuesrc/renderer/src/components/editor/mention/MentionList.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/components/**/*.vue : Name Vue components using PascalCase (e.g., `ChatInput.vue`, `MessageItemUser.vue`)
Applied to files:
src/renderer/src/components/chat-input/SkillsPanel.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/components/ : Organize Vue component directories using lowercase with dashes (e.g., `chat-input/`, `message/`)
Applied to files:
src/renderer/src/components/chat-input/SkillsPanel.vue
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Applied to files:
src/renderer/src/i18n/en-US/chat.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ko-KR/routes.jsonsrc/renderer/src/i18n/ja-JP/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Applied to files:
src/renderer/src/i18n/en-US/chat.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ko-KR/routes.jsonsrc/renderer/src/i18n/ja-JP/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/en-US/**/*.json : Use English (en-US) as the reference for translation accuracy when adding new keys
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/ja-JP/routes.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/routes.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ko-KR/routes.jsonsrc/renderer/src/i18n/ja-JP/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/{zh-CN,zh-HK,zh-TW}/**/*.json : For Chinese translations, consider using '智能体' for 'Agents' instead of the English term
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Ensure all code comments are in English and all log messages are in English, with no non-English text in code comments or console statements
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Run 'pnpm run i18n' to check translation completeness using zh-CN as source, or 'pnpm run i18n:en' to check using en-US as source
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Use the i18n-code-reviewer agent to validate translation quality, check for missing translations, review proper noun handling, and ensure consistency across languages
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/he-IL/settings.json
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Applies to src/renderer/src/i18n/**/*.ts : All user-facing strings must use vue-i18n keys defined in `src/renderer/src/i18n/`
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ko-KR/routes.jsonsrc/renderer/src/i18n/ja-JP/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : All user-facing strings must use i18n keys, not hardcoded text
Applied to files:
src/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.json
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Import Shadcn Vue components from `@/shadcn/components/ui/` path alias
Applied to files:
src/renderer/settings/components/skills/SyncStatusSection.vue
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Use `defineAsyncComponent()` for lazy loading heavy Vue components
Applied to files:
src/renderer/settings/components/skills/SyncStatusSection.vue
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: PRs must include clear description, link related issues with `Closes #123`, include screenshots/GIFs for UI changes with BEFORE/AFTER ASCII layout blocks, pass lint/typecheck/tests, and keep changes focused
Applied to files:
docs/specs/skills-ux-redesign/tasks.md
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/renderer/**/*.vue : All user-facing strings must use i18n keys via vue-i18n
Applied to files:
src/renderer/src/i18n/fr-FR/settings.json
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use English for logs and comments in TypeScript/JavaScript code
Applied to files:
src/renderer/src/i18n/he-IL/settings.json
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Project uses Node.js ≥ 20.19 and pnpm ≥ 10.11 (pnpm only); Windows developers must enable Developer Mode for symlinks
Applied to files:
package.json
📚 Learning: 2026-01-05T02:41:01.044Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/development-setup.mdc:0-0
Timestamp: 2026-01-05T02:41:01.044Z
Learning: Require Node.js >= 20.19.0 and pnpm >= 10.11.0
Applied to files:
package.json
🧬 Code graph analysis (6)
src/renderer/src/components/chat-input/composables/useSlashMentionData.ts (4)
src/renderer/src/stores/skillsStore.ts (1)
useSkillsStore(6-137)src/renderer/src/components/chat-input/composables/useAgentMcpData.ts (1)
useAgentMcpData(7-47)src/renderer/src/components/editor/mention/suggestion.ts (1)
CategorizedData(9-18)src/renderer/src/components/editor/mention/slashSuggestion.ts (1)
slashMentionData(12-12)
src/renderer/src/components/chat-input/composables/useSkillsData.ts (4)
src/renderer/src/composables/usePresenter.ts (1)
usePresenter(103-105)src/renderer/src/stores/skillsStore.ts (1)
useSkillsStore(6-137)src/shared/types/skill.ts (1)
SkillMetadata(13-24)src/renderer/src/events.ts (1)
SKILL_EVENTS(166-169)
src/renderer/src/components/chat-input/composables/useMentionData.ts (1)
src/renderer/src/components/chat-input/composables/useAgentMcpData.ts (1)
useAgentMcpData(7-47)
src/renderer/src/events.ts (1)
src/main/events.ts (1)
SKILL_EVENTS(265-272)
src/renderer/src/components/editor/mention/slashSuggestion.ts (1)
src/renderer/src/components/editor/mention/suggestion.ts (1)
CategorizedData(9-18)
src/renderer/src/components/editor/mention/suggestion.ts (1)
src/shared/types/presenters/legacy.presenters.d.ts (2)
ResourceListEntry(137-144)PromptListEntry(100-113)
🪛 LanguageTool
docs/specs/skills-ux-redesign/tasks.md
[grammar] ~76-~76: Ensure spelling is correct
Context: ...y rows in the list --- ### 1.4 Update CategorizedData Type ✅ File: `src/renderer/src/com...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🪛 markdownlint-cli2 (0.18.1)
docs/specs/skills-ux-redesign/tasks.md
18-18: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
docs/specs/skills-ux-redesign/spec.md
233-233: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
docs/specs/skills-ux-redesign/analysis.md
45-45: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
47-47: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
97-97: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
114-114: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
129-129: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
| const tools = ref<ScanResult[]>([]) | ||
| const scanning = ref(false) | ||
| const syncingTools = ref<Set<string>>(new Set()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syncingTools state is never updated.
The syncingTools Set is initialized on line 88 but never modified. The handleSync function emits an event but doesn't add/remove the toolId from syncingTools. This means the syncing indicator in SyncStatusCard will never show.
🐛 Proposed fix if syncing state should be managed locally
const handleSync = async (toolId: string) => {
const tool = tools.value.find((t) => t.toolId === toolId)
if (!tool || !tool.available) return
+ syncingTools.value.add(toolId)
+ // Note: If syncing is handled by the parent via emit, you may need to
+ // expose a method to clear the syncing state when import completes
+
// Emit event to open sync dialog with preselected tool
emit(
'import',
toolId,
tool.skills.map((s) => s.name)
)
}Alternatively, if the parent component manages the syncing state, consider removing the syncingTools ref entirely and passing the syncing state as a prop.
Also applies to: 129-139
🤖 Prompt for AI Agents
In @src/renderer/settings/components/skills/SyncStatusSection.vue at line 88,
The syncingTools Set is never updated so SyncStatusCard never shows activity;
update the handleSync logic to add the toolId to syncingTools when a sync starts
and remove it when the sync completes or fails (ensure removals happen in both
success and catch/finally paths), or if parent should own state, remove
syncingTools and forward a prop instead; target the syncingTools ref and the
handleSync handler (and any emit calls that indicate start/finish) to implement
adding/removing toolId so the SyncStatusCard receives the correct syncing state.
| for (const tool of tools.value) { | ||
| items.push({ | ||
| id: `${tool.server.name}.${tool.function.name ?? ''}`, | ||
| label: `${tool.server.icons}${' '}${tool.function.name ?? ''}`, | ||
| icon: undefined, // Using emoji icon in MentionList.vue | ||
| type: 'item' as const, | ||
| category: 'tools', | ||
| description: tool.function.description ?? '' | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential undefined value in tool label.
tool.server.icons may be undefined, which would result in "undefined toolName" being displayed. Add null coalescing to handle this case.
🐛 Proposed fix
items.push({
id: `${tool.server.name}.${tool.function.name ?? ''}`,
- label: `${tool.server.icons}${' '}${tool.function.name ?? ''}`,
+ label: `${tool.server.icons ?? ''}${tool.server.icons ? ' ' : ''}${tool.function.name ?? ''}`,
icon: undefined, // Using emoji icon in MentionList.vue
type: 'item' as const,
category: 'tools',
description: tool.function.description ?? ''
})🤖 Prompt for AI Agents
In @src/renderer/src/components/chat-input/composables/useSlashMentionData.ts
around lines 58 - 67, The label construction in the items.push loop can include
"undefined" because tool.server.icons may be undefined; update the label
expression inside the for (const tool of tools.value) loop (the items.push call)
to coalesce tool.server.icons to an empty string (e.g., use tool.server.icons ??
'') and ensure the separating space is only added when an icon exists so you
don't render a leading/extra space before tool.function.name.
| <p v-if="skill.allowedTools?.length" class="text-xs text-muted-foreground"> | ||
| Tools: {{ skill.allowedTools.join(', ') }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded string should use i18n.
The "Tools: " prefix is hardcoded instead of using an i18n key. This should be translated for consistency with the rest of the UI.
🐛 Proposed fix
<p v-if="skill.allowedTools?.length" class="text-xs text-muted-foreground">
- Tools: {{ skill.allowedTools.join(', ') }}
+ {{ t('chat.skills.panel.tools') }}: {{ skill.allowedTools.join(', ') }}
</p>And add the corresponding i18n key to chat.json files:
"panel": {
"title": "Skills",
"manage": "Manage",
"empty": "No skills installed",
"tools": "Tools"
}🤖 Prompt for AI Agents
In @src/renderer/src/components/chat-input/SkillsPanel.vue around lines 30 - 31,
Replace the hardcoded "Tools: " prefix in SkillsPanel.vue with the i18n
translation key (use the existing i18n helper, e.g., $t or t depending on
project conventions) when rendering the allowedTools list for a skill (the <p
v-if="skill.allowedTools?.length"> block referencing skill.allowedTools.join).
Also add the "panel.tools": "Tools" entry to the chat.json locale files (under
the existing "panel" object) so the UI uses the translated string.
| </button> | ||
| </div> | ||
| <div v-else class="p-1 text-sm text-muted-foreground">No result</div> | ||
| <div v-else class="p-2 text-sm text-muted-foreground text-center">No result</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded user-facing string violates i18n guidelines.
The "No result" text should use an i18n key instead of a hardcoded English string. As per coding guidelines, all user-facing strings must use i18n keys via vue-i18n.
🔧 Suggested fix
- <div v-else class="p-2 text-sm text-muted-foreground text-center">No result</div>
+ <div v-else class="p-2 text-sm text-muted-foreground text-center">{{ t('common.noResults') }}</div>Also ensure useI18n is imported and t is destructured in the script setup.
🤖 Prompt for AI Agents
In @src/renderer/src/components/editor/mention/MentionList.vue at line 44,
Replace the hardcoded "No result" text in the MentionList.vue template with an
i18n key by calling the translator (e.g., t('mention.noResult')), and ensure you
import useI18n in the <script setup> and destructure t from it (const { t } =
useI18n()). Update the template's fallback div to use the t(...) call and add
the new i18n key "mention.noResult" to your locale files; keep the key name
consistent with other mention-related keys used in this component.
| "skills": { | ||
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", | ||
| "nameHint": "Le nom de la skill ne peut pas être modifié après sa création." | ||
| }, | ||
| "conflict": { | ||
| "title": "Skill Already Exists", | ||
| "description": "A skill named \"{name}\" already exists. Do you want to overwrite it?", | ||
| "overwrite": "Overwrite" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Check the French translation file at the specified lines
wc -l src/renderer/src/i18n/fr-FR/settings.jsonRepository: ThinkInAIXYZ/deepchat
Length of output: 111
🏁 Script executed:
# Read the French translation file at the specified lines
sed -n '1043,1102p' src/renderer/src/i18n/fr-FR/settings.json | head -80Repository: ThinkInAIXYZ/deepchat
Length of output: 2488
🏁 Script executed:
# Check if the en-US file exists and read the same section for reference
wc -l src/renderer/src/i18n/en-US/settings.jsonRepository: ThinkInAIXYZ/deepchat
Length of output: 111
🏁 Script executed:
# Read en-US for comparison
sed -n '1043,1102p' src/renderer/src/i18n/en-US/settings.json | head -80Repository: ThinkInAIXYZ/deepchat
Length of output: 2469
🏁 Script executed:
# Check if "skills" section exists in all required language files
for lang in da-DK en-US fa-IR fr-FR he-IL ja-JP ko-KR pt-BR ru-RU zh-CN zh-HK zh-TW; do
if grep -q '"skills"' src/renderer/src/i18n/$lang/settings.json 2>/dev/null; then
echo "$lang: HAS skills section"
else
echo "$lang: MISSING skills section"
fi
doneRepository: ThinkInAIXYZ/deepchat
Length of output: 376
🏁 Script executed:
# Check one of the other language files to see if it has proper translation
sed -n '1043,1100p' src/renderer/src/i18n/da-DK/settings.json 2>/dev/null | head -60Repository: ThinkInAIXYZ/deepchat
Length of output: 2470
🏁 Script executed:
# Check another language file
sed -n '1043,1100p' src/renderer/src/i18n/pt-BR/settings.json 2>/dev/null | head -60Repository: ThinkInAIXYZ/deepchat
Length of output: 2588
French translation file missing for skills section.
The skills section in fr-FR/settings.json (lines 1043–1102) contains English strings instead of French translations. This violates the i18n requirement to translate all new content across all locales with consistent key names. All other language files (da-DK, pt-BR, etc.) have proper translations for this section—only the French file lacks them.
Strings requiring translation include the top-level fields (description, openFolder, addSkill, empty, etc.) and all nested fields within install, delete, edit, and conflict subsections. Only nameHint is partially translated.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/fr-FR/settings.json around lines 1043 - 1102, The
French i18n block "skills" currently contains English text; translate every
string under the "skills" key (including nested keys "install", "delete",
"edit", "conflict" and their fields such as "description", "openFolder",
"addSkill", "empty", "noResults", "search", "count", "install.title",
"install.description", "install.tabFolder", "install.tabZip", "install.tabUrl",
"install.fromFolder", "install.selectFolder", "install.folderHint",
"install.folderTip", "install.zipHint", "install.urlPlaceholder",
"install.urlHint", "install.installButton", "install.installing",
"install.success", "install.successMessage", "install.failed",
"install.dragNotSupported", "delete.title", "delete.description",
"delete.success", "delete.successMessage", "delete.failed", "edit.title",
"edit.placeholder", "edit.readFailed", "edit.success", "edit.failed",
"edit.name", "edit.namePlaceholder", "edit.description",
"edit.descriptionPlaceholder", "edit.allowedTools",
"edit.allowedToolsPlaceholder", "edit.allowedToolsHint", "edit.content",
"edit.files", "edit.noFiles", and "conflict.title/description/overwrite") into
proper French, preserving the exact JSON keys and placeholders (e.g., {count},
{name}) and matching phrasing used in other locale files for consistency; update
the fr-FR/settings.json "skills" block with those French strings so all locales
remain consistent.
| "settings-mcp-market": "MCP市場", | ||
| "settings-acp": "ACPエージェント" | ||
| "settings-acp": "ACPエージェント", | ||
| "settings-skills": "Skills" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Translate "Skills" to Japanese for consistency.
The value "Skills" is in English within the Japanese locale file, breaking the established pattern where common English terms are transliterated to katakana (e.g., "playground" → "プレイグラウンド", "knowledge base" → "ナレッジベース", "agent" → "エージェント"). Only technical acronyms like MCP and ACP remain untranslated.
🌐 Proposed fix
- "settings-skills": "Skills"
+ "settings-skills": "スキル"Or, to match the pattern of other settings entries that include "設定":
- "settings-skills": "Skills"
+ "settings-skills": "スキル設定"Based on coding guidelines and established patterns in this file.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/ja-JP/routes.json at line 17, Replace the English
value for the "settings-skills" key in routes.json with the Japanese
transliteration; update "settings-skills": "Skills" to use the katakana form
(e.g., "スキル") or, to match other settings keys that include "設定", use "スキル設定" so
the Japanese locale is consistent.
| "skills": { | ||
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", | ||
| "nameHint": "skill作成後、名称は変更できません。" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Missing Japanese translations in skills section.
Most of the skills section (lines 1043-1096) contains English text instead of Japanese translations. This violates the i18n guideline that all user-facing strings must be properly translated for each locale.
Examples of untranslated strings:
- Line 1044:
"title": "Skills" - Line 1045:
"description": "Manage and configure AI assistant skills" - Line 1046:
"openFolder": "Open Folder" - Line 1086:
"name": "Name" - Line 1093:
"content": "Skill Content"
Only line 1096 (nameHint) and the sync section (1104-1173) have proper Japanese translations.
All strings in the skills section should be translated to Japanese. Please either:
- Translate all English strings to Japanese, or
- Reference the en-US locale as the source and ensure ja-JP has complete translations
Based on learnings and coding guidelines.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/ja-JP/settings.json around lines 1043 - 1096, The
skills i18n block contains English strings instead of Japanese; update every key
under the "skills" object (including nested objects "install", "delete", "edit"
and keys like "title", "description", "openFolder", "addSkill", "emptyHint",
"search", "count", "install.title", "install.description", "install.tabFolder",
"install.tabZip", "install.tabUrl", "install.fromFolder",
"install.selectFolder", "install.folderHint", "install.folderTip",
"install.zipHint", "install.urlPlaceholder", "install.urlHint",
"install.installButton", "install.installing", "install.success",
"install.successMessage", "install.failed", "install.dragNotSupported",
"delete.title", "delete.description", "delete.success", "delete.successMessage",
"delete.failed", "edit.title", "edit.placeholder", "edit.readFailed",
"edit.success", "edit.failed", "edit.name", "edit.namePlaceholder",
"edit.description", "edit.descriptionPlaceholder", "edit.allowedTools",
"edit.allowedToolsPlaceholder", "edit.allowedToolsHint", "edit.content",
"edit.files", "edit.noFiles", "nameHint") with correct Japanese translations (or
pull the corresponding strings from en-US as source and translate them),
ensuring plural/placeholders like "{count}" and "{name}" are preserved and
existing Japanese text (e.g., "nameHint") is kept consistent.
| "settings-mcp-market": "MCP 시장", | ||
| "settings-acp": "ACP 프록시" | ||
| "settings-acp": "ACP 프록시", | ||
| "settings-skills": "Skills" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Translate the value to Korean.
The value "Skills" is in English, but this is the Korean locale file (ko-KR). All other non-acronym menu items in this file are properly translated to Korean (e.g., "채팅", "설정", "지식 기반", "프롬프트 관리"). "Skills" should be translated to maintain consistency with the localization pattern.
Consider using "스킬" (commonly used transliteration in Korean tech contexts) or "기술" (literal translation for skills).
Proposed fix
- "settings-skills": "Skills"
+ "settings-skills": "스킬"🤖 Prompt for AI Agents
In @src/renderer/src/i18n/ko-KR/routes.json at line 17, The "settings-skills"
localization entry currently uses the English string "Skills"; update its value
to a Korean translation to match the ko-KR locale (change the value for the
"settings-skills" key to a Korean term such as "스킬" or "기술"). Ensure you only
replace the right-hand string value for the "settings-skills" key in the
routes.json so formatting and key names remain unchanged.
| "skills": { | ||
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Missing Korean translations in skills section.
The majority of the skills section (lines 1043-1095) contains English text instead of Korean translations. This violates the i18n guideline requiring all user-facing strings to be translated for each locale.
Examples of untranslated strings:
- Line 1044:
"title": "Skills" - Line 1045:
"description": "Manage and configure AI assistant skills" - Line 1046:
"openFolder": "Open Folder" - Line 1086:
"name": "Name" - Line 1093:
"content": "Skill Content"
Only line 1096 (nameHint) and the sync section (1104-1173) have proper Korean translations.
All strings in the skills section should be translated to Korean. Please either:
- Translate all English strings to Korean, or
- Reference the en-US locale as the source and ensure ko-KR has complete translations
Based on learnings and coding guidelines.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/ko-KR/settings.json around lines 1043 - 1095, The
skills localization block ("skills" object and its nested keys like "install",
"delete", "edit") contains English strings (e.g., "title", "description",
"openFolder", "addSkill", "name", "content", etc.); replace those English values
with Korean translations (or copy corresponding values from en-US into ko-KR and
then translate) so every user-facing string in the "skills" section is
translated; ensure you update all nested keys shown (install.tabFolder,
install.tabZip, install.tabUrl, install.fromFolder, install.selectFolder,
install.folderHint, install.folderTip, install.zipHint, install.urlPlaceholder,
install.urlHint,
install.installButton/installing/success/successMessage/failed/dragNotSupported,
delete.title/description/success/successMessage/failed,
edit.title/placeholder/readFailed/success/failed/name/namePlaceholder/description/descriptionPlaceholder/allowedTools/allowedToolsPlaceholder/allowedToolsHint/content/files/noFiles)
to Korean to satisfy i18n completeness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @src/renderer/src/i18n/fr-FR/settings.json:
- Around line 1044-1102: The French locale contains untranslated English strings
in the "Skills" section: update the string values for keys title ("Skills"),
description, openFolder, addSkill, empty, emptyHint, noResults, search, count,
and all nested objects install, delete, edit (except edit.nameHint which is
already French), and conflict so they are proper French translations while
preserving placeholders like {name} and {count} and existing keys such as
install.title, install.description, install.tabFolder, install.tabZip,
install.tabUrl, install.fromFolder, install.selectFolder, install.folderHint,
install.folderTip, install.zipHint, install.urlPlaceholder, install.urlHint,
install.installButton, install.installing, install.success,
install.successMessage, install.failed, install.dragNotSupported, delete.title,
delete.description, delete.success, delete.successMessage, delete.failed,
edit.title, edit.placeholder, edit.readFailed, edit.success, edit.failed,
edit.name, edit.namePlaceholder, edit.description, edit.descriptionPlaceholder,
edit.allowedTools, edit.allowedToolsPlaceholder, edit.allowedToolsHint,
edit.content, edit.files, edit.noFiles, and conflict.title,
conflict.description, conflict.overwrite; ensure French grammar and keep
interpolation tokens and punctuation exactly as they appear.
In @src/renderer/src/i18n/he-IL/settings.json:
- Around line 1043-1102: The skills localization block contains many English
strings; translate all untranslated keys under "skills" — including the
top-level keys
("title","description","openFolder","addSkill","empty","emptyHint","noResults","search","count"),
the entire "install" object (all nested keys like
"title","description","tabFolder","tabZip","tabUrl","fromFolder","selectFolder","folderHint","folderTip","zipHint","urlPlaceholder","urlHint","installButton","installing","success","successMessage","failed","dragNotSupported"),
the "delete" object ("title","description","success","successMessage","failed"),
the remaining "edit" keys except existing Hebrew "nameHint" (e.g.,
"title","placeholder","readFailed","success","failed","name","namePlaceholder","description","descriptionPlaceholder","allowedTools","allowedToolsPlaceholder","allowedToolsHint","content","files","noFiles"),
and the "conflict" object ("title","description","overwrite"); update the Hebrew
strings to accurate translations matching the meaning and tone used in other
locales and ensure the same key names are added to all other locale files for
consistency.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
src/renderer/src/i18n/da-DK/chat.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/fa-IR/chat.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/fr-FR/chat.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/chat.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/ja-JP/chat.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/chat.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/pt-BR/chat.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/ru-RU/chat.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/zh-HK/chat.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/zh-TW/chat.jsonsrc/renderer/src/i18n/zh-TW/settings.json
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,ts,tsx,jsx,vue,json,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use Prettier as the code formatter
Files:
src/renderer/src/i18n/da-DK/chat.jsonsrc/renderer/src/i18n/ja-JP/chat.jsonsrc/renderer/src/i18n/fa-IR/chat.jsonsrc/renderer/src/i18n/fr-FR/chat.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/he-IL/chat.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.json
src/renderer/src/i18n/**/*.json
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
src/renderer/src/i18n/**/*.json: Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Files:
src/renderer/src/i18n/da-DK/chat.jsonsrc/renderer/src/i18n/ja-JP/chat.jsonsrc/renderer/src/i18n/fa-IR/chat.jsonsrc/renderer/src/i18n/fr-FR/chat.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/he-IL/chat.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.json
🧠 Learnings (12)
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales
Applied to files:
src/renderer/src/i18n/da-DK/chat.jsonsrc/renderer/src/i18n/ja-JP/chat.jsonsrc/renderer/src/i18n/fa-IR/chat.jsonsrc/renderer/src/i18n/fr-FR/chat.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/he-IL/chat.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Use dot-separated hierarchical structure for translation key naming with lowercase letters and descriptive names grouped by feature/context (e.g., common.button.submit, chat.send.placeholder)
Applied to files:
src/renderer/src/i18n/da-DK/chat.jsonsrc/renderer/src/i18n/ja-JP/chat.jsonsrc/renderer/src/i18n/fa-IR/chat.jsonsrc/renderer/src/i18n/fr-FR/chat.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/he-IL/chat.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep proper nouns and technical terms untranslated (e.g., 'DeepChat', 'MCP', 'Agents' in English) unless established convention exists, and follow established conventions for well-known technical terms
Applied to files:
src/renderer/src/i18n/da-DK/chat.jsonsrc/renderer/src/i18n/fr-FR/chat.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/he-IL/chat.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/en-US/**/*.json : Use English (en-US) as the reference for translation accuracy when adding new keys
Applied to files:
src/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/i18n/{zh-CN,zh-HK,zh-TW}/**/*.json : For Chinese translations, consider using '智能体' for 'Agents' instead of the English term
Applied to files:
src/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,vue} : Ensure all code comments are in English and all log messages are in English, with no non-English text in code comments or console statements
Applied to files:
src/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.json
📚 Learning: 2026-01-05T02:41:45.219Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T02:41:45.219Z
Learning: Applies to src/renderer/src/i18n/**/*.ts : All user-facing strings must use vue-i18n keys defined in `src/renderer/src/i18n/`
Applied to files:
src/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Run 'pnpm run i18n' to check translation completeness using zh-CN as source, or 'pnpm run i18n:en' to check using en-US as source
Applied to files:
src/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/he-IL/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : All user-facing strings must use i18n keys, not hardcoded text
Applied to files:
src/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.json
📚 Learning: 2026-01-05T02:41:13.302Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2026-01-05T02:41:13.302Z
Learning: Use the i18n-code-reviewer agent to validate translation quality, check for missing translations, review proper noun handling, and ensure consistency across languages
Applied to files:
src/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/he-IL/settings.json
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use English for logs and comments in TypeScript/JavaScript code
Applied to files:
src/renderer/src/i18n/he-IL/settings.json
📚 Learning: 2026-01-05T02:40:52.841Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T02:40:52.841Z
Learning: Applies to src/renderer/**/*.vue : All user-facing strings must use i18n keys via vue-i18n
Applied to files:
src/renderer/src/i18n/fr-FR/settings.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-check (x64)
🔇 Additional comments (6)
src/renderer/src/i18n/he-IL/chat.json (1)
151-161: LGTM!The Hebrew translations for the skills block in the chat context are complete and consistent. Good use of the Hebrew term "כישורים" for skills.
src/renderer/src/i18n/da-DK/chat.json (1)
151-161: LGTM!The Danish translations for the skills block are complete and use appropriate terminology ("færdigheder" for skills).
src/renderer/src/i18n/fa-IR/chat.json (1)
151-160: LGTM – Skills translations for Persian look good.The key structure follows the hierarchical naming convention, and the translations appropriately localize the skills-related UI strings for the Persian locale.
src/renderer/src/i18n/fr-FR/chat.json (1)
151-160: LGTM – French translations for skills section are well-structured.The hierarchical key naming is consistent with other locales, and the French translations are natural and appropriate.
src/renderer/src/i18n/ja-JP/chat.json (1)
151-160: LGTM – Japanese skills translations are consistent and accurate.The key structure matches other locales, and the Japanese translations use appropriate terminology (スキル for "skill").
src/renderer/src/i18n/da-DK/settings.json (1)
1043-1052: LGTM – Danish skills translations follow conventions.The key structure is hierarchical and consistent. "Skills" is kept in English for the title, which aligns with the guideline to keep technical terms untranslated when no established convention exists. The Danish translations for other strings are appropriate.
| "skills": { | ||
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", | ||
| "nameHint": "پس از ایجاد skill، نام قابل تغییر نیست" | ||
| }, | ||
| "conflict": { | ||
| "title": "Skill Already Exists", | ||
| "description": "A skill named \"{name}\" already exists. Do you want to overwrite it?", | ||
| "overwrite": "Overwrite" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete Persian translations in skills block.
Similar to the ja-JP file, most strings in the skills block remain in English while sync, syncPrompt, syncStatus, and edit.nameHint are properly translated to Persian. This creates an inconsistent user experience for Persian-speaking users.
Strings requiring translation include:
- Lines 1044-1052: Top-level skills keys (title, description, openFolder, etc.)
- Lines 1053-1072: The entire
installblock - Lines 1073-1079: The entire
deleteblock - Lines 1080-1095: Most of the
editblock (except nameHint) - Lines 1098-1102: The
conflictblock
Please complete the Persian translations for consistency with the rest of the locale. Based on coding guidelines, add translations to ALL language files with consistent key names across all locales.
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", | ||
| "nameHint": "Le nom de la skill ne peut pas être modifié après sa création." | ||
| }, | ||
| "conflict": { | ||
| "title": "Skill Already Exists", | ||
| "description": "A skill named \"{name}\" already exists. Do you want to overwrite it?", | ||
| "overwrite": "Overwrite" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Untranslated English strings in French locale file.
The skills section from lines 1044-1102 contains English text instead of French translations. This includes the description, openFolder, addSkill, empty, emptyHint, noResults, search, count keys, and the entire install, delete, edit, and conflict subsections (except edit.nameHint on line 1096 which is correctly in French).
The sync, syncPrompt, and syncStatus sections (lines 1103-1194) are properly translated to French, indicating this was likely an incomplete translation pass.
Please translate the English strings to French to maintain consistency with the locale and other translated sections.
Example fixes for the first few keys
"skills": {
- "title": "Skills",
- "description": "Manage and configure AI assistant skills",
- "openFolder": "Open Folder",
- "addSkill": "Add Skill",
- "empty": "No skills yet",
- "emptyHint": "Click \"Add Skill\" to install a new skill",
- "noResults": "No matching skills found",
- "search": "Search skills...",
- "count": "{count} skills",
+ "title": "Compétences",
+ "description": "Gérer et configurer les compétences de l'assistant IA",
+ "openFolder": "Ouvrir le dossier",
+ "addSkill": "Ajouter une compétence",
+ "empty": "Aucune compétence pour le moment",
+ "emptyHint": "Cliquez sur \"Ajouter une compétence\" pour installer une nouvelle compétence",
+ "noResults": "Aucune compétence correspondante trouvée",
+ "search": "Rechercher des compétences...",
+ "count": "{count} compétences",🤖 Prompt for AI Agents
In @src/renderer/src/i18n/fr-FR/settings.json around lines 1044 - 1102, The
French locale contains untranslated English strings in the "Skills" section:
update the string values for keys title ("Skills"), description, openFolder,
addSkill, empty, emptyHint, noResults, search, count, and all nested objects
install, delete, edit (except edit.nameHint which is already French), and
conflict so they are proper French translations while preserving placeholders
like {name} and {count} and existing keys such as install.title,
install.description, install.tabFolder, install.tabZip, install.tabUrl,
install.fromFolder, install.selectFolder, install.folderHint, install.folderTip,
install.zipHint, install.urlPlaceholder, install.urlHint, install.installButton,
install.installing, install.success, install.successMessage, install.failed,
install.dragNotSupported, delete.title, delete.description, delete.success,
delete.successMessage, delete.failed, edit.title, edit.placeholder,
edit.readFailed, edit.success, edit.failed, edit.name, edit.namePlaceholder,
edit.description, edit.descriptionPlaceholder, edit.allowedTools,
edit.allowedToolsPlaceholder, edit.allowedToolsHint, edit.content, edit.files,
edit.noFiles, and conflict.title, conflict.description, conflict.overwrite;
ensure French grammar and keep interpolation tokens and punctuation exactly as
they appear.
| "skills": { | ||
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", | ||
| "nameHint": "לא ניתן לשנות את שם הskill לאחר יצירתו" | ||
| }, | ||
| "conflict": { | ||
| "title": "Skill Already Exists", | ||
| "description": "A skill named \"{name}\" already exists. Do you want to overwrite it?", | ||
| "overwrite": "Overwrite" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete Hebrew translations in skills block.
The skills block follows the same incomplete translation pattern as other locales. Most UI strings remain in English while sync, syncPrompt, syncStatus, and edit.nameHint are translated to Hebrew.
Untranslated sections:
- Lines 1044-1052: Top-level skills keys
- Lines 1053-1072:
installblock - Lines 1073-1079:
deleteblock - Lines 1080-1095: Most of
editblock - Lines 1098-1102:
conflictblock
Please complete the Hebrew translations. As per coding guidelines, new translations should be added to ALL language files with consistent key names across all locales.
🤖 Prompt for AI Agents
In @src/renderer/src/i18n/he-IL/settings.json around lines 1043 - 1102, The
skills localization block contains many English strings; translate all
untranslated keys under "skills" — including the top-level keys
("title","description","openFolder","addSkill","empty","emptyHint","noResults","search","count"),
the entire "install" object (all nested keys like
"title","description","tabFolder","tabZip","tabUrl","fromFolder","selectFolder","folderHint","folderTip","zipHint","urlPlaceholder","urlHint","installButton","installing","success","successMessage","failed","dragNotSupported"),
the "delete" object ("title","description","success","successMessage","failed"),
the remaining "edit" keys except existing Hebrew "nameHint" (e.g.,
"title","placeholder","readFailed","success","failed","name","namePlaceholder","description","descriptionPlaceholder","allowedTools","allowedToolsPlaceholder","allowedToolsHint","content","files","noFiles"),
and the "conflict" object ("title","description","overwrite"); update the Hebrew
strings to accurate translations matching the meaning and tone used in other
locales and ensure the same key names are added to all other locale files for
consistency.
| "skills": { | ||
| "title": "Skills", | ||
| "description": "Manage and configure AI assistant skills", | ||
| "openFolder": "Open Folder", | ||
| "addSkill": "Add Skill", | ||
| "empty": "No skills yet", | ||
| "emptyHint": "Click \"Add Skill\" to install a new skill", | ||
| "noResults": "No matching skills found", | ||
| "search": "Search skills...", | ||
| "count": "{count} skills", | ||
| "install": { | ||
| "title": "Install Skill", | ||
| "description": "Choose skill installation method", | ||
| "tabFolder": "Folder", | ||
| "tabZip": "ZIP", | ||
| "tabUrl": "URL", | ||
| "fromFolder": "Install from Folder", | ||
| "selectFolder": "Select Skill Folder", | ||
| "folderHint": "Click to select a skill folder", | ||
| "folderTip": "Import skills from other clients like ~/.claude/skills/", | ||
| "zipHint": "Click to select a ZIP file", | ||
| "urlPlaceholder": "Enter skill ZIP download URL", | ||
| "urlHint": "Enter the download link for the skill package", | ||
| "installButton": "Install", | ||
| "installing": "Installing...", | ||
| "success": "Installation Successful", | ||
| "successMessage": "Skill {name} has been installed successfully", | ||
| "failed": "Installation Failed", | ||
| "dragNotSupported": "Drag not supported, please click to select" | ||
| }, | ||
| "delete": { | ||
| "title": "Delete Skill", | ||
| "description": "Are you sure you want to delete skill {name}? This action cannot be undone.", | ||
| "success": "Deletion Successful", | ||
| "successMessage": "Skill {name} has been deleted successfully", | ||
| "failed": "Deletion Failed" | ||
| }, | ||
| "edit": { | ||
| "title": "Edit Skill", | ||
| "placeholder": "Edit skill content here...", | ||
| "readFailed": "Failed to read", | ||
| "success": "Saved Successfully", | ||
| "failed": "Failed to save", | ||
| "name": "Name", | ||
| "namePlaceholder": "Skill name", | ||
| "description": "Description", | ||
| "descriptionPlaceholder": "Skill description", | ||
| "allowedTools": "Allowed Tools", | ||
| "allowedToolsPlaceholder": "Read, Grep, Bash", | ||
| "allowedToolsHint": "Comma-separated list of tool names", | ||
| "content": "Skill Content", | ||
| "files": "Folder Contents", | ||
| "noFiles": "No files", | ||
| "nameHint": "skill作成後、名称は変更できません。" | ||
| }, | ||
| "conflict": { | ||
| "title": "Skill Already Exists", | ||
| "description": "A skill named \"{name}\" already exists. Do you want to overwrite it?", | ||
| "overwrite": "Overwrite" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete Japanese translations in skills block.
Most strings in the skills block (title, description, openFolder, addSkill, install, delete, edit, conflict sections) remain in English, while sync, syncPrompt, and syncStatus are properly translated to Japanese. This inconsistency will result in a mixed-language UI for Japanese users.
For example:
- Line 1044:
"title": "Skills"should be"title": "スキル" - Line 1045:
"description": "Manage and configure AI assistant skills"should be translated - Lines 1053-1072: The entire
installblock is in English - Lines 1073-1079: The entire
deleteblock is in English
Please translate the remaining English strings to Japanese to maintain consistency with the rest of the locale file. Based on learnings, technical terms like "Skills" may be kept in English if there's an established convention, but UI action strings (e.g., "Open Folder", "Add Skill", "Install") should be localized.
|
LGTM |
Summary by CodeRabbit
New Features
Skill Sync
UI / Settings
Documentation
Tests
Chore
✏️ Tip: You can customize this high-level summary in your review settings.