diff --git a/dsh/install.sh b/dsh/install.sh
index 6baab09..d95fd5c 100755
--- a/dsh/install.sh
+++ b/dsh/install.sh
@@ -412,6 +412,15 @@ ensure_pinned_clone() {
echo "FAIL: move it away and run: $CLONE_HINT" >&2
return 2
fi
+ # A foreign git repository must never be mutated by fetch/checkout: a real
+ # superpowers clone always carries skills/using-superpowers/SKILL.md (I-1).
+ # The marker also holds for the moved-tag flow — skills are present in
+ # every pinned clone.
+ if [ ! -f "$dir/skills/using-superpowers/SKILL.md" ]; then
+ echo "FAIL: $dir is not a superpowers clone (no skills/using-superpowers/SKILL.md); left untouched" >&2
+ echo "FAIL: move it away or use --superpowers-from
" >&2
+ return 2
+ fi
# Online: refresh the pin. Fetching from $DSH_SUPERPOWERS_REPO (not from
# the clone's origin) makes a repo change take effect on the next run
# (T3.3); the force refspec updates the LOCAL tag as well as FETCH_HEAD, so
diff --git a/dsh/tests/install.test.mjs b/dsh/tests/install.test.mjs
index ae6b717..3f2d1ed 100644
--- a/dsh/tests/install.test.mjs
+++ b/dsh/tests/install.test.mjs
@@ -282,6 +282,40 @@ test('--skills removes a half-cloned dir after a failed fresh clone (RF5)', () =
}
})
+test('a foreign git repo in $DSH_HOME/superpowers is never touched (I-1)', () => {
+ const work = tempDir()
+ try {
+ const home = join(work, 'home')
+ const pinned = makePinnedRepo(work)
+ const foreign = join(home, '.dsh', 'superpowers')
+ mkdirSync(foreign, { recursive: true })
+ git(foreign, ['init', '-b', 'main'])
+ git(foreign, ['config', 'user.email', 'dsh-harness@example.invalid'])
+ git(foreign, ['config', 'user.name', 'DSH Harness Test'])
+ write(join(foreign, 'witness.txt'), 'do not touch\n')
+ git(foreign, ['add', 'witness.txt'])
+ git(foreign, ['commit', '-m', 'foreign repo'])
+ const headBefore = git(foreign, ['rev-parse', 'HEAD'])
+
+ const run = runInstaller(home, ['--skills'], { DSH_SUPERPOWERS_REPO: `file://${pinned}` })
+ const out = run.stdout + run.stderr
+ assert.equal(run.status, 1, out)
+ assert.match(out, /not a superpowers clone/)
+ assert.match(out, /move it away or use --superpowers-from/)
+ assert.equal(readFileSync(join(foreign, 'witness.txt'), 'utf8'), 'do not touch\n')
+ assert.equal(git(foreign, ['rev-parse', 'HEAD']), headBefore, 'HEAD must not move')
+ assert.equal(existsSync(join(foreign, 'skills')), false, 'no skills tree may appear')
+
+ // --check agrees: the foreign repo is not reported as a source.
+ const check = runInstaller(home, ['--check'], { DSH_SUPERPOWERS_REPO: `file://${pinned}` })
+ const checkOut = check.stdout + check.stderr
+ assert.equal(check.status, 0, checkOut)
+ assert.doesNotMatch(checkOut, /ok: superpowers source/)
+ } finally {
+ cleanup(work)
+ }
+})
+
test('--skills reuses an on-ref clone when offline (WARN)', () => {
const work = tempDir()
try {