verify-skill-invocation-metadata.spec.ts 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
  2. import { tmpdir } from 'node:os'
  3. import { join } from 'node:path'
  4. import { afterEach, describe, expect, it } from 'vitest'
  5. import { collectSkillInvocationMetadataViolations } from './verify-skill-invocation-metadata.ts'
  6. const roots: string[] = []
  7. afterEach(() => {
  8. for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
  9. })
  10. function fixtureRoot(): string {
  11. const root = mkdtempSync(join(tmpdir(), 'dsh-skill-invocation-metadata-'))
  12. roots.push(root)
  13. return root
  14. }
  15. function writeSkill(root: string, name: string, frontmatter: string, policy = ''): void {
  16. const directory = join(root, '.agents/skills', name)
  17. mkdirSync(join(directory, 'agents'), { recursive: true })
  18. writeFileSync(join(directory, 'SKILL.md'), `---\nname: ${name}\ndescription: Test skill\n${frontmatter}---\n\nTest.\n`)
  19. writeFileSync(
  20. join(directory, 'agents/openai.yaml'),
  21. `interface:\n display_name: "Test"\n${policy}`,
  22. )
  23. }
  24. describe('cross-product skill invocation metadata gate', () => {
  25. it('accepts aligned default and manual-only policies', () => {
  26. const root = fixtureRoot()
  27. writeSkill(root, 'default-skill', '')
  28. writeSkill(
  29. root,
  30. 'manual-skill',
  31. 'disable-model-invocation: true\nuser-invocable: true\n',
  32. 'policy:\n allow_implicit_invocation: false\n',
  33. )
  34. expect(collectSkillInvocationMetadataViolations(root)).toEqual([])
  35. })
  36. it('rejects either direction of a manual-only policy mismatch', () => {
  37. const root = fixtureRoot()
  38. writeSkill(root, 'claude-only', 'disable-model-invocation: true\n')
  39. writeSkill(root, 'codex-only', '', 'policy:\n allow_implicit_invocation: false\n')
  40. expect(collectSkillInvocationMetadataViolations(root)).toEqual([
  41. '.agents/skills/claude-only: Claude Code manual-only=true but Codex manual-only=false',
  42. '.agents/skills/codex-only: Claude Code manual-only=false but Codex manual-only=true',
  43. ])
  44. })
  45. })