kimaki 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,332 @@
1
+ ---
2
+ name: parallel-security-review
3
+ repo: https://github.com/remorses/kimaki
4
+ description: >
5
+ DeepSec-inspired security review workflow for branch or PR changes. Use when
6
+ the user asks for a thorough, high-signal security review that should derive
7
+ repository context, split candidate discovery across focused agents, and run
8
+ per-finding false-positive validation before reporting.
9
+ allowed-tools:
10
+ - Bash(git status:*)
11
+ - Bash(git diff:*)
12
+ - Bash(git log:*)
13
+ - Bash(git show:*)
14
+ - Read
15
+ - Glob
16
+ - Grep
17
+ - Task
18
+ ---
19
+
20
+ # Parallel security review
21
+
22
+ Use this workflow for **high-confidence security reviews** of branch or PR
23
+ changes. It borrows the useful parts of DeepSec's prompt architecture: small
24
+ core instructions, repo context, tech-specific reminders, finding-specific
25
+ false-positive rules, concrete target files, strict output, and independent
26
+ validation.
27
+
28
+ ## Goal
29
+
30
+ Report only **real vulnerabilities introduced by the change under review**.
31
+ Do not report hardening gaps, existing issues, test-only problems, style, or
32
+ generic best-practice concerns.
33
+
34
+ Keep the final report short. If there are no high-confidence findings, say so.
35
+
36
+ ## Review architecture
37
+
38
+ Split the work into discovery and validation:
39
+
40
+ ```text
41
+ main reviewer
42
+
43
+ ├─► context scout
44
+ │ auth model, trust boundaries, safe patterns, tech stack
45
+
46
+ ├─► candidate discovery agents
47
+ │ focused by vulnerability family, not by random files
48
+
49
+ ├─► one validator per candidate
50
+ │ tries to disprove exploitability and assign confidence
51
+
52
+ └─► final report
53
+ only validated findings with confidence >= 8/10
54
+ ```
55
+
56
+ Discovery agents can be broad. Validator agents must be skeptical.
57
+
58
+ ## Step 1: collect branch context
59
+
60
+ Use git to understand the review scope. Prefer the repository's base branch if
61
+ known; otherwise use the merge base with the upstream/default branch when
62
+ available.
63
+
64
+ Read:
65
+
66
+ - `git status -s -u`
67
+ - changed file names
68
+ - commit messages on the branch
69
+ - complete diff for the review range
70
+
71
+ Do not modify files. Do not commit.
72
+
73
+ ## Step 2: derive a compact repo security context
74
+
75
+ Launch a **context scout** subagent before vulnerability discovery. Ask it to
76
+ read relevant unchanged files too, not just the diff.
77
+
78
+ The context scout returns at most 40 lines:
79
+
80
+ ```md
81
+ ## Security context
82
+
83
+ **Auth primitives**
84
+ - `requireUser()` protects server routes
85
+ - `canManageTeam()` gates team admin actions
86
+
87
+ **Trust boundaries**
88
+ - HTTP route params and request bodies are attacker-controlled
89
+ - CLI flags and environment variables are trusted for this review
90
+
91
+ **Sensitive sinks**
92
+ - database writes through `db.*`
93
+ - filesystem writes under workspace root
94
+ - subprocess execution through `execAsync()`
95
+
96
+ **Known safe patterns**
97
+ - Prisma object filters are parameterized
98
+ - React text interpolation is escaped by default
99
+
100
+ **Tech notes**
101
+ - Next.js Server Actions are public POST endpoints unless guarded server-side
102
+ ```
103
+
104
+ Use this context in every later subagent prompt.
105
+
106
+ ## Step 3: inject tech-specific reminders
107
+
108
+ Add only the reminders that match the repository. Keep them short.
109
+
110
+ **React / Angular**
111
+ - Do not report XSS for normal text interpolation.
112
+ - Only investigate raw HTML sinks like `dangerouslySetInnerHTML`,
113
+ `bypassSecurityTrustHtml`, DOM writes, or template compilation.
114
+
115
+ **Next.js / React Server Components**
116
+ - Server Actions are public POST endpoints. Check backend auth inside the
117
+ action, not just UI visibility.
118
+ - Middleware is useful but not sufficient if routes can be reached through
119
+ alternate paths or internal handlers.
120
+ - `params`, `searchParams`, headers, cookies, and request bodies are untrusted.
121
+
122
+ **Express / Node HTTP servers**
123
+ - Middleware order matters. Verify auth/validation runs before handlers.
124
+ - For command execution, prove untrusted input reaches a shell or argv sink.
125
+ - For path traversal, require a containment check like resolved path starts
126
+ with the intended root.
127
+
128
+ **Prisma / SQL ORMs**
129
+ - Prisma object filters are safe from SQL injection by default.
130
+ - Investigate raw SQL APIs like `$queryRawUnsafe`, string-built SQL, or manual
131
+ driver calls.
132
+
133
+ **Cloudflare Workers / edge apps**
134
+ - Request headers, cookies, URL, and body are untrusted.
135
+ - Bindings and environment variables are trusted.
136
+ - Verify tenant/account identifiers are checked server-side before data access.
137
+
138
+ **Rust / Go / memory-safe services**
139
+ - Do not report memory safety issues unless unsafe/native code is directly in
140
+ scope and the exploit path is concrete.
141
+ - Focus on auth, deserialization, filesystem, command execution, and SSRF.
142
+
143
+ ## Step 4: candidate discovery agents
144
+
145
+ Launch focused agents in parallel when the diff is non-trivial. Use fewer
146
+ agents for tiny diffs.
147
+
148
+ Good split:
149
+
150
+ 1. **Auth and authorization**
151
+ - auth bypass
152
+ - privilege escalation
153
+ - cross-tenant access
154
+ - missing server-side checks
155
+
156
+ 2. **Injection and code execution**
157
+ - SQL/NoSQL/template injection
158
+ - command execution
159
+ - unsafe deserialization
160
+ - XSS through raw HTML sinks
161
+
162
+ 3. **Filesystem, network, and data exposure**
163
+ - path traversal
164
+ - SSRF with attacker-controlled host/protocol
165
+ - sensitive data leakage
166
+ - unsafe file upload/download paths
167
+
168
+ 4. **Business logic and state transitions**
169
+ - payment/subscription bypass
170
+ - permission state confusion
171
+ - irreversible actions missing authorization
172
+ - trust boundary changes introduced by the PR
173
+
174
+ Each discovery agent must output **candidates**, not final findings:
175
+
176
+ ```md
177
+ ## Candidate
178
+
179
+ - File: `path/to/file.ts:42`
180
+ - Category: `auth_bypass`
181
+ - Changed code: what changed
182
+ - Input source: where attacker-controlled data enters
183
+ - Sensitive sink or boundary: what is reached
184
+ - Possible exploit path: concrete steps if real
185
+ - Why it might be false positive: known guard, framework protection, trusted input, etc.
186
+ ```
187
+
188
+ Discard candidates that do not name both an **attacker-controlled source** and a
189
+ **security-sensitive sink or privilege boundary**.
190
+
191
+ ## Step 5: finding-specific false-positive rules
192
+
193
+ Before validation, attach the relevant rules to each candidate.
194
+
195
+ **SSRF**
196
+ - Only report if attacker controls host, protocol, or can route to internal
197
+ services.
198
+ - Do not report path-only control as SSRF.
199
+
200
+ **SQL injection**
201
+ - Only report if untrusted input reaches string-built SQL or explicitly unsafe
202
+ raw SQL APIs.
203
+ - Do not report parameterized queries or normal ORM object filters.
204
+
205
+ **Command injection**
206
+ - Report shell injection when untrusted input reaches a shell string.
207
+ - For argv-array execution, prove the called program interprets the argument as
208
+ code, flags, paths with dangerous semantics, or another command.
209
+ - Shell scripts are usually trusted tooling. Only report if there is a concrete
210
+ untrusted input path.
211
+
212
+ **Path traversal**
213
+ - Report only when untrusted path segments can escape the intended root and
214
+ read/write/delete sensitive files.
215
+ - A correct `resolve()` plus `startsWith(root)` or equivalent containment check
216
+ is usually enough to reject the finding.
217
+
218
+ **XSS**
219
+ - In React/Angular, ignore normal interpolation.
220
+ - Look for raw HTML sinks, DOM APIs, markdown-to-HTML without sanitization, or
221
+ user-controlled script/style URLs.
222
+
223
+ **Auth bypass**
224
+ - Client-side missing checks are not vulnerabilities by themselves.
225
+ - Prove the backend route/action/job lacks the required authorization or trusts
226
+ client-provided roles, tenant IDs, user IDs, or ownership claims.
227
+
228
+ **Data exposure**
229
+ - Logging URLs or non-PII data is not enough.
230
+ - Report plaintext secrets, passwords, tokens, or concrete PII exposure.
231
+
232
+ **AI prompt injection**
233
+ - User-controlled content in an AI prompt is not automatically a vulnerability.
234
+ - Report only if the prompt output controls a privileged tool, data access,
235
+ command execution, or authorization decision without a guard.
236
+
237
+ ## Step 6: one validator per candidate
238
+
239
+ Launch one validator subagent per candidate, in parallel. The validator's job is
240
+ to **disprove** the candidate.
241
+
242
+ Validator prompt shape:
243
+
244
+ ````md
245
+ You are validating one security-review candidate. Try to prove this is a false
246
+ positive before accepting it.
247
+
248
+ Repository security context:
249
+ <paste compact context>
250
+
251
+ Candidate:
252
+ <paste one candidate>
253
+
254
+ Rules:
255
+ - Only consider vulnerabilities introduced by the reviewed diff.
256
+ - Require a concrete attacker-controlled source.
257
+ - Require a concrete sensitive sink or authorization boundary.
258
+ - Apply the finding-specific false-positive rules.
259
+ - Ignore tests, docs, generated files, hardening gaps, DoS, rate limiting,
260
+ dependency age, regex injection, regex DoS, and log spoofing.
261
+
262
+ Return exactly:
263
+
264
+ ```json
265
+ {
266
+ "verdict": "true_positive",
267
+ "confidence": 8,
268
+ "reasoning": "short explanation",
269
+ "exploitPath": "concrete exploit steps",
270
+ "fix": "specific fix"
271
+ }
272
+ ```
273
+
274
+ Use `"verdict": "false_positive"` or `"verdict": "uncertain"` when the exploit
275
+ path is not concrete enough. Set `exploitPath` and `fix` to `null` in those
276
+ cases.
277
+ ````
278
+
279
+ Only keep candidates with `verdict: "true_positive"` and confidence `>= 8`.
280
+
281
+ ## Step 7: final report format
282
+
283
+ If there are no findings:
284
+
285
+ ```md
286
+ # Security review
287
+
288
+ No high-confidence security findings in the reviewed changes.
289
+ ```
290
+
291
+ If there are findings:
292
+
293
+ ```md
294
+ # Security review
295
+
296
+ ## Finding 1: <category> in `<file>:<line>`
297
+
298
+ - **Severity:** High | Medium
299
+ - **Confidence:** 8/10
300
+ - **Category:** `auth_bypass`
301
+ - **Introduced by:** short description of the changed code
302
+ - **Exploit path:** concrete attacker steps
303
+ - **Impact:** what the attacker gains
304
+ - **Recommendation:** specific fix
305
+ ```
306
+
307
+ Never include rejected candidates in the final report unless the user asks for
308
+ review notes or methodology.
309
+
310
+ ## Severity rules
311
+
312
+ - **High:** direct path to authentication bypass, privilege escalation, RCE,
313
+ sensitive data breach, cross-tenant access, or account takeover.
314
+ - **Medium:** concrete exploit path with meaningful impact but extra
315
+ preconditions or limited blast radius.
316
+ - **Low:** do not report by default. Mention only if the user explicitly asks
317
+ for defense-in-depth findings.
318
+
319
+ ## Hard exclusions
320
+
321
+ Never report these as findings:
322
+
323
+ - Denial of service, resource exhaustion, memory/CPU/file descriptor leaks
324
+ - Rate limiting gaps
325
+ - Dependency age or vulnerable dependency advisories
326
+ - Documentation-only issues
327
+ - Test-only issues
328
+ - Log spoofing
329
+ - Regex injection or regex DoS
330
+ - Generic lack of hardening, audit logs, or best practices
331
+ - Environment variable or CLI flag attacks, unless the repo explicitly treats
332
+ them as untrusted input
@@ -119,7 +119,7 @@ cli
119
119
 
