feat(dsh): --openspec добавляет agents-адаптер в проект (#42)
Co-Authored-By: DeepSeek Harness <noreply@deepseek.com>
This commit is contained in:
parent
5d0fdc4c0d
commit
748f8e115a
@ -13,6 +13,7 @@
|
|||||||
# bash dsh/install.sh --skills
|
# bash dsh/install.sh --skills
|
||||||
# bash dsh/install.sh --roles
|
# bash dsh/install.sh --roles
|
||||||
# bash dsh/install.sh --all
|
# bash dsh/install.sh --all
|
||||||
|
# bash dsh/install.sh --openspec
|
||||||
#
|
#
|
||||||
# Run it from a terminal, not from inside a DSH session: --global, --skills and
|
# Run it from a terminal, not from inside a DSH session: --global, --skills and
|
||||||
# --roles write outside the session workspace and would require approval.
|
# --roles write outside the session workspace and would require approval.
|
||||||
@ -25,16 +26,18 @@ DO_CHECK=0
|
|||||||
DO_GLOBAL=0
|
DO_GLOBAL=0
|
||||||
DO_SKILLS=0
|
DO_SKILLS=0
|
||||||
DO_ROLES=0
|
DO_ROLES=0
|
||||||
|
DO_OPENSPEC=0
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: bash dsh/install.sh [--check] [--global] [--skills] [--roles] [--all]
|
Usage: bash dsh/install.sh [--check] [--global] [--skills] [--roles] [--all] [--openspec]
|
||||||
|
|
||||||
--check verify python3, git, node, hooks, bundle, skills, and roles
|
--check verify python3, git, node, hooks, bundle, skills, and roles
|
||||||
--global back up and write \$DSH_HOME/AGENTS.md from global/CLAUDE.md
|
--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
|
--skills copy superpowers skills and local dsh/skills into \$DSH_HOME/skills
|
||||||
--roles copy roles/ into \$DSH_HOME/roles
|
--roles copy roles/ into \$DSH_HOME/roles
|
||||||
--all --check + --global + --skills + --roles, then print the bundle step
|
--all --check + --global + --skills + --roles, then print the bundle step
|
||||||
|
--openspec add the agents skill adapter to the current OpenSpec project
|
||||||
|
|
||||||
Bundle installation itself is done in DSH:
|
Bundle installation itself is done in DSH:
|
||||||
Plugins -> install a bundle from $BUNDLE
|
Plugins -> install a bundle from $BUNDLE
|
||||||
@ -52,6 +55,7 @@ for arg in "$@"; do
|
|||||||
--global) DO_GLOBAL=1 ;;
|
--global) DO_GLOBAL=1 ;;
|
||||||
--skills) DO_SKILLS=1 ;;
|
--skills) DO_SKILLS=1 ;;
|
||||||
--roles) DO_ROLES=1 ;;
|
--roles) DO_ROLES=1 ;;
|
||||||
|
--openspec) DO_OPENSPEC=1 ;;
|
||||||
--all) DO_CHECK=1; DO_GLOBAL=1; DO_SKILLS=1; DO_ROLES=1 ;;
|
--all) DO_CHECK=1; DO_GLOBAL=1; DO_SKILLS=1; DO_ROLES=1 ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) echo "unknown option: $arg" >&2; usage >&2; exit 2 ;;
|
*) echo "unknown option: $arg" >&2; usage >&2; exit 2 ;;
|
||||||
@ -278,6 +282,20 @@ print("ok: role dsh notes inserted")
|
|||||||
PY
|
PY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_openspec() {
|
||||||
|
local dir="${1:-$PWD}"
|
||||||
|
if ! command -v openspec >/dev/null 2>&1; then
|
||||||
|
echo "FAIL: openspec CLI not found; install it first" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ ! -d "$dir/openspec" ]; then
|
||||||
|
echo "FAIL: $dir has no openspec/ root; run: openspec init --tools agents" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
( cd "$dir" && openspec init --tools agents )
|
||||||
|
echo "ok: openspec agents adapter in $dir"
|
||||||
|
}
|
||||||
|
|
||||||
print_bundle_step() {
|
print_bundle_step() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
|
|
||||||
@ -307,6 +325,11 @@ if [ "$DO_ROLES" = 1 ]; then
|
|||||||
install_roles
|
install_roles
|
||||||
install_role_dsh_notes
|
install_role_dsh_notes
|
||||||
fi
|
fi
|
||||||
|
# A failed --openspec step must not abort the other steps: the explicit return 1
|
||||||
|
# becomes the script status here, like check() above.
|
||||||
|
if [ "$DO_OPENSPEC" = 1 ]; then
|
||||||
|
install_openspec "$PWD" || status=1
|
||||||
|
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
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import assert from 'node:assert/strict'
|
import assert from 'node:assert/strict'
|
||||||
import { spawnSync } from 'node:child_process'
|
import { spawnSync } from 'node:child_process'
|
||||||
import { chmodSync, existsSync, mkdirSync, readFileSync, symlinkSync } from 'node:fs'
|
import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, symlinkSync } from 'node:fs'
|
||||||
import { dirname, join } from 'node:path'
|
import { dirname, join } from 'node:path'
|
||||||
import test from 'node:test'
|
import test from 'node:test'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
@ -66,3 +66,47 @@ test('install.sh --check warns but does not fail without the openspec CLI', () =
|
|||||||
cleanup(home)
|
cleanup(home)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function fakeOpenspec(dir, log) {
|
||||||
|
const bin = join(dir, 'bin')
|
||||||
|
write(join(bin, 'openspec'), `#!/bin/sh\necho "$PWD $@" >> ${JSON.stringify(log)}\nexit 0\n`)
|
||||||
|
chmodSync(join(bin, 'openspec'), 0o755)
|
||||||
|
return bin
|
||||||
|
}
|
||||||
|
|
||||||
|
test('--openspec adds the agents adapter to an existing OpenSpec project', () => {
|
||||||
|
const home = tempDir()
|
||||||
|
try {
|
||||||
|
const project = join(home, 'project')
|
||||||
|
mkdirSync(join(project, 'openspec'), { recursive: true })
|
||||||
|
// macOS: os.tmpdir() идёт через симлинк /var -> /private/var, а $PWD в шелле — уже реальный путь
|
||||||
|
const realProject = realpathSync(project)
|
||||||
|
const log = join(home, 'calls.log')
|
||||||
|
const bin = fakeOpenspec(home, log)
|
||||||
|
const env = { ...process.env, HOME: home, DSH_HOME: join(home, '.dsh'), PATH: `${bin}:${process.env.PATH}` }
|
||||||
|
const run = spawnSync('bash', [script, '--openspec'], { cwd: project, env, encoding: 'utf8' })
|
||||||
|
assert.equal(run.status, 0, run.stderr)
|
||||||
|
assert.match(run.stdout, /ok: openspec agents adapter in /)
|
||||||
|
assert.match(readFileSync(log, 'utf8'), new RegExp(`^${realProject} init --tools agents$`, 'm'))
|
||||||
|
} finally {
|
||||||
|
cleanup(home)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('--openspec refuses a project without an openspec root', () => {
|
||||||
|
const home = tempDir()
|
||||||
|
try {
|
||||||
|
const project = join(home, 'plain')
|
||||||
|
mkdirSync(project, { recursive: true })
|
||||||
|
const log = join(home, 'calls.log')
|
||||||
|
const bin = fakeOpenspec(home, log)
|
||||||
|
const env = { ...process.env, HOME: home, DSH_HOME: join(home, '.dsh'), PATH: `${bin}:${process.env.PATH}` }
|
||||||
|
const run = spawnSync('bash', [script, '--openspec'], { cwd: project, env, encoding: 'utf8' })
|
||||||
|
assert.notEqual(run.status, 0)
|
||||||
|
assert.match(run.stdout + run.stderr, /has no openspec\/ root/)
|
||||||
|
assert.equal(existsSync(log), false, 'openspec must not run in a non-OpenSpec project')
|
||||||
|
assert.equal(existsSync(join(project, 'openspec')), false)
|
||||||
|
} finally {
|
||||||
|
cleanup(home)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user