import assert from 'node:assert/strict' import { spawnSync } from 'node:child_process' import { existsSync, lstatSync, readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import test from 'node:test' import { fileURLToPath } from 'node:url' import { cleanup, tempDir, write } from './helpers.mjs' const here = dirname(fileURLToPath(import.meta.url)) const script = join(here, '..', 'install.sh') test('install.sh copies skills and roles instead of linking into ~/.claude', () => { const home = tempDir() try { const dshHome = join(home, '.dsh') const cache = join( home, '.claude', 'plugins', 'cache', 'claude-plugins-official', 'superpowers', '7.0.0', 'skills', ) write(join(cache, 'using-superpowers', 'SKILL.md'), '---\nname: using-superpowers\n---\nbody\n') write(join(cache, 'brainstorming', 'SKILL.md'), '---\nname: brainstorming\n---\nbody\n') const env = { ...process.env, HOME: home, DSH_HOME: dshHome } const run = () => spawnSync('bash', [script, '--skills', '--roles'], { env, encoding: 'utf8' }) const first = run() assert.equal(first.status, 0, first.stderr) assert.match(first.stdout, /copied 2 superpowers skills/) assert.match(first.stdout, /copied 2 local skills/) assert.match(first.stdout, /installed 4 roles/) const skill = join(dshHome, 'skills', 'using-superpowers') assert.equal(lstatSync(skill).isSymbolicLink(), false) assert.equal(lstatSync(skill).isDirectory(), true) assert.match(readFileSync(join(skill, 'SKILL.md'), 'utf8'), /body/) const roleSkill = join(dshHome, 'skills', 'role', 'SKILL.md') assert.ok(existsSync(roleSkill), 'role skill was not installed') assert.match(readFileSync(roleSkill, 'utf8'), /name: role/) const sddSkill = join(dshHome, 'skills', 'sdd', 'SKILL.md') assert.ok(existsSync(sddSkill), 'sdd skill was not installed') assert.match(readFileSync(sddSkill, 'utf8'), /name: sdd/) for (const role of ['dev', 'admin', 'analyst', 'qa']) { assert.ok(existsSync(join(dshHome, 'roles', `${role}.md`)), `missing ${role}.md`) } const devRole = join(dshHome, 'roles', 'dev.md') const devText = readFileSync(devRole, 'utf8') assert.match(devText, /В DSH-сессии исполнение плана идёт по skill `sdd`/) assert.match(devText, /`maxDepth: 2`/) assert.match(first.stdout, /role dsh notes/) assert.equal(countNotes(devText), 1, 'note must be inserted once') const second = run() assert.equal(second.status, 0, second.stderr) assert.equal(lstatSync(skill).isSymbolicLink(), false) assert.ok(existsSync(roleSkill)) assert.match(second.stdout, /role dsh notes/) assert.equal(countNotes(readFileSync(devRole, 'utf8')), 1, 'note must not duplicate') const check = spawnSync('bash', [script, '--check'], { env, encoding: 'utf8' }) assert.match(check.stdout, /ok: role dsh notes/) } finally { cleanup(home) } }) function countNotes(text) { return (text.match(/В DSH-сессии исполнение плана/g) ?? []).length }