120
120
  cli
121
121
  .command('tunnel', 'Expose a local port via tunnel')
122
- .option('-p, --port <port>', 'Local port to expose (required)')
122
+ .option('-p, --port <port>', 'Local port to expose (optional when command output reveals one)')
123
123
  .option(
124
124
  '-t, --tunnel-id [id]',
125
125
  'Custom tunnel ID (only for services safe to expose publicly; prefer random default)',
@@ -128,25 +128,24 @@ cli
128
128
  .option('-s, --server [url]', 'Tunnel server URL')
129
129
  .option('-k, --kill', 'Kill any existing process on the port before starting')
130
130
  .action(async (options) => {
131
- const { runTunnel, parseCommandFromArgv, CLI_NAME } = await import(
131
+ const { runTunnel, parseCommandFromArgv } = await import(
132
132
  'traforo/run-tunnel'
133
133
  )
134
+ const { command } = parseCommandFromArgv(process.argv)
134
135
 
135
- if (!options.port) {
136
- cliLogger.error('Error: --port is required')
137
- cliLogger.error(`\nUsage: kimaki tunnel -p <port> [-- command]`)
136
+ if (!options.port && command.length === 0) {
137
+ cliLogger.error('Error: --port is required unless a command is provided after --')
138
+ cliLogger.error(`\nUsage: kimaki tunnel [-- command]`)
139
+ cliLogger.error(` or: kimaki tunnel --port <port>`)
138
140
  process.exit(EXIT_NO_RESTART)
139
141
  }
140
142
 
141
- const port = parseInt(options.port, 10)
142
- if (isNaN(port) || port < 1 || port > 65535) {
143
+ const port = options.port ? parseInt(options.port, 10) : undefined
144
+ if (options.port && (!port || port < 1 || port > 65535)) {
143
145
  cliLogger.error(`Error: Invalid port number: ${options.port}`)
144
146
  process.exit(EXIT_NO_RESTART)
145
147
  }
146
148
 
147
- // Parse command after -- from argv
148
- const { command } = parseCommandFromArgv(process.argv)
149
-
150
149
  await runTunnel({
151
150
  port,
152
151
  tunnelId: options.tunnelId || undefined,
@@ -20,7 +20,7 @@ import {
20
20
  stopOpencodeServer,
21
21
  } from './opencode.js'
22
22
  import { formatAutoWorktreeName, createWorktreeInBackground, worktreeCreatingMessage } from './commands/new-worktree.js'
23
- import { validateWorktreeDirectory, git } from './worktrees.js'
23
+ import { validateWorktreeDirectory, git, isGitRepositoryRoot } from './worktrees.js'
24
24
  import { WORKTREE_PREFIX } from './commands/merge-worktree.js'
25
25
  import {
26
26
  escapeBackticksInCodeBlocks,
@@ -857,9 +857,21 @@ export async function startDiscordBot({
857
857
  .replace(/\s+/g, ' ')
858
858
  .trim() || 'kimaki thread'
859
859
 
860
- // Check if worktrees should be enabled (CLI flag OR channel setting)
861
- const shouldUseWorktrees =
860
+ // Check if worktrees should be enabled (CLI flag OR channel setting).
861
+ // Only create worktrees from the configured project directory when that
862
+ // directory is itself the git root. If the user registered a non-git
863
+ // workspace folder under a larger repo, git would create the worktree
864
+ // from the parent repo and strand follow-up messages on failure.
865
+ const wantsWorktrees =
862
866
  useWorktrees || (await getChannelWorktreesEnabled(channel.id))
867
+ const shouldUseWorktrees =
868
+ wantsWorktrees && (await isGitRepositoryRoot(projectDirectory))
869
+
870
+ if (wantsWorktrees && !shouldUseWorktrees) {
871
+ discordLogger.warn(
872
+ `[WORKTREE] Skipping automatic worktree for non-git project directory: ${projectDirectory}`,
873
+ )
874
+ }
863
875
 
864
876
  // Add worktree prefix if worktrees are enabled
865
877
  const threadName = shouldUseWorktrees
@@ -1062,7 +1074,7 @@ export async function startDiscordBot({
1062
1074
  // The runtime is created immediately so follow-up messages queue
1063
1075
  // naturally; the worktree promise is awaited inside enqueueIncoming.
1064
1076
  let worktreePromise: Promise<string | Error> | undefined
1065
- if (marker.worktree) {
1077
+ if (marker.worktree && (await isGitRepositoryRoot(projectDirectory))) {
1066
1078
  discordLogger.log(`[BOT_SESSION] Creating worktree: ${marker.worktree}`)
1067
1079
 
1068
1080
  const worktreeStatusMessage = await thread
@@ -1079,6 +1091,10 @@ export async function startDiscordBot({
1079
1091
  projectDirectory,
1080
1092
  rest: discordClient.rest,
1081
1093
  })
1094
+ } else if (marker.worktree) {
1095
+ discordLogger.warn(
1096
+ `[BOT_SESSION] Skipping requested worktree for non-git project directory: ${projectDirectory}`,
1097
+ )
1082
1098
  }
1083
1099
 
1084
1100
  // --cwd: reuse an existing worktree directory. Revalidate at bot-time
@@ -136,7 +136,7 @@ Pick a random port between 3000-9000 to avoid conflicts:
136
136
 
137
137
  ${backticks}bash
138
138
  PORT=$((RANDOM % 6000 + 3000))
139
- bunx tuistory launch "PORT=$PORT kimaki tunnel -p $PORT -- bun run server.ts" -s game-dev --cwd "$PWD"
139
+ bunx tuistory launch "PORT=$PORT kimaki tunnel -- bun run server.ts" -s game-dev --cwd "$PWD"
140
140
  ${backticks}
141
141
 
142
142
  Wait a moment, then get the tunnel URL:
@@ -97,23 +97,26 @@ describe('system-message', () => {
97
97
 
98
98
  Only do this when the user explicitly asks to close or archive the thread, and only after your final message.
99
99
 
100
- ## searching discord users
100
+ ## discord user mentions
101
101
 
102
- To search for Discord users in a guild (needed for mentions like <@userId>), run:
102
+ Prefer Discord user IDs for mentions. Discord bots cannot ping by @name; use \`<@userId>\` in message text or pass the ID to \`--user\`.
103
+ The current user's ID is available in the per-turn \`<discord-user ... user-id="..." />\` metadata.
104
+
105
+ To search for Discord users in a guild as a best-effort fallback, run:
103
106
 
104
107
  kimaki user list --guild guild_123 --query "username"
105
108
 
106
- This returns user IDs you can use for Discord mentions.
109
+ 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.
107
110
 
108
111
  ## starting new sessions from CLI
109
112
 
110
113
  To start a new thread/session in this channel pro-grammatically, run:
111
114
 
112
- kimaki send --channel chan_123 --prompt 'your prompt here' --agent <current_agent> --user 'Tommy'
115
+ kimaki send --channel chan_123 --prompt 'your prompt here' --agent <current_agent> --user '<discord-user-id>'
113
116
 
114
117
  You can use this to "spawn" parallel helper sessions like teammates: start new threads with focused prompts, then come back and collect the results.
115
118
  Prefer passing the current agent with \`--agent <current_agent>\` so spawned or scheduled sessions keep the same agent unless you are intentionally switching. Replace \`<current_agent>\` with the value from the per-turn \`Current agent\` reminder.
116
- 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.
119
+ 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 '<discord-user-id>'\` over \`--user 'name'\` because name lookup depends on optional Server Members Intent.
117
120
 
118
121
  IMPORTANT: NEVER use \`--worktree\` unless the user explicitly asks for a worktree. Default to creating normal threads without worktrees.
119
122
 
@@ -131,19 +134,19 @@ describe('system-message', () => {
131
134
 
132
135
  Use --notify-only to create a notification thread without starting an AI session:
133
136
 
134
- kimaki send --channel chan_123 --prompt 'User cancelled subscription' --notify-only --agent <current_agent> --user 'Tommy'
137
+ kimaki send --channel chan_123 --prompt 'User cancelled subscription' --notify-only --agent <current_agent> --user '<discord-user-id>'
135
138
 
136
- Use --user to add a specific Discord user to the new thread:
139
+ Use --user with a Discord user ID or raw mention to add a specific Discord user to the new thread:
137
140
 
138
- kimaki send --channel chan_123 --prompt 'Review the latest CI failure' --agent <current_agent> --user 'Tommy'
141
+ kimaki send --channel chan_123 --prompt 'Review the latest CI failure' --agent <current_agent> --user '<discord-user-id>'
139
142
 
140
143
  Use --worktree to create a git worktree for the session (ONLY when the user explicitly asks for a worktree):
141
144
 
142
- kimaki send --channel chan_123 --prompt 'Add dark mode support' --worktree dark-mode --agent <current_agent> --user 'Tommy'
145
+ kimaki send --channel chan_123 --prompt 'Add dark mode support' --worktree dark-mode --agent <current_agent> --user '<discord-user-id>'
143
146
 
144
147
  Use --cwd to start a session in an existing git worktree directory (must be a worktree of the project):
145
148
 
146
- kimaki send --channel chan_123 --prompt 'Continue work on feature' --cwd /path/to/existing-worktree --agent <current_agent> --user 'Tommy'
149
+ kimaki send --channel chan_123 --prompt 'Continue work on feature' --cwd /path/to/existing-worktree --agent <current_agent> --user '<discord-user-id>'
147
150
 
148
151
  Important:
149
152
  - NEVER use \`--worktree\` unless the user explicitly requests a worktree. Most tasks should use normal threads without worktrees.
@@ -154,7 +157,7 @@ describe('system-message', () => {
154
157
 
155
158
  Use --agent to specify which agent to use for the session:
156
159
 
157
- kimaki send --channel chan_123 --prompt 'Plan the refactor of the auth module' --agent plan --user 'Tommy'
160
+ kimaki send --channel chan_123 --prompt 'Plan the refactor of the auth module' --agent plan --user '<discord-user-id>'
158
161
 
159
162
 
160
163
  Available agents:
@@ -166,7 +169,7 @@ describe('system-message', () => {
166
169
  You can trigger registered opencode commands (slash commands, skills, MCP prompts) by starting the \`--prompt\` with \`/commandname\`:
167
170
 
168
171
  kimaki send --thread <thread_id> --prompt '/review fix the auth module' --agent <current_agent>
169
- kimaki send --channel chan_123 --prompt '/build-cmd update dependencies' --agent <current_agent> --user 'Tommy'
172
+ kimaki send --channel chan_123 --prompt '/build-cmd update dependencies' --agent <current_agent> --user '<discord-user-id>'
170
173
 
171
174
  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\`).
172
175
 
@@ -182,8 +185,8 @@ describe('system-message', () => {
182
185
 
183
186
  Use \`--send-at\` to schedule a one-time or recurring task:
184
187
 
185
- kimaki send --channel chan_123 --prompt 'Reminder: review open PRs' --send-at '2026-03-01T09:00:00Z' --agent <current_agent> --user 'Tommy'
186
- kimaki send --channel chan_123 --prompt 'Run weekly test suite and summarize failures' --send-at '0 9 * * 1' --agent <current_agent> --user 'Tommy'
188
+ kimaki send --channel chan_123 --prompt 'Reminder: review open PRs' --send-at '2026-03-01T09:00:00Z' --agent <current_agent> --user '<discord-user-id>'
189
+ kimaki send --channel chan_123 --prompt 'Run weekly test suite and summarize failures' --send-at '0 9 * * 1' --agent <current_agent> --user '<discord-user-id>'
187
190
 
188
191
  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).
189
192
  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.
@@ -241,7 +244,7 @@ describe('system-message', () => {
241
244
  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:
242
245
 
243
246
  \`\`\`bash
244
- kimaki send --channel chan_123 --prompt 'your task description' --worktree worktree-name --agent <current_agent> --user 'Tommy'
247
+ kimaki send --channel chan_123 --prompt 'your task description' --worktree worktree-name --agent <current_agent> --user '<discord-user-id>'
245
248
  \`\`\`
246
249
 
247
250
  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.
@@ -257,7 +260,7 @@ describe('system-message', () => {
257
260
  Use \`--cwd\` to start a session in an existing git worktree directory instead of creating a new one:
258
261
 
259
262
  \`\`\`bash
260
- kimaki send --channel chan_123 --prompt 'Continue work on feature X' --cwd /path/to/existing-worktree --agent <current_agent> --user 'Tommy'
263
+ kimaki send --channel chan_123 --prompt 'Continue work on feature X' --cwd /path/to/existing-worktree --agent <current_agent> --user '<discord-user-id>'
261
264
  \`\`\`
262
265
 
263
266
  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.
@@ -271,7 +274,7 @@ describe('system-message', () => {
271
274
  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:
272
275
 
273
276
  \`\`\`bash
274
- kimaki send --channel chan_123 --prompt 'Continuing from previous session: <summary of current task and state>' --agent <current_agent> --user 'Tommy'
277
+ kimaki send --channel chan_123 --prompt 'Continuing from previous session: <summary of current task and state>' --agent <current_agent> --user '<discord-user-id>'
275
278
  \`\`\`
276
279
 
277
280
  The command automatically handles long prompts (over 2000 chars) by sending them as file attachments.
@@ -504,11 +507,11 @@ describe('system-message', () => {
504
507
 
505
508
  Use random tunnel IDs by default. Only pass \`-t\` when exposing a service that is safe to be publicly discoverable.
506
509
 
507
- \`kimaki tunnel\` injects \`TRAFORO_URL\` into the child process. Prefer wiring your app to that URL so OAuth callbacks, webhook URLs, and absolute links use the public tunnel instead of localhost.
510
+ \`kimaki tunnel\` injects \`TRAFORO_URL\` into the child process. Prefer wiring your app to that URL so OAuth callbacks, webhook URLs, and absolute links use the public tunnel instead of localhost. The local port is detected from the child process output, so do not pass \`-p\` when launching a dev server command unless detection fails.
508
511
 
509
512
  \`\`\`bash
510
513
  # Start the dev server in a named background session
511
- bunx tuistory launch "kimaki tunnel -p 3000 -- pnpm dev" -s myapp-dev
514
+ bunx tuistory launch "kimaki tunnel -- pnpm dev" -s myapp-dev
512
515
 
513
516
  # Wait until the dev server prints something useful, then inspect it
514
517
  bunx tuistory -s myapp-dev wait "/ready|local|tunnel/i" --timeout 30000
@@ -517,7 +520,7 @@ describe('system-message', () => {
517
520
 
518
521
  ### passing the public URL to your app
519
522
 
520
- If you launch the server command through \`kimaki tunnel -- ...\`, the local port is auto-detected from the child process logs in many common dev-server setups, so \`--port\` is often unnecessary.
523
+ If you launch the server command through \`kimaki tunnel -- ...\`, the local port is auto-detected from the child process logs in many common dev-server setups. Use \`--port\` only when the dev server does not print a detectable localhost URL or port line.
521
524
 
522
525
  \`\`\`bash
523
526
  # Your app can read process.env.TRAFORO_URL directly
@@ -544,13 +547,13 @@ describe('system-message', () => {
544
547
 
545
548
  \`\`\`bash
546
549
  # Next.js project
547
- bunx tuistory launch "kimaki tunnel -p 3000 -- pnpm dev" -s projectname-nextjs-dev-3000
550
+ bunx tuistory launch "kimaki tunnel -- pnpm dev" -s projectname-nextjs-dev
548
551
 
549
- # Vite project on port 5173
550
- bunx tuistory launch "kimaki tunnel -p 5173 -- pnpm dev" -s vite-dev-5173
552
+ # Vite project
553
+ bunx tuistory launch "kimaki tunnel -- pnpm dev" -s vite-dev
551
554
 
552
555
  # Custom tunnel ID (only for intentionally public-safe services)
553
- bunx tuistory launch "kimaki tunnel -p 3000 -t holocron -- pnpm dev" -s holocron-dev
556
+ bunx tuistory launch "kimaki tunnel -t holocron -- pnpm dev" -s holocron-dev
554
557
  \`\`\`
555
558
 
556
559
  ### stopping the dev server
@@ -139,11 +139,11 @@ Use a tuistory session with a descriptive name like \`projectname-dev\` so you c
139
139
 
140
140
  Use random tunnel IDs by default. Only pass \`-t\` when exposing a service that is safe to be publicly discoverable.
141
141
 
142
- \`kimaki tunnel\` injects \`TRAFORO_URL\` into the child process. Prefer wiring your app to that URL so OAuth callbacks, webhook URLs, and absolute links use the public tunnel instead of localhost.
142
+ \`kimaki tunnel\` injects \`TRAFORO_URL\` into the child process. Prefer wiring your app to that URL so OAuth callbacks, webhook URLs, and absolute links use the public tunnel instead of localhost. The local port is detected from the child process output, so do not pass \`-p\` when launching a dev server command unless detection fails.
143
143
 
144
144
  \`\`\`bash
145
145
  # Start the dev server in a named background session
146
- bunx tuistory launch "kimaki tunnel -p 3000 -- pnpm dev" -s myapp-dev
146
+ bunx tuistory launch "kimaki tunnel -- pnpm dev" -s myapp-dev
147
147
 
148
148
  # Wait until the dev server prints something useful, then inspect it
149
149
  bunx tuistory -s myapp-dev wait "/ready|local|tunnel/i" --timeout 30000
@@ -152,7 +152,7 @@ bunx tuistory read -s myapp-dev
152
152
 
153
153
  ### passing the public URL to your app
154
154
 
155
- If you launch the server command through \`kimaki tunnel -- ...\`, the local port is auto-detected from the child process logs in many common dev-server setups, so \`--port\` is often unnecessary.
155
+ If you launch the server command through \`kimaki tunnel -- ...\`, the local port is auto-detected from the child process logs in many common dev-server setups. Use \`--port\` only when the dev server does not print a detectable localhost URL or port line.
156
156
 
157
157
  \`\`\`bash
158
158
  # Your app can read process.env.TRAFORO_URL directly
@@ -179,13 +179,13 @@ bunx tuistory read -s myapp-dev
179
179
 
180
180
  \`\`\`bash
181
181
  # Next.js project
182
- bunx tuistory launch "kimaki tunnel -p 3000 -- pnpm dev" -s projectname-nextjs-dev-3000
182
+ bunx tuistory launch "kimaki tunnel -- pnpm dev" -s projectname-nextjs-dev
183
183
 
184
- # Vite project on port 5173
185
- bunx tuistory launch "kimaki tunnel -p 5173 -- pnpm dev" -s vite-dev-5173
184
+ # Vite project
185
+ bunx tuistory launch "kimaki tunnel -- pnpm dev" -s vite-dev
186
186
 
187
187
  # Custom tunnel ID (only for intentionally public-safe services)
188
- bunx tuistory launch "kimaki tunnel -p 3000 -t holocron -- pnpm dev" -s holocron-dev
188
+ bunx tuistory launch "kimaki tunnel -t holocron -- pnpm dev" -s holocron-dev
189
189
  \`\`\`
190
190
 
191
191
  ### stopping the dev server
@@ -865,6 +865,14 @@ e2eTest('voice message handling', () => {
865
865
  queueMessage: true,
866
866
  })
867
867
 
868
+ await waitForBotMessageContaining({
869
+ discord,
870
+ threadId: thread.id,
871
+ userId: TEST_USER_ID,
872
+ text: 'using deterministic-provider/deterministic-v2',
873
+ timeout: 4_000,
874
+ })
875
+
868
876
  await th.user(TEST_USER_ID).sendVoiceMessage()
869
877
 
870
878
  // 3. Transcription should appear, followed by queue notification