diff --git a/bridges/kimaki.sh b/bridges/kimaki.sh
index 8b9ad1a..afbc685 100644
--- a/bridges/kimaki.sh
+++ b/bridges/kimaki.sh
@@ -174,7 +174,8 @@ _kimaki_install_systemd() {
local ENV_BLOCK="Environment=HOME=$SERVICE_HOME
Environment=PATH=$PATH_VALUE
-Environment=KIMAKI_DATA_DIR=$KIMAKI_DATA_DIR"
+Environment=KIMAKI_DATA_DIR=$KIMAKI_DATA_DIR
+Environment=DATAMACHINE_SITE_PATH=$SITE_PATH"
if [ -n "$KIMAKI_BOT_TOKEN" ]; then
ENV_BLOCK="$ENV_BLOCK
Environment=KIMAKI_BOT_TOKEN=$KIMAKI_BOT_TOKEN"
@@ -398,7 +399,8 @@ bridge_update_systemd() {
local TEMPLATE_ENV="Environment=HOME=$SERVICE_HOME
Environment=PATH=$PATH_VALUE
-Environment=KIMAKI_DATA_DIR=$KIMAKI_DATA_DIR"
+Environment=KIMAKI_DATA_DIR=$KIMAKI_DATA_DIR
+Environment=DATAMACHINE_SITE_PATH=$SITE_PATH"
local MERGED_ENV
MERGED_ENV=$(_merge_systemd_env_lines "$CURRENT_ENV" "$TEMPLATE_ENV")
@@ -528,7 +530,9 @@ bridge_render_launchd() {
PATH
$path_value
KIMAKI_DATA_DIR
- $KIMAKI_DATA_DIR$(if [ -n "${KIMAKI_BOT_TOKEN:-}" ]; then echo "
+ $KIMAKI_DATA_DIR
+ DATAMACHINE_SITE_PATH
+ $SITE_PATH$(if [ -n "${KIMAKI_BOT_TOKEN:-}" ]; then echo "
KIMAKI_BOT_TOKEN
$KIMAKI_BOT_TOKEN"; fi)
diff --git a/bridges/kimaki/bin/datamachine-kimaki b/bridges/kimaki/bin/datamachine-kimaki
index 821fd94..2ece7bc 100755
--- a/bridges/kimaki/bin/datamachine-kimaki
+++ b/bridges/kimaki/bin/datamachine-kimaki
@@ -9,12 +9,12 @@ 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:
+agent identity and site-root memory. This adapter normalizes the Kimaki `send`
+agent slot before delegating to the real Kimaki binary:
send --agent -> send --agent build
- send --cwd -> stripped
- send --worktree [name] -> blocked before native Kimaki worktree handling
+
+Kimaki project, --cwd, and --worktree routing are passed through unchanged.
Set DATAMACHINE_REAL_KIMAKI to the real Kimaki binary path. If unset, the
adapter resolves the next non-adapter `kimaki` from PATH.
@@ -57,8 +57,6 @@ resolve_real_kimaki() {
normalize_send_args() {
normalized_args=()
- saw_worktree=false
- local worktree_value=""
while [[ $# -gt 0 ]]; do
case "$1" in
@@ -73,28 +71,6 @@ normalize_send_args() {
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
@@ -102,12 +78,6 @@ normalize_send_args() {
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
}
diff --git a/bridges/kimaki/plugins/dm-agent-sync.ts b/bridges/kimaki/plugins/dm-agent-sync.ts
index 79a3807..0735682 100644
--- a/bridges/kimaki/plugins/dm-agent-sync.ts
+++ b/bridges/kimaki/plugins/dm-agent-sync.ts
@@ -43,6 +43,7 @@ interface DmPaths {
const dmAgentSync: Plugin = async ({ $ }) => {
return {
config: async (config) => {
+ const sitePath = getSitePath();
const wpAvailable = await $`command -v wp`.quiet().nothrow();
if (wpAvailable.exitCode !== 0) {
return;
@@ -55,13 +56,17 @@ const dmAgentSync: Plugin = async ({ $ }) => {
// edits, or other external processes would leave AGENTS.md stale.
// Running compose here guarantees the file matches live state at the
// moment OpenCode loads the session prompt.
- const composeResult = await $`wp datamachine memory compose --allow-root`.quiet().nothrow();
+ const composeResult = sitePath
+ ? await $`wp --path=${sitePath} datamachine memory compose --allow-root`.quiet().nothrow()
+ : await $`wp datamachine memory compose --allow-root`.quiet().nothrow();
if (composeResult.exitCode !== 0) {
console.warn(`[dm-agent-sync] memory compose failed (exit ${composeResult.exitCode}): ${await shellOutputText(composeResult)}`);
}
// Query all agents from Data Machine.
- const agentsResult = await $`wp datamachine agents list --format=json --allow-root`.quiet().nothrow();
+ const agentsResult = sitePath
+ ? await $`wp --path=${sitePath} datamachine agents list --format=json --allow-root`.quiet().nothrow()
+ : await $`wp datamachine agents list --format=json --allow-root`.quiet().nothrow();
if (agentsResult.exitCode !== 0) {
console.warn(`[dm-agent-sync] agents list failed (exit ${agentsResult.exitCode}): ${await shellOutputText(agentsResult)}`);
return;
@@ -89,7 +94,9 @@ const dmAgentSync: Plugin = async ({ $ }) => {
continue;
}
- const pathsResult = await $`wp datamachine memory paths --agent=${agent.agent_slug} --format=json --allow-root`.quiet().nothrow();
+ const pathsResult = sitePath
+ ? await $`wp --path=${sitePath} datamachine memory paths --agent=${agent.agent_slug} --format=json --allow-root`.quiet().nothrow()
+ : await $`wp datamachine memory paths --agent=${agent.agent_slug} --format=json --allow-root`.quiet().nothrow();
if (pathsResult.exitCode !== 0) {
console.warn(`[dm-agent-sync] memory paths failed for ${agent.agent_slug} (exit ${pathsResult.exitCode}): ${await shellOutputText(pathsResult)}`);
continue;
@@ -108,9 +115,10 @@ const dmAgentSync: Plugin = async ({ $ }) => {
continue;
}
+ const promptRoot = sitePath || ".";
const prompt = [
- "{file:./AGENTS.md}",
- ...paths.relative_files.map((f: string) => `{file:./${f}}`),
+ `{file:${promptRoot}/AGENTS.md}`,
+ ...paths.relative_files.map((f: string) => `{file:${promptRoot}/${f}}`),
].join("\n");
const agentModel =
@@ -161,6 +169,10 @@ const dmAgentSync: Plugin = async ({ $ }) => {
};
};
+function getSitePath(): string {
+ return process.env.DATAMACHINE_SITE_PATH || process.env.SITE_PATH || process.env.PWD || "";
+}
+
/**
* Populate build/plan defaults without clobbering user-authored fields.
*
diff --git a/bridges/kimaki/plugins/dm-context-filter.ts b/bridges/kimaki/plugins/dm-context-filter.ts
index 9113a70..1b2925a 100644
--- a/bridges/kimaki/plugins/dm-context-filter.ts
+++ b/bridges/kimaki/plugins/dm-context-filter.ts
@@ -10,13 +10,9 @@
// site on VPS). Tunnels are task-specific for inbound public URLs like
// webhooks/OAuth callbacks, not the default way to interact with the site.
// 3. Critique — ~900 tokens of diff-sharing instructions. We use GitHub PRs.
-// 4. Worktree creation — ~150 tokens. We use feature branches in workspace repos.
-// 5. Cross-project commands — ~200 tokens. Single-project fleet servers.
-// 6. Waiting for sessions — ~150 tokens. Rarely used, discoverable via --help.
-// 7. Session/workspace conflicts — Kimaki's generic session, project, agent,
-// and worktree docs describe Kimaki as the workspace orchestrator. On a Data
-// Machine install, Data Machine Code owns workspace creation and Kimaki is
-// the Discord/session bridge.
+// 4. Waiting for sessions — ~150 tokens. Rarely used, discoverable via --help.
+// 5. Session/workspace conflicts — generic Kimaki agent override examples can
+// bypass the Data Machine-bound default agent slot.
// 8. Permissions — ~80 tokens describing which Discord roles can message the
// bot. The agent has no capability to act on this; pure metadata leakage.
// 9. Upgrading kimaki — ~80 tokens of /upgrade-and-restart playbook. The user
@@ -25,13 +21,7 @@
// --project` / `session search --channel `. These are cross-project
// discovery vectors; on a single-project fleet server the agent only ever
// needs to list sessions in the current project (no flags required).
-// 11. Project discovery/guidance inlines — scattered prose and examples for
-// `kimaki project list|add|create`, `kimaki send --project`, bare
-// `kimaki send --channel `, `#project-name` resolution, and
-// "project channel" routing. These let the agent discover other Discord
-// channels and route minion sessions away from the current thread. See
-// Extra-Chill/data-machine-code#49.
-// 12. Agent override inlines — `--agent ` examples from the
+// 11. Agent override inlines — `--agent ` examples from the
// generic Kimaki prompt. On DM-managed sites the Discord channel owns the
// personal-agent binding; passing the runtime agent (for example `opencode`)
// bypasses that binding and starts the wrong kind of minion session.
@@ -50,10 +40,6 @@
// 8. MEMORY.md injection — Kimaki reads MEMORY.md from the project directory and
// injects a condensed TOC. Conflicts with Data Machine's own memory files.
// 9. "Update MEMORY.md" time-gap reminder — Redundant with external memory system.
-// 10. Worktree system-reminder — Kimaki injects a telling the
-// agent to operate inside its worktree and not touch the main repo. This
-// overrides Data Machine Code's workspace, which is the real working dir.
-//
// Total savings: ~2,400+ tokens per session.
//
// How to use:
@@ -75,10 +61,7 @@ const fleetContextFilter: Plugin = async () => {
result = stripSection(result, "## upgrading kimaki");
result = stripSection(result, "## scheduled sends and task management");
result = stripSection(result, "## running dev servers with tunnel access");
- result = stripSection(result, "## starting new sessions from CLI");
- result = stripSection(result, "## creating worktrees");
result = stripSection(result, "## worktree");
- result = stripSection(result, "## cross-project commands");
result = stripSection(result, "## reading other sessions");
result = stripSection(result, "## waiting for a session to finish");
result = stripSection(result, "## running opencode commands via kimaki send");
@@ -88,8 +71,6 @@ const fleetContextFilter: Plugin = async () => {
result = stripSection(result, "### always show diff at end of session");
result = stripSection(result, "### fetching user comments from critique diffs");
result = stripSection(result, "### reviewing diffs with AI");
- result = stripWorktreeInlines(result);
- result = stripProjectDiscoveryInlines(result);
result = stripAgentOverrideInlines(result);
// Clean up leftover double/triple blank lines.
result = result.replace(/\n{3,}/g, "\n\n");
@@ -97,8 +78,7 @@ const fleetContextFilter: Plugin = async () => {
});
},
- // Filter out Kimaki's MEMORY.md injection, time-gap MEMORY.md reminders,
- // and worktree system-reminders.
+ // Filter out Kimaki's MEMORY.md injection and time-gap MEMORY.md reminders.
"chat.message": async (_input, output) => {
// Walk backwards so splice indices stay valid.
for (let i = output.parts.length - 1; i >= 0; i--) {
@@ -123,12 +103,6 @@ const fleetContextFilter: Plugin = async () => {
continue;
}
- // Remove Kimaki's worktree system-reminder. Data Machine Code manages
- // the real working directory; Kimaki's reminder conflicts with it.
- if (text.includes("running inside a git worktree")) {
- output.parts.splice(i, 1);
- continue;
- }
}
},
};
@@ -187,159 +161,6 @@ function stripSection(block: string, heading: string): string {
return [...before, ...after].join("\n");
}
-/**
- * Remove inline worktree/--worktree/--cwd content that lives inside sections
- * we otherwise want to keep (like "## starting new sessions from CLI") or
- * as standalone paragraphs between sections.
- *
- * These exist because Kimaki assumes worktrees are a first-class feature, but
- * Data Machine Code owns the workspace and worktrees on DM-managed sites.
- * Leaving the language in causes the agent to try `kimaki send --worktree`
- * or treat a Kimaki worktree as its working directory instead of using the
- * DM Code workspace.
- *
- * @param {string} block System prompt block.
- * @return {string} System prompt block without worktree inline guidance.
- */
-function stripWorktreeInlines(block: string): string {
- let result = block;
-
- // Standalone lead-in paragraph that sits above the (stripped) "## creating
- // worktrees" section. After the section is stripped, this line is orphaned.
- result = result.replace(
- /\n+Worktrees are useful for handing off parallel tasks[^\n]*\n/g,
- "\n"
- );
-
- // Inline "IMPORTANT: NEVER use --worktree" warning inside
- // "## starting new sessions from CLI".
- result = result.replace(
- /\n+IMPORTANT: NEVER use `--worktree`[^\n]*\n/g,
- "\n"
- );
-
- // "Use --worktree to create a git worktree for the session..." example block.
- // Covers the intro line, the code example, and trailing blank line.
- result = result.replace(
- /\n+Use --worktree to create a git worktree[\s\S]*?--worktree [^\n]*\n/g,
- "\n"
- );
-
- // "Use --cwd to start a session in an existing git worktree..." example block.
- result = result.replace(
- /\n+Use --cwd to start a session in an existing git worktree[\s\S]*?--cwd [^\n]*\n/g,
- "\n"
- );
-
- // "Important:" bullet list about --worktree that follows the examples above.
- // Only strip if the list is clearly about worktrees (first bullet mentions it).
- result = result.replace(
- /\n+Important:\n(?:- [^\n]*\n)*?- NEVER use `--worktree`[^\n]*\n(?:- [^\n]*\n)*/g,
- "\n"
- );
-
- return result;
-}
-
-/**
- * Remove project / channel discovery guidance that survives section stripping.
- *
- * The system prompt bakes the current channel ID into most `kimaki send`
- * examples via `${channelId}`, which is the safe/correct form for this
- * session. But several other forms leak the *capability* to target other
- * channels or projects:
- *
- * - `kimaki project list|add|create` — enumerates every registered project
- * with its Discord channel ID.
- * - `kimaki send --project ` — resolves a channel from a project dir.
- * - `kimaki send --channel ` with a literal ``
- * placeholder (as opposed to the baked-in current-channel ID) — teaches
- * the agent it can pick a channel freely.
- * - `#project-name` / "project channel" prose — teaches the agent to treat
- * repo/project channels as valid routing targets.
- *
- * On DM-managed sites the current Discord thread is the only correct target
- * for minion sessions. Cross-repo work uses DM Code's workspace worktrees,
- * not cross-channel kimaki sends. See Extra-Chill/data-machine-code#49.
- *
- * We keep `${channelId}` examples untouched — those are the intended,
- * session-scoped forms.
- *
- * @param {string} block System prompt block.
- * @return {string} System prompt block without project discovery guidance.
- */
-function stripProjectDiscoveryInlines(block: string): string {
- let result = block;
-
- // If upstream renames the cross-project section, strip its distinctive prose
- // before deleting leftover command lines below.
- result = result.replace(
- /\n+When the user references another project by name,[\s\S]*?root project directories\.\n/g,
- "\n"
- );
-
- result = result.replace(
- /\n+When the user uses `#project-name` syntax,[\s\S]*?before acting,[^\n]*\n/g,
- "\n"
- );
-
- result = result.replace(
- /\n+To send a task to another project:[\s\S]*?(?=\n\S|$)/g,
- "\n"
- );
-
- result = result.replace(
- /\n+When sending prompts to other projects,[^\n]*\n/g,
- "\n"
- );
-
- // Standalone `kimaki project ...` commands on their own lines (inside any
- // surviving section or orphaned between sections). Covers list|add|create.
- result = result.replace(
- /\n+kimaki project (?:list|add|create)[^\n]*\n/g,
- "\n"
- );
-
- // `kimaki send --project /path/...` bash examples, as full lines.
- result = result.replace(
- /\n+kimaki send --project [^\n]*\n/g,
- "\n"
- );
-
- // `kimaki send --channel ...` examples that use the literal
- // placeholder `` rather than the baked-in session channel.
- // The `${channelId}` form is template-substituted before this plugin runs,
- // so by the time we see the prompt the current channel is already a
- // concrete numeric ID — it will not match `` and stays intact.
- result = result.replace(
- /\n+kimaki send --channel [^\n]*\n/g,
- "\n"
- );
-
- // Matching `kimaki session search "..." --channel ` examples.
- result = result.replace(
- /\n+kimaki session search [^\n]*--channel [^\n]*\n/g,
- "\n"
- );
-
- // Any remaining `--project /path/...` flag usage in inline prose or code
- // blocks. Conservative: only strip whole lines where the flag is the
- // dominant content (starts with command + --project).
- result = result.replace(
- /\n+kimaki (?:session|task) [^\n]*--project [^\n]*\n/g,
- "\n"
- );
-
- // Remove any whole-line project-routing prose that may survive if Kimaki
- // changes surrounding headings or examples.
- result = result.replace(
- /\n+[^\n]*(?:project channel|cross-project|#project-name|other project|another project)[^\n]*\n/gi,
- "\n"
- );
-
- return result;
-}
-
/**
* Remove generic Kimaki agent override examples from surviving sections.
*
@@ -368,8 +189,8 @@ function stripAgentOverrideInlines(block: string): string {
"\n"
);
- // Surviving `kimaki send` examples should rely on channel routing. This
- // keeps examples usable while removing the footgun.
+ // Surviving `kimaki send` examples should rely on channel routing and the
+ // Data Machine-bound default agent slot.
result = result.replace(/ --agent /g, "");
return result;
diff --git a/tests/__snapshots__/bridges/kimaki-launchd b/tests/__snapshots__/bridges/kimaki-launchd
index a7adb74..1ce6b3a 100644
--- a/tests/__snapshots__/bridges/kimaki-launchd
+++ b/tests/__snapshots__/bridges/kimaki-launchd
@@ -26,6 +26,8 @@
/home/chubes/.local/bin:/opt/homebrew/bin:/home/chubes/.opencode/bin:/home/chubes/.bun/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
KIMAKI_DATA_DIR
/home/chubes/.kimaki
+ DATAMACHINE_SITE_PATH
+ /var/www/site
diff --git a/tests/__snapshots__/bridges/kimaki-systemd b/tests/__snapshots__/bridges/kimaki-systemd
index 6bf7a29..d8e44c8 100644
--- a/tests/__snapshots__/bridges/kimaki-systemd
+++ b/tests/__snapshots__/bridges/kimaki-systemd
@@ -9,6 +9,7 @@ WorkingDirectory=/var/www/site
Environment=HOME=/home/chubes
Environment=PATH=/usr/bin:/usr/local/bin:/bin
Environment=KIMAKI_DATA_DIR=/home/chubes/.kimaki
+Environment=DATAMACHINE_SITE_PATH=/var/www/site
# Reap stray opencode-serve children left behind by the previous kimaki
# process before starting a fresh one. Each kimaki session spawns its own
# opencode-serve worker; if kimaki exits uncleanly (crash, OOM, manual
diff --git a/tests/bridge-render.sh b/tests/bridge-render.sh
index 0c402ca..2b8f985 100755
--- a/tests/bridge-render.sh
+++ b/tests/bridge-render.sh
@@ -88,7 +88,8 @@ kimaki_env_block() {
path_value=$(_compose_path_value "$kimaki_bin_dir" "$node_bin_dir" /usr/local/bin /usr/bin /bin)
local out="Environment=HOME=$SERVICE_HOME
Environment=PATH=$path_value
-Environment=KIMAKI_DATA_DIR=$KIMAKI_DATA_DIR"
+Environment=KIMAKI_DATA_DIR=$KIMAKI_DATA_DIR
+Environment=DATAMACHINE_SITE_PATH=$SITE_PATH"
if [ -n "${KIMAKI_BOT_TOKEN:-}" ]; then
out="$out
Environment=KIMAKI_BOT_TOKEN=$KIMAKI_BOT_TOKEN"
diff --git a/tests/datamachine-kimaki-adapter.sh b/tests/datamachine-kimaki-adapter.sh
index 4fae94e..c633102 100755
--- a/tests/datamachine-kimaki-adapter.sh
+++ b/tests/datamachine-kimaki-adapter.sh
@@ -32,18 +32,6 @@ if actual != expected:
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
@@ -54,19 +42,19 @@ assert_args send --prompt hi --agent build
assert_args send --prompt hi --agent build
"$ADAPTER" send --prompt hi --cwd /tmp/elsewhere
-assert_args send --prompt hi
+assert_args send --prompt hi --cwd /tmp/elsewhere
"$ADAPTER" send --prompt hi --cwd=/tmp/elsewhere --agent opencode
-assert_args send --prompt hi --agent build
+assert_args send --prompt hi --cwd=/tmp/elsewhere --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_args send --prompt hi --cwd --model anthropic/test
-assert_fails_without_call send --prompt hi --worktree feature-x
-grep -q 'Native Kimaki worktrees are disabled' "$TMP/stderr"
+"$ADAPTER" send --prompt hi --worktree feature-x
+assert_args send --prompt hi --worktree feature-x
"$ADAPTER" session list --project /tmp/site
assert_args session list --project /tmp/site
@@ -78,6 +66,6 @@ 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
+assert_args send --prompt hi --agent build --cwd /tmp/site
-echo "OK: datamachine-kimaki adapter normalizes Kimaki send flags"
+echo "OK: datamachine-kimaki adapter normalizes agent send flags"
diff --git a/tests/dm-agent-sync.mjs b/tests/dm-agent-sync.mjs
index 8a943db..c87f807 100644
--- a/tests/dm-agent-sync.mjs
+++ b/tests/dm-agent-sync.mjs
@@ -3,6 +3,9 @@
import assert from "node:assert/strict"
import dmAgentSync from "../bridges/kimaki/plugins/dm-agent-sync.ts"
+const sitePath = "/tmp/datamachine-site"
+process.env.DATAMACHINE_SITE_PATH = sitePath
+
function output(stdout = "", exitCode = 0, stderr = "") {
return {
exitCode,
@@ -53,8 +56,8 @@ const agentsJson = JSON.stringify([
const commonResponses = [
[/^command -v wp$/, output("/usr/local/bin/wp")],
- [/^wp datamachine memory compose/, output("composed")],
- [/^wp datamachine agents list/, output(`${agentsJson}\nTotal: 2 agent(s).`)],
+ [/^wp --path=\/tmp\/datamachine-site datamachine memory compose/, output("composed")],
+ [/^wp --path=\/tmp\/datamachine-site datamachine agents list/, output(`${agentsJson}\nTotal: 2 agent(s).`)],
[/--agent=franklin /, output(JSON.stringify({ agent_slug: "franklin", relative_files: ["SITE.md", "SOUL.md"] }))],
[/--agent=julia /, output(JSON.stringify({ agent_slug: "julia", relative_files: ["SITE.md", "MEMORY.md"] }))],
]
@@ -68,10 +71,10 @@ const commonResponses = [
},
}
const warnings = await runConfig(config, commonResponses)
- assert.match(config.agent.build.prompt, /\{file:\.\/AGENTS\.md\}/)
- assert.match(config.agent.plan.prompt, /\{file:\.\/SOUL\.md\}/)
+ assert.match(config.agent.build.prompt, /\{file:\/tmp\/datamachine-site\/AGENTS\.md\}/)
+ assert.match(config.agent.plan.prompt, /\{file:\/tmp\/datamachine-site\/SOUL\.md\}/)
assert.equal(config.agent.build.model, "anthropic/claude-opus-4-7")
- assert.match(config.agent.franklin.prompt, /\{file:\.\/SITE\.md\}/)
+ assert.match(config.agent.franklin.prompt, /\{file:\/tmp\/datamachine-site\/SITE\.md\}/)
assert.match(config.agent.julia.description, /Data Machine agent: Julia/)
assert.ok(warnings.some((line) => line.includes("registered 2 Data Machine agent(s)")))
}
@@ -85,8 +88,8 @@ const commonResponses = [
}
await runConfig(config, commonResponses)
assert.deepEqual(config.agent.build.tools, { bash: true })
- assert.match(config.agent.build.prompt, /\{file:\.\/SOUL\.md\}/)
- assert.match(config.agent.plan.prompt, /\{file:\.\/SOUL\.md\}/)
+ assert.match(config.agent.build.prompt, /\{file:\/tmp\/datamachine-site\/SOUL\.md\}/)
+ assert.match(config.agent.plan.prompt, /\{file:\/tmp\/datamachine-site\/SOUL\.md\}/)
}
{
@@ -97,15 +100,15 @@ const commonResponses = [
}
await runConfig(config, commonResponses)
assert.equal(config.agent.build.prompt, "custom prompt")
- assert.match(config.agent.plan.prompt, /\{file:\.\/SOUL\.md\}/)
+ assert.match(config.agent.plan.prompt, /\{file:\/tmp\/datamachine-site\/SOUL\.md\}/)
}
{
const config = {}
const warnings = await runConfig(config, [
[/^command -v wp$/, output("/usr/local/bin/wp")],
- [/^wp datamachine memory compose/, output("composed")],
- [/^wp datamachine agents list/, output("", 1, "db down")],
+ [/^wp --path=\/tmp\/datamachine-site datamachine memory compose/, output("composed")],
+ [/^wp --path=\/tmp\/datamachine-site datamachine agents list/, output("", 1, "db down")],
])
assert.ok(warnings.some((line) => line.includes("agents list failed")))
assert.equal(config.agent, undefined)
diff --git a/tests/effective-prompt/README.md b/tests/effective-prompt/README.md
index 408cbaf..90d686e 100644
--- a/tests/effective-prompt/README.md
+++ b/tests/effective-prompt/README.md
@@ -2,15 +2,14 @@
Pluggable harness that renders the kimaki opencode system prompt, runs the
`dm-context-filter` plugin over it, snapshots the result, and asserts that
-no banned phrases (`worktree`, `--cwd`, `kimaki project`, `--project`,
-`#project-name`, etc) leak into the filtered prompt that an opencode session
-actually sees.
+no banned `--agent` override examples leak into the filtered prompt that an
+opencode session actually sees.
## Why
`dm-context-filter.ts` is a security-and-context plugin. It strips ~5,000
tokens of kimaki-shipped instructions that conflict with Data Machine's
-worktree, memory, and channel-routing model. When the filter has a bug,
+memory and channel-bound agent model. When the filter has a bug,
the leaked content is invisible until you go reading the system prompt by
hand. This harness catches those leaks at test time.
@@ -48,10 +47,10 @@ Each scenario is a JSON file in `scenarios/`. Override any of:
exercise different Discord contexts (multi-agent, no-thread, etc).
- **`filter`** — name from `filters.mjs`. Default `"current"`.
- **`baseline`** — name from `filters.mjs`. Default
- `"broken-stripsection"` (proves new filter strips strictly more).
+ `"broken-stripsection"` (kept as diff evidence for reviewers).
- **`triggers`** — array of `{ name, pattern }`. Pattern is a JS regex
- string; prefix with `(?i)` for case-insensitive. Default: `worktree`,
- `--cwd`, `--agent`, and Kimaki project/channel routing guidance.
+ string; prefix with `(?i)` for case-insensitive. Default: `--agent`, which
+ catches generic Kimaki agent override examples.
- **`allowLeakInSection`** — array of section headings (e.g. `"## Minion
Session Routing"`) where trigger matches are intentional and must not
count as leaks.
@@ -65,12 +64,9 @@ For every scenario, after running both the current filter and the
baseline filter:
1. **No leaks in current**: `filtered_leaks.length === 0`.
-2. **Strictly smaller than baseline**: the current filter must remove
- more characters than the baseline filter, otherwise the baseline is
- no longer a baseline.
-3. **No regression in leak count**: current must not leak more than
+2. **No regression in leak count**: current must not leak more than
baseline.
-4. **Snapshot match**: the rendered raw / baseline / filtered prompts
+3. **Snapshot match**: the rendered raw / baseline / filtered prompts
match the committed snapshots. Run with `--update` after an
intentional change.
diff --git a/tests/effective-prompt/__snapshots__/default.baseline.txt b/tests/effective-prompt/__snapshots__/default.baseline.txt
index f565b9d..533cd94 100644
--- a/tests/effective-prompt/__snapshots__/default.baseline.txt
+++ b/tests/effective-prompt/__snapshots__/default.baseline.txt
@@ -60,6 +60,9 @@ kimaki project list --json # machine-readable output
# Send to a specific channel
# Or use --project to resolve from directory
+
+# Or use --cwd for an existing checkout/worktree path
+kimaki send --cwd /path/to/other-repo-worktree --prompt 'Plan how to update this checkout'
```
Use cases:
@@ -70,6 +73,9 @@ Use cases:
# Send to an existing thread and wait
kimaki send --thread --prompt 'Run the tests' --wait
+
+# Wait for a session that was already started elsewhere
+kimaki session wait
```
The command exits with the session markdown on stdout once the model finishes responding.
@@ -97,6 +103,8 @@ bunx critique main...new-branch --web "Describe branch changes"
# Share a single commit
bunx critique --commit HEAD --web "Describe latest commit"
+If the user asks to see a diff and you already committed the changes, prefer showing a separate diff URL for each commit instead of one unified diff. Run one `bunx critique --commit --web` per commit so each change is clearly scoped. Run all the critique calls in parallel tool calls.
+
If there are other unrelated changes in the working directory, filter to only show the files you edited:
# Share only specific files
diff --git a/tests/effective-prompt/__snapshots__/default.filtered.txt b/tests/effective-prompt/__snapshots__/default.filtered.txt
index bd7b050..3a1928c 100644
--- a/tests/effective-prompt/__snapshots__/default.filtered.txt
+++ b/tests/effective-prompt/__snapshots__/default.filtered.txt
@@ -48,6 +48,146 @@ kimaki user list --guild 1493321868415996064 --query "username"
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' --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.
+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.
+
+Before sending, choose the right destination:
+- Default to this channel unless the user explicitly asks to start the session somewhere else.
+- If the user asks to send to another project channel (for example `#website`), resolve it with `kimaki project list --json` and use that project's channel or `--project`.
+- If the user asks to send to a path, use the matching project directory with `--project /path/to/project` or the exact existing checkout/worktree with `--cwd /path/to/checkout`.
+- NEVER use `--worktree` unless the user explicitly asks for a worktree. Default to creating normal threads without worktrees.
+
+To send a prompt to an existing thread instead of creating a new one:
+
+kimaki send --thread --prompt 'follow-up prompt'
+
+Use this when you already have the Discord thread ID.
+
+To send to the thread associated with a known session:
+
+kimaki send --session --prompt 'follow-up prompt'
+
+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 --user ''
+
+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' --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 --user ''
+
+Use --cwd to start a session in an existing project subfolder or git worktree directory:
+
+kimaki send --channel 1493345787894038649 --prompt 'Run the restricted task' --cwd /path/to/project/restricted-task --user ''
+
+Important:
+- NEVER use `--worktree` unless the user explicitly requests a worktree. Most tasks should use normal threads without worktrees.
+- Use `--cwd` to reuse an existing project subfolder or worktree directory. Use `--worktree` to create a new worktree.
+- The prompt passed to `--worktree` is the task for the new thread running inside that worktree.
+- Do NOT tell that prompt to "create a new worktree" again, or it can create recursive worktree threads.
+- Ask the new session to operate on its current checkout only (e.g. "validate current worktree", "run checks in this repo").
+
+Available agents:
+- `build`: default coding agent
+- `plan`: planning agent
+
+## creating worktrees
+
+ONLY create worktrees when the user explicitly asks for one. Never proactively use `--worktree` for normal tasks.
+
+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 --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.
+
+By default, worktrees are created from `HEAD`, which means whatever commit or branch the current checkout is on. If you want a different base, pass `--base-branch` or use the slash command option explicitly.
+
+Critical recursion guard:
+- If you already are in a worktree thread, do not create another worktree unless the user explicitly asks for a nested worktree.
+- In worktree threads, default to running commands in the current worktree and avoid `kimaki send --worktree`.
+
+### Sending sessions to existing directories
+
+Use `--cwd` to start a session in an existing project subfolder or git worktree directory instead of the project root:
+
+```bash
+kimaki send --channel 1493345787894038649 --prompt 'Run restricted task X' --cwd /path/to/project/restricted-task --user ''
+```
+
+The path must be inside the project or be a git worktree of the project (validated via `git worktree list`). The session resolves to the correct project channel but uses that path as its working directory, so subfolder `opencode.json` config can apply. Passing the project root itself is allowed and behaves like the default. Use `--worktree` to create a new worktree, `--cwd` to reuse an existing directory.
+
+**Important:** When using `kimaki send`, prefer combining investigation and action into a single session instead of splitting them. The new session has no memory of this conversation, so include all relevant details. Use **bold**, `code`, lists, and > quotes for readability.
+
+This is useful for automation (cron jobs, GitHub webhooks, n8n, etc.)
+
+### Session handoff
+
+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: ' --user ''
+```
+
+The command automatically handles long prompts (over 2000 chars) by sending them as file attachments.
+
+Use this for handoff when:
+- User asks to "handoff", "continue in new thread", or "start fresh session"
+- You detect you're running low on context window space
+- A complex task would benefit from a clean slate with summarized context
+
+## cross-project commands
+
+When the user references another project by name, run `kimaki project list` to find its directory path and channel ID. Then read files, search code, or run commands directly in that directory. If the project is not listed, use `kimaki project add /path/to/repo` to register it and create a Discord channel for it. Do not add subfolders of an existing project — only add root project directories.
+
+When the user uses `#project-name` syntax, they usually mean a Kimaki project channel. Use `kimaki project list --json` to resolve the `channel_name` to its repo working directory. Try the lookup yourself before acting, for example filter by `channel_name` with jq: `kimaki project list --json | jq -r '.[] | select(.channel_name == "project-name") | .directory'`.
+
+```bash
+# List all registered projects with their channel IDs
+kimaki project list
+kimaki project list --json # machine-readable output
+kimaki project list --json | jq -r '.[] | select(.channel_name == "project-name") | .directory'
+
+# Create a new project in ~/.kimaki/projects/ (folder + git init + Discord channel)
+kimaki project create my-new-app
+
+# Add an existing directory as a project
+kimaki project add /path/to/repo
+```
+
+To send a task to another project:
+
+```bash
+# Send to a specific channel
+kimaki send --channel --prompt 'Plan how to update the API client to v2'
+
+# Or use --project to resolve from directory
+kimaki send --project /path/to/other-repo --prompt 'Plan how to bump version to 1.2.0'
+
+# Or use --cwd for an existing checkout/worktree path
+kimaki send --cwd /path/to/other-repo-worktree --prompt 'Plan how to update this checkout'
+```
+
+When the user explicitly asks to send prompts to other projects, target the project/channel/path they named instead of the current channel. Ask the agent to plan first, never build upfront. The prompt should start with "Plan how to ..." so the user can review before greenlighting implementation.
+
+Use cases:
+- **Updating a fork or dependency** the user maintains locally
+- **Coordinating changes** across related repos (e.g., SDK + docs)
+- **Delegating subtasks** to isolated sessions in other projects
+
## submodules
When pulling submodules and they jump to a new commit, commit that submodule pointer update right away before doing other work. Otherwise critique diffs later will include the noisy submodule jump along with the real changes.
@@ -67,6 +207,8 @@ bunx critique main...new-branch --web "Describe branch changes"
# Share a single commit
bunx critique --commit HEAD --web "Describe latest commit"
+If the user asks to see a diff and you already committed the changes, prefer showing a separate diff URL for each commit instead of one unified diff. Run one `bunx critique --commit --web` per commit so each change is clearly scoped. Run all the critique calls in parallel tool calls.
+
If there are other unrelated changes in the working directory, filter to only show the files you edited:
# Share only specific files
diff --git a/tests/effective-prompt/__snapshots__/default.raw.txt b/tests/effective-prompt/__snapshots__/default.raw.txt
index 1bdb7a6..3118fdb 100644
--- a/tests/effective-prompt/__snapshots__/default.raw.txt
+++ b/tests/effective-prompt/__snapshots__/default.raw.txt
@@ -77,7 +77,11 @@ You can use this to "spawn" parallel helper sessions like teammates: start new t
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. 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.
+Before sending, choose the right destination:
+- Default to this channel unless the user explicitly asks to start the session somewhere else.
+- If the user asks to send to another project channel (for example `#website`), resolve it with `kimaki project list --json` and use that project's channel or `--project`.
+- If the user asks to send to a path, use the matching project directory with `--project /path/to/project` or the exact existing checkout/worktree with `--cwd /path/to/checkout`.
+- NEVER use `--worktree` unless the user explicitly asks for a worktree. Default to creating normal threads without worktrees.
To send a prompt to an existing thread instead of creating a new one:
@@ -103,13 +107,13 @@ Use --worktree to create a git worktree for the session (ONLY when the user expl
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):
+Use --cwd to start a session in an existing project subfolder or git worktree directory:
-kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature' --cwd /path/to/existing-worktree --agent --user ''
+kimaki send --channel 1493345787894038649 --prompt 'Run the restricted task' --cwd /path/to/project/restricted-task --agent --user ''
Important:
- NEVER use `--worktree` unless the user explicitly requests a worktree. Most tasks should use normal threads without worktrees.
-- Use `--cwd` to reuse an existing worktree directory. Use `--worktree` to create a new one.
+- Use `--cwd` to reuse an existing project subfolder or worktree directory. Use `--worktree` to create a new worktree.
- The prompt passed to `--worktree` is the task for the new thread running inside that worktree.
- Do NOT tell that prompt to "create a new worktree" again, or it can create recursive worktree threads.
- Ask the new session to operate on its current checkout only (e.g. "validate current worktree", "run checks in this repo").
@@ -214,15 +218,15 @@ Critical recursion guard:
- If you already are in a worktree thread, do not create another worktree unless the user explicitly asks for a nested worktree.
- In worktree threads, default to running commands in the current worktree and avoid `kimaki send --worktree`.
-### Sending sessions to existing worktrees
+### Sending sessions to existing directories
-Use `--cwd` to start a session in an existing git worktree directory instead of creating a new one:
+Use `--cwd` to start a session in an existing project subfolder or git worktree directory instead of the project root:
```bash
-kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature X' --cwd /path/to/existing-worktree --agent --user ''
+kimaki send --channel 1493345787894038649 --prompt 'Run restricted task X' --cwd /path/to/project/restricted-task --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.
+The path must be inside the project or be a git worktree of the project (validated via `git worktree list`). The session resolves to the correct project channel but uses that path as its working directory, so subfolder `opencode.json` config can apply. Passing the project root itself is allowed and behaves like the default. Use `--worktree` to create a new worktree, `--cwd` to reuse an existing directory.
**Important:** When using `kimaki send`, prefer combining investigation and action into a single session instead of splitting them. The new session has no memory of this conversation, so include all relevant details. Use **bold**, `code`, lists, and > quotes for readability.
@@ -298,9 +302,12 @@ kimaki send --channel --prompt 'Plan how to update the API client t
# Or use --project to resolve from directory
kimaki send --project /path/to/other-repo --prompt 'Plan how to bump version to 1.2.0' --agent
+
+# Or use --cwd for an existing checkout/worktree path
+kimaki send --cwd /path/to/other-repo-worktree --prompt 'Plan how to update this checkout' --agent
```
-When sending prompts to other projects, always ask the agent to plan first, never build upfront. The prompt should start with "Plan how to ..." so the user can review before greenlighting implementation.
+When the user explicitly asks to send prompts to other projects, target the project/channel/path they named instead of the current channel. Ask the agent to plan first, never build upfront. The prompt should start with "Plan how to ..." so the user can review before greenlighting implementation.
Use cases:
- **Updating a fork or dependency** the user maintains locally
@@ -311,8 +318,9 @@ Use cases:
Use `--wait` to block until a session completes and print its full conversation to stdout. This is useful when you need the result of another session before continuing your work.
-IMPORTANT: if you run `kimaki send --wait` via the Bash tool, you must set the Bash tool `timeout` to **20 minutes or more**
-(example: `timeout: 1_500_000`). Otherwise the tool will terminate early (default is 2 minutes) and you won't see long sessions.
+When the user asks you to wait for an existing session, run `kimaki session wait ` yourself via Bash, then continue from the printed session markdown. Do not tell the user to run the command.
+
+IMPORTANT: if you run `kimaki send --wait` or `kimaki session wait ` via the Bash tool, you must set the Bash tool `timeout` to **20 minutes or more** (example: `timeout: 1_500_000`). Otherwise the tool will terminate early (default is 2 minutes) and you won't see long sessions.
If your Bash tool timeout triggers anyway, fall back to reading the session output from disk:
@@ -324,6 +332,9 @@ kimaki send --channel --prompt 'Fix the auth bug' --wait --agent --prompt 'Run the tests' --wait --agent
+
+# Wait for a session that was already started elsewhere
+kimaki session wait
```
The command exits with the session markdown on stdout once the model finishes responding.
@@ -361,6 +372,8 @@ bunx critique main...new-branch --web "Describe branch changes"
# Share a single commit
bunx critique --commit HEAD --web "Describe latest commit"
+If the user asks to see a diff and you already committed the changes, prefer showing a separate diff URL for each commit instead of one unified diff. Run one `bunx critique --commit --web` per commit so each change is clearly scoped. Run all the critique calls in parallel tool calls.
+
If there are other unrelated changes in the working directory, filter to only show the files you edited:
# Share only specific files
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 8eee712..0bfe088 100644
--- a/tests/effective-prompt/__snapshots__/no-agents-no-thread.baseline.txt
+++ b/tests/effective-prompt/__snapshots__/no-agents-no-thread.baseline.txt
@@ -58,6 +58,9 @@ kimaki project list --json # machine-readable output
# Send to a specific channel
# Or use --project to resolve from directory
+
+# Or use --cwd for an existing checkout/worktree path
+kimaki send --cwd /path/to/other-repo-worktree --prompt 'Plan how to update this checkout'
```
Use cases:
@@ -68,6 +71,9 @@ Use cases:
# Send to an existing thread and wait
kimaki send --thread --prompt 'Run the tests' --wait
+
+# Wait for a session that was already started elsewhere
+kimaki session wait
```
The command exits with the session markdown on stdout once the model finishes responding.
@@ -95,6 +101,8 @@ bunx critique main...new-branch --web "Describe branch changes"
# Share a single commit
bunx critique --commit HEAD --web "Describe latest commit"
+If the user asks to see a diff and you already committed the changes, prefer showing a separate diff URL for each commit instead of one unified diff. Run one `bunx critique --commit --web` per commit so each change is clearly scoped. Run all the critique calls in parallel tool calls.
+
If there are other unrelated changes in the working directory, filter to only show the files you edited:
# Share only specific files
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 92567fe..d35702c 100644
--- a/tests/effective-prompt/__snapshots__/no-agents-no-thread.filtered.txt
+++ b/tests/effective-prompt/__snapshots__/no-agents-no-thread.filtered.txt
@@ -46,6 +46,142 @@ kimaki user list --guild --query "username"
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' --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.
+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.
+
+Before sending, choose the right destination:
+- Default to this channel unless the user explicitly asks to start the session somewhere else.
+- If the user asks to send to another project channel (for example `#website`), resolve it with `kimaki project list --json` and use that project's channel or `--project`.
+- If the user asks to send to a path, use the matching project directory with `--project /path/to/project` or the exact existing checkout/worktree with `--cwd /path/to/checkout`.
+- NEVER use `--worktree` unless the user explicitly asks for a worktree. Default to creating normal threads without worktrees.
+
+To send a prompt to an existing thread instead of creating a new one:
+
+kimaki send --thread --prompt 'follow-up prompt'
+
+Use this when you already have the Discord thread ID.
+
+To send to the thread associated with a known session:
+
+kimaki send --session --prompt 'follow-up prompt'
+
+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 --user ''
+
+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' --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 --user ''
+
+Use --cwd to start a session in an existing project subfolder or git worktree directory:
+
+kimaki send --channel 1493345787894038649 --prompt 'Run the restricted task' --cwd /path/to/project/restricted-task --user ''
+
+Important:
+- NEVER use `--worktree` unless the user explicitly requests a worktree. Most tasks should use normal threads without worktrees.
+- Use `--cwd` to reuse an existing project subfolder or worktree directory. Use `--worktree` to create a new worktree.
+- The prompt passed to `--worktree` is the task for the new thread running inside that worktree.
+- Do NOT tell that prompt to "create a new worktree" again, or it can create recursive worktree threads.
+- Ask the new session to operate on its current checkout only (e.g. "validate current worktree", "run checks in this repo").
+
+## creating worktrees
+
+ONLY create worktrees when the user explicitly asks for one. Never proactively use `--worktree` for normal tasks.
+
+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 --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.
+
+By default, worktrees are created from `HEAD`, which means whatever commit or branch the current checkout is on. If you want a different base, pass `--base-branch` or use the slash command option explicitly.
+
+Critical recursion guard:
+- If you already are in a worktree thread, do not create another worktree unless the user explicitly asks for a nested worktree.
+- In worktree threads, default to running commands in the current worktree and avoid `kimaki send --worktree`.
+
+### Sending sessions to existing directories
+
+Use `--cwd` to start a session in an existing project subfolder or git worktree directory instead of the project root:
+
+```bash
+kimaki send --channel 1493345787894038649 --prompt 'Run restricted task X' --cwd /path/to/project/restricted-task --user ''
+```
+
+The path must be inside the project or be a git worktree of the project (validated via `git worktree list`). The session resolves to the correct project channel but uses that path as its working directory, so subfolder `opencode.json` config can apply. Passing the project root itself is allowed and behaves like the default. Use `--worktree` to create a new worktree, `--cwd` to reuse an existing directory.
+
+**Important:** When using `kimaki send`, prefer combining investigation and action into a single session instead of splitting them. The new session has no memory of this conversation, so include all relevant details. Use **bold**, `code`, lists, and > quotes for readability.
+
+This is useful for automation (cron jobs, GitHub webhooks, n8n, etc.)
+
+### Session handoff
+
+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: ' --user ''
+```
+
+The command automatically handles long prompts (over 2000 chars) by sending them as file attachments.
+
+Use this for handoff when:
+- User asks to "handoff", "continue in new thread", or "start fresh session"
+- You detect you're running low on context window space
+- A complex task would benefit from a clean slate with summarized context
+
+## cross-project commands
+
+When the user references another project by name, run `kimaki project list` to find its directory path and channel ID. Then read files, search code, or run commands directly in that directory. If the project is not listed, use `kimaki project add /path/to/repo` to register it and create a Discord channel for it. Do not add subfolders of an existing project — only add root project directories.
+
+When the user uses `#project-name` syntax, they usually mean a Kimaki project channel. Use `kimaki project list --json` to resolve the `channel_name` to its repo working directory. Try the lookup yourself before acting, for example filter by `channel_name` with jq: `kimaki project list --json | jq -r '.[] | select(.channel_name == "project-name") | .directory'`.
+
+```bash
+# List all registered projects with their channel IDs
+kimaki project list
+kimaki project list --json # machine-readable output
+kimaki project list --json | jq -r '.[] | select(.channel_name == "project-name") | .directory'
+
+# Create a new project in ~/.kimaki/projects/ (folder + git init + Discord channel)
+kimaki project create my-new-app
+
+# Add an existing directory as a project
+kimaki project add /path/to/repo
+```
+
+To send a task to another project:
+
+```bash
+# Send to a specific channel
+kimaki send --channel --prompt 'Plan how to update the API client to v2'
+
+# Or use --project to resolve from directory
+kimaki send --project /path/to/other-repo --prompt 'Plan how to bump version to 1.2.0'
+
+# Or use --cwd for an existing checkout/worktree path
+kimaki send --cwd /path/to/other-repo-worktree --prompt 'Plan how to update this checkout'
+```
+
+When the user explicitly asks to send prompts to other projects, target the project/channel/path they named instead of the current channel. Ask the agent to plan first, never build upfront. The prompt should start with "Plan how to ..." so the user can review before greenlighting implementation.
+
+Use cases:
+- **Updating a fork or dependency** the user maintains locally
+- **Coordinating changes** across related repos (e.g., SDK + docs)
+- **Delegating subtasks** to isolated sessions in other projects
+
## submodules
When pulling submodules and they jump to a new commit, commit that submodule pointer update right away before doing other work. Otherwise critique diffs later will include the noisy submodule jump along with the real changes.
@@ -65,6 +201,8 @@ bunx critique main...new-branch --web "Describe branch changes"
# Share a single commit
bunx critique --commit HEAD --web "Describe latest commit"
+If the user asks to see a diff and you already committed the changes, prefer showing a separate diff URL for each commit instead of one unified diff. Run one `bunx critique --commit --web` per commit so each change is clearly scoped. Run all the critique calls in parallel tool calls.
+
If there are other unrelated changes in the working directory, filter to only show the files you edited:
# Share only specific files
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 b3f0b72..a711269 100644
--- a/tests/effective-prompt/__snapshots__/no-agents-no-thread.raw.txt
+++ b/tests/effective-prompt/__snapshots__/no-agents-no-thread.raw.txt
@@ -75,7 +75,11 @@ You can use this to "spawn" parallel helper sessions like teammates: start new t
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. 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.
+Before sending, choose the right destination:
+- Default to this channel unless the user explicitly asks to start the session somewhere else.
+- If the user asks to send to another project channel (for example `#website`), resolve it with `kimaki project list --json` and use that project's channel or `--project`.
+- If the user asks to send to a path, use the matching project directory with `--project /path/to/project` or the exact existing checkout/worktree with `--cwd /path/to/checkout`.
+- NEVER use `--worktree` unless the user explicitly asks for a worktree. Default to creating normal threads without worktrees.
To send a prompt to an existing thread instead of creating a new one:
@@ -101,13 +105,13 @@ Use --worktree to create a git worktree for the session (ONLY when the user expl
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):
+Use --cwd to start a session in an existing project subfolder or git worktree directory:
-kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature' --cwd /path/to/existing-worktree --agent --user ''
+kimaki send --channel 1493345787894038649 --prompt 'Run the restricted task' --cwd /path/to/project/restricted-task --agent --user ''
Important:
- NEVER use `--worktree` unless the user explicitly requests a worktree. Most tasks should use normal threads without worktrees.
-- Use `--cwd` to reuse an existing worktree directory. Use `--worktree` to create a new one.
+- Use `--cwd` to reuse an existing project subfolder or worktree directory. Use `--worktree` to create a new worktree.
- The prompt passed to `--worktree` is the task for the new thread running inside that worktree.
- Do NOT tell that prompt to "create a new worktree" again, or it can create recursive worktree threads.
- Ask the new session to operate on its current checkout only (e.g. "validate current worktree", "run checks in this repo").
@@ -208,15 +212,15 @@ Critical recursion guard:
- If you already are in a worktree thread, do not create another worktree unless the user explicitly asks for a nested worktree.
- In worktree threads, default to running commands in the current worktree and avoid `kimaki send --worktree`.
-### Sending sessions to existing worktrees
+### Sending sessions to existing directories
-Use `--cwd` to start a session in an existing git worktree directory instead of creating a new one:
+Use `--cwd` to start a session in an existing project subfolder or git worktree directory instead of the project root:
```bash
-kimaki send --channel 1493345787894038649 --prompt 'Continue work on feature X' --cwd /path/to/existing-worktree --agent --user ''
+kimaki send --channel 1493345787894038649 --prompt 'Run restricted task X' --cwd /path/to/project/restricted-task --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.
+The path must be inside the project or be a git worktree of the project (validated via `git worktree list`). The session resolves to the correct project channel but uses that path as its working directory, so subfolder `opencode.json` config can apply. Passing the project root itself is allowed and behaves like the default. Use `--worktree` to create a new worktree, `--cwd` to reuse an existing directory.
**Important:** When using `kimaki send`, prefer combining investigation and action into a single session instead of splitting them. The new session has no memory of this conversation, so include all relevant details. Use **bold**, `code`, lists, and > quotes for readability.
@@ -292,9 +296,12 @@ kimaki send --channel --prompt 'Plan how to update the API client t
# Or use --project to resolve from directory
kimaki send --project /path/to/other-repo --prompt 'Plan how to bump version to 1.2.0' --agent
+
+# Or use --cwd for an existing checkout/worktree path
+kimaki send --cwd /path/to/other-repo-worktree --prompt 'Plan how to update this checkout' --agent
```
-When sending prompts to other projects, always ask the agent to plan first, never build upfront. The prompt should start with "Plan how to ..." so the user can review before greenlighting implementation.
+When the user explicitly asks to send prompts to other projects, target the project/channel/path they named instead of the current channel. Ask the agent to plan first, never build upfront. The prompt should start with "Plan how to ..." so the user can review before greenlighting implementation.
Use cases:
- **Updating a fork or dependency** the user maintains locally
@@ -305,8 +312,9 @@ Use cases:
Use `--wait` to block until a session completes and print its full conversation to stdout. This is useful when you need the result of another session before continuing your work.
-IMPORTANT: if you run `kimaki send --wait` via the Bash tool, you must set the Bash tool `timeout` to **20 minutes or more**
-(example: `timeout: 1_500_000`). Otherwise the tool will terminate early (default is 2 minutes) and you won't see long sessions.
+When the user asks you to wait for an existing session, run `kimaki session wait ` yourself via Bash, then continue from the printed session markdown. Do not tell the user to run the command.
+
+IMPORTANT: if you run `kimaki send --wait` or `kimaki session wait ` via the Bash tool, you must set the Bash tool `timeout` to **20 minutes or more** (example: `timeout: 1_500_000`). Otherwise the tool will terminate early (default is 2 minutes) and you won't see long sessions.
If your Bash tool timeout triggers anyway, fall back to reading the session output from disk:
@@ -318,6 +326,9 @@ kimaki send --channel --prompt 'Fix the auth bug' --wait --agent --prompt 'Run the tests' --wait --agent
+
+# Wait for a session that was already started elsewhere
+kimaki session wait
```
The command exits with the session markdown on stdout once the model finishes responding.
@@ -355,6 +366,8 @@ bunx critique main...new-branch --web "Describe branch changes"
# Share a single commit
bunx critique --commit HEAD --web "Describe latest commit"
+If the user asks to see a diff and you already committed the changes, prefer showing a separate diff URL for each commit instead of one unified diff. Run one `bunx critique --commit --web` per commit so each change is clearly scoped. Run all the critique calls in parallel tool calls.
+
If there are other unrelated changes in the working directory, filter to only show the files you edited:
# Share only specific files
diff --git a/tests/effective-prompt/run.mjs b/tests/effective-prompt/run.mjs
index 61349d1..c043480 100644
--- a/tests/effective-prompt/run.mjs
+++ b/tests/effective-prompt/run.mjs
@@ -25,10 +25,10 @@
// (the real dm-context-filter from kimaki/plugins/).
// - baseline: name of a baseline filter to compare against. Default:
// "broken-stripsection" (the regex-only stripSection that misfires
-// on fenced bash comments). The harness asserts the baseline
-// strips strictly LESS than the current filter.
-// - triggers: array of { name, pattern }. Default: worktree, --cwd,
-// --agent, and Kimaki project/channel routing language.
+// on fenced bash comments). The harness keeps baseline output available
+// as diff evidence, but leak detection is the correctness gate.
+// - triggers: array of { name, pattern }. Default: --agent override
+// examples that bypass the Data Machine-bound agent slot.
// Lines matching any trigger in the filtered output count as leaks.
// - allowLeakInSection: array of section headings where trigger
// matches are intentional (e.g. the appended Minion Routing note
@@ -74,21 +74,12 @@ const VERBOSE = args.includes("--verbose")
// ---------------------------------------------------------------------------
const DEFAULT_TRIGGERS = [
- { name: "worktree", pattern: "(?i)worktree" },
- { name: "--cwd", pattern: "--cwd" },
{ name: "--agent", pattern: "--agent" },
- { name: "kimaki project", pattern: "kimaki project" },
- { name: "--project", pattern: "--project" },
- { name: "project channel", pattern: "(?i)project channel" },
- { name: "cross-project", pattern: "(?i)cross-project" },
- { name: "#project-name", pattern: "#project-name" },
- { name: "other project", pattern: "(?i)other project" },
- { name: "another project", pattern: "(?i)another project" },
]
// The filter is strip-only — it never appends sections. Any trigger word
-// (worktree, --cwd, --agent, etc.) appearing in the filtered output is a
-// real leak that needs investigation, not an intentional appendix.
+// appearing in the filtered output is a real leak that needs investigation,
+// not an intentional appendix.
const DEFAULT_ALLOW_LEAK_SECTIONS = []
const DEFAULT_SCENARIO = {
@@ -242,12 +233,6 @@ async function runScenario(name, scenario) {
if (filteredLeaks.length > 0) {
failures.push(`filtered prompt has ${filteredLeaks.length} trigger leaks (expected 0)`)
}
- if (filteredOut.length >= baselineOut.length) {
- failures.push(
- `filtered prompt (${filteredOut.length} chars) is not strictly smaller than baseline ` +
- `(${baselineOut.length} chars) — current filter is regressing on baseline`,
- )
- }
if (baselineLeaks.length === 0 && filteredLeaks.length === 0) {
// Both filters strip cleanly — nothing to assert about improvement
// beyond size. Fine.
@@ -287,7 +272,9 @@ function printResult(r) {
console.log(` raw : ${fmt(r.raw_chars)} chars`)
console.log(` baseline : ${fmt(r.baseline_chars)} chars (stripped ${fmt(r.stripped_baseline)}, ~${Math.round(r.stripped_baseline / 4)} tokens)`)
console.log(` filtered : ${fmt(r.filtered_chars)} chars (stripped ${fmt(r.stripped_filtered)}, ~${Math.round(r.stripped_filtered / 4)} tokens)`)
- console.log(` delta : current strips ${fmt(r.stripped_filtered - r.stripped_baseline)} more chars than baseline (+${Math.round((r.stripped_filtered - r.stripped_baseline) / 4)} tokens)`)
+ const delta = r.stripped_filtered - r.stripped_baseline
+ const deltaDirection = delta >= 0 ? "more" : "fewer"
+ console.log(` delta : current strips ${fmt(Math.abs(delta))} ${deltaDirection} chars than baseline (~${Math.abs(Math.round(delta / 4))} tokens)`)
console.log(` baseline leaks: ${r.baseline_leaks.length}`)
console.log(` filtered leaks: ${r.filtered_leaks.length}`)