Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/shell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
87 changes: 65 additions & 22 deletions bridges/kimaki.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.

# ============================================================================
Expand Down Expand Up @@ -51,34 +53,73 @@ 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"
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
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")
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

RESOLVED_KIMAKI_HELPER="$HELPER_TARGET"
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() {
Expand Down Expand Up @@ -302,11 +343,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.
Expand Down Expand Up @@ -637,6 +676,10 @@ 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}"
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"
}
129 changes: 129 additions & 0 deletions bridges/kimaki/bin/datamachine-kimaki
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env bash
# wp-coding-agents datamachine-kimaki adapter
set -euo pipefail

usage() {
cat <<'EOF'
Usage: datamachine-kimaki <kimaki-args...>

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 <anything> -> send --agent build
send --cwd <anything> -> 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 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
echo "DATAMACHINE_REAL_KIMAKI is not executable: $DATAMACHINE_REAL_KIMAKI" >&2
exit 2
fi
printf '%s\n' "$DATAMACHINE_REAL_KIMAKI"
return 0
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

echo "real kimaki is required on PATH after the adapter, or set DATAMACHINE_REAL_KIMAKI" >&2
exit 2
}

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[@]}"
83 changes: 83 additions & 0 deletions tests/datamachine-kimaki-adapter.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/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

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"
Loading