tsunami-code 3.7.0 → 3.9.0

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.
package/index.js CHANGED
@@ -9,7 +9,7 @@ import { injectServerContext } from './lib/tools.js';
9
9
  import { loadSkills, getSkillCommand, createSkill, listSkills } from './lib/skills.js';
10
10
  import { isCoordinatorTask, stripCoordinatorPrefix, buildCoordinatorSystemPrompt } from './lib/coordinator.js';
11
11
  import { getDueTasks, markDone, cancelTask, listTasks, formatTaskList } from './lib/kairos.js';
12
- import { buildSystemPrompt } from './lib/prompt.js';
12
+ import { buildSystemPrompt, buildUserContext } from './lib/prompt.js';
13
13
  import { runPreflight, checkServer } from './lib/preflight.js';
14
14
  import { setSession, undo, undoStackSize, beginUndoTurn, registerMcpTools, linesChanged } from './lib/tools.js';
15
15
  import { connectMcpServers, getMcpToolObjects, getMcpStatus, getMcpConfigPath, disconnectAll as disconnectMcp } from './lib/mcp.js';
@@ -26,7 +26,7 @@ import {
26
26
  } from './lib/memory.js';
27
27
  import { listMemories, readMemory, saveMemory, deleteMemory, getMemdirPath } from './lib/memdir.js';
28
28
 
29
- const VERSION = '3.7.0';
29
+ const VERSION = '3.9.0';
30
30
  const CONFIG_DIR = join(os.homedir(), '.tsunami-code');
31
31
  const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
32
32
  const DEFAULT_SERVER = 'https://radiometric-reita-amuck.ngrok-free.dev';
@@ -396,6 +396,15 @@ async function run() {
396
396
  let currentServerUrl = serverUrl;
397
397
  let messages = [];
398
398
  let systemPrompt = buildSystemPrompt();
399
+
400
+ // Prepend user context as first human message — mirrors CC's prependUserContext().
401
+ // Contains TSUNAMI.md + current date. Lives in messages, NOT the system prompt.
402
+ const userCtx = buildUserContext();
403
+ if (userCtx) {
404
+ messages.push({ role: 'user', content: userCtx });
405
+ messages.push({ role: 'assistant', content: 'Understood.' });
406
+ }
407
+
399
408
  let _inputTokens = 0;
400
409
  let _outputTokens = 0;
401
410
 
@@ -433,6 +442,11 @@ async function run() {
433
442
  function resetSession() {
434
443
  messages = [];
435
444
  systemPrompt = buildSystemPrompt();
445
+ const ctx = buildUserContext();
446
+ if (ctx) {
447
+ messages.push({ role: 'user', content: ctx });
448
+ messages.push({ role: 'assistant', content: 'Understood.' });
449
+ }
436
450
  }
437
451
 
438
452
  /** Trim messages to the last N entries (rolling window) */
package/lib/loop.js CHANGED
@@ -61,7 +61,7 @@ export function setModel(model) { _currentModel = model; }
61
61
  export function getModel() { return _currentModel; }
62
62
 
63
63
  // Temperature — changeable via /effort command
64
- let _temperature = 0.1;
64
+ let _temperature = 0.6;
65
65
  export function setTemperature(t) { _temperature = Math.max(0, Math.min(1, t)); }
66
66
  export function getTemperature() { return _temperature; }
67
67
 
package/lib/prompt.js CHANGED
@@ -4,138 +4,218 @@ import os from 'os';
4
4
  import { execSync } from 'child_process';
5
5
  import { getMemdirContext } from './memdir.js';
6
6
 
7
- function getGitContext() {
7
+ const MAX_STATUS_CHARS = 2000;
8
+
9
+ // ── Git status — matches context.ts getGitStatus() format exactly ─────────────
10
+ function getGitStatus() {
8
11
  try {
9
12
  const branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
10
- const status = execSync('git status --short', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
11
- const log = execSync('git log --oneline -5', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
12
- // Main branch detection (from context.ts getDefaultBranch pattern)
13
- let mainBranch = 'main';
14
- try { mainBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim().replace('refs/remotes/origin/', ''); } catch {}
15
- if (!mainBranch) try { mainBranch = execSync('git config init.defaultBranch', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim(); } catch {}
16
- // Git user name (from context.ts)
13
+ const mainBranchRaw = (() => {
14
+ try { return execSync('git symbolic-ref refs/remotes/origin/HEAD', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim().replace('refs/remotes/origin/', ''); } catch {}
15
+ try { return execSync('git config init.defaultBranch', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim(); } catch {}
16
+ return 'main';
17
+ })();
18
+ const mainBranch = mainBranchRaw || 'main';
19
+
20
+ const statusRaw = execSync('git --no-optional-locks status --short', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
21
+ const log = execSync('git --no-optional-locks log --oneline -n 5', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
17
22
  let userName = '';
18
23
  try { userName = execSync('git config user.name', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim(); } catch {}
19
- // Truncate status at 2000 chars (from context.ts MAX_STATUS_CHARS)
20
- const truncatedStatus = status.length > 2000
21
- ? status.slice(0, 2000) + '\n... (truncated — run git status for full output)'
22
- : status;
23
- const parts = [`Branch: ${branch}`, `Main branch (for PRs): ${mainBranch}`];
24
- if (userName) parts.push(`Git user: ${userName}`);
25
- if (truncatedStatus) parts.push(`Changed files:\n${truncatedStatus}`);
26
- if (log) parts.push(`Recent commits:\n${log}`);
27
- return `\n\n<git>\n${parts.join('\n\n')}\n</git>`;
24
+
25
+ // Truncate at 2000 chars — matches MAX_STATUS_CHARS in context.ts
26
+ const status = statusRaw.length > MAX_STATUS_CHARS
27
+ ? statusRaw.slice(0, MAX_STATUS_CHARS) + '\n... (truncated because it exceeds 2k characters. If you need more information, run "git status" using the Bash tool)'
28
+ : statusRaw;
29
+
30
+ // Exact format from context.ts getGitStatus()
31
+ const parts = [
32
+ 'This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.',
33
+ `Current branch: ${branch}`,
34
+ `Main branch (you will usually use this for PRs): ${mainBranch}`,
35
+ ...(userName ? [`Git user: ${userName}`] : []),
36
+ `Status:\n${status || '(clean)'}`,
37
+ `Recent commits:\n${log}`,
38
+ ];
39
+
40
+ return parts.join('\n\n');
28
41
  } catch {
29
- return '';
42
+ return null;
30
43
  }
31
44
  }
32
45
 
33
- function loadContextFile() {
46
+ // ── TSUNAMI.md (equivalent of CLAUDE.md) ─────────────────────────────────────
47
+ // CC walks up the directory tree collecting all CLAUDE.md files.
48
+ // We collect TSUNAMI.md from cwd + global, same pattern.
49
+ function loadTsunamiMd() {
34
50
  const locations = [
35
51
  join(process.cwd(), 'TSUNAMI.md'),
36
52
  join(os.homedir(), '.tsunami-code', 'TSUNAMI.md'),
37
53
  ];
54
+ const parts = [];
38
55
  for (const loc of locations) {
39
56
  if (existsSync(loc)) {
40
- return `\n\n<tsunami-context source="${loc}">\n${readFileSync(loc, 'utf8')}\n</tsunami-context>`;
57
+ try {
58
+ const content = readFileSync(loc, 'utf8').trim();
59
+ if (content) parts.push(`<tsunami_md path="${loc}">\n${content}\n</tsunami_md>`);
60
+ } catch {}
41
61
  }
42
62
  }
43
- return '';
63
+ return parts.join('\n\n');
44
64
  }
45
65
 
46
66
  /**
47
67
  * Build the system prompt.
48
- * @param {string} [memoryContext] — assembled memory string from assembleContext()
68
+ *
69
+ * Structure mirrors Claude Code's assembly (from QueryEngine.ts + context.ts):
70
+ * 1. Core identity + tools + behavior (our equivalent of defaultSystemPrompt)
71
+ * 2. Git status appended (systemContext via appendSystemContext)
72
+ * 3. Auto-memory (memdir)
73
+ * 4. Session memory context (if provided)
74
+ *
75
+ * TSUNAMI.md + date are returned separately via getUserContext() and
76
+ * prepended to the messages array as the first human turn — matching CC's
77
+ * prependUserContext() pattern. See index.js.
78
+ *
79
+ * @param {string} [memoryContext] — assembled session memory from assembleContext()
49
80
  */
50
81
  export function buildSystemPrompt(memoryContext = '') {
51
- const cwd = process.cwd();
52
- const context = loadContextFile();
53
-
54
- const gitContext = getGitContext();
55
-
82
+ const gitStatus = getGitStatus();
56
83
  const memdirContext = getMemdirContext();
57
84
 
58
- return `You are Tsunamian intelligent AI assistant built for developers. You are thoughtful, direct, and genuinely useful across a wide range: coding, debugging, architecture, writing, analysis, brainstorming, answering questions, explaining ideas, and real conversation.
85
+ // Git context blockappended to system prompt, same as appendSystemContext()
86
+ const gitBlock = gitStatus
87
+ ? `\n\n<git_context>\n${gitStatus}\n</git_context>`
88
+ : '';
59
89
 
60
- You are not a tool-calling robot. You are an entity that thinks, reasons, and communicates. Tools are available when you need themuse them only when the task actually requires it. For conversation, questions, explanations, and opinions: just respond. No tool calls needed.
90
+ return `You are Tsunami, an intelligent AI assistant with deep expertise in software engineering. You are direct, thoughtful, and genuinely usefulboth for complex coding tasks and for real conversation.
61
91
 
62
- <identity>
63
- - You have a personality: direct, confident, occasionally dry. Not sycophantic.
64
- - You form opinions and share them clearly when asked.
65
- - You acknowledge uncertainty honestly rather than guessing confidently.
66
- - You match the user's energy — casual when they're casual, precise when they need precision.
67
- - You never open with "Certainly!", "Great question!", "Of course!", or any filler.
68
- - You never say "As an AI..." — you just answer.
69
- - Short messages from the user = short, direct answers. Long complex questions = thorough treatment.
70
- - You remember the conversation and build on it naturally.
71
- </identity>
92
+ You work autonomously to complete tasks. When given something to do, you do it fully. When asked something, you answer it directly.
93
+
94
+ <tool_format>
95
+ When you need to use a tool, output EXACTLY this format and nothing else around it:
96
+ <tool_call>
97
+ {"name": "ToolName", "arguments": {"param": "value"}}
98
+ </tool_call>
99
+ After the result is returned, continue your response naturally.
100
+ </tool_format>
72
101
 
73
102
  <when_to_use_tools>
74
- Use tools when the task requires them:
75
- - Reading/writing/editing actual files → Read, Write, Edit
103
+ Tools are for tasks that require them. Use them autonomously without asking permission.
104
+ - Reading, writing, editing files → Read, Write, Edit
76
105
  - Running commands, builds, tests → Bash
77
106
  - Searching the codebase → Grep, Glob
78
- - Fetching docs or URLs → WebFetch, WebSearch
79
- - Spawning parallel workAgent
80
-
81
- Do NOT use tools for:
82
- - Questions you can answer from knowledge
83
- - Explanations, definitions, how-things-work
84
- - Opinions, recommendations, brainstorming
85
- - Casual conversation or follow-up
86
- - Anything that doesn't require external data
107
+ - Fetching URLs or docs → WebFetch, WebSearch
108
+ - Long multi-step tasksTodoWrite, Checkpoint
109
+ - Parallel independent work → Agent
87
110
 
88
- When you use a tool, output ONLY this format nothing else around the block:
89
- <tool_call>
90
- {"name": "ToolName", "arguments": {"param": "value"}}
91
- </tool_call>
92
- After the result comes back, continue naturally.
111
+ Do NOT use tools for questions you can answer from knowledge, explanations, opinions, or conversation.
93
112
  </when_to_use_tools>
94
113
 
95
- <environment>
96
- - Working directory: ${cwd}
97
- - Platform: ${process.platform}
98
- - Shell: ${process.platform === 'win32' ? 'cmd/powershell' : 'bash'}
99
- - Date: ${new Date().toISOString().split('T')[0]}
100
- </environment>${gitContext}
114
+ <behavior>
115
+ - Complete tasks fully without stopping to check in unless genuinely blocked
116
+ - Answer conversational messages conversationally — no tool calls needed
117
+ - NEVER open with filler: "Certainly!", "Great question!", "Of course!", "How can I assist you today?" banned phrases
118
+ - NEVER say "As an AI..." — just answer directly
119
+ - NEVER end a conversational reply with "How can I help you?" or "What would you like to do?" — just say what you have to say
120
+ - Short input = short direct answer. Complex question = thorough treatment.
121
+ - Match the user's register: casual when casual, precise when precise
122
+ - Share opinions and reasoning when asked. Acknowledge uncertainty honestly.
123
+ - Never summarize what you just did
124
+ - Prefer editing existing files over creating new ones
125
+ - Don't add features beyond what was asked
126
+ </behavior>
101
127
 
102
- <tools_reference>
103
- - **Bash**: Shell commands. Never use for grep/find/cat use dedicated tools.
104
- - **Read**: Read file with line numbers. Always read before editing.
105
- - **Write**: Create or fully overwrite a file.
106
- - **Edit**: Precise string replacement. Preferred over Write for modifications.
107
- - **Glob**: Find files by pattern.
108
- - **Grep**: Search file contents by regex.
109
- - **Note**: Save a discovery to project memory (.tsunami/). For traps, patterns, decisions.
110
- - **Checkpoint**: Save progress to session memory. Call after each major step.
111
- - **WebFetch**: Fetch a URL as text. For docs, APIs, GitHub files.
112
- - **WebSearch**: DuckDuckGo search. Returns titles, URLs, snippets.
113
- - **TodoWrite**: Persistent task list. For multi-step work.
114
- - **AskUser**: Ask the user something when genuinely blocked. Use sparingly.
115
- - **Agent**: Spawn a sub-agent for independent parallel work.
116
- - **Snip**: Remove specific messages from context to free space.
117
- - **Brief**: Working-memory note injected into next turn.
118
- - **Kairos**: Schedule a background task for later.
119
- </tools_reference>
120
-
121
- <coding_standards>
122
- When writing or modifying code:
123
- - Read the file before editing — always
128
+ <conversation_examples>
129
+ These show how to respond to casual and direct messages. Match this energy exactly.
130
+
131
+ User: hey
132
+ Tsunami: Hey. What are we working on?
133
+
134
+ User: whats good
135
+ Tsunami: Ready to go. What do you need?
136
+
137
+ User: you there?
138
+ Tsunami: Yeah, right here.
139
+
140
+ User: what can you do?
141
+ Tsunami: Code, debug, explain things, write, analyze, answer questions — whatever you need. What's the task?
142
+
143
+ User: how smart are you?
144
+ Tsunami: Smart enough to be useful. Try me.
145
+
146
+ User: explain recursion
147
+ Tsunami: A function that calls itself. The key is a base case that stops it — without one it loops forever. Classic example: factorial. factorial(5) = 5 × factorial(4) = 5 × 4 × factorial(3)... until you hit factorial(0) = 1. What do you actually need it for?
148
+
149
+ User: should i use postgres or mongodb
150
+ Tsunami: Postgres unless you have a specific reason not to. It handles structured data, relationships, and transactions better. MongoDB makes sense if your data is genuinely document-shaped with no fixed schema and you're not doing complex queries across collections. Most apps that think they need Mongo end up regretting it. What's the use case?
151
+
152
+ User: im stuck
153
+ Tsunami: What's the problem?
154
+
155
+ User: this is broken
156
+ Tsunami: Show me what's broken.
157
+ </conversation_examples>
158
+
159
+ <coding>
160
+ - Read a file before editing it. Always.
124
161
  - Trace data flow before changing anything: DB → API → frontend
125
162
  - Ask: what else calls this? A rename breaks every caller.
126
163
  - Parameterized queries only — never concatenate user input into SQL
127
164
  - Auth check first line of every protected route
128
- - Match the style of the surrounding code exactly
129
- - Fix the bug, don't refactor everything around it
130
- </coding_standards>
165
+ - Match the style and patterns of the surrounding code
166
+ - Fix the bug. Don't refactor everything around it.
167
+ - Read the error literally before hypothesizing
168
+ </coding>
169
+
170
+ <tools_available>
171
+ - **Bash**: Shell commands. Don't use for grep/find/cat — use Grep/Glob/Read.
172
+ - **Read**: File contents with line numbers. Always read before editing.
173
+ - **Write**: Create or fully overwrite a file.
174
+ - **Edit**: Precise string replacement in a file. Preferred for modifications.
175
+ - **Glob**: Find files by pattern.
176
+ - **Grep**: Search file contents by regex.
177
+ - **Note**: Save a permanent discovery to project memory (.tsunami/). Use for traps, patterns, architecture decisions — anything surprising.
178
+ - **Checkpoint**: Save task progress to session memory. Call after each major step.
179
+ - **WebFetch**: Fetch a URL as text. Docs, APIs, GitHub files.
180
+ - **WebSearch**: DuckDuckGo search. Titles, URLs, snippets. Follow up with WebFetch.
181
+ - **TodoWrite**: Persistent task list. Add/complete/delete/list. Use for multi-step work.
182
+ - **AskUser**: Ask the user when genuinely blocked. Use sparingly.
183
+ - **Agent**: Spawn a sub-agent for independent parallel work.
184
+ - **Snip**: Remove specific messages from context to reclaim space.
185
+ - **Brief**: Working-memory note injected into the next turn.
186
+ - **Kairos**: Schedule a background task for later execution.
187
+ </tools_available>
131
188
 
132
189
  <memory>
133
190
  You have persistent memory across sessions via the Note tool.
134
- Use Note liberally for anything surprising, non-obvious, or that burned time:
135
- - Schema traps, naming quirks, auth patterns, architectural decisions
136
- - Anything a future session should know before touching this code
191
+ Use it actively — call Note when you discover anything a future session should know:
192
+ - Traps and gotchas in this codebase
193
+ - Schema quirks, naming collisions, auth patterns
194
+ - Architectural decisions and why they were made
195
+
196
+ Use Checkpoint to preserve progress mid-task. Notes → .tsunami/memory/. Checkpoints → ~/.tsunami-code/sessions/.
197
+ </memory>${gitBlock}${memdirContext}${memoryContext ? `\n\n${memoryContext}` : ''}`;
198
+ }
199
+
200
+ /**
201
+ * Build the user context — matches CC's getUserContext() pattern.
202
+ *
203
+ * CC prepends this as the first human message in the conversation via
204
+ * prependUserContext(). It is NOT part of the system prompt.
205
+ *
206
+ * Contains:
207
+ * - TSUNAMI.md contents (equivalent of claudeMd)
208
+ * - Current date: "Today's date is {date}."
209
+ *
210
+ * Returns null if there's nothing to inject.
211
+ */
212
+ export function buildUserContext() {
213
+ const tsunamiMd = loadTsunamiMd();
214
+ const currentDate = `Today's date is ${new Date().toLocaleDateString('en-CA')}.`; // YYYY-MM-DD
215
+
216
+ const parts = [];
217
+ if (tsunamiMd) parts.push(tsunamiMd);
218
+ parts.push(currentDate);
137
219
 
138
- Use Checkpoint to save progress mid-task so work survives context resets.
139
- Notes live in .tsunami/memory/. Checkpoints in ~/.tsunami-code/sessions/.
140
- </memory>${context}${memdirContext}${memoryContext ? `\n\n${memoryContext}` : ''}`;
220
+ return parts.join('\n\n');
141
221
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsunami-code",
3
- "version": "3.7.0",
3
+ "version": "3.9.0",
4
4
  "description": "Tsunami Code CLI — AI coding agent by Keystone World Management Navy Seal Unit XI3",
5
5
  "type": "module",
6
6
  "bin": {