Skip to content

Conversation

@deepinfect
Copy link
Collaborator

@deepinfect deepinfect commented Jan 8, 2026

  • 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.

Summary by CodeRabbit

  • New Features

    • Full Skills system: discover, install (folder/ZIP/URL), edit, uninstall, hot-reload, per-conversation activation, agent integration, and permission prompts.
  • Skill Sync

    • Import/export wizards with external tool scanning, format adapters, conversion, conflict resolution, and safety checks.
  • UI / Settings

    • Skills settings page, install/editor dialogs, folder tree/cards, sync dialogs, chat skills indicator/panel, new route, and extensive i18n.
  • Documentation

    • Comprehensive design, research, format specs, tasks, UX redesign, and code-review guidance.
  • Tests

    • Extensive unit tests covering presenters, adapters, sync and tooling.
  • Chore

    • Bundled skill resources and added UI/utility dependencies.

✏️ Tip: You can customize this high-level summary in your review settings.

- 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.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

Important

Review skipped

Too 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 reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Core Skill runtime
src/main/presenter/skillPresenter/..., src/main/presenter/skillPresenter/skillTools.ts
New SkillPresenter + SkillTools: discovery, frontmatter parsing, lazy SKILL.md loading, install/uninstall/update (folder/zip/url), ZIP safety, folder tree, watcher/hot-reload, per-conversation activeSkills, allowed-tools aggregation, renderer events.
Skill Sync & adapters
src/main/presenter/skillSyncPresenter/..., src/main/presenter/skillSyncPresenter/adapters/*, src/main/presenter/skillSyncPresenter/formatConverter.ts, src/main/presenter/skillSyncPresenter/toolScanner.ts, src/main/presenter/skillSyncPresenter/security.ts
New SkillSyncPresenter, FormatConverter, ToolScanner, security validations, adapter registry and many adapters (claude-code, copilot, cursor, windsurf, kiro, antigravity, codex, copilot-user, goose, kilocode, opencode). Import/export preview/execute flows and conversion warnings.
Presenter wiring & config
src/main/presenter/index.ts, src/main/presenter/configPresenter/index.ts
Presenter now wires SkillPresenter/SkillSyncPresenter and FilePermissionService; adds enableSkills and skillsPath getters/setters and initializeSkills gating.
Agent integration & tooling
src/main/presenter/agentPresenter/..., src/main/presenter/toolPresenter/index.ts, src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts, src/main/presenter/agentPresenter/acp/agentToolManager.ts
Augments agent prompt assembly with skills metadata/content; merges per-skill allowedTools into MCP toolset; adds skill tools (skill_list, skill_control) and routes/permission handling; AgentToolManager now receives configPresenter.
File permission & filesystem
src/main/presenter/permission/*, src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts, src/main/presenter/permission/filePermissionService.ts
Adds FilePermissionService and FilePermissionRequiredError; path resolution/validation changes (resolvePath, enforceAllowed option); approve/clear approved paths; permission grants can resume agent flows.
DB & session persistence
src/main/presenter/sqlitePresenter/tables/conversations.ts, src/main/presenter/sessionPresenter/*
DB migration adds active_skills column; DEFAULT_SETTINGS and conversation/session types include activeSkills; ConversationManager initializes activeSkills.
Types & public API
src/shared/types/skill.ts, src/shared/types/skillSync.ts, src/shared/types/index.d.ts, src/shared/types/presenters/*
New shared types and presenter interfaces (ISkillPresenter, ISkillSyncPresenter, CanonicalSkill, ExternalToolConfig, SyncResult, etc.) + re-exports and presenter d.ts updates.
Renderer: Skills UI & store
src/renderer/settings/main.ts, src/renderer/settings/components/skills/*, src/renderer/src/stores/skillsStore.ts
New /skills route and settings UI: SkillCard, SkillsHeader, SkillInstallDialog, SkillEditorSheet, folder tree, SkillSyncDialog, wizards; new Pinia useSkillsStore.
Skill Sync UI
src/renderer/settings/components/skills/SkillSyncDialog/*
Import/Export wizards, ToolSelector, SkillSelector, ConflictResolver, SyncResult and dialog wiring for sync flows.
Chat input & slash UX
src/renderer/src/components/chat-input/*, src/renderer/src/components/editor/mention/*
Adds SkillsIndicator, SkillsPanel, useSkillsData/useSlashMentionData composables, slash mention/suggestion, flattened MentionList, ChatInput exposes pending-skills APIs and wires skill activation.
i18n & navigation
src/renderer/src/i18n/*/routes.json, src/renderer/src/i18n/*/settings.json, src/renderer/src/i18n/*/common.json, src/renderer/src/i18n/*/chat.json
Adds settings-skills route labels and comprehensive skills translation namespaces across locales; wizard next/back keys; chat indicator/panel strings.
Device & events
src/main/presenter/devicePresenter/index.ts, src/main/events.ts, src/renderer/src/events.ts
Adds DevicePresenter.selectFiles API; new event groups SKILL_EVENTS / SKILL_SYNC_EVENTS; renderer SKILL_EVENTS with ACTIVATED/DEACTIVATED.
Packaging & resources
package.json, electron-builder.yml, resources/skills/*
Adds dependencies: chokidar, gray-matter, @antv/infographic; includes resources/skills in extraResource; adds builtin skills examples.
Tests & docs
test/main/presenter/skillPresenter/*, test/main/presenter/skillSyncPresenter/*, docs/specs/skills-system/*, CLAUDE.md
Extensive unit tests for SkillPresenter, SkillTools and adapters; many spec/design docs, tasks, research and code-review docs; CLAUDE.md updated.
Agent FS & permission flow changes
src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts, src/main/presenter/agentPresenter/permission/permissionHandler.ts
Path validation options (enforceAllowed flag), relaxed enforcement for read ops, permission grant handling extracts/approves paths and restarts agent loop on approval.

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
Loading
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
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

codex

Poem

🐇
I found a folder in the loam,
Tiny SKILLs to call my own.
Zip or URL, I tuck them in,
Agents hop and whisper, grin.
Hot-reload hops — the meadow’s grown.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing a comprehensive skills management system with both UI and backend components. It is concise, specific, and clearly communicates the primary purpose of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 86.84% which is sufficient. The required threshold is 80.00%.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@deepinfect
Copy link
Collaborator Author

deepinfect commented Jan 8, 2026

@coderabbitai

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

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:

  • Reviewing the changes in this PR
  • Answering questions about the code
  • Generating tests or documentation
  • Analyzing specific parts of the implementation
  • Providing suggestions for improvements

Please let me know what you'd like me to do!

@deepinfect
Copy link
Collaborator Author

@coderabbitai help

@coderabbitai

This comment was marked as resolved.

@deepinfect
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

- 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.
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 ```text or ```mermaid if converting to proper diagrams
  • The TypeScript sections already have correct ```typescript identifiers ✓
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 text or plaintext.

Example fix for line 40
-```
+```text
 .claude/skills/
 ├── skill-name/
 │   ├── SKILL.md (必需)         # 元数据 + 指令
src/main/presenter/index.ts (1)

271-283: Consider adding initialize() and destroy() to ISkillPresenter interface.

The type assertions (this.skillPresenter as SkillPresenter).initialize() and (this.skillPresenter as SkillPresenter).destroy() suggest these lifecycle methods should be part of the ISkillPresenter interface 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() or setSyncEnabled() which emit events to notify renderers, setSkillsEnabled() and setSkillsPath() 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 injecting skillPresenter instead of accessing global presenter.

getSkillTools() accesses presenter.skillPresenter directly, creating tight coupling to the global singleton. For consistency with the configPresenter pattern already used, consider passing skillPresenter via 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. For deactivate, affectedSkills includes 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.

SkillPresenter is imported but only used for type assertions. Since presenter.skillPresenter already implements ISkillPresenter, 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 SkillPresenter with presenter.skillPresenter as ISkillPresenter or access the methods directly if the presenter type is properly typed.


30-35: Consider parallel loading of skill content.

The sequential await inside 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 with SkillPresenter.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.

discoverSkills clears contentCache even though skill content may not have changed. This forces re-loading content from disk on the next access. Consider only clearing contentCache entries 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 change and add event handlers are async but exceptions aren't caught. If parseSkillMetadata or eventBus.sendToRenderer throws, 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 add handler.


766-781: Consider handling symlinks in copyDirectory.

Similar to buildFolderTree, copyDirectory could 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

📥 Commits

Reviewing files that changed from the base of the PR and between 599dec7 and bcea35f.

📒 Files selected for processing (29)
  • CLAUDE.md
  • docs/specs/skills-system/design.md
  • docs/specs/skills-system/research.md
  • docs/specs/skills-system/tasks.md
  • docs/specs/skills-system/ui-design.md
  • package.json
  • src/main/events.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/configPresenter/index.ts
  • src/main/presenter/index.ts
  • src/main/presenter/sessionPresenter/const.ts
  • src/main/presenter/sessionPresenter/types.ts
  • src/main/presenter/skillPresenter/index.ts
  • src/main/presenter/skillPresenter/skillTools.ts
  • src/main/presenter/sqlitePresenter/tables/conversations.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/renderer/settings/components/SkillsSettings.vue
  • src/renderer/settings/main.ts
  • src/renderer/src/i18n/en-US/routes.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/zh-CN/routes.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/stores/skillsStore.ts
  • src/shared/types/index.d.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/shared/types/presenters/thread.presenter.d.ts
  • src/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.ts
  • src/main/events.ts
  • src/shared/types/presenters/thread.presenter.d.ts
  • src/shared/types/index.d.ts
  • src/shared/types/skill.ts
  • src/main/presenter/sqlitePresenter/tables/conversations.ts
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/renderer/src/stores/skillsStore.ts
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/configPresenter/index.ts
  • src/main/presenter/index.ts
  • src/renderer/settings/main.ts
  • src/main/presenter/skillPresenter/skillTools.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/main/presenter/sessionPresenter/const.ts
  • src/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.ts
  • src/main/events.ts
  • src/shared/types/presenters/thread.presenter.d.ts
  • src/shared/types/index.d.ts
  • src/shared/types/skill.ts
  • src/main/presenter/sqlitePresenter/tables/conversations.ts
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/renderer/src/stores/skillsStore.ts
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/configPresenter/index.ts
  • src/main/presenter/index.ts
  • src/renderer/settings/main.ts
  • src/main/presenter/skillPresenter/skillTools.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/main/presenter/sessionPresenter/const.ts
  • src/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 via mainWindow.webContents.send()
Implement one presenter per functional domain in the main process

Files:

  • src/main/presenter/sessionPresenter/types.ts
  • src/main/presenter/sqlitePresenter/tables/conversations.ts
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/configPresenter/index.ts
  • src/main/presenter/index.ts
  • src/main/presenter/skillPresenter/skillTools.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/main/presenter/sessionPresenter/const.ts
  • src/main/presenter/skillPresenter/index.ts
src/main/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

src/main/**/*.ts: Use EventBus from src/main/eventbus.ts for decoupled inter-process communication
Context isolation must be enabled with preload scripts for secure IPC communication

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

