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.
- package/index.js +16 -2
- package/lib/prompt.js +141 -93
- 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,138 +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 context = loadContextFile();
|
|
53
|
-
|
|
54
|
-
const gitContext = getGitContext();
|
|
55
|
-
|
|
82
|
+
const gitStatus = getGitStatus();
|
|
56
83
|
const memdirContext = getMemdirContext();
|
|
57
84
|
|
|
58
|
-
|
|
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
|
+
: '';
|
|
59
89
|
|
|
60
|
-
You are
|
|
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.
|
|
61
91
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
75
|
-
- Reading
|
|
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
|
|
79
|
-
-
|
|
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 tasks → TodoWrite, Checkpoint
|
|
109
|
+
- Parallel independent work → Agent
|
|
87
110
|
|
|
88
|
-
|
|
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
|
-
<
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
-
|
|
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
|
|
129
|
-
- Fix the bug
|
|
130
|
-
|
|
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
|
|
135
|
-
-
|
|
136
|
-
-
|
|
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
|
-
|
|
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
|
}
|