From c25df37232e0ffc15eafbaed20c6188a8da7bd36 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Mon, 4 May 2026 22:48:50 -0400 Subject: [PATCH 1/2] fix(kimaki): normalize Data Machine send flags --- .github/workflows/shell.yml | 8 ++ bridges/kimaki.sh | 58 +++++++------ bridges/kimaki/bin/datamachine-kimaki | 117 ++++++++++++++++++++++++++ tests/datamachine-kimaki-adapter.sh | 74 ++++++++++++++++ 4 files changed, 233 insertions(+), 24 deletions(-) create mode 100755 bridges/kimaki/bin/datamachine-kimaki create mode 100755 tests/datamachine-kimaki-adapter.sh diff --git a/.github/workflows/shell.yml b/.github/workflows/shell.yml index 59232a8..42a8594 100644 --- a/.github/workflows/shell.yml +++ b/.github/workflows/shell.yml @@ -39,3 +39,11 @@ jobs: - uses: actions/checkout@v4 - name: Run tests/opencode-wrapper-removal.sh run: ./tests/opencode-wrapper-removal.sh + + datamachine-kimaki-adapter: + name: Data Machine Kimaki adapter regression + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run tests/datamachine-kimaki-adapter.sh + run: ./tests/datamachine-kimaki-adapter.sh diff --git a/bridges/kimaki.sh b/bridges/kimaki.sh index 246bb1c..2096f4d 100644 --- a/bridges/kimaki.sh +++ b/bridges/kimaki.sh @@ -9,12 +9,14 @@ # Install layout: # VPS: /opt/kimaki-config/{plugins,post-upgrade.sh,skills-kill-list.txt} # + /usr/local/bin/datamachine-kimaki-session +# + /usr/local/bin/datamachine-kimaki # + /etc/systemd/system/kimaki.service (ExecStartPre runs post-upgrade.sh) # Local: $(npm root -g)/kimaki/plugins for plugins (lives inside the npm # package; wiped on `npm update -g kimaki`), # $KIMAKI_DATA_DIR/kimaki-config/ for post-upgrade.sh + kill list # (executed inline at upgrade time — no launchd ExecStartPre hook), # + $HOME/.local/bin/datamachine-kimaki-session +# + $HOME/.local/bin/datamachine-kimaki # + $HOME/Library/LaunchAgents/com.wp.kimaki.plist on macOS. # ============================================================================ @@ -51,34 +53,42 @@ bridge_install() { _kimaki_install_systemd fi - _kimaki_sync_handoff_helper + _kimaki_sync_bin_helpers } -_kimaki_sync_handoff_helper() { - [ -f "$SCRIPT_DIR/bridges/kimaki/bin/datamachine-kimaki-session" ] || return 0 +_kimaki_sync_bin_helpers() { + [ -d "$SCRIPT_DIR/bridges/kimaki/bin" ] || return 0 - local HELPER_TARGET + local HELPER_DIR if [ "$LOCAL_MODE" = true ]; then - HELPER_TARGET="$SERVICE_HOME/.local/bin/datamachine-kimaki-session" + HELPER_DIR="$SERVICE_HOME/.local/bin" else - HELPER_TARGET="/usr/local/bin/datamachine-kimaki-session" + HELPER_DIR="/usr/local/bin" fi - if [ "$DRY_RUN" = true ]; then - if ! cmp -s "$SCRIPT_DIR/bridges/kimaki/bin/datamachine-kimaki-session" "$HELPER_TARGET" 2>/dev/null; then - echo -e "${BLUE}[dry-run]${NC} Would update $HELPER_TARGET" - fi - else - mkdir -p "$(dirname "$HELPER_TARGET")" - if ! cmp -s "$SCRIPT_DIR/bridges/kimaki/bin/datamachine-kimaki-session" "$HELPER_TARGET" 2>/dev/null; then - cp "$SCRIPT_DIR/bridges/kimaki/bin/datamachine-kimaki-session" "$HELPER_TARGET" - chmod +x "$HELPER_TARGET" - log " Updated $HELPER_TARGET" - UPDATED_ITEMS+=("datamachine-kimaki-session helper") + local helper_file name helper_target + for helper_file in "$SCRIPT_DIR"/bridges/kimaki/bin/*; do + [ -f "$helper_file" ] || continue + name=$(basename "$helper_file") + helper_target="$HELPER_DIR/$name" + + if [ "$DRY_RUN" = true ]; then + if ! cmp -s "$helper_file" "$helper_target" 2>/dev/null; then + echo -e "${BLUE}[dry-run]${NC} Would update $helper_target" + fi + else + mkdir -p "$HELPER_DIR" + if ! cmp -s "$helper_file" "$helper_target" 2>/dev/null; then + cp "$helper_file" "$helper_target" + chmod +x "$helper_target" + log " Updated $helper_target" + UPDATED_ITEMS+=("$name helper") + fi fi - fi + done - RESOLVED_KIMAKI_HELPER="$HELPER_TARGET" + RESOLVED_KIMAKI_HELPER="$HELPER_DIR/datamachine-kimaki-session" + RESOLVED_DATAMACHINE_KIMAKI="$HELPER_DIR/datamachine-kimaki" } _kimaki_install_launchd() { @@ -302,11 +312,9 @@ bridge_sync_config() { fi fi - # Install wp-coding-agents' Kimaki bridge helper. The helper is intentionally - # outside Kimaki's npm package so `npm update -g kimaki` cannot wipe it. - # It adapts DMC workspace checkouts into Kimaki thread metadata while keeping - # DMC generic and Kimaki unpatched. - _kimaki_sync_handoff_helper + # Install wp-coding-agents' Kimaki bridge helpers. They are intentionally + # outside Kimaki's npm package so `npm update -g kimaki` cannot wipe them. + _kimaki_sync_bin_helpers # On local, execute post-upgrade.sh inline to enforce the kill list. # On VPS, kimaki.service ExecStartPre runs it on next service restart. @@ -637,6 +645,8 @@ bridge_vps_start_preamble() { bridge_verify_extra() { local PLUGINS_DIR="${RESOLVED_KIMAKI_PLUGINS_DIR:-/opt/kimaki-config/plugins}" local HELPER="${RESOLVED_KIMAKI_HELPER:-/usr/local/bin/datamachine-kimaki-session}" + local ADAPTER="${RESOLVED_DATAMACHINE_KIMAKI:-/usr/local/bin/datamachine-kimaki}" echo "test -f $PLUGINS_DIR/dm-context-filter.ts && test -f $PLUGINS_DIR/dm-agent-sync.ts # DM OpenCode plugins installed" echo "test -x $HELPER # DMC Kimaki session handoff helper installed" + echo "test -x $ADAPTER # DM Kimaki command adapter installed" } diff --git a/bridges/kimaki/bin/datamachine-kimaki b/bridges/kimaki/bin/datamachine-kimaki new file mode 100755 index 0000000..5c0b573 --- /dev/null +++ b/bridges/kimaki/bin/datamachine-kimaki @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: datamachine-kimaki + +Data Machine compatibility adapter for Kimaki. + +In Data Machine mode, Kimaki is the Discord transport while Data Machine owns +agent identity, site root, and workspace lifecycle. This adapter normalizes the +Kimaki `send` footgun flags before delegating to the real Kimaki binary: + + send --agent -> send --agent build + send --cwd -> stripped + send --worktree [name] -> blocked before native Kimaki worktree handling + +Set DATAMACHINE_REAL_KIMAKI to the real Kimaki binary path. If unset, the +adapter resolves `kimaki` from PATH when invoked as `datamachine-kimaki`. +EOF +} + +resolve_real_kimaki() { + if [[ -n "${DATAMACHINE_REAL_KIMAKI:-}" ]]; then + if [[ ! -x "$DATAMACHINE_REAL_KIMAKI" ]]; then + echo "DATAMACHINE_REAL_KIMAKI is not executable: $DATAMACHINE_REAL_KIMAKI" >&2 + exit 2 + fi + printf '%s\n' "$DATAMACHINE_REAL_KIMAKI" + return 0 + fi + + if [[ "$(basename "$0")" == "kimaki" ]]; then + echo "DATAMACHINE_REAL_KIMAKI is required when the adapter is installed as kimaki" >&2 + exit 2 + fi + + if ! command -v kimaki >/dev/null 2>&1; then + echo "kimaki is required on PATH, or set DATAMACHINE_REAL_KIMAKI" >&2 + exit 2 + fi + + command -v kimaki +} + +normalize_send_args() { + normalized_args=() + saw_worktree=false + local worktree_value="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --agent) + normalized_args+=(--agent build) + shift + if [[ $# -gt 0 && "$1" != --* ]]; then + shift + fi + ;; + --agent=*) + normalized_args+=(--agent build) + shift + ;; + --cwd) + shift + if [[ $# -gt 0 && "$1" != --* ]]; then + shift + fi + ;; + --cwd=*) + shift + ;; + --worktree) + saw_worktree=true + shift + if [[ $# -gt 0 && "$1" != --* ]]; then + worktree_value="$1" + shift + fi + ;; + --worktree=*) + saw_worktree=true + worktree_value="${1#--worktree=}" + shift + ;; + *) + normalized_args+=("$1") + shift + ;; + esac + done + + if [[ "$saw_worktree" == true ]]; then + echo "Data Machine mode intercepted kimaki send --worktree${worktree_value:+ $worktree_value}." >&2 + echo "Native Kimaki worktrees are disabled on wp-coding-agents installs; use Data Machine Code worktrees instead." >&2 + exit 2 + fi + + return 0 +} + +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + usage + exit 0 +fi + +real_kimaki="$(resolve_real_kimaki)" + +if [[ "${1:-}" != "send" ]]; then + exec "$real_kimaki" "$@" +fi + +shift +normalized_args=() +normalize_send_args "$@" + +exec "$real_kimaki" send "${normalized_args[@]}" diff --git a/tests/datamachine-kimaki-adapter.sh b/tests/datamachine-kimaki-adapter.sh new file mode 100755 index 0000000..5821f09 --- /dev/null +++ b/tests/datamachine-kimaki-adapter.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ADAPTER="$ROOT/bridges/kimaki/bin/datamachine-kimaki" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +REAL_KIMAKI="$TMP/kimaki-real" +CALL_LOG="$TMP/calls.log" + +cat > "$REAL_KIMAKI" <<'SH' +#!/usr/bin/env bash +printf '%s\0' "$@" > "$CALL_LOG" +SH +chmod +x "$REAL_KIMAKI" + +export DATAMACHINE_REAL_KIMAKI="$REAL_KIMAKI" +export CALL_LOG + +assert_args() { + python3 - "$CALL_LOG" "$@" <<'PY' +import sys + +path = sys.argv[1] +expected = sys.argv[2:] +with open(path, 'rb') as handle: + raw = handle.read() +actual = [part.decode() for part in raw.split(b'\0') if part] +if actual != expected: + raise SystemExit(f"expected {expected!r}, got {actual!r}") +PY +} + +assert_fails_without_call() { + rm -f "$CALL_LOG" + if "$ADAPTER" "$@" >"$TMP/stdout" 2>"$TMP/stderr"; then + echo "expected adapter failure for: $*" >&2 + exit 1 + fi + if [[ -f "$CALL_LOG" ]]; then + echo "real kimaki should not have been called for: $*" >&2 + exit 1 + fi +} + +"$ADAPTER" send --prompt hi --agent opencode +assert_args send --prompt hi --agent build + +"$ADAPTER" send --prompt hi --agent plan +assert_args send --prompt hi --agent build + +"$ADAPTER" send --prompt hi --agent=general +assert_args send --prompt hi --agent build + +"$ADAPTER" send --prompt hi --cwd /tmp/elsewhere +assert_args send --prompt hi + +"$ADAPTER" send --prompt hi --cwd=/tmp/elsewhere --agent opencode +assert_args send --prompt hi --agent build + +"$ADAPTER" send --prompt hi --agent --model anthropic/test +assert_args send --prompt hi --agent build --model anthropic/test + +"$ADAPTER" send --prompt hi --cwd --model anthropic/test +assert_args send --prompt hi --model anthropic/test + +assert_fails_without_call send --prompt hi --worktree feature-x +grep -q 'Native Kimaki worktrees are disabled' "$TMP/stderr" + +"$ADAPTER" session list --project /tmp/site +assert_args session list --project /tmp/site + +echo "OK: datamachine-kimaki adapter normalizes Kimaki send flags" From 21238df0a3dd55270726a5686e7e9261cc7bb4dd Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Mon, 4 May 2026 22:50:51 -0400 Subject: [PATCH 2/2] fix(kimaki): install Data Machine command shim --- bridges/kimaki.sh | 33 ++++++++++++++++++++++++++ bridges/kimaki/bin/datamachine-kimaki | 34 ++++++++++++++++++--------- tests/datamachine-kimaki-adapter.sh | 9 +++++++ 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/bridges/kimaki.sh b/bridges/kimaki.sh index 2096f4d..e931a25 100644 --- a/bridges/kimaki.sh +++ b/bridges/kimaki.sh @@ -87,8 +87,39 @@ _kimaki_sync_bin_helpers() { fi done + _kimaki_sync_command_shim "$HELPER_DIR" + RESOLVED_KIMAKI_HELPER="$HELPER_DIR/datamachine-kimaki-session" RESOLVED_DATAMACHINE_KIMAKI="$HELPER_DIR/datamachine-kimaki" + RESOLVED_KIMAKI_SHIM="$HELPER_DIR/kimaki" +} + +_kimaki_sync_command_shim() { + local helper_dir="$1" + local adapter_source="$SCRIPT_DIR/bridges/kimaki/bin/datamachine-kimaki" + local shim_target="$helper_dir/kimaki" + [ -f "$adapter_source" ] || return 0 + + if [ -e "$shim_target" ] && ! grep -q 'wp-coding-agents datamachine-kimaki adapter' "$shim_target" 2>/dev/null; then + warn " $shim_target exists and is not the Data Machine Kimaki adapter — leaving it untouched" + warn " Install $helper_dir earlier on PATH or call datamachine-kimaki directly to normalize Kimaki send flags" + return 0 + fi + + if [ "$DRY_RUN" = true ]; then + if ! cmp -s "$adapter_source" "$shim_target" 2>/dev/null; then + echo -e "${BLUE}[dry-run]${NC} Would update $shim_target" + fi + return 0 + fi + + mkdir -p "$helper_dir" + if ! cmp -s "$adapter_source" "$shim_target" 2>/dev/null; then + cp "$adapter_source" "$shim_target" + chmod +x "$shim_target" + log " Updated $shim_target" + UPDATED_ITEMS+=("kimaki command shim") + fi } _kimaki_install_launchd() { @@ -646,7 +677,9 @@ bridge_verify_extra() { local PLUGINS_DIR="${RESOLVED_KIMAKI_PLUGINS_DIR:-/opt/kimaki-config/plugins}" local HELPER="${RESOLVED_KIMAKI_HELPER:-/usr/local/bin/datamachine-kimaki-session}" local ADAPTER="${RESOLVED_DATAMACHINE_KIMAKI:-/usr/local/bin/datamachine-kimaki}" + local SHIM="${RESOLVED_KIMAKI_SHIM:-/usr/local/bin/kimaki}" echo "test -f $PLUGINS_DIR/dm-context-filter.ts && test -f $PLUGINS_DIR/dm-agent-sync.ts # DM OpenCode plugins installed" echo "test -x $HELPER # DMC Kimaki session handoff helper installed" echo "test -x $ADAPTER # DM Kimaki command adapter installed" + echo "test -x $SHIM # kimaki command shim installed" } diff --git a/bridges/kimaki/bin/datamachine-kimaki b/bridges/kimaki/bin/datamachine-kimaki index 5c0b573..821fd94 100755 --- a/bridges/kimaki/bin/datamachine-kimaki +++ b/bridges/kimaki/bin/datamachine-kimaki @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# wp-coding-agents datamachine-kimaki adapter set -euo pipefail usage() { @@ -16,10 +17,17 @@ Kimaki `send` footgun flags before delegating to the real Kimaki binary: send --worktree [name] -> blocked before native Kimaki worktree handling Set DATAMACHINE_REAL_KIMAKI to the real Kimaki binary path. If unset, the -adapter resolves `kimaki` from PATH when invoked as `datamachine-kimaki`. +adapter resolves the next non-adapter `kimaki` from PATH. EOF } +adapter_path() { + local dir base + dir="$(cd "$(dirname "$0")" && pwd -P)" + base="$(basename "$0")" + printf '%s/%s\n' "$dir" "$base" +} + resolve_real_kimaki() { if [[ -n "${DATAMACHINE_REAL_KIMAKI:-}" ]]; then if [[ ! -x "$DATAMACHINE_REAL_KIMAKI" ]]; then @@ -30,17 +38,21 @@ resolve_real_kimaki() { return 0 fi - if [[ "$(basename "$0")" == "kimaki" ]]; then - echo "DATAMACHINE_REAL_KIMAKI is required when the adapter is installed as kimaki" >&2 - exit 2 - fi - - if ! command -v kimaki >/dev/null 2>&1; then - echo "kimaki is required on PATH, or set DATAMACHINE_REAL_KIMAKI" >&2 - exit 2 - fi + local self candidate dir resolved_candidate + self="$(adapter_path)" + IFS=: read -r -a path_entries <<< "${PATH:-}" + for dir in "${path_entries[@]}"; do + [[ -n "$dir" ]] || continue + candidate="$dir/kimaki" + [[ -x "$candidate" ]] || continue + resolved_candidate="$(cd "$(dirname "$candidate")" && pwd -P)/$(basename "$candidate")" + [[ "$resolved_candidate" != "$self" ]] || continue + printf '%s\n' "$resolved_candidate" + return 0 + done - command -v kimaki + echo "real kimaki is required on PATH after the adapter, or set DATAMACHINE_REAL_KIMAKI" >&2 + exit 2 } normalize_send_args() { diff --git a/tests/datamachine-kimaki-adapter.sh b/tests/datamachine-kimaki-adapter.sh index 5821f09..4fae94e 100755 --- a/tests/datamachine-kimaki-adapter.sh +++ b/tests/datamachine-kimaki-adapter.sh @@ -71,4 +71,13 @@ grep -q 'Native Kimaki worktrees are disabled' "$TMP/stderr" "$ADAPTER" session list --project /tmp/site assert_args session list --project /tmp/site +unset DATAMACHINE_REAL_KIMAKI +shim_dir="$TMP/shim-bin" +real_dir="$TMP/real-bin" +mkdir -p "$shim_dir" "$real_dir" +cp "$ADAPTER" "$shim_dir/kimaki" +cp "$REAL_KIMAKI" "$real_dir/kimaki" +PATH="$shim_dir:$real_dir:$PATH" "$shim_dir/kimaki" send --prompt hi --agent opencode --cwd /tmp/site +assert_args send --prompt hi --agent build + echo "OK: datamachine-kimaki adapter normalizes Kimaki send flags"