Files:

  • src/main/presenter/sessionPresenter/types.ts
  • src/main/events.ts
  • src/main/presenter/sqlitePresenter/tables/conversations.ts
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/configPresenter/index.ts
  • src/main/presenter/index.ts
  • src/main/presenter/skillPresenter/skillTools.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/main/presenter/sessionPresenter/const.ts
  • src/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.ts
  • src/main/events.ts
  • src/shared/types/presenters/thread.presenter.d.ts
  • src/shared/types/index.d.ts
  • src/shared/types/skill.ts
  • src/main/presenter/sqlitePresenter/tables/conversations.ts
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/renderer/src/stores/skillsStore.ts
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/configPresenter/index.ts
  • src/main/presenter/index.ts
  • src/renderer/settings/main.ts
  • src/main/presenter/skillPresenter/skillTools.ts
  • src/renderer/settings/components/SkillsSettings.vue
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/main/presenter/sessionPresenter/const.ts
  • src/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.ts
  • src/main/events.ts
  • src/shared/types/presenters/thread.presenter.d.ts
  • src/shared/types/index.d.ts
  • src/shared/types/skill.ts
  • src/main/presenter/sqlitePresenter/tables/conversations.ts
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/renderer/src/stores/skillsStore.ts
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/configPresenter/index.ts
  • src/main/presenter/index.ts
  • src/renderer/settings/main.ts
  • src/main/presenter/skillPresenter/skillTools.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/main/presenter/sessionPresenter/const.ts
  • src/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.ts
  • src/main/events.ts
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/en-US/routes.json
  • src/renderer/src/i18n/zh-CN/routes.json
  • src/shared/types/presenters/thread.presenter.d.ts
  • src/shared/types/index.d.ts
  • package.json
  • src/shared/types/skill.ts
  • src/main/presenter/sqlitePresenter/tables/conversations.ts
  • src/renderer/src/i18n/en-US/settings.json
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/renderer/src/stores/skillsStore.ts
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/configPresenter/index.ts
  • src/main/presenter/index.ts
  • src/renderer/settings/main.ts
  • src/main/presenter/skillPresenter/skillTools.ts
  • src/renderer/settings/components/SkillsSettings.vue
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/main/presenter/sessionPresenter/const.ts
  • src/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. Run pnpm run format after completing features

Files:

  • src/main/presenter/sessionPresenter/types.ts
  • src/main/events.ts
  • src/shared/types/presenters/thread.presenter.d.ts
  • src/shared/types/index.d.ts
  • src/shared/types/skill.ts
  • src/main/presenter/sqlitePresenter/tables/conversations.ts
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/renderer/src/stores/skillsStore.ts
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/configPresenter/index.ts
  • src/main/presenter/index.ts
  • src/renderer/settings/main.ts
  • src/main/presenter/skillPresenter/skillTools.ts
  • src/renderer/settings/components/SkillsSettings.vue
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/main/presenter/sessionPresenter/const.ts
  • src/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.json
  • src/renderer/src/i18n/en-US/routes.json
  • src/renderer/src/i18n/zh-CN/routes.json
  • 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-CN/settings.json
  • src/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.json
  • src/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 in src/shared/
IPC contract definitions must be placed in src/shared/

Shared TypeScript types and utilities should be placed in src/shared/

Files:

  • src/shared/types/presenters/thread.presenter.d.ts
  • src/shared/types/index.d.ts
  • src/shared/types/skill.ts
  • src/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.ts composable for renderer-to-main IPC communication via direct presenter method calls

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

Use VueUse composables for common utilities like useLocalStorage, useClipboard, useDebounceFn

