feat(mosaic): add secure skill registration CLI (#826)
All checks were successful
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful

This commit was merged in pull request #826.
This commit is contained in:
2026-07-17 23:45:35 +00:00
parent d3bf52898b
commit d801d6c4c8
18 changed files with 1240 additions and 26 deletions

View File

@@ -7,6 +7,7 @@ import {
mkdirSync,
readdirSync,
readFileSync,
readlinkSync,
rmSync,
statSync,
symlinkSync,
@@ -300,6 +301,50 @@ describe('repairFleetCommsTools', () => {
});
describe('runFrameworkReseed', () => {
it('auto-registers every canonical skill after a successful upgrade re-seed', () => {
const root = mkdtempSync(join(tmpdir(), 'mosaic-reseed-skills-'));
const framework = join(root, 'framework');
const home = join(root, 'mosaic');
const claudeSkills = join(root, '.claude', 'skills');
mkdirSync(framework, { recursive: true });
mkdirSync(join(home, 'skills', 'added-after-setup'), { recursive: true });
mkdirSync(join(home, 'skills', 'another-new-skill'), { recursive: true });
writeFileSync(join(framework, 'install.sh'), '#!/usr/bin/env bash\nexit 0\n', { mode: 0o755 });
const res = runFrameworkReseed(framework, home, claudeSkills);
expect(res.ok).toBe(true);
expect(res.skillSync).toMatchObject({
registered: ['added-after-setup', 'another-new-skill'],
conflicts: [],
});
expect(readlinkSync(join(claudeSkills, 'added-after-setup'))).toBe(
join(home, 'skills', 'added-after-setup'),
);
expect(readlinkSync(join(claudeSkills, 'another-new-skill'))).toBe(
join(home, 'skills', 'another-new-skill'),
);
rmSync(root, { recursive: true, force: true });
});
it('keeps a successful framework re-seed successful when bridge reconciliation fails', () => {
const root = mkdtempSync(join(tmpdir(), 'mosaic-reseed-bridge-failure-'));
const framework = join(root, 'framework');
const home = join(root, 'mosaic');
const claudeSkills = join(root, '.claude', 'skills');
mkdirSync(framework, { recursive: true });
mkdirSync(home, { recursive: true });
writeFileSync(join(home, 'skills'), 'invalid canonical root\n');
writeFileSync(join(framework, 'install.sh'), '#!/usr/bin/env bash\nexit 0\n', { mode: 0o755 });
const res = runFrameworkReseed(framework, home, claudeSkills);
expect(res.ok).toBe(true);
expect(res.skillSync).toBeUndefined();
expect(res.skillSyncError).toMatch(/not a directory/i);
rmSync(root, { recursive: true, force: true });
});
it('reports not-ok (not throw) when the installer is absent', () => {
const missing = mkdtempSync(join(tmpdir(), 'mosaic-noinstaller-'));
const res = runFrameworkReseed(missing, join(missing, 'home'));