feat(skills): add confirmed no-op execution receipts
This commit is contained in:
+78
-3
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
@@ -230,6 +230,11 @@ describe('skills CLI helpers', () => {
|
||||
);
|
||||
|
||||
expect(view.execution).toBe('stub');
|
||||
expect(view.mode).toBe('stub');
|
||||
expect(view.confirmed).toBe(false);
|
||||
expect(view.execution_enabled).toBe(false);
|
||||
expect(view.executed).toEqual([]);
|
||||
expect(view.reason).toBe('execution_disabled');
|
||||
expect(view.wouldRun.length).toBe(1);
|
||||
expect(view.wouldRun[0]).toContain('download https://example.com/tool.tgz');
|
||||
});
|
||||
@@ -238,6 +243,11 @@ describe('skills CLI helpers', () => {
|
||||
const output = renderSkillInstallerExecutionStub({
|
||||
skill: { name: 'exec-stub', tier: 'bundled', version: '1.0.0' },
|
||||
execution: 'stub',
|
||||
mode: 'stub',
|
||||
confirmed: false,
|
||||
execution_enabled: false,
|
||||
executed: [],
|
||||
reason: 'execution_disabled',
|
||||
wouldRun: ['brew install jq'],
|
||||
skipped: [{ installerType: 'node', reason: 'neither pnpm nor npm available in PATH' }],
|
||||
});
|
||||
@@ -258,9 +268,14 @@ describe('skills CLI helpers', () => {
|
||||
skipped: [],
|
||||
};
|
||||
|
||||
const view = toSkillInstallerExecutionStubFromPreflight(preflight);
|
||||
const view = toSkillInstallerExecutionStubFromPreflight(preflight, { mode: 'install', confirmed: true });
|
||||
|
||||
expect(view.execution).toBe('stub');
|
||||
expect(view.mode).toBe('install');
|
||||
expect(view.confirmed).toBe(true);
|
||||
expect(view.execution_enabled).toBe(false);
|
||||
expect(view.executed).toEqual([]);
|
||||
expect(view.reason).toBe('execution_disabled');
|
||||
expect(view.wouldRun).toEqual(['download https://example.com/a.tgz -> /tmp/a.tgz']);
|
||||
});
|
||||
|
||||
@@ -336,7 +351,7 @@ describe('skills CLI helpers', () => {
|
||||
);
|
||||
|
||||
const installer = new SkillInstaller(managedDir);
|
||||
const result = runSkillInstallAction(installer, sourceDir, { mode: 'plan-only', asJson: false });
|
||||
const result = runSkillInstallAction(installer, sourceDir, { mode: 'plan-only', asJson: false, confirmed: false });
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(existsSync(join(managedDir, 'plan-skill', 'SKILL.md'))).toBe(false);
|
||||
@@ -344,6 +359,66 @@ describe('skills CLI helpers', () => {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('emits plan-only JSON with no-op execution receipt fields', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
|
||||
const sourceDir = join(root, 'source-skill');
|
||||
const managedDir = join(root, 'managed');
|
||||
mkdirSync(sourceDir, { recursive: true });
|
||||
writeFileSync(join(sourceDir, 'SKILL.md'), '# Plan Skill\nInstructions');
|
||||
writeFileSync(
|
||||
join(sourceDir, 'manifest.json'),
|
||||
JSON.stringify({ name: 'plan-skill', description: 'Plan only', version: '1.0.0' }),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const installer = new SkillInstaller(managedDir);
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||
|
||||
const result = runSkillInstallAction(installer, sourceDir, { mode: 'plan-only', asJson: true, confirmed: true });
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(logSpy).toHaveBeenCalledTimes(1);
|
||||
const payload = JSON.parse(String(logSpy.mock.calls[0]?.[0]));
|
||||
expect(payload.execution.confirmed).toBe(true);
|
||||
expect(payload.execution.mode).toBe('plan-only');
|
||||
expect(payload.execution.execution_enabled).toBe(false);
|
||||
expect(payload.execution.executed).toEqual([]);
|
||||
expect(payload.execution.reason).toBe('execution_disabled');
|
||||
|
||||
logSpy.mockRestore();
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('emits install JSON with no-op execution receipt fields', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
|
||||
const sourceDir = join(root, 'source-skill');
|
||||
const managedDir = join(root, 'managed');
|
||||
mkdirSync(sourceDir, { recursive: true });
|
||||
writeFileSync(join(sourceDir, 'SKILL.md'), '# Install Skill\nInstructions');
|
||||
writeFileSync(
|
||||
join(sourceDir, 'manifest.json'),
|
||||
JSON.stringify({ name: 'install-skill', description: 'Install', version: '1.0.0' }),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const installer = new SkillInstaller(managedDir);
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||
|
||||
const result = runSkillInstallAction(installer, sourceDir, { mode: 'install', asJson: true, confirmed: false });
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(logSpy.mock.calls.length).toBeGreaterThan(0);
|
||||
const payload = JSON.parse(String(logSpy.mock.calls[logSpy.mock.calls.length - 1]?.[0]));
|
||||
expect(payload.execution.confirmed).toBe(false);
|
||||
expect(payload.execution.mode).toBe('install');
|
||||
expect(payload.execution.execution_enabled).toBe(false);
|
||||
expect(payload.execution.executed).toEqual([]);
|
||||
expect(payload.execution.reason).toBe('execution_disabled');
|
||||
|
||||
logSpy.mockRestore();
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('requires --yes confirmation for uninstall helper', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
|
||||
const installer = new SkillInstaller(join(root, 'managed'));
|
||||
|
||||
Reference in New Issue
Block a user