Vue 3 renderer app code should be organized in src/renderer/src with subdirectories for components/, stores/, views/, i18n/, and lib/

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}: Use ref for primitives and references, reactive for objects in Vue 3 Composition API
Prefer computed properties over methods for derived state in Vue components
Import Shadcn Vue components from @/shadcn/components/ui/ path alias
Use the cn() utility function combining clsx and tailwind-merge for dynamic Tailwind classes
Use defineAsyncComponent() 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
Use usePresenter composable 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 with defineStore function 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}: Use shallowRef and shallowReactive for optimizing reactivity with large objects
Prefer type over interface in TypeScript unless using inheritance with extends

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.json
  • src/renderer/src/i18n/zh-CN/routes.json
  • src/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.json
  • src/renderer/src/i18n/en-US/routes.json
  • src/renderer/src/i18n/zh-CN/routes.json
  • src/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.json
  • src/renderer/src/i18n/en-US/routes.json
  • src/renderer/src/i18n/zh-CN/routes.json
  • src/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.json
  • src/renderer/src/i18n/en-US/routes.json
  • src/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.json
  • src/renderer/src/i18n/zh-CN/routes.json
  • src/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.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/main/presenter/index.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/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.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/main/presenter/index.ts
  • src/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.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/index.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/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.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • 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: 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.ts
  • 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: 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.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/configPresenter/index.ts
  • src/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.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • 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/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)

