fix(skills): preserve SKILL.md content when renaming
Rename skills by moving the skill directory and updating frontmatter name instead of recreate-with-stub-description, which wiped the body and supporting files. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
This commit is contained in:
co-authored by
Serhii Dziupin
parent
2ba8ae8bd4
commit
f0591515fd
@@ -49,16 +49,16 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) =>
|
||||
skills,
|
||||
setSelectedSkill,
|
||||
setSkillDraft,
|
||||
createSkill,
|
||||
deleteSkill,
|
||||
renameSkill,
|
||||
getSkillDetail,
|
||||
} = useSkillsStore(useShallow((s) => ({
|
||||
selectedSkillName: s.selectedSkillName,
|
||||
skills: s.skills,
|
||||
setSelectedSkill: s.setSelectedSkill,
|
||||
setSkillDraft: s.setSkillDraft,
|
||||
createSkill: s.createSkill,
|
||||
deleteSkill: s.deleteSkill,
|
||||
renameSkill: s.renameSkill,
|
||||
getSkillDetail: s.getSkillDetail,
|
||||
})));
|
||||
|
||||
@@ -169,31 +169,11 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) =>
|
||||
return;
|
||||
}
|
||||
|
||||
// Get full detail to copy
|
||||
const detail = await getSkillDetail(renameDialogSkill.name);
|
||||
if (!detail) {
|
||||
toast.error(t('settings.skills.sidebar.toast.renameLoadFailed'));
|
||||
setRenameDialogSkill(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new skill with new name
|
||||
const success = await createSkill({
|
||||
name: sanitizedName,
|
||||
description: 'Renamed skill', // Will need proper description
|
||||
scope: renameDialogSkill.scope,
|
||||
source: renameDialogSkill.source,
|
||||
});
|
||||
|
||||
// Rename in place on disk so SKILL.md body and supporting files are preserved.
|
||||
const success = await renameSkill(renameDialogSkill.name, sanitizedName);
|
||||
if (success) {
|
||||
// Delete old skill
|
||||
const deleteSuccess = await deleteSkill(renameDialogSkill.name);
|
||||
if (deleteSuccess) {
|
||||
toast.success(`Skill renamed to "${sanitizedName}"`);
|
||||
setSelectedSkill(sanitizedName);
|
||||
} else {
|
||||
toast.error(t('settings.skills.sidebar.toast.removeOldAfterRenameFailed'));
|
||||
}
|
||||
toast.success(t('settings.skills.sidebar.toast.skillRenamed', { name: sanitizedName }));
|
||||
setSelectedSkill(sanitizedName);
|
||||
} else {
|
||||
toast.error(t('settings.skills.sidebar.toast.renameFailed'));
|
||||
}
|
||||
|
||||
@@ -669,6 +669,7 @@ export const settingsDict = {
|
||||
'settings.skills.sidebar.toast.renameLoadFailed': 'Skill-Details konnten nicht geladen werden',
|
||||
'settings.skills.sidebar.toast.removeOldAfterRenameFailed': 'Alter Skill konnte nach Umbenennung nicht entfernt werden',
|
||||
'settings.skills.sidebar.toast.renameFailed': 'Skill konnte nicht umbenannt werden',
|
||||
'settings.skills.sidebar.toast.skillRenamed': 'Skill umbenannt in "{name}"',
|
||||
'settings.skills.sidebar.deleteDialog.title': 'Skill löschen',
|
||||
'settings.skills.sidebar.deleteDialog.description': 'Möchten Sie den Skill "{name}" wirklich löschen?',
|
||||
'settings.skills.sidebar.renameDialog.title': 'Skill umbenennen',
|
||||
|
||||
@@ -721,6 +721,7 @@ export const settingsDict = {
|
||||
'settings.skills.sidebar.toast.renameLoadFailed': 'Failed to load skill details',
|
||||
'settings.skills.sidebar.toast.removeOldAfterRenameFailed': 'Failed to remove old skill after rename',
|
||||
'settings.skills.sidebar.toast.renameFailed': 'Failed to rename skill',
|
||||
'settings.skills.sidebar.toast.skillRenamed': 'Skill renamed to "{name}"',
|
||||
'settings.skills.sidebar.deleteDialog.title': 'Delete Skill',
|
||||
'settings.skills.sidebar.deleteDialog.description': 'Are you sure you want to delete skill "{name}"?',
|
||||
'settings.skills.sidebar.renameDialog.title': 'Rename Skill',
|
||||
|
||||
@@ -688,6 +688,7 @@ export const settingsDict = {
|
||||
"settings.skills.sidebar.toast.renameLoadFailed": "No se pudo cargar la información de la habilidad",
|
||||
"settings.skills.sidebar.toast.removeOldAfterRenameFailed": "No se pudo eliminar la habilidad antigua después del cambio de nombre",
|
||||
"settings.skills.sidebar.toast.renameFailed": "No se pudo cambiar el nombre de la habilidad",
|
||||
"settings.skills.sidebar.toast.skillRenamed": "Habilidad renombrada a \"{name}\"",
|
||||
"settings.skills.sidebar.deleteDialog.title": "Eliminar habilidad",
|
||||
"settings.skills.sidebar.deleteDialog.description": "¿Estás seguro de que quieres eliminar la habilidad \"{name}\"?",
|
||||
"settings.skills.sidebar.renameDialog.title": "Cambiar nombre habilidad",
|
||||
|
||||
@@ -609,6 +609,7 @@ export const settingsDict = {
|
||||
'settings.skills.sidebar.toast.renameLoadFailed': 'Échec du chargement des détails du skill',
|
||||
'settings.skills.sidebar.toast.removeOldAfterRenameFailed': 'Échec de la suppression de l\'ancien skill après le renommage',
|
||||
'settings.skills.sidebar.toast.renameFailed': 'Échec du renommage du skill',
|
||||
'settings.skills.sidebar.toast.skillRenamed': 'Skill renommé en "{name}"',
|
||||
'settings.skills.sidebar.deleteDialog.title': 'Supprimer le skill',
|
||||
'settings.skills.sidebar.deleteDialog.description': 'Êtes-vous sûr de vouloir supprimer le skill « {name} » ?',
|
||||
'settings.skills.sidebar.renameDialog.title': 'Renommer le skill',
|
||||
|
||||
@@ -721,6 +721,7 @@ export const settingsDict = {
|
||||
'settings.skills.sidebar.toast.renameLoadFailed': 'Skill 詳細の読み込みに失敗しました',
|
||||
'settings.skills.sidebar.toast.removeOldAfterRenameFailed': '名前変更後に古い Skill の削除に失敗しました',
|
||||
'settings.skills.sidebar.toast.renameFailed': 'Skill の名前変更に失敗しました',
|
||||
'settings.skills.sidebar.toast.skillRenamed': 'Skill の名前を「{name}」に変更しました',
|
||||
'settings.skills.sidebar.deleteDialog.title': 'Skill を削除',
|
||||
'settings.skills.sidebar.deleteDialog.description': 'Skill "{name}" を削除してもよろしいですか?',
|
||||
'settings.skills.sidebar.renameDialog.title': 'Skill の名前変更',
|
||||
|
||||
@@ -688,6 +688,7 @@ export const settingsDict = {
|
||||
'settings.skills.sidebar.toast.renameLoadFailed': '스킬 세부 정보를 로드하지 못했습니다',
|
||||
'settings.skills.sidebar.toast.removeOldAfterRenameFailed': '이름 변경 후 이전 스킬을 제거하지 못했습니다',
|
||||
'settings.skills.sidebar.toast.renameFailed': '스킬 이름을 변경하지 못했습니다',
|
||||
'settings.skills.sidebar.toast.skillRenamed': '스킬 이름이 "{name}"(으)로 변경되었습니다',
|
||||
'settings.skills.sidebar.deleteDialog.title': '스킬 삭제',
|
||||
'settings.skills.sidebar.deleteDialog.description': '스킬 "{name}"을 삭제하시겠습니까?',
|
||||
'settings.skills.sidebar.renameDialog.title': '스킬 이름 변경',
|
||||
|
||||
@@ -1867,6 +1867,7 @@ export const settingsDict = {
|
||||
'settings.skills.sidebar.toast.renameFailed': 'Nie udało się zmienić nazwy umiejętności',
|
||||
'settings.skills.sidebar.toast.renameLoadFailed': 'Nie udało się załadować szczegółów umiejętności',
|
||||
'settings.skills.sidebar.toast.skillDeleted': 'Umiejętność „{name}” została usunięta',
|
||||
'settings.skills.sidebar.toast.skillRenamed': 'Zmieniono nazwę umiejętności na „{name}”',
|
||||
'settings.skills.sidebar.total': 'Suma: {count}',
|
||||
'settings.usage.pace.prediction': 'Prognoza: {prediction}',
|
||||
'settings.usage.pace.predictionLabel': 'Prognoza: ',
|
||||
|
||||
@@ -688,6 +688,7 @@ export const settingsDict = {
|
||||
"settings.skills.sidebar.toast.renameLoadFailed": "Não foi possível carregar as informações da habilidade",
|
||||
"settings.skills.sidebar.toast.removeOldAfterRenameFailed": "Não foi possível excluir a habilidade antiga depois da renomeação",
|
||||
"settings.skills.sidebar.toast.renameFailed": "Não foi possível renomear da habilidade",
|
||||
"settings.skills.sidebar.toast.skillRenamed": "Habilidade renomeada para \"{name}\"",
|
||||
"settings.skills.sidebar.deleteDialog.title": "Excluir habilidade",
|
||||
"settings.skills.sidebar.deleteDialog.description": "Tem certeza de que deseja excluir a habilidade \"{name}\"?",
|
||||
"settings.skills.sidebar.renameDialog.title": "Renomear habilidade",
|
||||
|
||||
@@ -688,6 +688,7 @@ export const settingsDict = {
|
||||
"settings.skills.sidebar.toast.renameLoadFailed": "Не вдалося завантажити деталі навичок",
|
||||
"settings.skills.sidebar.toast.removeOldAfterRenameFailed": "Не вдалося видалити стару навичку після перейменування",
|
||||
"settings.skills.sidebar.toast.renameFailed": "Не вдалося перейменувати навичку",
|
||||
"settings.skills.sidebar.toast.skillRenamed": "Навичку перейменовано на \"{name}\"",
|
||||
"settings.skills.sidebar.deleteDialog.title": "Видалити навичку",
|
||||
"settings.skills.sidebar.deleteDialog.description": "Ви впевнені, що бажаєте видалити навичку «{name}»?",
|
||||
"settings.skills.sidebar.renameDialog.title": "Перейменувати навичку",
|
||||
|
||||
@@ -688,6 +688,7 @@ export const settingsDict = {
|
||||
'settings.skills.sidebar.toast.renameLoadFailed': '加载技能详情失败',
|
||||
'settings.skills.sidebar.toast.removeOldAfterRenameFailed': '重命名后移除旧技能失败',
|
||||
'settings.skills.sidebar.toast.renameFailed': '重命名技能失败',
|
||||
'settings.skills.sidebar.toast.skillRenamed': '技能已重命名为“{name}”',
|
||||
'settings.skills.sidebar.deleteDialog.title': '删除技能',
|
||||
'settings.skills.sidebar.deleteDialog.description': '确定要删除技能“{name}”吗?',
|
||||
'settings.skills.sidebar.renameDialog.title': '重命名技能',
|
||||
|
||||
@@ -685,6 +685,7 @@
|
||||
'settings.skills.sidebar.toast.renameLoadFailed': '載入 skill 詳情失敗',
|
||||
'settings.skills.sidebar.toast.removeOldAfterRenameFailed': '重新命名後移除舊 skill 失敗',
|
||||
'settings.skills.sidebar.toast.renameFailed': '重新命名 skill 失敗',
|
||||
'settings.skills.sidebar.toast.skillRenamed': 'skill 已重新命名為「{name}」',
|
||||
'settings.skills.sidebar.deleteDialog.title': '刪除 Skill',
|
||||
'settings.skills.sidebar.deleteDialog.description': '確定要刪除 skill「{name}」嗎?',
|
||||
'settings.skills.sidebar.renameDialog.title': '重新命名 Skill',
|
||||
|
||||
@@ -143,6 +143,7 @@ interface SkillsStore {
|
||||
getSkillDetail: (name: string) => Promise<SkillDetail | null>;
|
||||
createSkill: (config: SkillConfig) => Promise<boolean>;
|
||||
updateSkill: (name: string, config: Partial<SkillConfig>) => Promise<boolean>;
|
||||
renameSkill: (name: string, newName: string) => Promise<boolean>;
|
||||
deleteSkill: (name: string) => Promise<boolean>;
|
||||
getSkillByName: (name: string) => DiscoveredSkill | undefined;
|
||||
|
||||
@@ -382,6 +383,50 @@ export const useSkillsStore = create<SkillsStore>()(
|
||||
}
|
||||
},
|
||||
|
||||
renameSkill: async (name: string, newName: string) => {
|
||||
startConfigUpdate("Renaming skill...");
|
||||
let requiresReload = false;
|
||||
try {
|
||||
const currentDirectory = getCurrentDirectory();
|
||||
const queryParams = currentDirectory ? `?directory=${encodeURIComponent(currentDirectory)}` : '';
|
||||
|
||||
const response = await runtimeFetch(`/api/config/skills/${encodeURIComponent(name)}${queryParams}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ renameTo: newName }),
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
const message = payload?.error || 'Failed to rename skill';
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const needsReload = payload?.requiresReload ?? false;
|
||||
invalidateSkillsLoadCache(currentDirectory);
|
||||
if (needsReload) {
|
||||
requiresReload = true;
|
||||
await refreshSkillsAfterOpenCodeRestart({
|
||||
message: payload?.message,
|
||||
delayMs: payload?.reloadDelayMs,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
const loaded = await get().loadSkills();
|
||||
if (loaded) {
|
||||
emitConfigChange("skills", { source: CONFIG_EVENT_SOURCE });
|
||||
}
|
||||
return loaded;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
if (!requiresReload) {
|
||||
finishConfigUpdate();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
deleteSkill: async (name: string) => {
|
||||
startConfigUpdate("Deleting skill...");
|
||||
let requiresReload = false;
|
||||
|
||||
Reference in New Issue
Block a user