test(skills): cover install and execute option parsing
This commit is contained in:
+10
-2
@@ -1478,6 +1478,14 @@
|
|||||||
"src/cli/skills.test.ts"
|
"src/cli/skills.test.ts"
|
||||||
],
|
],
|
||||||
"test_status": "pnpm typecheck + pnpm test:run src/cli/skills.test.ts + pnpm test:run + pnpm lint (warnings only, 0 errors) + pnpm build passing"
|
"test_status": "pnpm typecheck + pnpm test:run src/cli/skills.test.ts + pnpm test:run + pnpm lint (warnings only, 0 errors) + pnpm build passing"
|
||||||
|
},
|
||||||
|
"skills_cli_option_parsing_integration_tests": {
|
||||||
|
"status": "completed",
|
||||||
|
"description": "Added CLI-level integration tests for `skills install`/`skills execute` option parsing and failure modes around `--execute`, `--runner`, and `--confirm`, validating command wiring through commander actions",
|
||||||
|
"files_modified": [
|
||||||
|
"src/cli/skills.test.ts"
|
||||||
|
],
|
||||||
|
"test_status": "pnpm typecheck + pnpm test:run src/cli/skills.test.ts + pnpm test:run + pnpm lint (warnings only, 0 errors) + pnpm build passing"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1506,7 +1514,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"overall_progress": {
|
"overall_progress": {
|
||||||
"total_test_count": 1550,
|
"total_test_count": 1553,
|
||||||
"all_tests_passing": true,
|
"all_tests_passing": true,
|
||||||
"p0_completion": "3/3 (100%)",
|
"p0_completion": "3/3 (100%)",
|
||||||
"p1_completion": "4/4 (100%)",
|
"p1_completion": "4/4 (100%)",
|
||||||
@@ -1526,7 +1534,7 @@
|
|||||||
"gmail_auth_cli": "flynn gmail-auth command implemented with OAuth2 flow, doctor check, config routed to Telegram",
|
"gmail_auth_cli": "flynn gmail-auth command implemented with OAuth2 flow, doctor check, config routed to Telegram",
|
||||||
"native_audio_support": "completed — smart routing for native audio (Gemini/OpenAI/GitHub) vs Whisper transcription fallback",
|
"native_audio_support": "completed — smart routing for native audio (Gemini/OpenAI/GitHub) vs Whisper transcription fallback",
|
||||||
"remaining_phases_completion": "Phase 1: 3/3 (100%) — context levels, command registry, memory structure. Phase 2: 2/2 (100%) — component registry, confidence routing. Phase 3: 2/2 (100%) — adaptive memory/compaction, truthfulness/autonomy hardening",
|
"remaining_phases_completion": "Phase 1: 3/3 (100%) — context levels, command registry, memory structure. Phase 2: 2/2 (100%) — component registry, confidence routing. Phase 3: 2/2 (100%) — adaptive memory/compaction, truthfulness/autonomy hardening",
|
||||||
"next_up": "Skills infrastructure Phase 3: add CLI-level integration tests for `skills install/execute` option parsing (`--execute`, `--runner`, `--confirm`) to lock behavior and failure modes"
|
"next_up": "Skills infrastructure Phase 3: add explicit user-facing error messaging/tests for invalid `--execute` usage patterns (for example, `--execute` without `--confirm`) and runner/result edge-case receipts"
|
||||||
},
|
},
|
||||||
"soul_md_and_cron_create": {
|
"soul_md_and_cron_create": {
|
||||||
"date": "2026-02-11",
|
"date": "2026-02-11",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { describe, it, expect, vi } from 'vitest';
|
|||||||
import { mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync } from 'fs';
|
import { mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { tmpdir } from 'os';
|
import { tmpdir } from 'os';
|
||||||
|
import { Command } from 'commander';
|
||||||
import { SkillInstaller } from '../skills/index.js';
|
import { SkillInstaller } from '../skills/index.js';
|
||||||
import {
|
import {
|
||||||
toSkillListRows,
|
toSkillListRows,
|
||||||
@@ -27,6 +28,7 @@ import {
|
|||||||
resolveSkillInstallerCommandRunner,
|
resolveSkillInstallerCommandRunner,
|
||||||
runSkillExecuteAction,
|
runSkillExecuteAction,
|
||||||
runSkillInstallAction,
|
runSkillInstallAction,
|
||||||
|
registerSkillsCommand,
|
||||||
} from './skills.js';
|
} from './skills.js';
|
||||||
import type { Skill } from '../skills/index.js';
|
import type { Skill } from '../skills/index.js';
|
||||||
|
|
||||||
@@ -46,6 +48,23 @@ function buildSkill(overrides: Partial<Skill>): Skill {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function writeSkillsCliConfig(configPath: string, opts: { managedDir: string; bundledDir: string; workspaceDir: string }): void {
|
||||||
|
writeFileSync(
|
||||||
|
configPath,
|
||||||
|
[
|
||||||
|
'models:',
|
||||||
|
' default:',
|
||||||
|
' provider: ollama',
|
||||||
|
' model: test-model',
|
||||||
|
'skills:',
|
||||||
|
` managed_dir: ${opts.managedDir}`,
|
||||||
|
` bundled_dir: ${opts.bundledDir}`,
|
||||||
|
` workspace_dir: ${opts.workspaceDir}`,
|
||||||
|
].join('\n'),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
describe('skills CLI helpers', () => {
|
describe('skills CLI helpers', () => {
|
||||||
it('maps and sorts skill rows', () => {
|
it('maps and sorts skill rows', () => {
|
||||||
const rows = toSkillListRows([
|
const rows = toSkillListRows([
|
||||||
@@ -870,4 +889,118 @@ describe('skills CLI helpers', () => {
|
|||||||
|
|
||||||
rmSync(root, { recursive: true, force: true });
|
rmSync(root, { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('skills install reports invalid runner via CLI option parsing path', async () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
|
||||||
|
const configPath = join(root, 'config.yaml');
|
||||||
|
const managedDir = join(root, 'managed');
|
||||||
|
const bundledDir = join(root, 'bundled');
|
||||||
|
const workspaceDir = join(root, 'workspace');
|
||||||
|
mkdirSync(managedDir, { recursive: true });
|
||||||
|
mkdirSync(bundledDir, { recursive: true });
|
||||||
|
mkdirSync(workspaceDir, { recursive: true });
|
||||||
|
writeSkillsCliConfig(configPath, { managedDir, bundledDir, workspaceDir });
|
||||||
|
|
||||||
|
const program = new Command();
|
||||||
|
registerSkillsCommand(program);
|
||||||
|
|
||||||
|
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
|
process.exitCode = undefined;
|
||||||
|
|
||||||
|
await program.parseAsync(['skills', 'install', '/tmp/any-skill', '--runner', 'invalid', '-c', configPath], { from: 'user' });
|
||||||
|
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith("Invalid runner 'invalid'. Allowed values: noop, shell.");
|
||||||
|
expect(process.exitCode).toBe(1);
|
||||||
|
|
||||||
|
errorSpy.mockRestore();
|
||||||
|
process.exitCode = undefined;
|
||||||
|
rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skills install parses execute flags and emits execution-enabled JSON receipt', async () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
|
||||||
|
const configPath = join(root, 'config.yaml');
|
||||||
|
const sourceDir = join(root, 'source-skill');
|
||||||
|
const managedDir = join(root, 'managed');
|
||||||
|
const bundledDir = join(root, 'bundled');
|
||||||
|
const workspaceDir = join(root, 'workspace');
|
||||||
|
mkdirSync(sourceDir, { recursive: true });
|
||||||
|
mkdirSync(managedDir, { recursive: true });
|
||||||
|
mkdirSync(bundledDir, { recursive: true });
|
||||||
|
mkdirSync(workspaceDir, { recursive: true });
|
||||||
|
writeSkillsCliConfig(configPath, { managedDir, bundledDir, workspaceDir });
|
||||||
|
writeFileSync(join(sourceDir, 'SKILL.md'), '# Install Skill\nInstructions');
|
||||||
|
writeFileSync(
|
||||||
|
join(sourceDir, 'manifest.json'),
|
||||||
|
JSON.stringify({
|
||||||
|
name: 'cli-install-skill',
|
||||||
|
description: 'CLI install parse',
|
||||||
|
version: '1.0.0',
|
||||||
|
installers: [{ type: 'download', url: 'https://example.com/cli-install.tgz' }],
|
||||||
|
}),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
|
||||||
|
const program = new Command();
|
||||||
|
registerSkillsCommand(program);
|
||||||
|
|
||||||
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||||
|
process.exitCode = undefined;
|
||||||
|
|
||||||
|
await program.parseAsync(
|
||||||
|
['skills', 'install', sourceDir, '--json', '--execute', '--confirm', '--runner', 'noop', '-c', configPath],
|
||||||
|
{ from: 'user' },
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload = JSON.parse(String(logSpy.mock.calls[logSpy.mock.calls.length - 1]?.[0]));
|
||||||
|
expect(payload.execution.execution_enabled).toBe(true);
|
||||||
|
expect(payload.execution.reason).toBe('execution_enabled');
|
||||||
|
|
||||||
|
logSpy.mockRestore();
|
||||||
|
process.exitCode = undefined;
|
||||||
|
rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skills execute parses execute flags and emits execution-enabled JSON receipt', async () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
|
||||||
|
const configPath = join(root, 'config.yaml');
|
||||||
|
const managedDir = join(root, 'managed');
|
||||||
|
const bundledDir = join(root, 'bundled');
|
||||||
|
const workspaceDir = join(root, 'workspace');
|
||||||
|
const skillDir = join(managedDir, 'cli-exec-skill');
|
||||||
|
mkdirSync(skillDir, { recursive: true });
|
||||||
|
mkdirSync(bundledDir, { recursive: true });
|
||||||
|
mkdirSync(workspaceDir, { recursive: true });
|
||||||
|
writeSkillsCliConfig(configPath, { managedDir, bundledDir, workspaceDir });
|
||||||
|
writeFileSync(join(skillDir, 'SKILL.md'), '# Execute Skill\nInstructions');
|
||||||
|
writeFileSync(
|
||||||
|
join(skillDir, 'manifest.json'),
|
||||||
|
JSON.stringify({
|
||||||
|
name: 'cli-exec-skill',
|
||||||
|
description: 'CLI execute parse',
|
||||||
|
version: '1.0.0',
|
||||||
|
installers: [{ type: 'download', url: 'https://example.com/cli-exec.tgz' }],
|
||||||
|
}),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
|
||||||
|
const program = new Command();
|
||||||
|
registerSkillsCommand(program);
|
||||||
|
|
||||||
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||||
|
process.exitCode = undefined;
|
||||||
|
|
||||||
|
await program.parseAsync(
|
||||||
|
['skills', 'execute', 'cli-exec-skill', '--json', '--execute', '--confirm', '--runner', 'noop', '-c', configPath],
|
||||||
|
{ from: 'user' },
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload = JSON.parse(String(logSpy.mock.calls[0]?.[0]));
|
||||||
|
expect(payload.execution_enabled).toBe(true);
|
||||||
|
expect(payload.reason).toBe('execution_enabled');
|
||||||
|
|
||||||
|
logSpy.mockRestore();
|
||||||
|
process.exitCode = undefined;
|
||||||
|
rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user