Comment on lines +212 to +234
```
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 (完善)
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines 132 to 145
// 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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
// 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.

Comment on lines +499 to +521
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 }
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines 261 to 276
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'
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.
@deepinfect
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 selectDirectory method 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 existing yaml dependency instead of introducing js-yaml.

The repo already standardizes on yaml (v2.8.2), not js-yaml. Revise the code example to import from yaml:

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 during await 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.readFailed exists, 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 the watch(props.skill) loader.

  • When props.skill becomes null, the form keeps the previous skill’s values.
  • Rapidly switching skills can cause an earlier readFile to resolve later and overwrite the newer selection.

Consider clearing fields on null and using a request token (or onInvalidate) 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 compliance
src/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.ts already 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 Fixed won’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 skillName changes. 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 skillName only 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 when node.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 have group-hover on Line 34; the hovering ref + mouseenter/mouseleave can be removed, and adding group-focus-within helps 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.
If result.error can be empty/undefined, the destructive toast becomes unhelpful.

src/renderer/src/i18n/zh-CN/settings.json (1)

1043-1102: Looks good: zh-CN settings.skills is 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/@dragover handlers (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 loading and error state during their operations. This means:

  • The loading flag won't be true during install/uninstall operations (only during loadSkills)
  • Previous error messages remain in error until the next loadSkills call

While 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

📥 Commits

Reviewing files that changed from the base of the PR and between bcea35f and c50217c.

📒 Files selected for processing (42)
  • docs/specs/skills-system/code-review.md
  • docs/specs/skills-system/tasks.md
  • electron-builder.yml
  • package.json
  • resources/skills/code-review/SKILL.md
  • resources/skills/git-commit/SKILL.md
  • src/main/presenter/agentPresenter/message/messageBuilder.ts
  • src/main/presenter/devicePresenter/index.ts
  • src/renderer/settings/components/skills/SkillCard.vue
  • src/renderer/settings/components/skills/SkillEditorSheet.vue
  • src/renderer/settings/components/skills/SkillFolderTree.vue
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/renderer/settings/components/skills/SkillsHeader.vue
  • src/renderer/settings/components/skills/SkillsSettings.vue
  • src/renderer/settings/main.ts
  • src/renderer/src/i18n/da-DK/routes.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/en-US/settings.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
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/ko-KR/routes.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/pt-BR/routes.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/ru-RU/routes.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/zh-HK/routes.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/zh-TW/routes.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/stores/skillsStore.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • test/main/presenter/skillPresenter/skillPresenter.test.ts
  • test/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.ts
  • test/main/presenter/skillPresenter/skillTools.test.ts
  • src/renderer/src/stores/skillsStore.ts
  • test/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.ts
  • test/main/presenter/skillPresenter/skillTools.test.ts
  • src/renderer/src/stores/skillsStore.ts
  • test/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 via mainWindow.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 from src/main/eventbus.ts for decoupled inter-process communication
Context isolation must be enabled with preload scripts for secure IPC communication

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

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.ts
  • src/renderer/settings/components/skills/SkillFolderTree.vue
  • src/renderer/settings/components/skills/SkillCard.vue
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/renderer/settings/components/skills/SkillEditorSheet.vue
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • test/main/presenter/skillPresenter/skillTools.test.ts
  • src/renderer/src/stores/skillsStore.ts
  • test/main/presenter/skillPresenter/skillPresenter.test.ts
  • src/renderer/settings/components/skills/SkillsHeader.vue
  • src/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.ts
  • test/main/presenter/skillPresenter/skillTools.test.ts
  • src/renderer/src/stores/skillsStore.ts
  • test/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.ts
  • src/renderer/src/i18n/pt-BR/routes.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/settings/components/skills/SkillFolderTree.vue
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/ru-RU/routes.json
  • src/renderer/settings/components/skills/SkillCard.vue
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/fr-FR/routes.json
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/settings/components/skills/SkillEditorSheet.vue
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/da-DK/routes.json
  • test/main/presenter/skillPresenter/skillTools.test.ts
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/i18n/zh-HK/routes.json
  • src/renderer/src/stores/skillsStore.ts
  • src/renderer/src/i18n/he-IL/settings.json
  • test/main/presenter/skillPresenter/skillPresenter.test.ts
  • src/renderer/settings/components/skills/SkillsHeader.vue
  • src/renderer/settings/components/skills/SkillsSettings.vue
  • src/renderer/src/i18n/zh-TW/routes.json
  • src/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. Run pnpm run format after completing features

Files:

  • src/main/presenter/devicePresenter/index.ts
  • src/renderer/settings/components/skills/SkillFolderTree.vue
  • src/renderer/settings/components/skills/SkillCard.vue
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/renderer/settings/components/skills/SkillEditorSheet.vue
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • test/main/presenter/skillPresenter/skillTools.test.ts
  • src/renderer/src/stores/skillsStore.ts
  • test/main/presenter/skillPresenter/skillPresenter.test.ts
  • src/renderer/settings/components/skills/SkillsHeader.vue
  • src/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.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/ru-RU/routes.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/fr-FR/routes.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/da-DK/routes.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/i18n/zh-HK/routes.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/zh-TW/routes.json
  • src/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.vue
  • src/renderer/settings/components/skills/SkillCard.vue
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/renderer/settings/components/skills/SkillEditorSheet.vue
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • src/renderer/settings/components/skills/SkillsHeader.vue
  • src/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.vue
  • src/renderer/settings/components/skills/SkillCard.vue
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/renderer/settings/components/skills/SkillEditorSheet.vue
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • src/renderer/settings/components/skills/SkillsHeader.vue
  • src/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.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/i18n/zh-HK/routes.json
  • src/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.ts
  • test/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.ts
  • test/main/presenter/skillPresenter/skillPresenter.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • test/main/presenter/skillPresenter/skillTools.test.ts
  • test/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.ts composable for renderer-to-main IPC communication via direct presenter method calls

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

Use VueUse composables for common utilities like useLocalStorage, useClipboard, useDebounceFn

Vue 3 renderer app code should be organized in src/renderer/src with subdirectories for components/, stores/, views/, i18n/, and lib/

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}: Use ref for primitives and references, reactive for objects in Vue 3 Composition API
Prefer computed properties over methods for derived state in Vue components
Import Shadcn Vue components from @/shadcn/components/ui/ path alias
Use the cn() utility function combining clsx and tailwind-merge for dynamic Tailwind classes
Use defineAsyncComponent() 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
Use usePresenter composable 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 with defineStore function 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}: Use shallowRef and shallowReactive for optimizing reactivity with large objects
Prefer type over interface in TypeScript unless using inheritance with extends

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.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/ru-RU/routes.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/fr-FR/routes.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/da-DK/routes.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/i18n/zh-HK/routes.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/zh-TW/routes.json
  • src/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.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/ru-RU/routes.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/da-DK/routes.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/i18n/zh-HK/routes.json
  • src/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.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/ru-RU/routes.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/da-DK/routes.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/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.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/ru-RU/routes.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/fr-FR/routes.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/da-DK/routes.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/i18n/zh-HK/routes.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/zh-TW/routes.json
  • src/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.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/i18n/zh-HK/routes.json
  • src/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.vue
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • src/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.vue
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/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.vue
  • src/renderer/settings/components/skills/SkillCard.vue
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • 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/SkillFolderTree.vue
  • src/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.json
  • src/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.json
  • src/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.json
  • src/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.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/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.vue
  • src/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)

Comment on lines +278 to +302
// 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'
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +310 to +317
const handleConflictOverwrite = async () => {
conflictDialogOpen.value = false
if (pendingInstallAction.value) {
await pendingInstallAction.value()
pendingInstallAction.value = null
}
conflictSkillName.value = ''
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 1043 to 1102
"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"
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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:

  1. 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
  2. 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.

Comment on lines +113 to +115
const getSkillFolderTree = async (name: string) => {
return await skillPresenter.getSkillFolderTree(name)
}
Copy link
Contributor

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.

Comment on lines +1 to +4
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'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

deepinsect and others added 4 commits January 9, 2026 15:19
…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.
@deepinfect deepinfect marked this pull request as ready for review January 9, 2026 10:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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: Ensure activeSkills always defaults to an array (avoid undefined leaking into persisted settings).

Right now it’s only explicitly reset when inheriting from latestConversation.settings (Line 188). If DEFAULT_SETTINGS doesn’t include it, a new conversation can end up with activeSkills unset.

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., txt or plaintext).

-```
+```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:

  1. Blocking issues that must be fixed before merge
  2. Documented findings for future work/follow-up PRs
  3. 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 initialized flag is set to true anyway. 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} skills

If 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/a11y

Currently, the row is clickable for files too (toggles expanded but has no effect), and the cursor is cursor-default despite 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.message for display

Also consider guarding the error toast against undefined descriptions.

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

📥 Commits

Reviewing files that changed from the base of the PR and between c50217c and 9bcc1ce.

📒 Files selected for processing (19)
  • docs/specs/skills-system/code-review.md
  • docs/specs/skills-system/skills-syncing-antigravity.md
  • docs/specs/skills-system/skills-syncing-claude-code.md
  • docs/specs/skills-system/skills-syncing-copilot.md
  • docs/specs/skills-system/skills-syncing-cursor.md
  • docs/specs/skills-system/skills-syncing-kiro.md
  • docs/specs/skills-system/skills-syncing-windsurf.md
  • docs/specs/skills-system/skills-syncing.md
  • package.json
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/sessionPresenter/managers/conversationManager.ts
  • src/main/presenter/skillPresenter/index.ts
  • src/renderer/settings/components/skills/SkillEditorSheet.vue
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/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.ts
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/sessionPresenter/managers/conversationManager.ts
  • src/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.ts
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/sessionPresenter/managers/conversationManager.ts
  • src/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 in src/shared/
IPC contract definitions must be placed in src/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.ts
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/sessionPresenter/managers/conversationManager.ts
  • src/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.ts
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/sessionPresenter/managers/conversationManager.ts
  • src/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.ts
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/renderer/src/i18n/en-US/settings.json
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/sessionPresenter/managers/conversationManager.ts
  • src/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. Run pnpm run format after completing features

Files:

  • src/shared/types/presenters/thread.presenter.d.ts
  • src/renderer/settings/components/skills/SkillFolderTreeNode.vue
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/sessionPresenter/managers/conversationManager.ts
  • src/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.json
  • 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-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.vue
  • src/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.vue
  • src/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 via mainWindow.webContents.send()
Implement one presenter per functional domain in the main process

Files:

  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/sessionPresenter/managers/conversationManager.ts
  • src/main/presenter/skillPresenter/index.ts
src/main/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

src/main/**/*.ts: Use EventBus from src/main/eventbus.ts for decoupled inter-process communication
Context isolation must be enabled with preload scripts for secure IPC communication

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

Files:

  • src/main/presenter/agentPresenter/message/skillsPromptBuilder.ts
  • src/main/presenter/sessionPresenter/managers/conversationManager.ts
  • src/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.md
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/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.json
  • src/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.json
  • src/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.json
  • src/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.json
  • src/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.json
  • src/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.json
  • src/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.json
  • src/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.json
  • src/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.json
  • src/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 grayMatter for YAML frontmatter parsing (line 127) and assume a specific structure (data.description). Confirm that:

  1. The gray-matter version in package.json (^4.0.3) is compatible with this usage
  2. The backend SkillPresenter also uses gray-matter for consistent parsing
  3. 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:

  1. Issue 8 (YAML Injection, Medium severity, lines 224-254): Unsanitized user input in YAML frontmatter could corrupt skill files. The documentation recommends using js-yaml for serialization.

  2. Issue 14 (Symlink Handling, Medium severity, lines 383-412): Symlinks could cause infinite recursion and stack overflow. The documentation recommends checking entry.isSymbolicLink().

  3. 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.vue and src/main/presenter/skillPresenter/index.ts has 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 normalize activeSkills to [] 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 IConfigPresenter follows good design patterns. The path resolution logic properly handles both configured and default paths, with safe directory creation using recursive: 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 allowedTools array (Lines 167-169). The fallback to dirName (Line 163) ensures robustness.


181-191: LGTM! Excellent race condition protection pattern.

The discoveryPromise pattern (Lines 183-187) elegantly prevents concurrent discovery calls, ensuring thread-safe lazy initialization. The finally block 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 bounds

This 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 finally block 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:

  • getActiveSkills auto-corrects invalid skills (Lines 705-709), improving UX
  • setActiveSkills calculates 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 sessionPresenter provides 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)
  • awaitWriteFinish prevents partial-write race conditions (Lines 797-800)
  • Depth limiting and ignoreInitial optimize 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:

  • stopWatching includes null checks and logging (Lines 859-863)
  • copyDirectory skips symlinks consistently (Lines 876-878) with other traversal methods
  • destroy thoroughly 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.skillPresenter is typed as ISkillPresenter (not optional) and is always initialized in the presenter's constructor as new SkillPresenter(this.configPresenter). While isSkillsEnabled() 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 by isSkillsEnabled() checks and wrapped in try-catch blocks that return safe defaults, providing defensive safeguards even if skills are disabled.

The direct import of SkillPresenter on line 2 is unused for runtime behavior and the type assertions are safe given the non-optional typing of the property.

Comment on lines +17 to +32
```
~/.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
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +17 to +24
```
.cursor/commands/
├── code-review.md
├── refactor.md
├── lint-suite.md
├── create-pr.md
└── optimize-performance.md
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +17 to +25
```
.kiro/steering/
├── product.md # 产品概述(always included)
├── tech.md # 技术栈(always included)
├── structure.md # 项目结构(always included)
├── react-components.md # 条件包含
├── api-patterns.md # 按需引用
└── testing-guidelines.md # 按需引用
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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 -->

