From 840ffc8c8162641f034644cf61f0f830ee63ad1a Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Wed, 6 May 2026 16:18:29 -0400 Subject: [PATCH] fix(kimaki): point opencode plugin paths at persistent config Local installs wrote durable plugin copies under kimaki-config but kept loading OpenCode plugins from npm's package directory, so npm update -g kimaki could erase the loaded files. Load and repair managed plugin paths against the persistent config directory instead, and keep post-upgrade focused on skills plus plugin validation. --- bridges/kimaki.sh | 50 ++-------- bridges/kimaki/post-upgrade.sh | 92 +++++++++---------- lib/repair-opencode-json.py | 58 +++++++++++- .../__snapshots__/default.baseline.txt | 9 +- .../__snapshots__/default.filtered.txt | 9 +- .../__snapshots__/default.raw.txt | 37 ++++---- .../no-agents-no-thread.baseline.txt | 9 +- .../no-agents-no-thread.filtered.txt | 9 +- .../__snapshots__/no-agents-no-thread.raw.txt | 37 ++++---- tests/opencode-local-plugin-path.sh | 49 ++++++++++ tests/post-upgrade-restore.sh | 69 ++++++++------ tests/repair-opencode-json.sh | 4 +- upgrade.sh | 1 + 13 files changed, 265 insertions(+), 168 deletions(-) create mode 100755 tests/opencode-local-plugin-path.sh diff --git a/bridges/kimaki.sh b/bridges/kimaki.sh index e43edf3..8b9ad1a 100644 --- a/bridges/kimaki.sh +++ b/bridges/kimaki.sh @@ -13,9 +13,9 @@ # + /etc/systemd/system/kimaki.service (ExecStartPre runs post-upgrade.sh) # Local: $KIMAKI_DATA_DIR/kimaki-config/ for plugins, post-upgrade.sh + # kill list (executed inline at upgrade time — no launchd -# ExecStartPre hook). Plugins are mirrored into the npm package as a -# compatibility target, but opencode.json loads the durable data-dir -# copy because `npm update -g kimaki` wipes package-local files. +# ExecStartPre hook). opencode.json loads plugins directly from this +# durable data-dir copy because `npm update -g kimaki` wipes package- +# local files. # + $HOME/.local/bin/datamachine-kimaki-session # + $HOME/.local/bin/datamachine-kimaki # + $HOME/Library/LaunchAgents/com.wp.kimaki.plist on macOS. @@ -196,29 +196,19 @@ bridge_sync_config() { # and by ExecStartPre in kimaki.service). Config dir holds plugins + # post-upgrade.sh + skills-kill-list.txt. # Local: opencode.json points at $KIMAKI_DATA_DIR/kimaki-config/plugins, the - # durable source that survives `npm update -g kimaki`. We still mirror - # plugins into $(npm root -g)/kimaki/plugins for compatibility with old - # configs, and run post-upgrade.sh inline because launchd has no - # ExecStartPre hook. + # durable source that survives `npm update -g kimaki`. Existing configs + # that still reference package-local plugin paths are migrated by the + # opencode.json repair helper. local KIMAKI_CONFIG_DIR local KIMAKI_PLUGINS_DIR - local KIMAKI_NPM_PLUGINS_DIR="" local BACKUP_DIR if [ "$LOCAL_MODE" = true ]; then KIMAKI_CONFIG_DIR="${KIMAKI_DATA_DIR}/kimaki-config" - local NPM_ROOT - NPM_ROOT="$(npm root -g 2>/dev/null || true)" - if [ -n "$NPM_ROOT" ]; then - KIMAKI_NPM_PLUGINS_DIR="${NPM_ROOT}/kimaki/plugins" - fi KIMAKI_PLUGINS_DIR="${KIMAKI_CONFIG_DIR}/plugins" BACKUP_DIR="${KIMAKI_DATA_DIR}/backups/kimaki-config.$TIMESTAMP" log "Phase 2: Syncing kimaki config (local mode)..." log " Config dir: $KIMAKI_CONFIG_DIR" log " Plugins dir: $KIMAKI_PLUGINS_DIR (durable opencode target)" - if [ -n "$KIMAKI_NPM_PLUGINS_DIR" ]; then - log " NPM mirror: $KIMAKI_NPM_PLUGINS_DIR (compatibility)" - fi else KIMAKI_CONFIG_DIR="/opt/kimaki-config" KIMAKI_PLUGINS_DIR="/opt/kimaki-config/plugins" @@ -226,9 +216,9 @@ bridge_sync_config() { log "Phase 2: Syncing /opt/kimaki-config..." fi - # Local opencode loads from the durable kimaki-config dir. If the npm package - # is unavailable, skip only the compatibility mirror; do not skip installing - # the policy plugins themselves. + # Local opencode loads from the durable kimaki-config dir. Do not mirror these + # plugins into the npm package; `npm update -g kimaki` wipes that directory and + # the repair helper migrates older opencode.json files away from it. # VPS: if /opt/kimaki-config is missing, this install predates v0.4.0 (when # setup.sh started creating it). We're in the kimaki dispatch branch, so @@ -259,9 +249,7 @@ bridge_sync_config() { fi fi - # Copy plugins to the durable target that opencode.json loads. On local, - # additionally mirror to the npm package for older configs that still point - # there; the mirror is best-effort because npm updates wipe it. + # Copy plugins to the durable target that opencode.json loads. if [ -d "$SCRIPT_DIR/bridges/kimaki/plugins" ]; then if [ "$DRY_RUN" = false ]; then mkdir -p "$KIMAKI_CONFIG_DIR/plugins" 2>/dev/null || true @@ -283,24 +271,6 @@ bridge_sync_config() { UPDATED_ITEMS+=("kimaki-config/plugins/$name") fi fi - # Compatibility mirror for older local opencode.json files. VPS uses the - # durable target directly, so there is no separate mirror there. - if [ "$LOCAL_MODE" = true ] && [ -n "$KIMAKI_NPM_PLUGINS_DIR" ]; then - if [ "$DRY_RUN" = true ]; then - if ! cmp -s "$plugin_file" "$KIMAKI_NPM_PLUGINS_DIR/$name" 2>/dev/null; then - echo -e "${BLUE}[dry-run]${NC} Would update $KIMAKI_NPM_PLUGINS_DIR/$name" - else - echo -e "${BLUE}[dry-run]${NC} $name npm mirror: unchanged" - fi - else - mkdir -p "$KIMAKI_NPM_PLUGINS_DIR" 2>/dev/null || true - if ! cmp -s "$plugin_file" "$KIMAKI_NPM_PLUGINS_DIR/$name" 2>/dev/null; then - cp "$plugin_file" "$KIMAKI_NPM_PLUGINS_DIR/$name" - log " Updated $KIMAKI_NPM_PLUGINS_DIR/$name (compatibility mirror)" - UPDATED_ITEMS+=("kimaki npm mirror/$name") - fi - fi - fi done fi diff --git a/bridges/kimaki/post-upgrade.sh b/bridges/kimaki/post-upgrade.sh index 3df31be..ce99f05 100755 --- a/bridges/kimaki/post-upgrade.sh +++ b/bridges/kimaki/post-upgrade.sh @@ -1,23 +1,21 @@ #!/usr/bin/env bash -# post-upgrade.sh — Enforce kimaki skill + plugin state on every restart. +# post-upgrade.sh — Enforce kimaki skill state and validate plugin state. # -# Three symmetric passes run against the npm-installed kimaki package: +# Three passes run against the npm-installed kimaki package and persistent +# kimaki-config directory: # 1. KILL — remove unwanted bundled kimaki skills listed in # skills-kill-list.txt (target: $(npm root -g)/kimaki/skills/). # 2. RESTORE skills — re-copy wp-coding-agents skills from the persistent # source dir (kimaki-config/skills/) into kimaki/skills/. -# 3. RESTORE plugins — re-copy wp-coding-agents opencode plugins from the -# persistent source dir (kimaki-config/plugins/) into -# kimaki/plugins/. opencode.json references the plugin .ts -# files at $(npm root -g)/kimaki/plugins/.ts; without -# this restore pass dm-context-filter.ts and dm-agent-sync.ts -# silently disappear after every `npm update -g kimaki` and -# Discord agents lose their context-filter / agent-sync -# policies until the next manual upgrade.sh run. +# 3. VERIFY plugins — confirm required wp-coding-agents opencode plugins +# exist at the persistent kimaki-config/plugins path loaded by +# opencode.json. Local installs no longer restore plugins into +# $(npm root -g)/kimaki/plugins because package-local files are +# wiped by `npm update -g kimaki`. # -# `npm update -g kimaki` wipes both kimaki/skills/ AND kimaki/plugins/, so -# the persistent kimaki-config/ dir is the source of truth and this script -# rehydrates the npm install on every kimaki restart. +# `npm update -g kimaki` still wipes kimaki/skills/, so skills are rehydrated +# from persistent kimaki-config/ on every restart. Plugins are loaded directly +# from persistent kimaki-config/plugins and only need validation here. # # Invoked two ways: # VPS: ExecStartPre in kimaki.service (runs on every service start). @@ -28,10 +26,9 @@ # 2. $(npm root -g)/kimaki/skills (works on macOS + Linux when npm is on PATH) # 3. /usr/lib/node_modules/kimaki/skills (Linux VPS fallback when npm absent) # -# Plugins dir resolution priority (mirrors skills resolution): +# Plugin target dir resolution priority: # 1. KIMAKI_PLUGINS_DIR env var (explicit override) -# 2. $(npm root -g)/kimaki/plugins -# 3. /usr/lib/node_modules/kimaki/plugins (Linux VPS fallback when npm absent) +# 2. Persistent plugin source dir below (default for local and VPS) # # Persistent skill source dir resolution priority: # 1. KIMAKI_SKILL_SOURCE_DIR env var (explicit override) @@ -64,14 +61,6 @@ else SKILLS_DIR="/usr/lib/node_modules/kimaki/skills" fi -if [[ -n "${KIMAKI_PLUGINS_DIR:-}" ]]; then - PLUGINS_DIR="$KIMAKI_PLUGINS_DIR" -elif [[ -n "$NPM_ROOT" ]]; then - PLUGINS_DIR="$NPM_ROOT/kimaki/plugins" -else - PLUGINS_DIR="/usr/lib/node_modules/kimaki/plugins" -fi - KILL_LIST="$(dirname "$0")/skills-kill-list.txt" REQUIRED_PLUGINS=(dm-context-filter.ts dm-agent-sync.ts) @@ -151,13 +140,11 @@ else fi # ---------------------------------------------------------------------------- -# Pass 3: RESTORE plugins — re-copy wp-coding-agents opencode plugins from -# the persistent source dir into the npm-managed plugins dir. -# -# opencode.json references each plugin by absolute path at -# $(npm root -g)/kimaki/plugins/.ts. The kimaki npm package does NOT -# ship a plugins/ dir, so this directory only ever exists because we put it -# there. `npm update -g kimaki` wipes it clean every time. +# Pass 3: VERIFY plugins — opencode.json now loads wp-coding-agents plugins +# directly from persistent kimaki-config/plugins. When the target and source are +# the same directory (the default), there is nothing to restore. An explicit +# KIMAKI_PLUGINS_DIR override still receives a best-effort copy for operator +# controlled compatibility scenarios. # ---------------------------------------------------------------------------- if [[ -n "${KIMAKI_PLUGIN_SOURCE_DIR:-}" ]]; then @@ -171,27 +158,36 @@ else fi plugins_restored=0 +if [[ -n "${KIMAKI_PLUGINS_DIR:-}" ]]; then + PLUGINS_DIR="$KIMAKI_PLUGINS_DIR" +else + PLUGINS_DIR="$PLUGIN_SOURCE_DIR" +fi + if [[ -d "$PLUGIN_SOURCE_DIR" ]]; then - # Ensure the npm-managed plugins dir exists before copying. - mkdir -p "$PLUGINS_DIR" 2>/dev/null || true - if [[ ! -d "$PLUGINS_DIR" ]]; then - echo "kimaki-config: could not create plugins dir at $PLUGINS_DIR, skipping plugin restore" + if [[ "$PLUGINS_DIR" == "$PLUGIN_SOURCE_DIR" ]]; then + echo "kimaki-config: plugin restore not needed; opencode loads persistent plugins at $PLUGIN_SOURCE_DIR" else - shopt -s nullglob - for plugin_file in "$PLUGIN_SOURCE_DIR"/*.ts; do - plugin_name="$(basename "$plugin_file")" - target="$PLUGINS_DIR/$plugin_name" - # Idempotent: only copy if missing or different. cmp returns 0 on match. - if ! cmp -s "$plugin_file" "$target" 2>/dev/null; then - cp "$plugin_file" "$target" - echo "kimaki-config: restored plugin $plugin_name" - plugins_restored=$((plugins_restored + 1)) - fi - done - shopt -u nullglob + mkdir -p "$PLUGINS_DIR" 2>/dev/null || true + if [[ ! -d "$PLUGINS_DIR" ]]; then + echo "kimaki-config: could not create plugins dir at $PLUGINS_DIR, skipping plugin restore" + else + shopt -s nullglob + for plugin_file in "$PLUGIN_SOURCE_DIR"/*.ts; do + plugin_name="$(basename "$plugin_file")" + target="$PLUGINS_DIR/$plugin_name" + # Idempotent: only copy if missing or different. cmp returns 0 on match. + if ! cmp -s "$plugin_file" "$target" 2>/dev/null; then + cp "$plugin_file" "$target" + echo "kimaki-config: restored plugin $plugin_name" + plugins_restored=$((plugins_restored + 1)) + fi + done + shopt -u nullglob + fi fi else - echo "kimaki-config: WARNING: persistent plugin source dir not found at $PLUGIN_SOURCE_DIR; dm-context-filter.ts and dm-agent-sync.ts cannot be restored" + echo "kimaki-config: WARNING: persistent plugin source dir not found at $PLUGIN_SOURCE_DIR; dm-context-filter.ts and dm-agent-sync.ts cannot be loaded" fi missing_required_plugins=0 diff --git a/lib/repair-opencode-json.py b/lib/repair-opencode-json.py index 854c8bb..52a1bfc 100755 --- a/lib/repair-opencode-json.py +++ b/lib/repair-opencode-json.py @@ -63,7 +63,10 @@ import os import shutil import sys -from typing import List +from typing import List, Tuple + + +MANAGED_KIMAKI_PLUGIN_NAMES = {"dm-context-filter.ts", "dm-agent-sync.ts"} def expected_plugins( @@ -113,6 +116,38 @@ def diff_plugins(current: List[str], expected: List[str]) -> dict: } +def normalize_managed_kimaki_plugin_paths( + current: List[str], kimaki_plugins_dir: str +) -> Tuple[List[str], List[dict]]: + """Rewrite managed Kimaki plugin paths to the durable configured dir. + + Older local installs pointed opencode.json at npm package-local plugin files + under ``$(npm root -g)/kimaki/plugins``. Those files disappear on + ``npm update -g kimaki``. Treat any managed plugin basename outside the + configured persistent dir as a stale wp-coding-agents-owned entry and rewrite + it in place, preserving user-added plugins. + """ + normalized: List[str] = [] + rewrites: List[dict] = [] + seen = set() + plugins_dir = kimaki_plugins_dir.rstrip("/") + + for plugin in current: + basename = os.path.basename(plugin) + replacement = plugin + if basename in MANAGED_KIMAKI_PLUGIN_NAMES: + expected = f"{plugins_dir}/{basename}" + if os.path.dirname(plugin).rstrip("/") != plugins_dir: + replacement = expected + rewrites.append({"from": plugin, "to": replacement}) + + if replacement not in seen: + normalized.append(replacement) + seen.add(replacement) + + return normalized, rewrites + + def repair( data: dict, expected: List[str], preserve_extras: bool = False ) -> List[str]: @@ -336,6 +371,13 @@ def main() -> int: ) current: List[str] = list(data.get("plugin", [])) + normalized_current = current + plugin_rewrites: List[dict] = [] + if args.runtime == "opencode" and args.chat_bridge == "kimaki": + normalized_current, plugin_rewrites = normalize_managed_kimaki_plugin_paths( + current, + args.kimaki_plugins_dir, + ) # Claude Code / Studio Code: no plugin array concept here. Report ok # if current is empty or absent; otherwise let user know we skipped. @@ -354,8 +396,8 @@ def main() -> int: ) return 0 - diff = diff_plugins(current, expected) - has_plugin_drift = bool(diff["missing"] or diff["unexpected"]) + diff = diff_plugins(normalized_current, expected) + has_plugin_drift = bool(diff["missing"] or diff["unexpected"] or plugin_rewrites) has_prompt_drift = prompt_result["status"] == "needed" has_agent_cleanup_drift = agent_cleanup_result["status"] == "needed" has_any_drift = has_plugin_drift or has_prompt_drift or has_agent_cleanup_drift @@ -383,6 +425,8 @@ def main() -> int: "prompt_migration": prompt_result["status"], "agent_cleanup": agent_cleanup_result["status"], } + if plugin_rewrites: + result["rewritten"] = plugin_rewrites if has_plugin_drift: result["missing"] = diff["missing"] result["unexpected"] = diff["unexpected"] @@ -408,7 +452,9 @@ def main() -> int: if has_plugin_drift and not plugin_skipped: # --apply: replace with exactly `expected` (removes unexpected). # --additive: merge missing entries, preserving user additions. - data["plugin"] = repair(data, expected, preserve_extras=args.additive) + repaired_data = dict(data) + repaired_data["plugin"] = normalized_current + data["plugin"] = repair(repaired_data, expected, preserve_extras=args.additive) prompt_migration_status = "ok" if has_prompt_drift: @@ -438,6 +484,8 @@ def main() -> int: "prompt_migration": prompt_migration_status, "agent_cleanup": "removed" if removed_agent_blocks else "ok", } + if plugin_rewrites: + result["rewritten"] = plugin_rewrites if removed_agent_blocks: result["agent_cleanup_removed"] = removed_agent_blocks if still_unexpected: @@ -455,6 +503,8 @@ def main() -> int: "prompt_migration": prompt_migration_status, "agent_cleanup": "removed" if removed_agent_blocks else "ok", } + if plugin_rewrites: + result["rewritten"] = plugin_rewrites if removed_agent_blocks: result["agent_cleanup_removed"] = removed_agent_blocks print(json.dumps(result)) diff --git a/tests/effective-prompt/__snapshots__/default.baseline.txt b/tests/effective-prompt/__snapshots__/default.baseline.txt index f404091..71f0712 100644 --- a/tests/effective-prompt/__snapshots__/default.baseline.txt +++ b/tests/effective-prompt/__snapshots__/default.baseline.txt @@ -37,13 +37,16 @@ kimaki session archive --session ses_EFFECTIVE_PROMPT_TEST Only do this when the user explicitly asks to close or archive the thread, and only after your final message. -## searching discord users +## discord user mentions -To search for Discord users in a guild (needed for mentions like <@userId>), run: +Prefer Discord user IDs for mentions. Discord bots cannot ping by @name; use `<@userId>` in message text or pass the ID to `--user`. +The current user's ID is available in the per-turn `` metadata. + +To search for Discord users in a guild as a best-effort fallback, run: kimaki user list --guild 1493321868415996064 --query "username" -This returns user IDs you can use for Discord mentions. +This returns user IDs you can use for Discord mentions. It can fail when Server Members Intent is disabled, so prefer IDs from existing Discord metadata or raw mentions when possible. # List all registered projects with their channel IDs kimaki project list --json # machine-readable output diff --git a/tests/effective-prompt/__snapshots__/default.filtered.txt b/tests/effective-prompt/__snapshots__/default.filtered.txt index fe16e0a..bd7b050 100644 --- a/tests/effective-prompt/__snapshots__/default.filtered.txt +++ b/tests/effective-prompt/__snapshots__/default.filtered.txt @@ -37,13 +37,16 @@ kimaki session archive --session ses_EFFECTIVE_PROMPT_TEST Only do this when the user explicitly asks to close or archive the thread, and only after your final message. -## searching discord users +## discord user mentions -To search for Discord users in a guild (needed for mentions like <@userId>), run: +Prefer Discord user IDs for mentions. Discord bots cannot ping by @name; use `<@userId>` in message text or pass the ID to `--user`. +The current user's ID is available in the per-turn `` metadata. + +To search for Discord users in a guild as a best-effort fallback, run: kimaki user list --guild 1493321868415996064 --query "username" -This returns user IDs you can use for Discord mentions. +This returns user IDs you can use for Discord mentions. It can fail when Server Members Intent is disabled, so prefer IDs from existing Discord metadata or raw mentions when possible. ## submodules diff --git a/tests/effective-prompt/__snapshots__/default.raw.txt b/tests/effective-prompt/__snapshots__/default.raw.txt index 463c11d..64100e8 100644 --- a/tests/effective-prompt/__snapshots__/default.raw.txt +++ b/tests/effective-prompt/__snapshots__/default.raw.txt @@ -56,23 +56,26 @@ kimaki session archive --session ses_EFFECTIVE_PROMPT_TEST Only do this when the user explicitly asks to close or archive the thread, and only after your final message. -## searching discord users +## discord user mentions -To search for Discord users in a guild (needed for mentions like <@userId>), run: +Prefer Discord user IDs for mentions. Discord bots cannot ping by @name; use `<@userId>` in message text or pass the ID to `--user`. +The current user's ID is available in the per-turn `` metadata. + +To search for Discord users in a guild as a best-effort fallback, run: kimaki user list --guild 1493321868415996064 --query "username" -This returns user IDs you can use for Discord mentions. +This returns user IDs you can use for Discord mentions. It can fail when Server Members Intent is disabled, so prefer IDs from existing Discord metadata or raw mentions when possible. ## starting new sessions from CLI To start a new thread/session in this channel pro-grammatically, run: -kimaki send --channel 1493345787894038649 --prompt 'your prompt here' --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'your prompt here' --agent --user '' You can use this to "spawn" parallel helper sessions like teammates: start new threads with focused prompts, then come back and collect the results. Prefer passing the current agent with `--agent ` so spawned or scheduled sessions keep the same agent unless you are intentionally switching. Replace `` with the value from the per-turn `Current agent` reminder. -When writing `kimaki send` shell commands, use single quotes around `--prompt`, `--user`, `--send-at`, and other literal arguments so backticks inside prompts are not interpreted by the shell. +When writing `kimaki send` shell commands, use single quotes around `--prompt`, `--user`, `--send-at`, and other literal arguments so backticks inside prompts are not interpreted by the shell. Prefer `--user ''` over `--user 'name'` because name lookup depends on optional Server Members Intent. IMPORTANT: NEVER use `--worktree` unless the user explicitly asks for a worktree. Default to creating normal threads without worktrees. @@ -90,19 +93,19 @@ Use this when you have the OpenCode session ID. Use --notify-only to create a notification thread without starting an AI session: -kimaki send --channel 1493345787894038649 --prompt 'User cancelled subscription' --notify-only --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'User cancelled subscription' --notify-only --agent --user '' -Use --user to add a specific Discord user to the new thread: +Use --user with a Discord user ID or raw mention to add a specific Discord user to the new thread: -kimaki send --channel 1493345787894038649 --prompt 'Review the latest CI failure' --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Review the latest CI failure' --agent --user '' Use --worktree to create a git worktree for the session (ONLY when the user explicitly asks for a worktree): -kimaki send --channel 1493345787894038649 --prompt 'Add dark mode support' --worktree dark-mode --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Add dark mode support' --worktree dark-mode --agent --user '' Use --cwd to start a session in an existing git worktree directory (must be a worktree of the project): -kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature' --cwd /path/to/existing-worktree --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature' --cwd /path/to/existing-worktree --agent --user '' Important: - NEVER use `--worktree` unless the user explicitly requests a worktree. Most tasks should use normal threads without worktrees. @@ -113,7 +116,7 @@ Important: Use --agent to specify which agent to use for the session: -kimaki send --channel 1493345787894038649 --prompt 'Plan the refactor of the auth module' --agent plan --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Plan the refactor of the auth module' --agent plan --user '' Available agents: @@ -125,7 +128,7 @@ Available agents: You can trigger registered opencode commands (slash commands, skills, MCP prompts) by starting the `--prompt` with `/commandname`: kimaki send --thread --prompt '/review fix the auth module' --agent -kimaki send --channel 1493345787894038649 --prompt '/build-cmd update dependencies' --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt '/build-cmd update dependencies' --agent --user '' The command name must match a registered opencode command. If the command is not recognized, the prompt is sent as plain text to the model. This works for both new threads (`--channel`) and existing threads (`--thread`/`--session`). @@ -141,8 +144,8 @@ kimaki send --thread --prompt '/-agent' --agent --user 'chubes' -kimaki send --channel 1493345787894038649 --prompt 'Run weekly test suite and summarize failures' --send-at '0 9 * * 1' --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Reminder: review open PRs' --send-at '2026-03-01T09:00:00Z' --agent --user '' +kimaki send --channel 1493345787894038649 --prompt 'Run weekly test suite and summarize failures' --send-at '0 9 * * 1' --agent --user '' ALL scheduling is in UTC. Dates must be UTC ISO format ending with `Z`. Cron expressions also fire in UTC (e.g. `0 9 * * 1` means 9:00 UTC every Monday). When the user specifies a time without a timezone, ask them to confirm their timezone or the UTC equivalent. Never guess the user's timezone. @@ -200,7 +203,7 @@ ONLY create worktrees when the user explicitly asks for one. Never proactively u When the user asks to "create a worktree" or "make a worktree", they mean you should use the kimaki CLI to create it. Do NOT use raw `git worktree add` commands. Instead use: ```bash -kimaki send --channel 1493345787894038649 --prompt 'your task description' --worktree worktree-name --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'your task description' --worktree worktree-name --agent --user '' ``` This creates a new Discord thread with an isolated git worktree and starts a session in it. The worktree name should be kebab-case and descriptive of the task. @@ -216,7 +219,7 @@ Critical recursion guard: Use `--cwd` to start a session in an existing git worktree directory instead of creating a new one: ```bash -kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature X' --cwd /path/to/existing-worktree --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature X' --cwd /path/to/existing-worktree --agent --user '' ``` The path must be a git worktree of the project (validated via `git worktree list`). The session resolves to the correct project channel but uses the worktree as its working directory. Use `--worktree` to create a new worktree, `--cwd` to reuse an existing one. @@ -230,7 +233,7 @@ This is useful for automation (cron jobs, GitHub webhooks, n8n, etc.) When you are approaching the **context window limit** or the user explicitly asks to **handoff to a new thread**, use the `kimaki send` command to start a fresh session with context: ```bash -kimaki send --channel 1493345787894038649 --prompt 'Continuing from previous session: ' --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Continuing from previous session: ' --agent --user '' ``` The command automatically handles long prompts (over 2000 chars) by sending them as file attachments. diff --git a/tests/effective-prompt/__snapshots__/no-agents-no-thread.baseline.txt b/tests/effective-prompt/__snapshots__/no-agents-no-thread.baseline.txt index 61f4b1c..5d3fb60 100644 --- a/tests/effective-prompt/__snapshots__/no-agents-no-thread.baseline.txt +++ b/tests/effective-prompt/__snapshots__/no-agents-no-thread.baseline.txt @@ -35,13 +35,16 @@ kimaki session archive --session ses_MINIMAL Only do this when the user explicitly asks to close or archive the thread, and only after your final message. -## searching discord users +## discord user mentions -To search for Discord users in a guild (needed for mentions like <@userId>), run: +Prefer Discord user IDs for mentions. Discord bots cannot ping by @name; use `<@userId>` in message text or pass the ID to `--user`. +The current user's ID is available in the per-turn `` metadata. + +To search for Discord users in a guild as a best-effort fallback, run: kimaki user list --guild --query "username" -This returns user IDs you can use for Discord mentions. +This returns user IDs you can use for Discord mentions. It can fail when Server Members Intent is disabled, so prefer IDs from existing Discord metadata or raw mentions when possible. # List all registered projects with their channel IDs kimaki project list --json # machine-readable output diff --git a/tests/effective-prompt/__snapshots__/no-agents-no-thread.filtered.txt b/tests/effective-prompt/__snapshots__/no-agents-no-thread.filtered.txt index 0c7b39c..92567fe 100644 --- a/tests/effective-prompt/__snapshots__/no-agents-no-thread.filtered.txt +++ b/tests/effective-prompt/__snapshots__/no-agents-no-thread.filtered.txt @@ -35,13 +35,16 @@ kimaki session archive --session ses_MINIMAL Only do this when the user explicitly asks to close or archive the thread, and only after your final message. -## searching discord users +## discord user mentions -To search for Discord users in a guild (needed for mentions like <@userId>), run: +Prefer Discord user IDs for mentions. Discord bots cannot ping by @name; use `<@userId>` in message text or pass the ID to `--user`. +The current user's ID is available in the per-turn `` metadata. + +To search for Discord users in a guild as a best-effort fallback, run: kimaki user list --guild --query "username" -This returns user IDs you can use for Discord mentions. +This returns user IDs you can use for Discord mentions. It can fail when Server Members Intent is disabled, so prefer IDs from existing Discord metadata or raw mentions when possible. ## submodules diff --git a/tests/effective-prompt/__snapshots__/no-agents-no-thread.raw.txt b/tests/effective-prompt/__snapshots__/no-agents-no-thread.raw.txt index d4f17d0..510eb44 100644 --- a/tests/effective-prompt/__snapshots__/no-agents-no-thread.raw.txt +++ b/tests/effective-prompt/__snapshots__/no-agents-no-thread.raw.txt @@ -54,23 +54,26 @@ kimaki session archive --session ses_MINIMAL Only do this when the user explicitly asks to close or archive the thread, and only after your final message. -## searching discord users +## discord user mentions -To search for Discord users in a guild (needed for mentions like <@userId>), run: +Prefer Discord user IDs for mentions. Discord bots cannot ping by @name; use `<@userId>` in message text or pass the ID to `--user`. +The current user's ID is available in the per-turn `` metadata. + +To search for Discord users in a guild as a best-effort fallback, run: kimaki user list --guild --query "username" -This returns user IDs you can use for Discord mentions. +This returns user IDs you can use for Discord mentions. It can fail when Server Members Intent is disabled, so prefer IDs from existing Discord metadata or raw mentions when possible. ## starting new sessions from CLI To start a new thread/session in this channel pro-grammatically, run: -kimaki send --channel 1493345787894038649 --prompt 'your prompt here' --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'your prompt here' --agent --user '' You can use this to "spawn" parallel helper sessions like teammates: start new threads with focused prompts, then come back and collect the results. Prefer passing the current agent with `--agent ` so spawned or scheduled sessions keep the same agent unless you are intentionally switching. Replace `` with the value from the per-turn `Current agent` reminder. -When writing `kimaki send` shell commands, use single quotes around `--prompt`, `--user`, `--send-at`, and other literal arguments so backticks inside prompts are not interpreted by the shell. +When writing `kimaki send` shell commands, use single quotes around `--prompt`, `--user`, `--send-at`, and other literal arguments so backticks inside prompts are not interpreted by the shell. Prefer `--user ''` over `--user 'name'` because name lookup depends on optional Server Members Intent. IMPORTANT: NEVER use `--worktree` unless the user explicitly asks for a worktree. Default to creating normal threads without worktrees. @@ -88,19 +91,19 @@ Use this when you have the OpenCode session ID. Use --notify-only to create a notification thread without starting an AI session: -kimaki send --channel 1493345787894038649 --prompt 'User cancelled subscription' --notify-only --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'User cancelled subscription' --notify-only --agent --user '' -Use --user to add a specific Discord user to the new thread: +Use --user with a Discord user ID or raw mention to add a specific Discord user to the new thread: -kimaki send --channel 1493345787894038649 --prompt 'Review the latest CI failure' --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Review the latest CI failure' --agent --user '' Use --worktree to create a git worktree for the session (ONLY when the user explicitly asks for a worktree): -kimaki send --channel 1493345787894038649 --prompt 'Add dark mode support' --worktree dark-mode --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Add dark mode support' --worktree dark-mode --agent --user '' Use --cwd to start a session in an existing git worktree directory (must be a worktree of the project): -kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature' --cwd /path/to/existing-worktree --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature' --cwd /path/to/existing-worktree --agent --user '' Important: - NEVER use `--worktree` unless the user explicitly requests a worktree. Most tasks should use normal threads without worktrees. @@ -111,7 +114,7 @@ Important: Use --agent to specify which agent to use for the session: -kimaki send --channel 1493345787894038649 --prompt 'Plan the refactor of the auth module' --agent plan --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Plan the refactor of the auth module' --agent plan --user '' ## running opencode commands via kimaki send @@ -119,7 +122,7 @@ kimaki send --channel 1493345787894038649 --prompt 'Plan the refactor of the aut You can trigger registered opencode commands (slash commands, skills, MCP prompts) by starting the `--prompt` with `/commandname`: kimaki send --thread --prompt '/review fix the auth module' --agent -kimaki send --channel 1493345787894038649 --prompt '/build-cmd update dependencies' --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt '/build-cmd update dependencies' --agent --user '' The command name must match a registered opencode command. If the command is not recognized, the prompt is sent as plain text to the model. This works for both new threads (`--channel`) and existing threads (`--thread`/`--session`). @@ -135,8 +138,8 @@ kimaki send --thread --prompt '/-agent' --agent --user 'chubes' -kimaki send --channel 1493345787894038649 --prompt 'Run weekly test suite and summarize failures' --send-at '0 9 * * 1' --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Reminder: review open PRs' --send-at '2026-03-01T09:00:00Z' --agent --user '' +kimaki send --channel 1493345787894038649 --prompt 'Run weekly test suite and summarize failures' --send-at '0 9 * * 1' --agent --user '' ALL scheduling is in UTC. Dates must be UTC ISO format ending with `Z`. Cron expressions also fire in UTC (e.g. `0 9 * * 1` means 9:00 UTC every Monday). When the user specifies a time without a timezone, ask them to confirm their timezone or the UTC equivalent. Never guess the user's timezone. @@ -194,7 +197,7 @@ ONLY create worktrees when the user explicitly asks for one. Never proactively u When the user asks to "create a worktree" or "make a worktree", they mean you should use the kimaki CLI to create it. Do NOT use raw `git worktree add` commands. Instead use: ```bash -kimaki send --channel 1493345787894038649 --prompt 'your task description' --worktree worktree-name --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'your task description' --worktree worktree-name --agent --user '' ``` This creates a new Discord thread with an isolated git worktree and starts a session in it. The worktree name should be kebab-case and descriptive of the task. @@ -210,7 +213,7 @@ Critical recursion guard: Use `--cwd` to start a session in an existing git worktree directory instead of creating a new one: ```bash -kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature X' --cwd /path/to/existing-worktree --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature X' --cwd /path/to/existing-worktree --agent --user '' ``` The path must be a git worktree of the project (validated via `git worktree list`). The session resolves to the correct project channel but uses the worktree as its working directory. Use `--worktree` to create a new worktree, `--cwd` to reuse an existing one. @@ -224,7 +227,7 @@ This is useful for automation (cron jobs, GitHub webhooks, n8n, etc.) When you are approaching the **context window limit** or the user explicitly asks to **handoff to a new thread**, use the `kimaki send` command to start a fresh session with context: ```bash -kimaki send --channel 1493345787894038649 --prompt 'Continuing from previous session: ' --agent --user 'chubes' +kimaki send --channel 1493345787894038649 --prompt 'Continuing from previous session: ' --agent --user '' ``` The command automatically handles long prompts (over 2000 chars) by sending them as file attachments. diff --git a/tests/opencode-local-plugin-path.sh b/tests/opencode-local-plugin-path.sh new file mode 100755 index 0000000..04ce306 --- /dev/null +++ b/tests/opencode-local-plugin-path.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# tests/opencode-local-plugin-path.sh — local opencode.json uses durable plugins. +set -eu + +SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +SITE_PATH="$TMP/site" +KIMAKI_DATA_DIR="$TMP/kimaki-data" +mkdir -p "$SITE_PATH" "$KIMAKI_DATA_DIR" + +export SCRIPT_DIR +export SITE_PATH +export KIMAKI_DATA_DIR +export CHAT_BRIDGE="kimaki" +export LOCAL_MODE=true +export DRY_RUN=false +export OPENCODE_MODEL="" +export OPENCODE_SMALL_MODEL="" +export DM_WORKSPACE_DIR="$TMP/workspace" +export DM_AGENT_FILES="wp-content/uploads/datamachine-files/shared/SITE.md" + +log() { :; } +warn() { printf '%s\n' "$*" >&2; } + +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/runtimes/opencode.sh" + +runtime_generate_config + +python3 - "$SITE_PATH/opencode.json" "$KIMAKI_DATA_DIR" <<'PY' +import json +import sys + +opencode_json, kimaki_data_dir = sys.argv[1], sys.argv[2] +with open(opencode_json, encoding="utf-8") as handle: + data = json.load(handle) + +expected = [ + f"{kimaki_data_dir}/kimaki-config/plugins/dm-context-filter.ts", + f"{kimaki_data_dir}/kimaki-config/plugins/dm-agent-sync.ts", +] +actual = data.get("plugin") +if actual != expected: + raise SystemExit(f"unexpected local plugin paths: {actual}") +PY + +echo "PASS: tests/opencode-local-plugin-path.sh" diff --git a/tests/post-upgrade-restore.sh b/tests/post-upgrade-restore.sh index 0869ae8..87b5e0f 100755 --- a/tests/post-upgrade-restore.sh +++ b/tests/post-upgrade-restore.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # tests/post-upgrade-restore.sh — smoke test for bridges/kimaki/post-upgrade.sh # -# Verifies the three passes (kill, restore skills, restore plugins) using +# Verifies the three passes (kill, restore skills, verify plugins) using # temp-dir env overrides so the test never touches the real npm install or # user config. # @@ -9,12 +9,12 @@ # 1. Kill pass removes a blacklisted skill from the simulated skills dir. # 2. Skill restore pass copies a SKILL.md tree from the persistent source # back into the (wiped) skills dir. -# 3. Plugin restore pass copies *.ts files from the persistent source into -# the (wiped) plugins dir — the regression this script was added to fix. -# 4. Plugin restore is idempotent — running again does not re-copy files +# 3. Default plugin pass is a no-op because opencode loads the persistent +# source dir directly. +# 4. Explicit KIMAKI_PLUGINS_DIR compatibility override still copies *.ts +# files from the persistent source into the target dir. +# 5. Plugin restore is idempotent — running again does not re-copy files # that already match. -# 5. Plugin restore creates the live plugins dir if it does not exist -# (the post-`npm update` reality). # 6. KIMAKI_DATA_DIR is only a hint: if its kimaki-config source dirs do # not exist, skills and plugins fall through to HOME/.kimaki/kimaki-config. # @@ -36,7 +36,8 @@ fi TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT -# Simulated "npm-installed kimaki" layout — the targets the restore loop writes to. +# Simulated "npm-installed kimaki" layout — the targets the skill restore loop +# and explicit plugin compatibility override write to. LIVE_SKILLS="$TMP/npm/kimaki/skills" LIVE_PLUGINS="$TMP/npm/kimaki/plugins" @@ -45,7 +46,8 @@ SRC_SKILLS="$TMP/config/skills" SRC_PLUGINS="$TMP/config/plugins" mkdir -p "$LIVE_SKILLS" "$SRC_SKILLS" "$SRC_PLUGINS" -# Note: deliberately NOT creating LIVE_PLUGINS — the script must mkdir it. +# Note: deliberately NOT creating LIVE_PLUGINS — explicit override mode must +# still mkdir it. # Seed a blacklisted skill that the kill pass should remove. mkdir -p "$LIVE_SKILLS/blacklisted-skill" @@ -72,13 +74,14 @@ description: persisted but killed test fixture body EOF -# Seed two plugins in the persistent source that should be restored. -cat > "$SRC_PLUGINS/test-plugin-a.ts" <<'EOF' -// test-plugin-a.ts +# Seed two required plugins in the persistent source. Default mode verifies them +# in place; explicit override mode restores them into the requested target. +cat > "$SRC_PLUGINS/dm-context-filter.ts" <<'EOF' +// dm-context-filter.ts export default async () => ({}) EOF -cat > "$SRC_PLUGINS/test-plugin-b.ts" <<'EOF' -// test-plugin-b.ts +cat > "$SRC_PLUGINS/dm-agent-sync.ts" <<'EOF' +// dm-agent-sync.ts export default async () => ({}) EOF @@ -97,7 +100,6 @@ EOF # Run the script with explicit env overrides so it never touches the real # npm install or user config. KIMAKI_SKILLS_DIR="$LIVE_SKILLS" \ -KIMAKI_PLUGINS_DIR="$LIVE_PLUGINS" \ KIMAKI_SKILL_SOURCE_DIR="$SRC_SKILLS" \ KIMAKI_PLUGIN_SOURCE_DIR="$SRC_PLUGINS" \ "$TEST_SCRIPT_DIR/post-upgrade.sh" > "$TMP/run1.log" 2>&1 @@ -147,11 +149,22 @@ assert_log_contains "skipped killed skill persisted-blacklisted-skill" assert_present "$LIVE_SKILLS/restored-skill/SKILL.md" assert_log_contains "restored skill restored-skill" -# Pass 3: plugin restore created the dir AND copied both plugins. -assert_present "$LIVE_PLUGINS/test-plugin-a.ts" -assert_present "$LIVE_PLUGINS/test-plugin-b.ts" -assert_log_contains "restored plugin test-plugin-a.ts" -assert_log_contains "restored plugin test-plugin-b.ts" +# Pass 3: default plugin restore is a no-op because opencode loads the +# persistent source directly. +assert_missing "$LIVE_PLUGINS" +assert_log_contains "plugin restore not needed; opencode loads persistent plugins at $SRC_PLUGINS" + +# Explicit compatibility override still restores plugins into the requested dir. +KIMAKI_SKILLS_DIR="$LIVE_SKILLS" \ +KIMAKI_PLUGINS_DIR="$LIVE_PLUGINS" \ +KIMAKI_SKILL_SOURCE_DIR="$SRC_SKILLS" \ +KIMAKI_PLUGIN_SOURCE_DIR="$SRC_PLUGINS" \ + "$TEST_SCRIPT_DIR/post-upgrade.sh" > "$TMP/run-override.log" 2>&1 + +assert_present "$LIVE_PLUGINS/dm-context-filter.ts" +assert_present "$LIVE_PLUGINS/dm-agent-sync.ts" +assert_log_contains_file "$TMP/run-override.log" "restored plugin dm-context-filter.ts" +assert_log_contains_file "$TMP/run-override.log" "restored plugin dm-agent-sync.ts" # Idempotency: second run with the same state should restore zero plugins. KIMAKI_SKILLS_DIR="$LIVE_SKILLS" \ @@ -171,8 +184,7 @@ if ! grep -q "0 plugins restored" "$TMP/run2.log"; then exit 1 fi -# Wipe the live plugins dir to simulate `npm update -g kimaki` and confirm -# the next run rehydrates it from the persistent source — the actual fix. +# Wipe the explicit target and confirm override mode can still rehydrate it. rm -rf "$LIVE_PLUGINS" KIMAKI_SKILLS_DIR="$LIVE_SKILLS" \ @@ -181,8 +193,8 @@ KIMAKI_SKILL_SOURCE_DIR="$SRC_SKILLS" \ KIMAKI_PLUGIN_SOURCE_DIR="$SRC_PLUGINS" \ "$TEST_SCRIPT_DIR/post-upgrade.sh" > "$TMP/run3.log" 2>&1 -if [[ ! -f "$LIVE_PLUGINS/test-plugin-a.ts" ]]; then - echo "FAIL: plugins dir should be rehydrated after simulated npm update" +if [[ ! -f "$LIVE_PLUGINS/dm-context-filter.ts" ]]; then + echo "FAIL: explicit plugins dir should be rehydrated after simulated npm update" cat "$TMP/run3.log" exit 1 fi @@ -219,7 +231,6 @@ EOF HOME="$FALLBACK_HOME" \ KIMAKI_DATA_DIR="$FALLBACK_DATA" \ KIMAKI_SKILLS_DIR="$FALLBACK_LIVE_SKILLS" \ -KIMAKI_PLUGINS_DIR="$FALLBACK_LIVE_PLUGINS" \ "$TEST_SCRIPT_DIR/post-upgrade.sh" > "$TMP/run4.log" 2>&1 if [[ ! -f "$FALLBACK_LIVE_SKILLS/home-skill/SKILL.md" ]]; then @@ -227,8 +238,8 @@ if [[ ! -f "$FALLBACK_LIVE_SKILLS/home-skill/SKILL.md" ]]; then cat "$TMP/run4.log" exit 1 fi -if [[ ! -f "$FALLBACK_LIVE_PLUGINS/home-plugin.ts" ]]; then - echo "FAIL: missing KIMAKI_DATA_DIR plugins source should fall through to HOME source" +if [[ -e "$FALLBACK_LIVE_PLUGINS" ]]; then + echo "FAIL: default plugin verification should not create a separate live plugins dir" cat "$TMP/run4.log" exit 1 fi @@ -237,8 +248,8 @@ if ! grep -q "restored skill home-skill" "$TMP/run4.log"; then cat "$TMP/run4.log" exit 1 fi -if ! grep -q "restored plugin home-plugin.ts" "$TMP/run4.log"; then - echo "FAIL: fallback run should restore the HOME-backed plugin" +if ! grep -q "plugin restore not needed; opencode loads persistent plugins at $FALLBACK_HOME/.kimaki/kimaki-config/plugins" "$TMP/run4.log"; then + echo "FAIL: fallback run should verify the HOME-backed plugin source in place" cat "$TMP/run4.log" exit 1 fi @@ -253,7 +264,7 @@ KIMAKI_SKILL_SOURCE_DIR="$SRC_SKILLS" \ KIMAKI_PLUGIN_SOURCE_DIR="$MISSING_SRC" \ "$TEST_SCRIPT_DIR/post-upgrade.sh" > "$TMP/missing.log" 2>&1 -assert_log_contains_file "$TMP/missing.log" "WARNING: persistent plugin source dir not found at $MISSING_SRC; dm-context-filter.ts and dm-agent-sync.ts cannot be restored" +assert_log_contains_file "$TMP/missing.log" "WARNING: persistent plugin source dir not found at $MISSING_SRC; dm-context-filter.ts and dm-agent-sync.ts cannot be loaded" assert_log_contains_file "$TMP/missing.log" "WARNING: plugins dir not found at $MISSING_LIVE_PLUGINS; opencode.json plugin paths will be skipped by OpenCode" assert_log_contains_file "$TMP/missing.log" "2 required plugins missing" diff --git a/tests/repair-opencode-json.sh b/tests/repair-opencode-json.sh index abebe08..e3caeb3 100644 --- a/tests/repair-opencode-json.sh +++ b/tests/repair-opencode-json.sh @@ -122,7 +122,7 @@ python3 "$REPAIR" \ --runtime opencode \ --chat-bridge kimaki \ --kimaki-plugins-dir /Users/example/.kimaki/kimaki-config/plugins \ - --apply > "$TMP/local-plugin-path.out" || true + --additive > "$TMP/local-plugin-path.out" python3 - "$TMP/local-plugin-path.json" <<'PY' import json @@ -138,5 +138,7 @@ expected = [ if data.get("plugin") != expected: raise SystemExit(f"unexpected plugin paths: {data.get('plugin')}") PY +grep -q '"status": "additive_repaired"' "$TMP/local-plugin-path.out" +grep -q '"rewritten"' "$TMP/local-plugin-path.out" echo "OK: repair-opencode-json removes managed agent shells and repairs local plugin paths" diff --git a/upgrade.sh b/upgrade.sh index b347329..0794908 100755 --- a/upgrade.sh +++ b/upgrade.sh @@ -301,6 +301,7 @@ _run_filter_active() { if [ "$KIMAKI_ONLY" = true ] || [ "$PLUGINS_ONLY" = true ] || [ "$SKILLS_ONLY" = true ] || [ "$AGENTS_MD_ONLY" = true ]; then case "$phase" in kimaki) [ "$KIMAKI_ONLY" = true ]; return $? ;; + opencode-json) [ "$KIMAKI_ONLY" = true ]; return $? ;; plugins) [ "$PLUGINS_ONLY" = true ]; return $? ;; skills) [ "$SKILLS_ONLY" = true ]; return $? ;; agents-md) [ "$AGENTS_MD_ONLY" = true ]; return $? ;;