oy-cli 0.14.11

Focused coding agent integrations with repeatable audits, reviews, and one-finding fixes
Documentation
import assert from "node:assert/strict"
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"
import { dirname, join } from "node:path"
import test from "node:test"

import { removeBundledCursorSkills, writeBundledCursorSkills } from "../cursor-skills.js"

const assets = new URL("../assets", import.meta.url).pathname

test("mirrors bundled skills for Cursor and cleans up only generated copies", () => {
  const directory = mkdtempSync(join(process.cwd(), ".cursor-skills-test-"))
  try {
    writeBundledCursorSkills(directory, assets, assert.fail)
    const skill = join(directory, ".cursor", "skills", "oy-audit", "SKILL.md")
    assert.equal(existsSync(skill), true)
    assert.match(readFileSync(skill, "utf8"), /generated: oy-cursor-skill/)
    assert.match(readFileSync(join(directory, ".cursor", "skills", ".gitignore"), "utf8"), /oy-audit\//)

    removeBundledCursorSkills(directory)
    assert.equal(existsSync(skill), false)
  } finally {
    rmSync(directory, { recursive: true, force: true })
  }
})

test("preserves a user-owned Cursor skill", () => {
  const directory = mkdtempSync(join(process.cwd(), ".cursor-skills-user-test-"))
  const skill = join(directory, ".cursor", "skills", "oy-audit", "SKILL.md")
  mkdirSync(join(directory, ".cursor", "skills", "oy-audit"), { recursive: true })
  writeFileSync(skill, "---\nname: oy-audit\n---\n\nUser owned\n")
  try {
    writeBundledCursorSkills(directory, assets, () => {})
    removeBundledCursorSkills(directory)
    assert.match(readFileSync(skill, "utf8"), /User owned/)
  } finally {
    rmSync(directory, { recursive: true, force: true })
  }
})

test("refuses symlinked Cursor skill ancestors during writes and cleanup", () => {
  const cases = [
    { link: [".cursor"], escaped: ["skills", "oy-audit", "SKILL.md"] },
    { link: [".cursor", "skills"], escaped: ["oy-audit", "SKILL.md"] },
    { link: [".cursor", "skills", "oy-audit"], escaped: ["SKILL.md"] },
  ]
  for (const scenario of cases) {
    const directory = mkdtempSync(join(process.cwd(), ".cursor-skills-boundary-test-"))
    const outside = mkdtempSync(join(process.cwd(), ".cursor-skills-outside-test-"))
    const link = join(directory, ...scenario.link)
    const sentinel = join(outside, "sentinel")
    mkdirSync(dirname(link), { recursive: true })
    writeFileSync(sentinel, "keep")
    symlinkSync(outside, link, "dir")
    const warnings = []
    try {
      writeBundledCursorSkills(directory, assets, (warning) => warnings.push(warning))
      removeBundledCursorSkills(directory)
      assert.equal(readFileSync(sentinel, "utf8"), "keep")
      assert.equal(existsSync(join(outside, ...scenario.escaped)), false)
      assert.ok(warnings.some((warning) => warning.includes("symlinked workspace path")))
    } finally {
      rmSync(directory, { recursive: true, force: true })
      rmSync(outside, { recursive: true, force: true })
    }
  }
})