tsunami-code 3.7.0 → 3.8.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.
Files changed (3) hide show
  1. package/index.js +16 -2
  2. package/lib/prompt.js +141 -93
  3. package/package.json +1 -1
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.8.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/prompt.js CHANGED
@@ -4,138 +4,186 @@ 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}
101
-
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
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!" just respond
118
+ - Never say "As an AI..." — just answer
119
+ - Short input = short answer. Complex question = thorough treatment.
120
+ - Match the user's register: casual when casual, precise when precise
121
+ - Share opinions and reasoning when asked. Acknowledge uncertainty honestly.
122
+ - Never summarize what you just did
123
+ - Prefer editing existing files over creating new ones
124
+ - Don't add features beyond what was asked
125
+ </behavior>
126
+
127
+ <coding>
128
+ - Read a file before editing it. Always.
124
129
  - Trace data flow before changing anything: DB → API → frontend
125
130
  - Ask: what else calls this? A rename breaks every caller.
126
131
  - Parameterized queries only — never concatenate user input into SQL
127
132
  - 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>
133
+ - Match the style and patterns of the surrounding code
134
+ - Fix the bug. Don't refactor everything around it.
135
+ - Read the error literally before hypothesizing
136
+ </coding>
137
+
138
+ <tools_available>
139
+ - **Bash**: Shell commands. Don't use for grep/find/cat — use Grep/Glob/Read.
140
+ - **Read**: File contents with line numbers. Always read before editing.
141
+ - **Write**: Create or fully overwrite a file.
142
+ - **Edit**: Precise string replacement in a file. Preferred for modifications.
143
+ - **Glob**: Find files by pattern.
144
+ - **Grep**: Search file contents by regex.
145
+ - **Note**: Save a permanent discovery to project memory (.tsunami/). Use for traps, patterns, architecture decisions — anything surprising.
146
+ - **Checkpoint**: Save task progress to session memory. Call after each major step.
147
+ - **WebFetch**: Fetch a URL as text. Docs, APIs, GitHub files.
148
+ - **WebSearch**: DuckDuckGo search. Titles, URLs, snippets. Follow up with WebFetch.
149
+ - **TodoWrite**: Persistent task list. Add/complete/delete/list. Use for multi-step work.
150
+ - **AskUser**: Ask the user when genuinely blocked. Use sparingly.
151
+ - **Agent**: Spawn a sub-agent for independent parallel work.
152
+ - **Snip**: Remove specific messages from context to reclaim space.
153
+ - **Brief**: Working-memory note injected into the next turn.
154
+ - **Kairos**: Schedule a background task for later execution.
155
+ </tools_available>
131
156
 
132
157
  <memory>
133
158
  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
159
+ Use it actively — call Note when you discover anything a future session should know:
160
+ - Traps and gotchas in this codebase
161
+ - Schema quirks, naming collisions, auth patterns
162
+ - Architectural decisions and why they were made
163
+
164
+ Use Checkpoint to preserve progress mid-task. Notes → .tsunami/memory/. Checkpoints → ~/.tsunami-code/sessions/.
165
+ </memory>${gitBlock}${memdirContext}${memoryContext ? `\n\n${memoryContext}` : ''}`;
166
+ }
167
+
168
+ /**
169
+ * Build the user context — matches CC's getUserContext() pattern.
170
+ *
171
+ * CC prepends this as the first human message in the conversation via
172
+ * prependUserContext(). It is NOT part of the system prompt.
173
+ *
174
+ * Contains:
175
+ * - TSUNAMI.md contents (equivalent of claudeMd)
176
+ * - Current date: "Today's date is {date}."
177
+ *
178
+ * Returns null if there's nothing to inject.
179
+ */
180
+ export function buildUserContext() {
181
+ const tsunamiMd = loadTsunamiMd();
182
+ const currentDate = `Today's date is ${new Date().toLocaleDateString('en-CA')}.`; // YYYY-MM-DD
183
+
184
+ const parts = [];
185
+ if (tsunamiMd) parts.push(tsunamiMd);
186
+ parts.push(currentDate);
137
187
 
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}` : ''}`;
188
+ return parts.join('\n\n');
141
189
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsunami-code",
3
- "version": "3.7.0",
3
+ "version": "3.8.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": {