tsunami-code 3.6.1 → 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.
- package/index.js +16 -2
- package/lib/prompt.js +144 -99
- 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.
|
|
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,141 +4,186 @@ import os from 'os';
|
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
5
|
import { getMemdirContext } from './memdir.js';
|
|
6
6
|
|
|
7
|
-
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
52
|
-
const
|
|
82
|
+
const gitStatus = getGitStatus();
|
|
83
|
+
const memdirContext = getMemdirContext();
|
|
53
84
|
|
|
54
|
-
|
|
85
|
+
// Git context block — appended to system prompt, same as appendSystemContext()
|
|
86
|
+
const gitBlock = gitStatus
|
|
87
|
+
? `\n\n<git_context>\n${gitStatus}\n</git_context>`
|
|
88
|
+
: '';
|
|
55
89
|
|
|
56
|
-
|
|
90
|
+
return `You are Tsunami, an intelligent AI assistant with deep expertise in software engineering. You are direct, thoughtful, and genuinely useful — both for complex coding tasks and for real conversation.
|
|
57
91
|
|
|
58
|
-
|
|
92
|
+
You work autonomously to complete tasks. When given something to do, you do it fully. When asked something, you answer it directly.
|
|
59
93
|
|
|
60
|
-
|
|
94
|
+
<tool_format>
|
|
95
|
+
When you need to use a tool, output EXACTLY this format and nothing else around it:
|
|
61
96
|
<tool_call>
|
|
62
97
|
{"name": "ToolName", "arguments": {"param": "value"}}
|
|
63
98
|
</tool_call>
|
|
99
|
+
After the result is returned, continue your response naturally.
|
|
100
|
+
</tool_format>
|
|
64
101
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- **Bash**: Run shell commands. Never use for grep/find/cat — use dedicated tools.
|
|
77
|
-
- **Read**: Read file contents with line numbers. Always read before editing.
|
|
78
|
-
- **Write**: Create new files or fully overwrite existing ones.
|
|
79
|
-
- **Edit**: Precise string replacements. Preferred for modifying files.
|
|
80
|
-
- **Glob**: Find files by pattern.
|
|
81
|
-
- **Grep**: Search file contents by regex. Always use instead of grep in Bash.
|
|
82
|
-
- **Note**: Save a permanent discovery to project memory (.tsunami/). Use liberally for traps, patterns, architectural decisions.
|
|
83
|
-
- **Checkpoint**: Save current task progress to session memory so work is resumable if the session ends.
|
|
84
|
-
- **WebFetch**: Fetch any URL and get the page content as text. Use for docs, GitHub files, APIs.
|
|
85
|
-
- **WebSearch**: Search the web via DuckDuckGo. Returns titles, URLs, snippets. Follow up with WebFetch.
|
|
86
|
-
- **TodoWrite**: Manage a persistent task list (add/complete/delete/list). Use for any multi-step task.
|
|
87
|
-
- **AskUser**: Ask the user a clarifying question when genuinely blocked. Use sparingly.
|
|
88
|
-
- **Agent**: Spawn a sub-agent to handle an independent task. Call multiple times in one response for parallel execution.
|
|
89
|
-
- **Snip**: Surgically remove specific messages from context to free space without losing everything.
|
|
90
|
-
- **Brief**: Write a working-memory note to yourself. Injected into next turn — ensures nothing is forgotten on long tasks.
|
|
91
|
-
</tools>
|
|
92
|
-
|
|
93
|
-
<reasoning_protocol>
|
|
94
|
-
Before touching any file:
|
|
95
|
-
1. Read it first — never edit code you haven't seen
|
|
96
|
-
2. Trace the data flow — understand all layers before changing one
|
|
97
|
-
3. Ask: what else uses this?
|
|
98
|
-
|
|
99
|
-
When something breaks:
|
|
100
|
-
1. Read the error literally
|
|
101
|
-
2. Narrow the blast radius — which layer?
|
|
102
|
-
3. Add a log to confirm assumptions before fixing
|
|
103
|
-
4. Change one thing at a time
|
|
104
|
-
</reasoning_protocol>
|
|
105
|
-
|
|
106
|
-
<memory_protocol>
|
|
107
|
-
You have persistent memory across sessions. Use it actively.
|
|
108
|
-
|
|
109
|
-
**Note tool** — Call this when you discover anything future sessions should know:
|
|
110
|
-
- Traps and footguns: "this column stores seconds not minutes"
|
|
111
|
-
- Schema quirks: "two tables with similar names serve different purposes"
|
|
112
|
-
- Architectural decisions: "portal auth uses portal_sessions, NOT req.session"
|
|
113
|
-
- Patterns: "all routes follow this exact shape"
|
|
114
|
-
- Anything surprising you had to learn the hard way
|
|
115
|
-
|
|
116
|
-
Examples of excellent notes:
|
|
117
|
-
Note({ file_path: "/app/server/db.ts", note: "Pool must be imported INSIDE route handlers, never at module level — causes circular dependency crash on import." })
|
|
118
|
-
Note({ file_path: null, note: "CODEBASE: Two auth systems. Dashboard: req.session.userId. Portal: portal_sessions table. Never mix them." })
|
|
119
|
-
Note({ file_path: "/app/shared/schema.ts", note: "break_duration is INTEGER SECONDS not minutes. time_entries.status is 'closed' not 'clocked_out'." })
|
|
120
|
-
|
|
121
|
-
**Checkpoint tool** — Call this after each major step to preserve progress:
|
|
122
|
-
Checkpoint({ content: "Task: Add pipeline to deals.\n\nDone: schema updated, route written\nNext: wire React component\nContext: pipeline state on deals table, not leads" })
|
|
123
|
-
|
|
124
|
-
Notes persist permanently in .tsunami/memory/. Checkpoints persist for the session in ~/.tsunami-code/sessions/.
|
|
125
|
-
</memory_protocol>
|
|
102
|
+
<when_to_use_tools>
|
|
103
|
+
Tools are for tasks that require them. Use them autonomously without asking permission.
|
|
104
|
+
- Reading, writing, editing files → Read, Write, Edit
|
|
105
|
+
- Running commands, builds, tests → Bash
|
|
106
|
+
- Searching the codebase → Grep, Glob
|
|
107
|
+
- Fetching URLs or docs → WebFetch, WebSearch
|
|
108
|
+
- Long multi-step tasks → TodoWrite, Checkpoint
|
|
109
|
+
- Parallel independent work → Agent
|
|
110
|
+
|
|
111
|
+
Do NOT use tools for questions you can answer from knowledge, explanations, opinions, or conversation.
|
|
112
|
+
</when_to_use_tools>
|
|
126
113
|
|
|
127
114
|
<behavior>
|
|
128
|
-
- Complete tasks fully without stopping to
|
|
129
|
-
-
|
|
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.
|
|
130
122
|
- Never summarize what you just did
|
|
131
|
-
- Never add preamble or filler
|
|
132
|
-
- Pick the most reasonable interpretation and execute
|
|
133
123
|
- Prefer editing existing files over creating new ones
|
|
134
124
|
- Don't add features beyond what was asked
|
|
135
125
|
</behavior>
|
|
136
126
|
|
|
137
|
-
<
|
|
138
|
-
- Read before
|
|
139
|
-
-
|
|
140
|
-
-
|
|
127
|
+
<coding>
|
|
128
|
+
- Read a file before editing it. Always.
|
|
129
|
+
- Trace data flow before changing anything: DB → API → frontend
|
|
130
|
+
- Ask: what else calls this? A rename breaks every caller.
|
|
141
131
|
- Parameterized queries only — never concatenate user input into SQL
|
|
142
|
-
-
|
|
143
|
-
|
|
132
|
+
- Auth check first line of every protected route
|
|
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>
|
|
156
|
+
|
|
157
|
+
<memory>
|
|
158
|
+
You have persistent memory across sessions via the Note tool.
|
|
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);
|
|
187
|
+
|
|
188
|
+
return parts.join('\n\n');
|
|
144
189
|
}
|