feat(dsh): install superpowers from an explicit source (#49)
This commit is contained in:
parent
15cc2e38d7
commit
05178869fb
@ -33,6 +33,13 @@ risks.
|
||||
# Static check
|
||||
bash dsh/install.sh --check
|
||||
|
||||
# Superpowers source: get it once per machine. install.sh never downloads
|
||||
# anything and no longer depends on ~/.claude.
|
||||
git clone --depth 1 https://github.com/obra/superpowers ~/.dsh/superpowers
|
||||
# ...or point at any checkout of the superpowers repo / its skills directory:
|
||||
bash dsh/install.sh --skills --superpowers-from /path/to/superpowers
|
||||
# ...or export DSH_SUPERPOWERS_SOURCE=/path/to/superpowers
|
||||
|
||||
# Global instructions, superpowers skills, local role skill, and roles
|
||||
# (run from a terminal; these write outside the session workspace)
|
||||
bash dsh/install.sh --global
|
||||
@ -47,6 +54,12 @@ bash <harness>/dsh/install.sh --openspec
|
||||
# or ask the agent to call plugin_manager install_bundle with that target.
|
||||
```
|
||||
|
||||
`--skills` resolves superpowers in this order: `--superpowers-from` →
|
||||
`$DSH_SUPERPOWERS_SOURCE` → `~/.dsh/superpowers` → the Claude Code plugin cache
|
||||
(legacy, warns). A clean HOME with no source and no installed copy fails with
|
||||
the clone command above instead of pretending success; the local `role` and
|
||||
`sdd` skills are copied either way.
|
||||
|
||||
Do not hand-edit `$DSH_HOME/profiles/desktop/package.json` or
|
||||
`cordis.patch.yml`: the Desktop profile is Electron-managed and the plugin
|
||||
manager owns those writes.
|
||||
|
||||
157
dsh/install.sh
157
dsh/install.sh
@ -10,7 +10,7 @@
|
||||
# Usage:
|
||||
# bash dsh/install.sh --check
|
||||
# bash dsh/install.sh --global
|
||||
# bash dsh/install.sh --skills
|
||||
# bash dsh/install.sh --skills [--superpowers-from <dir>]
|
||||
# bash dsh/install.sh --roles
|
||||
# bash dsh/install.sh --all
|
||||
# bash dsh/install.sh --openspec
|
||||
@ -27,17 +27,27 @@ DO_GLOBAL=0
|
||||
DO_SKILLS=0
|
||||
DO_ROLES=0
|
||||
DO_OPENSPEC=0
|
||||
SUPERPOWERS_FROM=""
|
||||
CLONE_HINT="git clone --depth 1 https://github.com/obra/superpowers $DSH_HOME/superpowers"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: bash dsh/install.sh [--check] [--global] [--skills] [--roles] [--all] [--openspec]
|
||||
bash dsh/install.sh --skills --superpowers-from <dir>
|
||||
|
||||
--check verify python3, git, node, hooks, bundle, skills, and roles
|
||||
--global back up and write \$DSH_HOME/AGENTS.md from global/CLAUDE.md
|
||||
--skills copy superpowers skills and local dsh/skills into \$DSH_HOME/skills
|
||||
--roles copy roles/ into \$DSH_HOME/roles
|
||||
--all --check + --global + --skills + --roles, then print the bundle step
|
||||
--openspec add the agents skill adapter to the current OpenSpec project
|
||||
--check verify python3, git, node, hooks, bundle, skills, and roles
|
||||
--global back up and write \$DSH_HOME/AGENTS.md from global/CLAUDE.md
|
||||
--skills copy superpowers and local dsh/skills into \$DSH_HOME/skills
|
||||
--roles copy roles/ into \$DSH_HOME/roles
|
||||
--all --check + --global + --skills + --roles, then print the bundle step
|
||||
--openspec add the agents skill adapter to the current OpenSpec project
|
||||
--superpowers-from DIR copy superpowers skills from DIR (the repo root or its skills/)
|
||||
|
||||
Superpowers source, first match wins. The script never downloads anything:
|
||||
1. --superpowers-from DIR
|
||||
2. \$DSH_SUPERPOWERS_SOURCE
|
||||
3. \$DSH_HOME/superpowers -- get it once: $CLONE_HINT
|
||||
4. legacy: the Claude Code plugin cache, then ~/.claude/skills (warns)
|
||||
|
||||
Bundle installation itself is done in DSH:
|
||||
Plugins -> install a bundle from $BUNDLE
|
||||
@ -49,29 +59,88 @@ if [ "$#" -eq 0 ]; then
|
||||
DO_CHECK=1
|
||||
fi
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--check) DO_CHECK=1 ;;
|
||||
--global) DO_GLOBAL=1 ;;
|
||||
--skills) DO_SKILLS=1 ;;
|
||||
--roles) DO_ROLES=1 ;;
|
||||
--openspec) DO_OPENSPEC=1 ;;
|
||||
--all) DO_CHECK=1; DO_GLOBAL=1; DO_SKILLS=1; DO_ROLES=1 ;;
|
||||
--superpowers-from)
|
||||
if [ "$#" -lt 2 ]; then
|
||||
echo "option --superpowers-from needs a directory" >&2
|
||||
exit 2
|
||||
fi
|
||||
SUPERPOWERS_FROM="$2"
|
||||
shift
|
||||
;;
|
||||
--superpowers-from=*) SUPERPOWERS_FROM="${1#*=}" ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "unknown option: $arg" >&2; usage >&2; exit 2 ;;
|
||||
*) echo "unknown option: $1" >&2; usage >&2; exit 2 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
superpowers_skills() {
|
||||
local dir
|
||||
dir="$(ls -d "$HOME"/.claude/plugins/cache/claude-plugins-official/superpowers/*/skills 2>/dev/null | sort -V | tail -1 || true)"
|
||||
if [ -n "$dir" ] && [ -d "$dir" ]; then
|
||||
# Normalize a superpowers source: accept the repo root (with skills/) or the
|
||||
# skills directory itself. Prints the skills directory, returns 1 otherwise.
|
||||
normalize_superpowers_dir() {
|
||||
local dir="${1:-}"
|
||||
[ -n "$dir" ] || return 1
|
||||
if [ -f "$dir/skills/using-superpowers/SKILL.md" ]; then
|
||||
printf '%s\n' "$dir/skills"
|
||||
return 0
|
||||
fi
|
||||
if [ -f "$dir/using-superpowers/SKILL.md" ]; then
|
||||
printf '%s\n' "$dir"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Print "<skills-dir>\t<explicit|pinned|legacy>". Returns 2 when an explicitly
|
||||
# configured source is invalid (never fall back silently), 1 when none is found.
|
||||
superpowers_source() {
|
||||
local dir legacy
|
||||
if [ -n "$SUPERPOWERS_FROM" ]; then
|
||||
dir="$(normalize_superpowers_dir "$SUPERPOWERS_FROM")" || return 2
|
||||
printf '%s\texplicit\n' "$dir"
|
||||
return 0
|
||||
fi
|
||||
if [ -n "${DSH_SUPERPOWERS_SOURCE:-}" ]; then
|
||||
dir="$(normalize_superpowers_dir "$DSH_SUPERPOWERS_SOURCE")" || return 2
|
||||
printf '%s\texplicit\n' "$dir"
|
||||
return 0
|
||||
fi
|
||||
if dir="$(normalize_superpowers_dir "$DSH_HOME/superpowers")"; then
|
||||
printf '%s\tpinned\n' "$dir"
|
||||
return 0
|
||||
fi
|
||||
legacy="$(ls -d "$HOME"/.claude/plugins/cache/claude-plugins-official/superpowers/*/skills 2>/dev/null | sort -V | tail -1 || true)"
|
||||
if [ -n "$legacy" ] && [ -d "$legacy" ]; then
|
||||
printf '%s\tlegacy\n' "$legacy"
|
||||
return 0
|
||||
fi
|
||||
if dir="$(normalize_superpowers_dir "$HOME/.claude/skills")"; then
|
||||
printf '%s\tlegacy\n' "$dir"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Human-readable name of the source that made superpowers_source() return 2.
|
||||
explicit_source_name() {
|
||||
if [ -n "$SUPERPOWERS_FROM" ]; then
|
||||
printf '%s\n' "--superpowers-from $SUPERPOWERS_FROM"
|
||||
else
|
||||
printf '%s\n' "DSH_SUPERPOWERS_SOURCE=$DSH_SUPERPOWERS_SOURCE"
|
||||
fi
|
||||
}
|
||||
|
||||
report_invalid_explicit_source() {
|
||||
echo "FAIL: explicit superpowers source $(explicit_source_name) has no using-superpowers/SKILL.md (expected <dir>/skills/... or <dir>/...)" >&2
|
||||
}
|
||||
|
||||
check() {
|
||||
local failed=0
|
||||
for cmd in python3 git node; do
|
||||
@ -134,12 +203,30 @@ check() {
|
||||
else
|
||||
echo "WARN: .agents/skills missing; run --openspec in the project" >&2
|
||||
fi
|
||||
local src="" rc=0 kind
|
||||
src="$(superpowers_source)" || rc=$?
|
||||
if [ "$rc" -eq 2 ]; then
|
||||
report_invalid_explicit_source
|
||||
failed=1
|
||||
elif [ "$rc" -ne 0 ]; then
|
||||
if [ -f "$DSH_HOME/skills/using-superpowers/SKILL.md" ]; then
|
||||
echo "WARN: no superpowers source; installed skills in $DSH_HOME/skills are used" >&2
|
||||
else
|
||||
echo "WARN: no superpowers source; run: $CLONE_HINT" >&2
|
||||
fi
|
||||
else
|
||||
kind="${src##*$'\t'}"
|
||||
src="${src%%$'\t'*}"
|
||||
if [ "$kind" = legacy ]; then
|
||||
echo "WARN: legacy superpowers source ($src); prefer --superpowers-from or $DSH_HOME/superpowers" >&2
|
||||
else
|
||||
echo "ok: superpowers source ($src)"
|
||||
fi
|
||||
fi
|
||||
if [ -f "$DSH_HOME/skills/using-superpowers/SKILL.md" ]; then
|
||||
echo "ok: dsh skills ($DSH_HOME/skills)"
|
||||
elif superpowers_skills >/dev/null 2>&1; then
|
||||
echo "ok: superpowers source ($(superpowers_skills)); run --skills to copy"
|
||||
else
|
||||
echo "WARN: superpowers plugin not found; the bootstrap will be skipped" >&2
|
||||
elif [ "$rc" -eq 0 ]; then
|
||||
echo "WARN: superpowers skills are not copied yet; run --skills" >&2
|
||||
fi
|
||||
if [ -f "$DSH_HOME/skills/role/SKILL.md" ]; then
|
||||
echo "ok: role skill"
|
||||
@ -190,14 +277,30 @@ install_global() {
|
||||
|
||||
install_skills() {
|
||||
mkdir -p "$DSH_HOME/skills"
|
||||
local skills
|
||||
if ! skills="$(superpowers_skills)"; then
|
||||
echo "WARN: superpowers skills not found; nothing copied" >&2
|
||||
return 0
|
||||
local line="" rc=0 source kind count=0
|
||||
line="$(superpowers_source)" || rc=$?
|
||||
if [ "$rc" -eq 2 ]; then
|
||||
report_invalid_explicit_source
|
||||
return 1
|
||||
fi
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
if [ -f "$DSH_HOME/skills/using-superpowers/SKILL.md" ]; then
|
||||
echo "WARN: no superpowers source found; keeping installed skills in $DSH_HOME/skills" >&2
|
||||
echo "WARN: next time run: $CLONE_HINT" >&2
|
||||
return 0
|
||||
fi
|
||||
echo "FAIL: no superpowers source found and $DSH_HOME/skills has no using-superpowers" >&2
|
||||
echo "FAIL: get the source once: $CLONE_HINT" >&2
|
||||
echo "FAIL: or pass --superpowers-from <dir>; local skills (role, sdd) are installed by this run" >&2
|
||||
return 1
|
||||
fi
|
||||
kind="${line##*$'\t'}"
|
||||
source="${line%%$'\t'*}"
|
||||
if [ "$kind" = legacy ]; then
|
||||
echo "WARN: legacy superpowers source $source; prefer --superpowers-from or $DSH_HOME/superpowers" >&2
|
||||
fi
|
||||
local count=0
|
||||
local dir name dest
|
||||
for dir in "$skills"/*; do
|
||||
for dir in "$source"/*; do
|
||||
[ -f "$dir/SKILL.md" ] || continue
|
||||
name="$(basename "$dir")"
|
||||
dest="$DSH_HOME/skills/$name"
|
||||
@ -205,7 +308,7 @@ install_skills() {
|
||||
cp -R "$dir" "$dest"
|
||||
count=$((count + 1))
|
||||
done
|
||||
echo "copied $count superpowers skills into $DSH_HOME/skills"
|
||||
echo "copied $count superpowers skills into $DSH_HOME/skills (source: $source)"
|
||||
}
|
||||
|
||||
install_local_skills() {
|
||||
@ -321,7 +424,9 @@ if [ "$DO_GLOBAL" = 1 ]; then
|
||||
install_global
|
||||
fi
|
||||
if [ "$DO_SKILLS" = 1 ]; then
|
||||
install_skills
|
||||
# Local skills do not depend on superpowers: install them even when the
|
||||
# superpowers source is missing (the FAIL is reported by the exit status).
|
||||
install_skills || status=1
|
||||
install_local_skills
|
||||
fi
|
||||
if [ "$DO_ROLES" = 1 ]; then
|
||||
|
||||
@ -76,3 +76,206 @@ test('install.sh copies skills and roles instead of linking into ~/.claude', ()
|
||||
function countNotes(text) {
|
||||
return (text.match(/В DSH-сессии исполнение плана/g) ?? []).length
|
||||
}
|
||||
|
||||
function makeSuperpowers(root, body = 'body') {
|
||||
for (const name of ['using-superpowers', 'brainstorming']) {
|
||||
write(join(root, 'skills', name, 'SKILL.md'), `---\nname: ${name}\n---\n${body}\n`)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
function makeLegacyCache(home) {
|
||||
const root = join(home, '.claude', 'plugins', 'cache', 'claude-plugins-official', 'superpowers', '7.0.0')
|
||||
makeSuperpowers(root)
|
||||
return join(root, 'skills')
|
||||
}
|
||||
|
||||
function installerEnv(home) {
|
||||
const env = { ...process.env, HOME: home, DSH_HOME: join(home, '.dsh') }
|
||||
delete env.DSH_SUPERPOWERS_SOURCE
|
||||
return env
|
||||
}
|
||||
|
||||
function runInstaller(home, args, extraEnv = {}) {
|
||||
return spawnSync('bash', [script, ...args], {
|
||||
env: { ...installerEnv(home), ...extraEnv },
|
||||
encoding: 'utf8',
|
||||
})
|
||||
}
|
||||
|
||||
test('--skills takes superpowers from --superpowers-from in a clean HOME', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
const src = makeSuperpowers(join(home, 'src', 'superpowers'))
|
||||
const run = runInstaller(home, ['--skills', '--superpowers-from', src])
|
||||
const out = run.stdout + run.stderr
|
||||
assert.equal(run.status, 0, out)
|
||||
assert.match(run.stdout, /copied 2 superpowers skills/)
|
||||
assert.ok(run.stdout.includes(`source: ${join(src, 'skills')}`), out)
|
||||
assert.ok(existsSync(join(home, '.dsh', 'skills', 'using-superpowers', 'SKILL.md')))
|
||||
assert.ok(existsSync(join(home, '.dsh', 'skills', 'role', 'SKILL.md')))
|
||||
assert.ok(existsSync(join(home, '.dsh', 'skills', 'sdd', 'SKILL.md')))
|
||||
assert.equal(existsSync(join(home, '.claude')), false, 'clean HOME must stay clean')
|
||||
assert.doesNotMatch(out, /legacy/)
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
test('--superpowers-from accepts the skills directory itself', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
const src = makeSuperpowers(join(home, 'src', 'superpowers'))
|
||||
const run = runInstaller(home, ['--skills', '--superpowers-from', join(src, 'skills')])
|
||||
assert.equal(run.status, 0, run.stdout + run.stderr)
|
||||
assert.match(run.stdout, /copied 2 superpowers skills/)
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
test('--superpowers-from accepts a path with spaces', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
const src = makeSuperpowers(join(home, 'super powers'))
|
||||
const run = runInstaller(home, ['--skills', '--superpowers-from', src])
|
||||
assert.equal(run.status, 0, run.stdout + run.stderr)
|
||||
assert.ok(existsSync(join(home, '.dsh', 'skills', 'using-superpowers', 'SKILL.md')))
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
test('--superpowers-from wins over DSH_SUPERPOWERS_SOURCE', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
const fromFlag = makeSuperpowers(join(home, 'flag-source'), 'from-flag')
|
||||
const fromEnv = makeSuperpowers(join(home, 'env-source'), 'from-env')
|
||||
const run = runInstaller(home, ['--skills', '--superpowers-from', fromFlag], {
|
||||
DSH_SUPERPOWERS_SOURCE: fromEnv,
|
||||
})
|
||||
assert.equal(run.status, 0, run.stdout + run.stderr)
|
||||
assert.match(
|
||||
readFileSync(join(home, '.dsh', 'skills', 'using-superpowers', 'SKILL.md'), 'utf8'),
|
||||
/from-flag/,
|
||||
)
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
test('DSH_SUPERPOWERS_SOURCE is used when the flag is absent', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
const fromEnv = makeSuperpowers(join(home, 'env-source'), 'from-env')
|
||||
const run = runInstaller(home, ['--skills'], { DSH_SUPERPOWERS_SOURCE: fromEnv })
|
||||
assert.equal(run.status, 0, run.stdout + run.stderr)
|
||||
assert.match(
|
||||
readFileSync(join(home, '.dsh', 'skills', 'using-superpowers', 'SKILL.md'), 'utf8'),
|
||||
/from-env/,
|
||||
)
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
test('--skills fails when there is no source and nothing installed', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
const run = runInstaller(home, ['--skills'])
|
||||
const out = run.stdout + run.stderr
|
||||
assert.equal(run.status, 1, out)
|
||||
assert.match(out, /FAIL: no superpowers source/)
|
||||
assert.match(out, /git clone --depth 1 https:\/\/github\.com\/obra\/superpowers/)
|
||||
assert.ok(existsSync(join(home, '.dsh', 'skills', 'role', 'SKILL.md')), 'local skills still installed')
|
||||
assert.ok(existsSync(join(home, '.dsh', 'skills', 'sdd', 'SKILL.md')), 'local skills still installed')
|
||||
assert.equal(existsSync(join(home, '.dsh', 'skills', 'using-superpowers')), false)
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
test('--skills keeps installed copies when the source is gone', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
const installed = join(home, '.dsh', 'skills', 'using-superpowers', 'SKILL.md')
|
||||
write(installed, '---\nname: using-superpowers\n---\ninstalled\n')
|
||||
const run = runInstaller(home, ['--skills'])
|
||||
const out = run.stdout + run.stderr
|
||||
assert.equal(run.status, 0, out)
|
||||
assert.match(out, /WARN: no superpowers source/)
|
||||
assert.match(readFileSync(installed, 'utf8'), /installed/)
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
test('empty DSH_SUPERPOWERS_SOURCE counts as unset', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
const run = runInstaller(home, ['--skills'], { DSH_SUPERPOWERS_SOURCE: '' })
|
||||
const out = run.stdout + run.stderr
|
||||
assert.equal(run.status, 1, out)
|
||||
assert.match(out, /FAIL: no superpowers source/)
|
||||
assert.doesNotMatch(out, /explicit superpowers source/)
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
test('an invalid explicit source fails without touching the legacy cache', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
makeLegacyCache(home)
|
||||
const empty = join(home, 'not-superpowers')
|
||||
const run = runInstaller(home, ['--skills', '--superpowers-from', empty])
|
||||
const out = run.stdout + run.stderr
|
||||
assert.equal(run.status, 1, out)
|
||||
assert.match(out, /FAIL: explicit superpowers source .* has no using-superpowers\/SKILL\.md/)
|
||||
assert.equal(existsSync(join(home, '.dsh', 'skills', 'brainstorming')), false)
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
test('the legacy plugin cache still works and warns', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
makeLegacyCache(home)
|
||||
const run = runInstaller(home, ['--skills'])
|
||||
const out = run.stdout + run.stderr
|
||||
assert.equal(run.status, 0, out)
|
||||
assert.match(out, /WARN: legacy superpowers source/)
|
||||
assert.match(run.stdout, /copied 2 superpowers skills/)
|
||||
assert.ok(existsSync(join(home, '.dsh', 'skills', 'using-superpowers', 'SKILL.md')))
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
test('--check reports the pinned source', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
const pinned = join(home, '.dsh', 'superpowers')
|
||||
makeSuperpowers(pinned)
|
||||
const run = runInstaller(home, ['--check'])
|
||||
const out = run.stdout + run.stderr
|
||||
assert.equal(run.status, 0, out)
|
||||
assert.match(out, /ok: superpowers source \(.*superpowers\/skills\)/)
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
test('--check fails on an invalid explicit source', () => {
|
||||
const home = tempDir()
|
||||
try {
|
||||
const empty = join(home, 'not-superpowers')
|
||||
const run = runInstaller(home, ['--check'], { DSH_SUPERPOWERS_SOURCE: empty })
|
||||
const out = run.stdout + run.stderr
|
||||
assert.equal(run.status, 1, out)
|
||||
assert.match(out, /FAIL: explicit superpowers source/)
|
||||
} finally {
|
||||
cleanup(home)
|
||||
}
|
||||
})
|
||||
|
||||
@ -84,7 +84,10 @@ Issue: #49. Спека: `openspec/changes/dsh-install-source/specs/dsh-install/s
|
||||
|
||||
`--check` — диагностика, а не установка: отсутствие superpowers-источника
|
||||
остаётся `WARN`, код возврата меняют только жёсткие `FAIL` (python3, git, node,
|
||||
хуки, бандл). Так же уже сделано для openspec (#42, R-4).
|
||||
хуки, бандл). Так же уже сделано для openspec (#42, R-4). Исключение —
|
||||
невалидный **явный** источник (`--superpowers-from`/`DSH_SUPERPOWERS_SOURCE`):
|
||||
это ошибка конфигурации пользователя, `--check` даёт `FAIL`, иначе опечатка
|
||||
выглядит как «источника нет».
|
||||
|
||||
## 3. Формулировки, которые попадут в установленный AGENTS.md
|
||||
|
||||
|
||||
@ -84,7 +84,8 @@ SHALL оставаться fallback'ом для машин, где `--skills --r
|
||||
источник, но `--skills` печатает `WARN: legacy superpowers source` с
|
||||
подсказкой перейти на пиннинг/флаг.
|
||||
- `--check`: legacy-источник — `WARN`, явный/пиннинг — `ok`, отсутствие —
|
||||
`WARN` с подсказкой.
|
||||
`WARN` с подсказкой, невалидный явный источник — `FAIL` и код 1
|
||||
(пользователь задал его сам, подсказка «нет источника» здесь неверна).
|
||||
- Runtime: bootstrap и резолвер ролей сохраняют порядок `~/.dsh` → `~/.claude`,
|
||||
но при выборе legacy-пути один раз за процесс пишут в лог предупреждение.
|
||||
- Удаление fallback'ов — отдельное изменение; в этом change они не удаляются.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user