Browse Source

fix(install): avoid duplicate skill sync output

Alvin Unreal 1 month ago
parent
commit
c105c9cdb9
2 changed files with 51 additions and 4 deletions
  1. 40 1
      src/cli/install.test.ts
  2. 11 3
      src/cli/install.ts

+ 40 - 1
src/cli/install.test.ts

@@ -43,6 +43,7 @@ const originalManualBackgroundSubagentsInstructions =
 const originalGetExistingLiteConfigPath = actualPaths.getExistingLiteConfigPath;
 
 let importCounter = 0;
+let mockSkippedResult: string[] = [];
 let mockFailedResult: string[] = [];
 let mockStagedResult: string[] = [];
 let mockAdoptedResult: string[] = [];
@@ -56,7 +57,7 @@ mock.module('../hooks/auto-update-checker/skill-sync', () => {
       enableInstallMocks
         ? {
             installed: [],
-            skippedExisting: [],
+            skippedExisting: mockSkippedResult,
             failed: mockFailedResult,
             staged: mockStagedResult,
             adopted: mockAdoptedResult,
@@ -191,6 +192,7 @@ describe('install skill synchronization error mapping', () => {
 
   beforeEach(() => {
     enableInstallMocks = true;
+    mockSkippedResult = [];
     mockFailedResult = [];
     mockStagedResult = [];
     mockAdoptedResult = [];
@@ -341,4 +343,41 @@ describe('install skill synchronization error mapping', () => {
       ),
     ).toBe(true);
   });
+
+  test('does not double-print categorized skipped skills', async () => {
+    mockSkippedResult = ['staged-skill', 'adopted-skill', 'customized-skill'];
+    mockStagedResult = ['staged-skill'];
+    mockAdoptedResult = ['adopted-skill'];
+    mockCustomizedResult = ['customized-skill'];
+    const { install } = await import(`./install?test=${importCounter++}`);
+
+    await install({
+      skills: 'yes',
+      tui: false,
+      companion: 'no',
+    });
+
+    const calls = logSpy.mock.calls.map((call: any[]) => call[0] as string);
+    expect(
+      calls.some((msg: string) =>
+        msg?.includes('Skipped/Preserved: staged-skill'),
+      ),
+    ).toBe(false);
+    expect(
+      calls.some((msg: string) =>
+        msg?.includes('Skipped/Preserved: adopted-skill'),
+      ),
+    ).toBe(false);
+    expect(
+      calls.some((msg: string) =>
+        msg?.includes('Skipped/Preserved: customized-skill'),
+      ),
+    ).toBe(false);
+
+    const summaryMsg = calls.find((msg: string) =>
+      msg?.includes('Skill synchronization complete'),
+    );
+    expect(summaryMsg).toBeDefined();
+    expect(summaryMsg).toContain('0 skipped/preserved');
+  });
 });

+ 11 - 3
src/cli/install.ts

@@ -422,14 +422,22 @@ async function runInstall(config: InstallConfig): Promise<number> {
       try {
         const packageRoot = fileURLToPath(new URL('../..', import.meta.url));
         const result = syncBundledSkillsFromPackage(packageRoot);
+        const categorizedSkipped = new Set([
+          ...result.staged,
+          ...result.adopted,
+          ...result.customized,
+        ]);
+        const preservedSkills = result.skippedExisting.filter(
+          (skill) => !categorizedSkipped.has(skill),
+        );
 
         if (result.installed.length > 0) {
           for (const skill of result.installed) {
             printSuccess(`Installed/Updated: ${skill}`);
           }
         }
-        if (result.skippedExisting.length > 0) {
-          for (const skill of result.skippedExisting) {
+        if (preservedSkills.length > 0) {
+          for (const skill of preservedSkills) {
             printInfo(`Skipped/Preserved: ${skill}`);
           }
         }
@@ -466,7 +474,7 @@ async function runInstall(config: InstallConfig): Promise<number> {
         printSuccess(
           `Skill synchronization complete: ` +
             `${result.installed.length} installed/updated, ` +
-            `${result.skippedExisting.length} skipped/preserved, ` +
+            `${preservedSkills.length} skipped/preserved, ` +
             `${realFailed.length} failed.`,
         );
       } catch (err) {