DSH: install.sh --roles дописывает DSH-заметку в dev.md (#53) #54
@ -224,6 +224,12 @@ bash dsh/install.sh --global --skills --roles
|
|||||||
# mcp__codegraph__codegraph_explore.
|
# mcp__codegraph__codegraph_explore.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`--roles` also appends the DSH SDD note to `~/.dsh/roles/dev.md` (the bullet that
|
||||||
|
points `dev` at the `sdd` skill). The note lives only in the installed copy: the
|
||||||
|
shared `roles/dev.md` stays Claude Code-compatible, so a Claude session never
|
||||||
|
sees DSH-only instructions. The insertion is idempotent — every `--roles` run
|
||||||
|
recopies the role from the repository and re-inserts exactly one note.
|
||||||
|
|
||||||
Live checks:
|
Live checks:
|
||||||
|
|
||||||
1. New session: ask "what skills do you have?" — superpowers skills and the
|
1. New session: ask "what skills do you have?" — superpowers skills and the
|
||||||
|
|||||||
@ -141,6 +141,11 @@ check() {
|
|||||||
else
|
else
|
||||||
echo "WARN: roles not found; /role will fail until --roles is run" >&2
|
echo "WARN: roles not found; /role will fail until --roles is run" >&2
|
||||||
fi
|
fi
|
||||||
|
if [ -f "$DSH_HOME/roles/dev.md" ] && grep -q 'В DSH-сессии исполнение плана' "$DSH_HOME/roles/dev.md"; then
|
||||||
|
echo "ok: role dsh notes"
|
||||||
|
else
|
||||||
|
echo "WARN: role has no DSH SDD notes; run --roles" >&2
|
||||||
|
fi
|
||||||
echo "DSH_HOME=$DSH_HOME"
|
echo "DSH_HOME=$DSH_HOME"
|
||||||
return "$failed"
|
return "$failed"
|
||||||
}
|
}
|
||||||
@ -224,6 +229,45 @@ install_roles() {
|
|||||||
echo "installed $count roles into $DSH_HOME/roles"
|
echo "installed $count roles into $DSH_HOME/roles"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The DSH SDD note belongs to the installed role, not to the shared roles/ layer:
|
||||||
|
# in Claude Code the note does not apply, so it is written here, after the copy,
|
||||||
|
# and is idempotent (the source file is overwritten on every --roles run).
|
||||||
|
install_role_dsh_notes() {
|
||||||
|
local role="$DSH_HOME/roles/dev.md"
|
||||||
|
[ -f "$role" ] || return 0
|
||||||
|
python3 - "$role" <<'PY'
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
path = Path(sys.argv[1])
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
marker = "В DSH-сессии исполнение плана"
|
||||||
|
if marker in text:
|
||||||
|
print("ok: role dsh notes already present")
|
||||||
|
raise SystemExit(0)
|
||||||
|
note = (
|
||||||
|
"- В DSH-сессии исполнение плана идёт по skill `sdd`, а не "
|
||||||
|
"`subagent-driven-development`: асинхронный `subagent` (бриф и отчёт файлами, "
|
||||||
|
"ожидание завершением хода и уведомлением, ledger "
|
||||||
|
"`.superpowers/sdd/<plan>/progress.md` с `## Findings`, fix-loop до 5 раундов). "
|
||||||
|
"Режим контроллера — по числу параллельных планов: один план — главная сессия, "
|
||||||
|
"несколько — фоновый контроллер (нужен `maxDepth: 2` из "
|
||||||
|
"`dsh/subagent-models.yml`). В Claude Code строка не действует: там исходный "
|
||||||
|
"`subagent-driven-development`."
|
||||||
|
)
|
||||||
|
anchor = "- Режим по умолчанию: subagent-driven-development."
|
||||||
|
lines = text.splitlines(keepends=True)
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.startswith(anchor):
|
||||||
|
lines.insert(i + 1, note + "\n")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
lines.append((("" if text.endswith("\n") else "\n") + note + "\n"))
|
||||||
|
path.write_text("".join(lines), encoding="utf-8")
|
||||||
|
print("ok: role dsh notes inserted")
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
print_bundle_step() {
|
print_bundle_step() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
|
|
||||||
@ -251,6 +295,7 @@ if [ "$DO_SKILLS" = 1 ]; then
|
|||||||
fi
|
fi
|
||||||
if [ "$DO_ROLES" = 1 ]; then
|
if [ "$DO_ROLES" = 1 ]; then
|
||||||
install_roles
|
install_roles
|
||||||
|
install_role_dsh_notes
|
||||||
fi
|
fi
|
||||||
if [ "$DO_GLOBAL" = 1 ] || [ "$DO_SKILLS" = 1 ] || [ "$DO_ROLES" = 1 ]; then
|
if [ "$DO_GLOBAL" = 1 ] || [ "$DO_SKILLS" = 1 ] || [ "$DO_ROLES" = 1 ]; then
|
||||||
print_bundle_step
|
print_bundle_step
|
||||||
|
|||||||
@ -52,11 +52,27 @@ test('install.sh copies skills and roles instead of linking into ~/.claude', ()
|
|||||||
assert.ok(existsSync(join(dshHome, 'roles', `${role}.md`)), `missing ${role}.md`)
|
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()
|
const second = run()
|
||||||
assert.equal(second.status, 0, second.stderr)
|
assert.equal(second.status, 0, second.stderr)
|
||||||
assert.equal(lstatSync(skill).isSymbolicLink(), false)
|
assert.equal(lstatSync(skill).isSymbolicLink(), false)
|
||||||
assert.ok(existsSync(roleSkill))
|
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 {
|
} finally {
|
||||||
cleanup(home)
|
cleanup(home)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function countNotes(text) {
|
||||||
|
return (text.match(/В DSH-сессии исполнение плана/g) ?? []).length
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user