Skip to content

Commit c4359e5

Browse files
committed
feat(cli): flatten registries and skills to the same level across all config contexts for consistency
1 parent 955e146 commit c4359e5

12 files changed

Lines changed: 92 additions & 296 deletions

File tree

packages/cli/src/__tests__/lib/Config.test.ts

Lines changed: 30 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ describe('ConfigManager', () => {
425425
});
426426

427427
describe('addSkill', () => {
428-
it('adds a new skill entry to config when skills is an array', async () => {
428+
it('adds a new skill entry to config', async () => {
429429
const config: DevKitConfig = {
430430
version: '1.0.0',
431431
environments: ['cursor'],
@@ -444,46 +444,10 @@ describe('ConfigManager', () => {
444444
name: 'memory'
445445
});
446446

447-
expect(result.skills).toEqual({
448-
installed: [
449-
{ registry: 'codeaholicguy/ai-devkit', name: 'debug' },
450-
{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }
451-
]
452-
});
453-
expect(mockFs.writeJson).toHaveBeenCalled();
454-
});
455-
456-
it('adds a new skill entry when skills is an object with registries', async () => {
457-
const config = {
458-
version: '1.0.0',
459-
environments: ['cursor'],
460-
phases: [],
461-
skills: {
462-
registries: {
463-
'custom/repo': 'https://github.com/custom/repo.git'
464-
}
465-
},
466-
createdAt: '2024-01-01T00:00:00.000Z',
467-
updatedAt: '2024-01-01T00:00:00.000Z'
468-
};
469-
470-
(mockFs.pathExists as any).mockResolvedValue(true);
471-
(mockFs.readJson as any).mockResolvedValue(config);
472-
(mockFs.writeJson as any).mockResolvedValue(undefined);
473-
474-
const result = await configManager.addSkill({
475-
registry: 'custom/repo',
476-
name: 'my-skill'
477-
});
478-
479-
expect(result.skills).toEqual({
480-
registries: {
481-
'custom/repo': 'https://github.com/custom/repo.git'
482-
},
483-
installed: [
484-
{ registry: 'custom/repo', name: 'my-skill' }
485-
]
486-
});
447+
expect(result.skills).toEqual([
448+
{ registry: 'codeaholicguy/ai-devkit', name: 'debug' },
449+
{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }
450+
]);
487451
expect(mockFs.writeJson).toHaveBeenCalled();
488452
});
489453

@@ -527,46 +491,15 @@ describe('ConfigManager', () => {
527491
name: 'memory'
528492
});
529493

530-
expect(result.skills).toEqual({
531-
installed: [
532-
{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }
533-
]
534-
});
494+
expect(result.skills).toEqual([
495+
{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }
496+
]);
535497
expect(mockFs.writeJson).toHaveBeenCalled();
536498
});
537499
});
538500

539501
describe('removeSkill', () => {
540-
it('removes the skill entry from the installed array', async () => {
541-
const config: DevKitConfig = {
542-
version: '1.0.0',
543-
environments: ['claude'],
544-
phases: [],
545-
skills: {
546-
installed: [
547-
{ registry: 'codeaholicguy/ai-devkit', name: 'dev-lifecycle' },
548-
{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }
549-
]
550-
},
551-
createdAt: '2024-01-01T00:00:00.000Z',
552-
updatedAt: '2024-01-01T00:00:00.000Z'
553-
};
554-
555-
(mockFs.pathExists as any).mockResolvedValue(true);
556-
(mockFs.readJson as any).mockResolvedValue(config);
557-
(mockFs.writeJson as any).mockResolvedValue(undefined);
558-
559-
const result = await configManager.removeSkill('dev-lifecycle');
560-
561-
expect(result.skills).toEqual({
562-
installed: [
563-
{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }
564-
]
565-
});
566-
expect(mockFs.writeJson).toHaveBeenCalled();
567-
});
568-
569-
it('removes skill when skills is an array', async () => {
502+
it('removes the skill entry from the skills array', async () => {
570503
const config: DevKitConfig = {
571504
version: '1.0.0',
572505
environments: ['claude'],
@@ -585,24 +518,20 @@ describe('ConfigManager', () => {
585518

586519
const result = await configManager.removeSkill('dev-lifecycle');
587520

588-
expect(result.skills).toEqual({
589-
installed: [
590-
{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }
591-
]
592-
});
521+
expect(result.skills).toEqual([
522+
{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }
523+
]);
593524
expect(mockFs.writeJson).toHaveBeenCalled();
594525
});
595526

596-
it('results in an empty installed array when last skill is removed', async () => {
527+
it('results in an empty array when last skill is removed', async () => {
597528
const config: DevKitConfig = {
598529
version: '1.0.0',
599530
environments: ['claude'],
600531
phases: [],
601-
skills: {
602-
installed: [
603-
{ registry: 'codeaholicguy/ai-devkit', name: 'dev-lifecycle' }
604-
]
605-
},
532+
skills: [
533+
{ registry: 'codeaholicguy/ai-devkit', name: 'dev-lifecycle' }
534+
],
606535
createdAt: '2024-01-01T00:00:00.000Z',
607536
updatedAt: '2024-01-01T00:00:00.000Z'
608537
};
@@ -613,20 +542,18 @@ describe('ConfigManager', () => {
613542

614543
const result = await configManager.removeSkill('dev-lifecycle');
615544

616-
expect(result.skills).toEqual({ installed: [] });
545+
expect(result.skills).toEqual([]);
617546
expect(mockFs.writeJson).toHaveBeenCalled();
618547
});
619548

620-
it('is a no-op when skill name does not exist in installed', async () => {
549+
it('is a no-op when skill name does not exist', async () => {
621550
const config: DevKitConfig = {
622551
version: '1.0.0',
623552
environments: ['claude'],
624553
phases: [],
625-
skills: {
626-
installed: [
627-
{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }
628-
]
629-
},
554+
skills: [
555+
{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }
556+
],
630557
createdAt: '2024-01-01T00:00:00.000Z',
631558
updatedAt: '2024-01-01T00:00:00.000Z'
632559
};
@@ -637,9 +564,9 @@ describe('ConfigManager', () => {
637564

638565
const result = await configManager.removeSkill('nonexistent');
639566

640-
expect(result.skills).toEqual({
641-
installed: [{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }]
642-
});
567+
expect(result.skills).toEqual([
568+
{ registry: 'codeaholicguy/ai-devkit', name: 'memory' }
569+
]);
643570
});
644571

645572
it('throws when config file is not found', async () => {
@@ -651,100 +578,16 @@ describe('ConfigManager', () => {
651578
});
652579
});
653580

654-
describe('normalizeSkillsConfig', () => {
655-
it('normalizes an array to SkillsConfig', () => {
656-
const result = configManager.normalizeSkillsConfig([
657-
{ registry: 'r', name: 's' }
658-
]);
659-
expect(result).toEqual({
660-
installed: [{ registry: 'r', name: 's' }]
661-
});
662-
});
663-
664-
it('normalizes an object with registries and installed', () => {
665-
const result = configManager.normalizeSkillsConfig({
666-
registries: { 'r': 'https://example.com' },
667-
installed: [{ registry: 'r', name: 's' }]
668-
});
669-
expect(result).toEqual({
670-
registries: { 'r': 'https://example.com' },
671-
installed: [{ registry: 'r', name: 's' }]
672-
});
673-
});
674-
675-
it('normalizes an object with only registries', () => {
676-
const result = configManager.normalizeSkillsConfig({
677-
registries: { 'r': 'https://example.com' }
678-
});
679-
expect(result).toEqual({
680-
registries: { 'r': 'https://example.com' },
681-
installed: []
682-
});
683-
});
684-
685-
it('normalizes undefined to empty SkillsConfig', () => {
686-
const result = configManager.normalizeSkillsConfig(undefined);
687-
expect(result).toEqual({ installed: [] });
688-
});
689-
690-
it('normalizes null to empty SkillsConfig', () => {
691-
const result = configManager.normalizeSkillsConfig(null);
692-
expect(result).toEqual({ installed: [] });
693-
});
694-
});
695-
696-
describe('getInstalledSkills', () => {
697-
it('returns skills from array format', () => {
698-
const config: DevKitConfig = {
699-
version: '1.0.0',
700-
environments: [],
701-
phases: [],
702-
skills: [{ registry: 'r', name: 's' }],
703-
createdAt: '',
704-
updatedAt: ''
705-
};
706-
expect(configManager.getInstalledSkills(config)).toEqual([{ registry: 'r', name: 's' }]);
707-
});
708-
709-
it('returns skills from object format', () => {
710-
const config = {
711-
version: '1.0.0',
712-
environments: [],
713-
phases: [],
714-
skills: {
715-
registries: { 'r': 'https://example.com' },
716-
installed: [{ registry: 'r', name: 's' }]
717-
},
718-
createdAt: '',
719-
updatedAt: ''
720-
} as DevKitConfig;
721-
expect(configManager.getInstalledSkills(config)).toEqual([{ registry: 'r', name: 's' }]);
722-
});
723-
724-
it('returns empty array when skills is undefined', () => {
725-
const config: DevKitConfig = {
726-
version: '1.0.0',
727-
environments: [],
728-
phases: [],
729-
createdAt: '',
730-
updatedAt: ''
731-
};
732-
expect(configManager.getInstalledSkills(config)).toEqual([]);
733-
});
734-
});
735-
736581
describe('getSkillRegistries', () => {
737-
it('returns registries from skills.registries', async () => {
582+
it('returns registries from top-level registries field', async () => {
738583
(mockFs.pathExists as any).mockResolvedValue(true);
739584
(mockFs.readJson as any).mockResolvedValue({
740585
version: '1.0.0',
741586
environments: ['cursor'],
742587
phases: [],
743-
skills: {
744-
registries: {
745-
'nested/skills': 'https://github.com/nested/skills.git',
746-
'invalid/value': false
747-
}
588+
registries: {
589+
'my-org/skills': 'https://github.com/my-org/skills.git',
590+
'invalid/value': false
748591
},
749592
createdAt: '2024-01-01T00:00:00.000Z',
750593
updatedAt: '2024-01-01T00:00:00.000Z'
@@ -753,11 +596,11 @@ describe('ConfigManager', () => {
753596
const registries = await configManager.getSkillRegistries();
754597

755598
expect(registries).toEqual({
756-
'nested/skills': 'https://github.com/nested/skills.git'
599+
'my-org/skills': 'https://github.com/my-org/skills.git'
757600
});
758601
});
759602

760-
it('returns empty object when no registry map exists', async () => {
603+
it('returns empty object when no registries field exists', async () => {
761604
(mockFs.pathExists as any).mockResolvedValue(true);
762605
(mockFs.readJson as any).mockResolvedValue({
763606
version: '1.0.0',

packages/cli/src/__tests__/lib/GlobalConfig.test.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,8 @@ describe('GlobalConfigManager', () => {
4040

4141
it('should return parsed config when file exists', async () => {
4242
const config = {
43-
skills: {
44-
registries: {
45-
'my-org/skills': 'https://github.com/my-org/skills.git'
46-
}
43+
registries: {
44+
'my-org/skills': 'https://github.com/my-org/skills.git'
4745
}
4846
};
4947

@@ -78,11 +76,9 @@ describe('GlobalConfigManager', () => {
7876

7977
it('should return only string registry entries', async () => {
8078
const config = {
81-
skills: {
82-
registries: {
83-
'my-org/skills': 'https://github.com/my-org/skills.git',
84-
'bad/entry': 123
85-
}
79+
registries: {
80+
'my-org/skills': 'https://github.com/my-org/skills.git',
81+
'bad/entry': 123
8682
}
8783
};
8884

packages/cli/src/__tests__/lib/SkillManager.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,10 +407,8 @@ describe("SkillManager", () => {
407407
});
408408

409409
(mockedFs.readJson as any).mockResolvedValue({
410-
skills: {
411-
registries: {
412-
[mockRegistryId]: customGitUrl,
413-
},
410+
registries: {
411+
[mockRegistryId]: customGitUrl,
414412
},
415413
});
416414

packages/cli/src/__tests__/services/install/install.service.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ describe('install service', () => {
5050
const installConfig = {
5151
environments: ['codex' as const],
5252
phases: ['requirements' as const],
53+
registries: {},
5354
skills: [{ registry: 'codeaholicguy/ai-devkit', name: 'debug' }],
5455
mcpServers: {}
5556
};
@@ -147,6 +148,7 @@ describe('install service', () => {
147148
const configWithoutSkills = {
148149
environments: ['codex' as const],
149150
phases: ['requirements' as const],
151+
registries: {},
150152
skills: [],
151153
mcpServers: {}
152154
};

0 commit comments

Comments
 (0)