import assert from 'node:assert/strict' import { spawnSync } from 'node:child_process' import { cpSync, existsSync, readFileSync, readdirSync, rmSync } from 'node:fs' import { dirname, join, resolve } from 'node:path' import test from 'node:test' import { fileURLToPath } from 'node:url' import { cleanup, tempDir, write } from './helpers.mjs' const testDir = dirname(fileURLToPath(import.meta.url)) const repoRoot = resolve(testDir, '..', '..') const script = resolve(repoRoot, 'dsh/sync/sync-from-upstream.mjs') const fixture = resolve(testDir, 'fixtures', 'superpowers-min') const bundleDshTools = resolve(repoRoot, 'dsh/bundle/dsh-tools.md') /** A private copy of the reduced upstream checkout: tests mutate it. */ function fixtureCopy(work) { const upstream = join(work, 'upstream') cpSync(fixture, upstream, { recursive: true }) return upstream } function runSync(args) { return spawnSync(process.execPath, [script, ...args], { encoding: 'utf8' }) } test('sync happy path: transforms, namespace strip, override, meta', () => { const work = tempDir('dsh-sync-happy-') try { const upstream = fixtureCopy(work) const out = join(work, 'out') const result = runSync([upstream, '--out', out, '--ref', 'v7.0.1']) assert.equal(result.status, 0, `stderr: ${result.stderr}`) assert.match(result.stdout, /synced \d+ files/) assert.match(result.stdout, /transforms applied: 16/) const skillDirs = readdirSync(out).filter((name) => name !== '.sync-meta.json') assert.equal(skillDirs.length, 15, `expected all 15 upstream skills, got ${skillDirs.join(', ')}`) const using = readFileSync(join(out, 'using-superpowers', 'SKILL.md'), 'utf8') assert.match(using, /references\/dsh-tools\.md/) assert.doesNotMatch(using, /superpowers:/) assert.doesNotMatch(using, //) const override = readFileSync(join(out, 'using-superpowers', 'references', 'dsh-tools.md'), 'utf8') assert.equal(override, readFileSync(bundleDshTools, 'utf8')) const brainstorming = readFileSync(join(out, 'brainstorming', 'SKILL.md'), 'utf8') assert.doesNotMatch(brainstorming, /superpowers:/) assert.match(brainstorming, /writing-plans/) const meta = JSON.parse(readFileSync(join(out, '.sync-meta.json'), 'utf8')) assert.deepEqual(meta, { ref: 'v7.0.1', version: '7.0.1' }) assert.equal(existsSync(join(out, 'using-superpowers', 'references', 'claude-code-tools.md')), false) } finally { cleanup(work) } }) test('sync fails loud on a broken transform anchor and leaves --out untouched', () => { const work = tempDir('dsh-sync-anchor-') try { const upstream = fixtureCopy(work) const usingPath = join(upstream, 'skills', 'using-superpowers', 'SKILL.md') const anchor = 'If your harness appears here, read its reference file for special instructions:' const original = readFileSync(usingPath, 'utf8') assert.ok(original.includes(anchor), 'fixture must carry the upstream anchor verbatim') write(usingPath, original.replace(anchor, 'If your harness appears here, read the reference file:')) const out = join(work, 'out') write(join(out, 'keep.txt'), 'sentinel\n') const result = runSync([upstream, '--out', out, '--ref', 'v7.0.1']) assert.equal(result.status, 1) assert.match(result.stderr, /DSH platform reference/) assert.equal(readFileSync(join(out, 'keep.txt'), 'utf8'), 'sentinel\n') assert.deepEqual(readdirSync(out), ['keep.txt'], 'failed sync must not swap a partial tree in') assert.deepEqual( readdirSync(work).filter((name) => name.startsWith('out.tmp-')), [], 'failed sync must remove its staging tree', ) } finally { cleanup(work) } }) test('sync fails loud on a broken anchor of another transform', () => { const work = tempDir('dsh-sync-anchor-executing-plans-') try { const upstream = fixtureCopy(work) const skillPath = join(upstream, 'skills', 'executing-plans', 'SKILL.md') const anchor = 'Run `../subagent-driven-development/scripts/review-package PLAN_FILE MERGE_BASE HEAD`' const original = readFileSync(skillPath, 'utf8') assert.ok(original.includes(anchor), 'fixture must carry the review-package anchor verbatim') write(skillPath, original.replace(anchor, 'Run `../subagent-driven-development/scripts/review-package PLAN_FILE HEAD`')) const result = runSync([upstream, '--out', join(work, 'out'), '--ref', 'v7.0.1']) assert.equal(result.status, 1) assert.match(result.stderr, /invoke review-package through bash/) assert.equal(existsSync(join(work, 'out')), false) } finally { cleanup(work) } }) test('sync fails on a changed skill set', () => { const work = tempDir('dsh-sync-skill-set-') try { const upstream = fixtureCopy(work) rmSync(join(upstream, 'skills', 'brainstorming'), { recursive: true, force: true }) const result = runSync([upstream, '--out', join(work, 'out')]) assert.equal(result.status, 1) assert.match(result.stderr, /removed upstream:\s*brainstorming/) assert.equal(existsSync(join(work, 'out')), false) } finally { cleanup(work) } }) test('sync fails when the checkout has no skills/ directory', () => { const work = tempDir('dsh-sync-no-skills-') try { const upstream = fixtureCopy(work) rmSync(join(upstream, 'skills'), { recursive: true, force: true }) const result = runSync([upstream, '--out', join(work, 'out')]) assert.equal(result.status, 1) assert.match(result.stderr, /no skills\/ directory/) assert.equal(existsSync(join(work, 'out')), false) } finally { cleanup(work) } })