diff --git a/scripts/conductor-scripts.test.mjs b/scripts/conductor-scripts.test.mjs index ba0ac88e..3e83887b 100644 --- a/scripts/conductor-scripts.test.mjs +++ b/scripts/conductor-scripts.test.mjs @@ -17,49 +17,26 @@ describe('Conductor workspace scripts', () => { assert.equal(manifest.runScriptMode, 'nonconcurrent'); }); - it('orchestrates setup through focused step scripts', async () => { + it('sets up exact uv, local files, Python packages, JS packages, and the built CLI', async () => { const setupScript = await readText('scripts/conductor-setup.sh'); - assert.match(setupScript, /sh scripts\/conductor\/link-agent-overlays\.sh/); - assert.match(setupScript, /sh scripts\/conductor\/link-root-env-file\.sh/); - assert.match(setupScript, /source scripts\/conductor\/activate-workspace-uv\.sh/); - assert.match(setupScript, /sh scripts\/conductor\/install-python-dependencies\.sh/); - assert.match(setupScript, /sh scripts\/conductor\/install-js-dependencies\.sh/); - assert.match(setupScript, /sh scripts\/conductor\/rebuild-native-dependencies\.sh/); - assert.match(setupScript, /sh scripts\/conductor\/build-workspace\.sh/); - assert.match(setupScript, /sh scripts\/conductor\/run-setup-doctor\.sh/); - assert.doesNotMatch(setupScript, /read_required_uv_version\(\)/); - assert.doesNotMatch(setupScript, /uv sync --all-packages --all-groups/); - assert.doesNotMatch(setupScript, /pnpm install --frozen-lockfile --prefer-offline/); - assert.doesNotMatch(setupScript, /packages\/cli\/dist\/bin\.js dev doctor setup --no-input/); - }); - - it('keeps concrete setup commands in step scripts', async () => { - const resolveUvScript = await readText('scripts/conductor/resolve-uv.sh'); - const activateUvScript = await readText('scripts/conductor/activate-workspace-uv.sh'); - const pythonScript = await readText('scripts/conductor/install-python-dependencies.sh'); - const jsScript = await readText('scripts/conductor/install-js-dependencies.sh'); - const nativeScript = await readText('scripts/conductor/rebuild-native-dependencies.sh'); - const buildScript = await readText('scripts/conductor/build-workspace.sh'); - const doctorScript = await readText('scripts/conductor/run-setup-doctor.sh'); - - assert.match(resolveUvScript, /read_required_uv_version\(\)/); - assert.match(resolveUvScript, /\.context\/bin\/uv-\$required_version/); - assert.match(activateUvScript, /bash scripts\/conductor\/resolve-uv\.sh pyproject\.toml/); - assert.match(activateUvScript, /export PATH="\$\(dirname "\$KTX_UV_BIN"\):\$PATH"/); - assert.match(pythonScript, /uv sync --all-packages --all-groups/); - assert.match(jsScript, /pnpm install --frozen-lockfile --prefer-offline/); - assert.match(nativeScript, /pnpm run native:rebuild/); - assert.match(buildScript, /pnpm run build/); - assert.match(doctorScript, /packages\/cli\/dist\/bin\.js dev doctor setup --no-input/); + assert.match(setupScript, /read_required_uv_version\(\)/); + assert.match(setupScript, /\.context\/bin\/uv-\$required_version/); + assert.match(setupScript, /link_agent_overlays/); + assert.match(setupScript, /CONDUCTOR_ROOT_PATH/); + assert.match(setupScript, /uv sync --all-packages --all-groups/); + assert.match(setupScript, /pnpm install --frozen-lockfile --prefer-offline/); + assert.match(setupScript, /pnpm run native:rebuild/); + assert.match(setupScript, /pnpm run build/); + assert.match(setupScript, /packages\/cli\/dist\/bin\.js dev doctor setup --no-input/); + assert.doesNotMatch(setupScript, /scripts\/conductor\//); }); it('links private agent overlays when KAELIO_SKILLS_ROOT is set', async () => { - const workspaceScript = await readText('scripts/conductor/link-agent-overlays.sh'); + const workspaceScript = await readText('scripts/conductor-setup.sh'); assert.match(workspaceScript, /KAELIO_SKILLS_ROOT/); - assert.match(workspaceScript, /agents_source="\$\{KAELIO_SKILLS_ROOT\}\/\.agents"/); - assert.match(workspaceScript, /ln -s "\$\{agents_source\}" \.agents/); + assert.match(workspaceScript, /ln -s "\$\{KAELIO_SKILLS_ROOT\}\/\.agents" \.agents/); }); it('runs the KTX daemon on the documented fixed local port', async () => { diff --git a/scripts/conductor-setup.sh b/scripts/conductor-setup.sh index 8837a9ab..ee0271c4 100755 --- a/scripts/conductor-setup.sh +++ b/scripts/conductor-setup.sh @@ -1,20 +1,129 @@ #!/bin/bash # conductor-setup.sh - Runs once when Conductor creates a KTX workspace. # -# Orchestrates workspace setup. Step implementation lives in scripts/conductor/. +# Prepares the standalone pnpm + uv workspace and builds the local CLI. set -e set -o pipefail +read_required_uv_version() { + local project_file="$1" + + if [ ! -f "$project_file" ]; then + return 1 + fi + + sed -nE 's/^[[:space:]]*required-version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' "$project_file" | head -n 1 +} + +uv_version() { + local uv_bin="$1" + + "$uv_bin" --version 2>/dev/null | awk '{print $2}' +} + +install_workspace_uv() { + local required_version="$1" + local install_dir="$PWD/.context/bin/uv-$required_version" + + mkdir -p "$install_dir" + + if [ ! -x "$install_dir/uv" ] || [ "$(uv_version "$install_dir/uv")" != "$required_version" ]; then + echo "Installing workspace-local uv $required_version..." >&2 + curl -LsSf "https://astral.sh/uv/$required_version/install.sh" | + env UV_INSTALL_DIR="$install_dir" UV_NO_MODIFY_PATH=1 sh >&2 + fi + + printf '%s\n' "$install_dir/uv" +} + +resolve_uv_for_project() { + local project_file="$1" + local required_version + local system_uv + local system_version + local workspace_uv + + required_version="$(read_required_uv_version "$project_file" || true)" + required_version="${required_version#==}" + + if [ -z "$required_version" ]; then + command -v uv + return + fi + + if ! [[ "$required_version" =~ ^[0-9]+[.][0-9]+[.][0-9]+$ ]]; then + echo "WARNING: Unsupported uv required-version '$required_version'; using uv from PATH." >&2 + command -v uv + return + fi + + if command -v uv >/dev/null 2>&1; then + system_uv="$(command -v uv)" + system_version="$(uv_version "$system_uv")" + + if [ "$system_version" = "$required_version" ]; then + printf '%s\n' "$system_uv" + return + fi + + echo "Found uv $system_version at $system_uv; $project_file requires uv $required_version." >&2 + else + echo "uv is not installed on PATH; $project_file requires uv $required_version." >&2 + fi + + workspace_uv="$(install_workspace_uv "$required_version")" + + if [ "$(uv_version "$workspace_uv")" != "$required_version" ]; then + echo "ERROR: Expected uv $required_version at $workspace_uv, got $("$workspace_uv" --version 2>&1 || true)." >&2 + return 1 + fi + + printf '%s\n' "$workspace_uv" +} + +link_agent_overlays() { + if [ -z "${KAELIO_SKILLS_ROOT:-}" ] || [ ! -d "${KAELIO_SKILLS_ROOT}/.agents" ]; then + return 0 + fi + + if [ -L .agents ]; then + return 0 + fi + + if [ -e .agents ]; then + echo "Skipping .agents symlink because .agents already exists and is not a symlink." >&2 + return 0 + fi + + ln -s "${KAELIO_SKILLS_ROOT}/.agents" .agents +} + echo "=== Conductor KTX workspace setup ===" -sh scripts/conductor/link-agent-overlays.sh -sh scripts/conductor/link-root-env-file.sh -source scripts/conductor/activate-workspace-uv.sh -sh scripts/conductor/install-python-dependencies.sh -sh scripts/conductor/install-js-dependencies.sh -sh scripts/conductor/rebuild-native-dependencies.sh -sh scripts/conductor/build-workspace.sh -sh scripts/conductor/run-setup-doctor.sh +link_agent_overlays + +if [ -n "${CONDUCTOR_ROOT_PATH:-}" ] && [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then + ln -sf "$CONDUCTOR_ROOT_PATH/.env" .env + echo "Linked .env" +fi + +KTX_UV_BIN="$(resolve_uv_for_project "pyproject.toml")" +export PATH="$(dirname "$KTX_UV_BIN"):$PATH" + +echo "Installing KTX Python dependencies..." +uv sync --all-packages --all-groups + +echo "Installing KTX JS dependencies..." +pnpm install --frozen-lockfile --prefer-offline + +echo "Rebuilding native JS dependencies..." +pnpm run native:rebuild + +echo "Building KTX packages..." +pnpm run build + +echo "Running KTX setup doctor..." +node packages/cli/dist/bin.js dev doctor setup --no-input echo "=== Setup complete ===" diff --git a/scripts/conductor/activate-workspace-uv.sh b/scripts/conductor/activate-workspace-uv.sh deleted file mode 100755 index 306e0eb7..00000000 --- a/scripts/conductor/activate-workspace-uv.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -set -e -set -o pipefail - -KTX_UV_BIN="$(bash scripts/conductor/resolve-uv.sh pyproject.toml)" -export PATH="$(dirname "$KTX_UV_BIN"):$PATH" diff --git a/scripts/conductor/build-workspace.sh b/scripts/conductor/build-workspace.sh deleted file mode 100755 index 06ce97db..00000000 --- a/scripts/conductor/build-workspace.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -set -eu - -echo "Building KTX packages..." -pnpm run build diff --git a/scripts/conductor/install-js-dependencies.sh b/scripts/conductor/install-js-dependencies.sh deleted file mode 100755 index 0cee55f1..00000000 --- a/scripts/conductor/install-js-dependencies.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -set -eu - -echo "Installing KTX JS dependencies..." -pnpm install --frozen-lockfile --prefer-offline diff --git a/scripts/conductor/install-python-dependencies.sh b/scripts/conductor/install-python-dependencies.sh deleted file mode 100755 index aa297dac..00000000 --- a/scripts/conductor/install-python-dependencies.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -set -eu - -echo "Installing KTX Python dependencies..." -uv sync --all-packages --all-groups diff --git a/scripts/conductor/link-agent-overlays.sh b/scripts/conductor/link-agent-overlays.sh deleted file mode 100755 index 8e84145f..00000000 --- a/scripts/conductor/link-agent-overlays.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -set -eu - -if [ -z "${KAELIO_SKILLS_ROOT:-}" ]; then - exit 0 -fi - -agents_source="${KAELIO_SKILLS_ROOT}/.agents" - -if [ ! -d "${agents_source}" ]; then - exit 0 -fi - -if [ -L .agents ]; then - exit 0 -fi - -if [ -e .agents ]; then - echo "Skipping .agents symlink because .agents already exists and is not a symlink." >&2 - exit 0 -fi - -ln -s "${agents_source}" .agents diff --git a/scripts/conductor/link-root-env-file.sh b/scripts/conductor/link-root-env-file.sh deleted file mode 100755 index 27f6f2dc..00000000 --- a/scripts/conductor/link-root-env-file.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -set -eu - -if [ -n "${CONDUCTOR_ROOT_PATH:-}" ] && [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then - ln -sf "$CONDUCTOR_ROOT_PATH/.env" .env - echo "Linked .env" -fi diff --git a/scripts/conductor/rebuild-native-dependencies.sh b/scripts/conductor/rebuild-native-dependencies.sh deleted file mode 100755 index 7a5651ef..00000000 --- a/scripts/conductor/rebuild-native-dependencies.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -set -eu - -echo "Rebuilding native JS dependencies..." -pnpm run native:rebuild diff --git a/scripts/conductor/resolve-uv.sh b/scripts/conductor/resolve-uv.sh deleted file mode 100755 index 9bb70815..00000000 --- a/scripts/conductor/resolve-uv.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/bash -set -e -set -o pipefail - -read_required_uv_version() { - local project_file="$1" - - if [ ! -f "$project_file" ]; then - return 1 - fi - - sed -nE 's/^[[:space:]]*required-version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' "$project_file" | head -n 1 -} - -uv_version() { - local uv_bin="$1" - - "$uv_bin" --version 2>/dev/null | awk '{print $2}' -} - -install_workspace_uv() { - local required_version="$1" - local install_dir="$PWD/.context/bin/uv-$required_version" - - mkdir -p "$install_dir" - - if [ ! -x "$install_dir/uv" ] || [ "$(uv_version "$install_dir/uv")" != "$required_version" ]; then - echo "Installing workspace-local uv $required_version..." >&2 - curl -LsSf "https://astral.sh/uv/$required_version/install.sh" | - env UV_INSTALL_DIR="$install_dir" UV_NO_MODIFY_PATH=1 sh >&2 - fi - - printf '%s\n' "$install_dir/uv" -} - -resolve_uv_for_project() { - local project_file="$1" - local required_version - local system_uv - local system_version - local workspace_uv - - required_version="$(read_required_uv_version "$project_file" || true)" - required_version="${required_version#==}" - - if [ -z "$required_version" ]; then - command -v uv - return - fi - - if ! [[ "$required_version" =~ ^[0-9]+[.][0-9]+[.][0-9]+$ ]]; then - echo "WARNING: Unsupported uv required-version '$required_version'; using uv from PATH." >&2 - command -v uv - return - fi - - if command -v uv >/dev/null 2>&1; then - system_uv="$(command -v uv)" - system_version="$(uv_version "$system_uv")" - - if [ "$system_version" = "$required_version" ]; then - printf '%s\n' "$system_uv" - return - fi - - echo "Found uv $system_version at $system_uv; $project_file requires uv $required_version." >&2 - else - echo "uv is not installed on PATH; $project_file requires uv $required_version." >&2 - fi - - workspace_uv="$(install_workspace_uv "$required_version")" - - if [ "$(uv_version "$workspace_uv")" != "$required_version" ]; then - echo "ERROR: Expected uv $required_version at $workspace_uv, got $("$workspace_uv" --version 2>&1 || true)." >&2 - return 1 - fi - - printf '%s\n' "$workspace_uv" -} - -resolve_uv_for_project "${1:-pyproject.toml}" diff --git a/scripts/conductor/run-setup-doctor.sh b/scripts/conductor/run-setup-doctor.sh deleted file mode 100755 index 285aab00..00000000 --- a/scripts/conductor/run-setup-doctor.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -set -eu - -echo "Running KTX setup doctor..." -node packages/cli/dist/bin.js dev doctor setup --no-input