Comment on lines +201 to +226
```
┌─────────────────────────────────────────────────────────────────────┐
│ SkillSyncPresenter │
│ 统一的同步协调器,管理导入/导出流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────┐ ┌───────────────────┐ │
│ │ ToolScanner │ │ FormatConverter │ │
│ │ 扫描外部工具目录 │ │ 格式转换引擎 │ │
│ └─────────┬─────────┘ └─────────┬─────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Format Adapters (插件化) │ │
│ │ 每个外部工具一个 Adapter,负责解析和序列化该工具的格式 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
│ 调用
┌─────────────────────────────────────────────────────────────────────┐
│ SkillPresenter │
│ 现有的 Skill 管理器(安装/卸载/读写) │
└─────────────────────────────────────────────────────────────────────┘
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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)

Comment on lines +28 to +45
<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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.
@deepinfect
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

✅ Actions performed

Full review triggered.

@deepinfect deepinfect requested a review from zerob13 January 9, 2026 13:25
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 bash or text language 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: clarify supportsReferences semantics.

serialize() can inline references, but getCapabilities() reports supportsReferences: 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, and importProgress.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:

  1. Implement the event listener to update importProgress as the import proceeds, or
  2. 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 clickable div only).

At minimum add role="button", tabindex="0", and @keydown.enter/@keydown.space handlers; 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 returns false even if root is writable (edge case, but the function claims “nearest existing ancestor”). Easiest fix: check root once 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 Shadcn Input component 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: Prefer export type * here if these modules are types-only.

This file is a .d.ts barrel and already uses export type * for other entries; using export type * from './skill' / ./skillSync' keeps the public surface clearly type-only (helpful under strict TS / verbatimModuleSyntax setups). If either module exports runtime values, keep export * as-is.

src/renderer/settings/components/skills/SkillSyncDialog/SkillSelector.vue (1)

112-114: allSelected should check membership, not just array lengths.

Length equality can be wrong with duplicates or if selectedSkills contains names not in skills.

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 const on 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, but ConflictStrategy.SKIP is 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 text or mermaid (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 titleToName and nameToTitle methods 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 getToolIcon and getToolIconBg functions 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]+)/g uses [^\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 needsQuoting function 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-yaml for safer serialization, or expand the quoting rules to cover these cases. Since gray-matter is already a dependency, you might leverage its stringify capabilities.

src/shared/types/skillSync.ts (1)

170-179: Consider adding documentation for MERGE strategy.

The ConflictStrategy enum includes a MERGE option with the comment "only for specific scenarios". Consider adding JSDoc comments explaining when MERGE is 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; consider type aliases if you’re standardizing away from interface.

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). If gray-matter stringify (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 typing skillPresenter: ISkillPresenter and 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() iterates Map.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 replacing console.* 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 has detectAdapter).

autoDetectAndParse() re-implements the registry iteration. Consider calling detectAdapter() from ./adapters to keep behavior consistent and centralized.

Comment on lines +77 to +105
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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f "antigravityAdapter" --type file

Repository: ThinkInAIXYZ/deepchat

Length of output: 208


🏁 Script executed:

cat -n src/main/presenter/skillSyncPresenter/adapters/antigravityAdapter.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 5343


🏁 Script executed:

fd -t f "antigravity" -i | head -20

Repository: ThinkInAIXYZ/deepchat

Length of output: 263


🏁 Script executed:

rg -i "antigravity" --type ts --type json | head -30

Repository: ThinkInAIXYZ/deepchat

Length of output: 3372


🏁 Script executed:

cat -n docs/specs/skills-system/skills-syncing-antigravity.md

Repository: ThinkInAIXYZ/deepchat

Length of output: 6397


🏁 Script executed:

cat -n test/main/presenter/skillSyncPresenter/adapters/antigravityAdapter.test.ts

Repository: 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.

Comment on lines +31 to +56
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'
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine the claudeCodeAdapter.ts file
find . -name "claudeCodeAdapter.ts" -type f

Repository: 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.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 8477


🏁 Script executed:

# Check the shared types to understand CanonicalSkill and FormatCapabilities
find . -path "*shared/types*" -name "*.ts" | head -20

Repository: ThinkInAIXYZ/deepchat

Length of output: 931


🏁 Script executed:

# Search for other adapters to understand the pattern
find . -path "*adapters*" -name "*Adapter.ts" -type f

Repository: 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 -20

Repository: ThinkInAIXYZ/deepchat

Length of output: 96


🏁 Script executed:

# Search for CanonicalSkill definition
rg "interface CanonicalSkill|type CanonicalSkill" -A 20

Repository: 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 -100

Repository: 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/*.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 1828


🏁 Script executed:

# Check how antigravityAdapter handles extractName
sed -n '126,140p' src/main/presenter/skillSyncPresenter/adapters/antigravityAdapter.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 501


🏁 Script executed:

# Check copilotAdapter's extractName implementation
sed -n '169,185p' src/main/presenter/skillSyncPresenter/adapters/copilotAdapter.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 580


🏁 Script executed:

# Check if any adapter imports path module
grep -n "import.*path" src/main/presenter/skillSyncPresenter/adapters/*.ts

Repository: 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.ts

Repository: 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 model is a string and tags is a string array before assignment
  • Updating supportsModel: true to match actual behavior
  • Using path.basename() instead of split('/') 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).

Comment on lines 19 to 37
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)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +189 to +235
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 }
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +438 to +464
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
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested 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
}
}
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).

Comment on lines +56 to +60
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'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +74 to +123
// 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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

“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.

Comment on lines +17 to +18
"next": "Далее",
"back": "Назад",
Copy link
Contributor

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.json

Repository: 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}")
PY

Repository: 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)")
PY

Repository: 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
done

Repository: 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 -25

Repository: 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.json

Repository: 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).

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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' into enabled_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: Validate activeSkills JSON shape (ensure it’s always an array) + keep TS comments English-only.

  • getJsonField(result.active_skills, []) can return null/object if the stored JSON is unexpected; downstream likely expects string[].
  • 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; ensure activeSkills is 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 for DEFAULT_SETTINGS.

The current pattern creates a real risk: when latestConversation is absent, defaultSettings = DEFAULT_SETTINGS followed by shallow copies (e.g., { ...defaultSettings }) means activeSkills and acpWorkdirMap share 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 (\). Use node: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 ## Steps structure.

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: supportsModel declares false but adapter actively parses and serializes model.

ClaudeCodeAdapter.getCapabilities() reports supportsModel: false, yet the parse() method extracts the model field (line 48) and serialize() 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: true if 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 in index.ts), Antigravity-formatted files will be consistently misidentified.

Fix by narrowing Copilot's detection to require specific fields like agent or tools, 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 an afterEach hook 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-label or <span class="sr-only">{{ t('...') }}</span> with descriptive text for each button
  • Replace the hover-only visibility with group-focus-within:opacity-100 in addition to group-hover:opacity-100 to show buttons on keyboard focus
  • Remove the hovering ref and simplify to CSS-based state management
  • Ensure i18n keys exist for settings.skills.card.edit and settings.skills.card.delete
Proposed 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.5 and the input uses pl-8, which are directional utilities that don't automatically flip when dir="rtl" is applied to individual elements. The dir attribute 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 :class bindings 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 skills section 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: toggleAll doesn'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 skills section 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 via emit('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 previewImport to 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 translations

The skills section 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: supportsDescription should be true: Kiro format fully implements description parsing and serialization via blockquote

The implementation actively extracts descriptions from blockquotes in the parse() method (lines 162–205) and serializes them back as blockquotes in the serialize() method (lines 88–90). Tests explicitly validate this behavior in lines 161–175 (extraction) and 264–274 (serialization). The capability flag declaring supportsDescription: false contradicts 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 add role="button", tabindex="0", and @keydown.enter/@keydown.space so users can select tools via keyboard.

src/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.ts-48-55 (1)

48-55: Fix capability mismatch: adapter reads and writes model but reports supportsModel: false.

ClaudeCodeAdapter reads model from frontmatter during parse (line 48) and conditionally writes it during serialize (lines 78–79), yet getCapabilities() reports supportsModel: false. This is inconsistent with every other adapter: Copilot sets supportsModel: true and reads/writes model; Cursor, Kiro, Windsurf, and Antigravity set supportsModel: false and 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.lastModified will serialize to string across IPC, causing runtime type mismatches.

When scanExternalTools() or other ISkillSyncPresenter methods are called from the renderer (via the generic presenter:call IPC handler), the lastModified: Date fields 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 declares Date, breaking code that calls Date methods or uses instanceof Date checks. Use lastModifiedMs: number or update the type to Date | string instead.

src/renderer/src/i18n/en-US/settings.json-1103-1173 (1)

1103-1173: The sync sub-section is missing from fa-IR locale.

The en-US file includes a comprehensive skills.sync section (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 skills namespace 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 text for 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-review

Also 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. text for ASCII UI diagrams, typescript for TS snippets, and json for 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 between detect() and parse().

detect() requires both name and description (strict by design for auto-detection), while parse() uses folder path fallback when name is missing. This split is intentional—autoDetectAndParse() gates parse() with detect(), but parseExternal() calls parse() directly with explicit toolId. Consider adding a comment explaining that the lenient fallback in parse() 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 chars

Either 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: Ensure activeSkills is normalized to [] at consumption points

Optional type is fine for migration compatibility, but all consumers must normalize undefined to [] to prevent potential scattered nullish checks. Verify that code accessing activeSkills follows the established pattern: conversation?.settings?.activeSkills || [] or equivalent defaults on retrieval.

src/shared/types/skillSync.ts-203-223 (1)

203-223: Minor naming consistency: targetTool vs targetToolId.

Consider aligning to targetToolId (to match other APIs like previewExport(..., 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 > 0 gates selection, but the list still looks selectable and shows count: 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 use trimStart() 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:

  1. Removing the progress display and showing a simple spinner
  2. 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, and on-demand. However, the detection logic at lines 116-117 only checks for always and conditional, missing the on-demand mode.

🔧 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)

Comment on lines +54 to +58
// 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()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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 = finalInstructions

Alternatively, 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.

Comment on lines 1043 to 1102
"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"
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 _remember parameter.

The _remember parameter 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 future

Or 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 AgentFileSystemHandler is created on each callFileSystemTool invocation 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: false parameter allows reads to access any file outside allowedDirectories (including system files like /etc/passwd or ~/.ssh/). This appears intentional based on the code comment at line 1083 ("Preserve matches without allowlist filtering for read operations") and is consistent across readFile, listDirectory, grepSearch, directoryTree, getFileInfo, and globSearch.

Write operations enforce the allowlist strictly via both the default enforceAllowed: true and runtime permission checks in assertWritePermission. The asymmetry is deliberate: writes are constrained to allowedDirectories, 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

📥 Commits

Reviewing files that changed from the base of the PR and between e03fd04 and 0c2cc34.

📒 Files selected for processing (19)
  • resources/skills/infographic-syntax-creator/SKILL.md
  • resources/skills/infographic-syntax-creator/references/prompt.md
  • src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/main/presenter/agentPresenter/permission/permissionHandler.ts
  • src/main/presenter/index.ts
  • src/main/presenter/permission/filePermissionService.ts
  • src/main/presenter/permission/index.ts
  • src/main/presenter/sessionPresenter/index.ts
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/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.json
  • src/main/presenter/sessionPresenter/index.ts
  • src/main/presenter/permission/filePermissionService.ts
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/main/presenter/permission/index.ts
  • src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/main/presenter/agentPresenter/permission/permissionHandler.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/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.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/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.json
  • src/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.ts
  • src/main/presenter/permission/filePermissionService.ts
  • src/main/presenter/permission/index.ts
  • src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts
  • src/main/presenter/agentPresenter/permission/permissionHandler.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/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.ts
  • src/main/presenter/permission/filePermissionService.ts
  • src/main/presenter/permission/index.ts
  • src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts
  • src/main/presenter/agentPresenter/permission/permissionHandler.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/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 via mainWindow.webContents.send()
Implement one presenter per functional domain in the main process

Files:

  • src/main/presenter/sessionPresenter/index.ts
  • src/main/presenter/permission/filePermissionService.ts
  • src/main/presenter/permission/index.ts
  • src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts
  • src/main/presenter/agentPresenter/permission/permissionHandler.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/main/presenter/index.ts
src/main/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

src/main/**/*.ts: Use EventBus from src/main/eventbus.ts for decoupled inter-process communication
Context isolation must be enabled with preload scripts for secure IPC communication

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

Files:

  • src/main/presenter/sessionPresenter/index.ts
  • src/main/presenter/permission/filePermissionService.ts
  • src/main/presenter/permission/index.ts
  • src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts
  • src/main/presenter/agentPresenter/permission/permissionHandler.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/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.ts
  • src/main/presenter/permission/filePermissionService.ts
  • src/main/presenter/permission/index.ts
  • src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts
  • src/main/presenter/agentPresenter/permission/permissionHandler.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/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.ts
  • src/main/presenter/permission/filePermissionService.ts
  • src/main/presenter/permission/index.ts
  • src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts
  • src/main/presenter/agentPresenter/permission/permissionHandler.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/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. Run pnpm run format after completing features

Files:

  • src/main/presenter/sessionPresenter/index.ts
  • src/main/presenter/permission/filePermissionService.ts
  • src/main/presenter/permission/index.ts
  • src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts
  • src/main/presenter/agentPresenter/permission/permissionHandler.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • src/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.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/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.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/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.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/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.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/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.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/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.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/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.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/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.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/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.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.ts
  • src/main/presenter/agentPresenter/acp/agentToolManager.ts
  • 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 **/*.{ts,tsx,js,jsx} : Use English for logs and comments in TypeScript/JavaScript code

Applied to files:

  • src/renderer/src/i18n/zh-HK/settings.json
  • src/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.ts
  • 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/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.ts
  • src/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 skills namespace 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.resolve followed by path.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 PathValidationOptions interface 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 enableSkills config 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_control schema uses .refine() to ensure either skill_name or skills is 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 buildAllowedDirectories method 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 assertWritePermission method checks each target path against the allowed directories and throws FilePermissionRequiredError with the denied paths. This enables the permission UI to prompt for specific paths.


878-913: Skill tool execution with proper validation.

The callSkillTool method:

  • Checks skills are enabled before execution
  • Validates arguments with Zod schema
  • Properly handles both skill_name and skills array inputs
  • Returns structured JSON responses
src/renderer/src/i18n/ru-RU/settings.json (1)

1043-1174: No action required. All settings.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.

Comment on lines +825 to +830
private getSkillTools(): SkillTools {
if (!this.skillTools) {
this.skillTools = new SkillTools(presenter.skillPresenter)
}
return this.skillTools
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if AgentToolManager has a destroy method
rg -n "destroy\s*\(\)" src/main/presenter/agentPresenter/acp/agentToolManager.ts

Repository: 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.ts

Repository: 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.ts

Repository: 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.ts

Repository: 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.ts

Repository: 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.ts

Repository: 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.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 3761


🏁 Script executed:

#!/bin/bash
# Check the SkillTools class definition
cat -n src/main/presenter/skillPresenter/skillTools.ts | head -100

Repository: ThinkInAIXYZ/deepchat

Length of output: 2851


🏁 Script executed:

#!/bin/bash
# Search for constructor in agentToolManager
rg -n "constructor" src/main/presenter/agentPresenter/acp/agentToolManager.ts

Repository: 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 -30

Repository: 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.ts

Repository: 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.ts

Repository: 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 -20

Repository: 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.

Comment on lines 1044 to 1102
"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"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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.json

Repository: 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"
fi

Repository: 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/null

Repository: 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.

Comment on lines 1044 to 1102
"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"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 1043 to 1102
"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"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 1106 to 1107
"conflictDescription": "동일한 이름의 스킬 \\\"{name}\\\"이 이미 존재합니다.",
"deselectAll": "전체 선택 취소",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
"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.

Comment on lines 1042 to 1174
},
"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})..."
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 1043 to 1096
"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": "技能創建後名稱不可更改"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 id and name properties.

Consider refactoring to reduce duplication:

  • Option 1: Add a protected method in ClaudeCodeAdapter that 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.id if 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 mutating super.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 await in 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

📥 Commits

Reviewing files that changed from the base of the PR and between f01a8b0 and 5683b52.

📒 Files selected for processing (13)
  • src/main/presenter/skillSyncPresenter/adapters/claudeCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/codexAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/index.ts
  • src/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/toolScanner.ts
  • test/main/presenter/skillSyncPresenter/adapters/cursorAdapter.test.ts
  • test/main/presenter/skillSyncPresenter/adapters/index.test.ts
  • test/main/presenter/skillSyncPresenter/index.test.ts
  • test/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.ts
  • src/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/toolScanner.ts
  • src/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/codexAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/index.ts
  • src/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.ts
  • src/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/toolScanner.ts
  • src/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/codexAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/index.ts
  • src/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 via mainWindow.webContents.send()
Implement one presenter per functional domain in the main process

Files:

  • src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/toolScanner.ts
  • src/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/codexAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/index.ts
  • src/main/presenter/skillSyncPresenter/adapters/cursorAdapter.ts
src/main/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

src/main/**/*.ts: Use EventBus from src/main/eventbus.ts for decoupled inter-process communication
Context isolation must be enabled with preload scripts for secure IPC communication

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

Files:

  • src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/toolScanner.ts
  • src/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/codexAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/index.ts
  • src/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.ts
  • src/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/toolScanner.ts
  • src/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/codexAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/index.ts
  • src/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.ts
  • src/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/toolScanner.ts
  • src/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/codexAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/index.ts
  • src/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.ts
  • src/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/toolScanner.ts
  • src/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/codexAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/index.ts
  • src/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. Run pnpm run format after completing features

Files:

  • src/main/presenter/skillSyncPresenter/adapters/gooseAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/copilotUserAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/openCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/toolScanner.ts
  • src/main/presenter/skillSyncPresenter/adapters/kiloCodeAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/codexAdapter.ts
  • src/main/presenter/skillSyncPresenter/adapters/index.ts
  • src/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.ts
  • src/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.promises for 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 with isProjectLevel: 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 false gracefully.


311-371: LGTM!

The scanTool method 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.

Comment on lines +29 to +47
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)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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

deepinfect added 2 commits January 11, 2026 11:15
…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.
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 if content is malformed. Since content is 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, causing updateSlashMentionData() to be called three times during initialization. Consider consolidating the watchers or removing immediate: true from 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) and checkAndShow (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 component and popup variables 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5683b52 and 425ecdd.

📒 Files selected for processing (46)
  • .gitignore
  • docs/specs/skills-ux-redesign/analysis.md
  • docs/specs/skills-ux-redesign/spec.md
  • docs/specs/skills-ux-redesign/tasks.md
  • package.json
  • src/renderer/settings/components/skills/SkillsSettings.vue
  • src/renderer/settings/components/skills/SyncPromptDialog.vue
  • src/renderer/settings/components/skills/SyncStatusCard.vue
  • src/renderer/settings/components/skills/SyncStatusSection.vue
  • src/renderer/src/components/NewThread.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • src/renderer/src/components/chat-input/SkillsPanel.vue
  • src/renderer/src/components/chat-input/composables/useMentionData.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/src/components/chat-input/composables/useSlashMentionData.ts
  • src/renderer/src/components/editor/mention/MentionList.vue
  • src/renderer/src/components/editor/mention/slashMention.ts
  • src/renderer/src/components/editor/mention/slashSuggestion.ts
  • src/renderer/src/components/editor/mention/suggestion.ts
  • src/renderer/src/events.ts
  • src/renderer/src/i18n/da-DK/routes.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/en-US/chat.json
  • src/renderer/src/i18n/en-US/settings.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
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/ko-KR/routes.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/pt-BR/routes.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/ru-RU/routes.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/zh-CN/chat.json
  • src/renderer/src/i18n/zh-CN/routes.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/zh-HK/routes.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/zh-TW/routes.json
  • src/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.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/src/components/editor/mention/slashMention.ts
  • src/renderer/src/components/chat-input/composables/useMentionData.ts
  • src/renderer/src/events.ts
  • src/renderer/src/components/editor/mention/slashSuggestion.ts
  • src/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.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/src/components/editor/mention/slashMention.ts
  • src/renderer/src/components/chat-input/composables/useMentionData.ts
  • src/renderer/src/events.ts
  • src/renderer/src/components/editor/mention/slashSuggestion.ts
  • src/renderer/src/components/editor/mention/suggestion.ts
src/renderer/src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use usePresenter.ts composable for renderer-to-main IPC communication via direct presenter method calls

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

Use VueUse composables for common utilities like useLocalStorage, useClipboard, useDebounceFn

Vue 3 renderer app code should be organized in src/renderer/src with subdirectories for components/, stores/, views/, i18n/, and lib/

Files:

  • src/renderer/src/components/chat-input/composables/useSlashMentionData.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/src/components/NewThread.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • src/renderer/src/components/chat-input/SkillsPanel.vue
  • src/renderer/src/components/editor/mention/slashMention.ts
  • src/renderer/src/components/chat-input/composables/useMentionData.ts
  • src/renderer/src/events.ts
  • src/renderer/src/components/editor/mention/slashSuggestion.ts
  • src/renderer/src/components/editor/mention/MentionList.vue
  • src/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.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/settings/components/skills/SkillsSettings.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/settings/components/skills/SyncStatusCard.vue
  • src/renderer/src/components/NewThread.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • src/renderer/settings/components/skills/SyncPromptDialog.vue
  • src/renderer/src/components/chat-input/SkillsPanel.vue
  • src/renderer/src/components/editor/mention/slashMention.ts
  • src/renderer/src/components/chat-input/composables/useMentionData.ts
  • src/renderer/src/events.ts
  • src/renderer/settings/components/skills/SyncStatusSection.vue
  • src/renderer/src/components/editor/mention/slashSuggestion.ts
  • src/renderer/src/components/editor/mention/MentionList.vue
  • src/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.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/src/components/editor/mention/slashMention.ts
  • src/renderer/src/components/chat-input/composables/useMentionData.ts
  • src/renderer/src/events.ts
  • src/renderer/src/components/editor/mention/slashSuggestion.ts
  • src/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.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/settings/components/skills/SkillsSettings.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/settings/components/skills/SyncStatusCard.vue
  • src/renderer/src/components/NewThread.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • src/renderer/settings/components/skills/SyncPromptDialog.vue
  • src/renderer/src/components/chat-input/SkillsPanel.vue
  • src/renderer/src/components/editor/mention/slashMention.ts
  • src/renderer/src/i18n/en-US/chat.json
  • src/renderer/src/components/chat-input/composables/useMentionData.ts
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/events.ts
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/settings/components/skills/SyncStatusSection.vue
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/components/editor/mention/slashSuggestion.ts
  • src/renderer/src/i18n/ko-KR/routes.json
  • src/renderer/src/components/editor/mention/MentionList.vue
  • src/renderer/src/i18n/ja-JP/settings.json
  • package.json
  • src/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}: Use ref for primitives and references, reactive for objects in Vue 3 Composition API
Prefer computed properties over methods for derived state in Vue components
Import Shadcn Vue components from @/shadcn/components/ui/ path alias
Use the cn() utility function combining clsx and tailwind-merge for dynamic Tailwind classes
Use defineAsyncComponent() 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
Use usePresenter composable for main process communication instead of direct IPC calls

Files:

  • src/renderer/src/components/chat-input/composables/useSlashMentionData.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/src/components/NewThread.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • src/renderer/src/components/chat-input/SkillsPanel.vue
  • src/renderer/src/components/editor/mention/slashMention.ts
  • src/renderer/src/components/chat-input/composables/useMentionData.ts
  • src/renderer/src/events.ts
  • src/renderer/src/components/editor/mention/slashSuggestion.ts
  • src/renderer/src/components/editor/mention/MentionList.vue
  • src/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.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/src/components/editor/mention/slashMention.ts
  • src/renderer/src/components/chat-input/composables/useMentionData.ts
  • src/renderer/src/events.ts
  • src/renderer/src/components/editor/mention/slashSuggestion.ts
  • src/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}: Use shallowRef and shallowReactive for optimizing reactivity with large objects
Prefer type over interface in TypeScript unless using inheritance with extends

Files:

  • src/renderer/src/components/chat-input/composables/useSlashMentionData.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/src/components/editor/mention/slashMention.ts
  • src/renderer/src/components/chat-input/composables/useMentionData.ts
  • src/renderer/src/events.ts
  • src/renderer/src/components/editor/mention/slashSuggestion.ts
  • src/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. Run pnpm run format after completing features

Files:

  • src/renderer/src/components/chat-input/composables/useSlashMentionData.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/settings/components/skills/SkillsSettings.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/settings/components/skills/SyncStatusCard.vue
  • src/renderer/src/components/NewThread.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • src/renderer/settings/components/skills/SyncPromptDialog.vue
  • src/renderer/src/components/chat-input/SkillsPanel.vue
  • src/renderer/src/components/editor/mention/slashMention.ts
  • src/renderer/src/components/chat-input/composables/useMentionData.ts
  • src/renderer/src/events.ts
  • src/renderer/settings/components/skills/SyncStatusSection.vue
  • src/renderer/src/components/editor/mention/slashSuggestion.ts
  • src/renderer/src/components/editor/mention/MentionList.vue
  • src/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.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/settings/components/skills/SyncStatusCard.vue
  • src/renderer/src/components/NewThread.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • src/renderer/settings/components/skills/SyncPromptDialog.vue
  • src/renderer/src/components/chat-input/SkillsPanel.vue
  • src/renderer/settings/components/skills/SyncStatusSection.vue
  • src/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.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/settings/components/skills/SyncStatusCard.vue
  • src/renderer/src/components/NewThread.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • src/renderer/settings/components/skills/SyncPromptDialog.vue
  • src/renderer/src/components/chat-input/SkillsPanel.vue
  • src/renderer/settings/components/skills/SyncStatusSection.vue
  • src/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 using defineProps and defineEmits with TypeScript interfaces
Use provide/inject for 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 pattern lucide:{icon-name}
Use v-memo directive for memoizing expensive computations in templates
Use v-once directive for rendering static content without reactivity updates
Use virtual scrolling with RecycleScroller component for rendering long lists
Subscribe to events using rendererEvents.on() and unsubscribe in onUnmounted lifecycle hook

Files:

  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/src/components/NewThread.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • src/renderer/src/components/chat-input/SkillsPanel.vue
  • src/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.vue
  • src/renderer/src/components/NewThread.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • src/renderer/src/components/chat-input/SkillsPanel.vue
  • src/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.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ko-KR/routes.json
  • src/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.json
  • src/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.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/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.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/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.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/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.ts
  • src/renderer/src/components/chat-input/composables/useSkillsData.ts
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/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.vue
  • src/renderer/settings/components/skills/SyncStatusCard.vue
  • 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} : Define TypeScript interfaces for Vue component props and data structures

Applied to files:

  • src/renderer/settings/components/skills/SkillsSettings.vue
  • src/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.vue
  • 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 : Define props and emits explicitly in Vue components using `defineProps` and `defineEmits` with TypeScript interfaces

Applied to files:

  • src/renderer/settings/components/skills/SkillsSettings.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • 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/**/*.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.vue
  • src/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.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ko-KR/routes.json
  • src/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.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ko-KR/routes.json
  • src/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.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ko-KR/routes.json
  • src/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.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • 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: 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.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ko-KR/routes.json
  • src/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.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +58 to +67
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 ?? ''
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +30 to +31
<p v-if="skill.allowedTools?.length" class="text-xs text-muted-foreground">
Tools: {{ skill.allowedTools.join(', ') }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines 1043 to 1102
"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"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the French translation file at the specified lines
wc -l src/renderer/src/i18n/fr-FR/settings.json

Repository: 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 -80

Repository: 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.json

Repository: 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 -80

Repository: 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
done

Repository: 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 -60

Repository: 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 -60

Repository: 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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 1043 to 1096
"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作成後、名称は変更できません。"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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:

  1. Translate all English strings to Japanese, or
  2. 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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines 1043 to 1095
"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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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:

  1. Translate all English strings to Korean, or
  2. 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between be05e51 and 96e0c94.

📒 Files selected for processing (20)
  • src/renderer/src/i18n/da-DK/chat.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/ko-KR/chat.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/pt-BR/chat.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/ru-RU/chat.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/zh-HK/chat.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/zh-TW/chat.json
  • src/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.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • src/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.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/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.json
  • 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 **/*.{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.

Comment on lines 1043 to 1102
"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"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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 install block
  • Lines 1073-1079: The entire delete block
  • Lines 1080-1095: Most of the edit block (except nameHint)
  • Lines 1098-1102: The conflict block

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.

Comment on lines 1044 to 1102
"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"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 1043 to 1102
"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"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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: install block
  • Lines 1073-1079: delete block
  • Lines 1080-1095: Most of edit block
  • Lines 1098-1102: conflict block

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.

Comment on lines 1043 to 1102
"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"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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 install block is in English
  • Lines 1073-1079: The entire delete block 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.

@zerob13
Copy link
Collaborator

zerob13 commented Jan 13, 2026

LGTM

@zerob13 zerob13 merged commit 8fd50e4 into dev Jan 13, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants