Установленная копия роли теперь воспроизводима: после копирования roles/ install.sh вставляет в ~/.dsh/roles/dev.md пункт «в DSH исполнение плана — skill sdd» (после «Режим по умолчанию», с откатом в конец файла, если якоря нет). Вставка идемпотентна: каждая установка ролей перекопирует файл и добавляет ровно одну заметку. Общий roles/dev.md не меняется — в Claude Code строка не действует. --check сообщает ok/WARN про заметку; тест проверяет наличие, отсутствие дубля после второго запуска и строку в --check. PORT.md §6 объясняет. Co-Authored-By: DeepSeek Harness <noreply@deepseek.com>
79 lines
3.1 KiB
JavaScript
79 lines
3.1 KiB
JavaScript
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
|
||
}
|