winter-super-cli 2026.6.5 → 2026.6.7
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/bin/winter.js +1 -0
- package/package.json +3 -3
- package/src/agent/runtime.js +13 -16
- package/src/ai/model-capabilities.js +17 -1
- package/src/ai/prompts/system-prompt.js +33 -52
- package/src/ai/providers.js +179 -62
- package/src/ai/small-model-amplifier.js +7 -19
- package/src/cli/commands.js +162 -0
- package/src/cli/context-loader.js +1 -1
- package/src/cli/input-controller.js +55 -44
- package/src/cli/prompt-builder.js +20 -11
- package/src/cli/repl-commands.js +3 -0
- package/src/cli/repl.js +318 -444
- package/src/cli/slash-commands.js +1 -0
- package/src/cli/snowflake-logo.js +64 -86
- package/src/cli/terminal-ui.js +139 -85
- package/src/cli/tool-runtime.js +8 -3
- package/src/cli/tui.js +181 -0
- package/src/codebase-index/codegraph-adapter.js +154 -0
- package/src/codebase-index/indexer.js +1 -1
- package/src/codebase-index/search.js +31 -2
- package/src/context/router.js +4 -41
- package/src/context/token-juice.js +37 -10
- package/src/tools/executor.js +78 -3
package/src/cli/tui.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { terminalWidth, visibleWidth, wrapText } from './terminal-ui.js';
|
|
2
|
+
|
|
3
|
+
const WINTER_LOGO = [
|
|
4
|
+
' __ __ _____ _ _ _______ ______ _____ ',
|
|
5
|
+
' \\ \\ / /|_ _| \\ | |__ __| ____| __ \\ ',
|
|
6
|
+
' \\ \\ /\\ / / | | | \\| | | | | |__ | |__) |',
|
|
7
|
+
' \\ V V / | | | . | | | | __| | _ / ',
|
|
8
|
+
' \\_/\\_/ _| |_| |\\ | | | | |____| | \\ \\ ',
|
|
9
|
+
' |_____|_| \\_| |_| |______|_| \\_\\',
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function basenameFromPath(filePath, fallback = 'project') {
|
|
13
|
+
const parts = String(filePath || '').split(/[\\/]/).filter(Boolean);
|
|
14
|
+
return parts[parts.length - 1] || fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function buildTuiSnapshot(repl = {}) {
|
|
18
|
+
const provider = repl.ai?.getActiveProvider?.() || 'provider';
|
|
19
|
+
const model = repl.ai?.providers?.[provider]?.model || 'model';
|
|
20
|
+
return {
|
|
21
|
+
provider,
|
|
22
|
+
model,
|
|
23
|
+
projectPath: repl.projectPath || process.cwd(),
|
|
24
|
+
sessionShort: String(repl.session?.getSessionId?.() || 'session').slice(0, 8),
|
|
25
|
+
projectName: basenameFromPath(repl.projectPath || process.cwd()),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function renderInputPanel(snapshot, {
|
|
30
|
+
colors,
|
|
31
|
+
width = terminalWidth(66, 124),
|
|
32
|
+
} = {}) {
|
|
33
|
+
const c = colors || {};
|
|
34
|
+
const panelWidth = Math.max(64, width - 2);
|
|
35
|
+
const innerWidth = Math.max(20, panelWidth - 4);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
top: `${c.dim}┌${'─'.repeat(innerWidth + 2)}┐${c.reset}`,
|
|
39
|
+
status: '',
|
|
40
|
+
hint: '',
|
|
41
|
+
prompt: `${c.bright}${c.green}│${c.reset} `,
|
|
42
|
+
bottom: `${c.dim}└${'─'.repeat(innerWidth + 2)}┘${c.reset}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function renderLandingTui(snapshot, { colors } = {}) {
|
|
47
|
+
const c = colors || {};
|
|
48
|
+
const W = Math.max(60, Math.min(process.stdout.columns || 80, 140));
|
|
49
|
+
|
|
50
|
+
const bright = c.bright || '\x1b[1m';
|
|
51
|
+
const green = c.brightGreen || c.green || '\x1b[92m';
|
|
52
|
+
const white = c.white || '\x1b[37m';
|
|
53
|
+
const dim = c.dim || '\x1b[2m';
|
|
54
|
+
const reset = c.reset || '\x1b[0m';
|
|
55
|
+
const bgBlue = '\x1b[48;5;236m';
|
|
56
|
+
|
|
57
|
+
const logoLines = WINTER_LOGO.map(line => `${bright}${green}${line}${reset}`);
|
|
58
|
+
|
|
59
|
+
const leftStatus = ` ${snapshot.provider} · ${snapshot.model} `;
|
|
60
|
+
const rightStatus = ` ESC×2 exit · /help `;
|
|
61
|
+
const padding = Math.max(0, W - leftStatus.length - rightStatus.length);
|
|
62
|
+
const statusBar = `${bgBlue}${white}${leftStatus}${' '.repeat(padding)}${rightStatus}${reset}`;
|
|
63
|
+
|
|
64
|
+
const dock = renderInputPanel(snapshot, { colors });
|
|
65
|
+
|
|
66
|
+
return [
|
|
67
|
+
...logoLines,
|
|
68
|
+
'',
|
|
69
|
+
`${white}Winter will run commands on your behalf to help you build.${reset}`,
|
|
70
|
+
'',
|
|
71
|
+
`${white}Directory${reset} ${dim}${snapshot.projectPath}${reset}`,
|
|
72
|
+
'',
|
|
73
|
+
statusBar,
|
|
74
|
+
dock.top
|
|
75
|
+
].join('\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function renderStatusPanel(snapshot, { colors, title = 'Status' } = {}) {
|
|
79
|
+
const c = colors || {};
|
|
80
|
+
const bgBlue = '\x1b[48;5;236m';
|
|
81
|
+
const header = `${bgBlue}${c.white} ${title.toUpperCase()} ${c.reset}`;
|
|
82
|
+
|
|
83
|
+
return [
|
|
84
|
+
'',
|
|
85
|
+
header,
|
|
86
|
+
`${c.dim}Project :${c.reset} ${snapshot.projectName} (${snapshot.projectPath})`,
|
|
87
|
+
`${c.dim}Model :${c.reset} ${snapshot.provider}/${snapshot.model} (${snapshot.modelTier})`,
|
|
88
|
+
`${c.dim}Session :${c.reset} ${snapshot.sessionShort} | State: ${snapshot.statusText}`,
|
|
89
|
+
`${c.dim}Codebase:${c.reset} ${snapshot.codebaseFiles} files, ${snapshot.codebaseChunks} chunks`,
|
|
90
|
+
`${c.dim}Activity:${c.reset} ${snapshot.toolSummary || 'idle'}`,
|
|
91
|
+
''
|
|
92
|
+
].join('\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function renderStartupTui(snapshot, opts) { return renderLandingTui(snapshot, opts); }
|
|
96
|
+
export function renderConversationStartup(snapshot, opts) { return renderLandingTui(snapshot, opts); }
|
|
97
|
+
export function renderShellTui(snapshot, opts) { return renderLandingTui(snapshot, opts); }
|
|
98
|
+
|
|
99
|
+
export function renderCommandCenter({ colors, width = 80 } = {}) {
|
|
100
|
+
const c = colors || {};
|
|
101
|
+
const bgBlue = '\x1b[48;5;236m';
|
|
102
|
+
const header = `${bgBlue}${c.white} COMMAND CENTER ${c.reset}`;
|
|
103
|
+
|
|
104
|
+
return [
|
|
105
|
+
'',
|
|
106
|
+
header,
|
|
107
|
+
`${c.brightGreen}B${c.reset} ${c.bright}${c.cyan}Build ${c.reset} ${c.dim}/auto /debug /tdd /swe /composer${c.reset}`,
|
|
108
|
+
`${c.brightGreen}I${c.reset} ${c.bright}${c.cyan}Inspect${c.reset} ${c.dim}/read /grep /glob /search /context${c.reset}`,
|
|
109
|
+
`${c.brightGreen}M${c.reset} ${c.bright}${c.cyan}Model ${c.reset} ${c.dim}/provider /providers /model /models /scorecard${c.reset}`,
|
|
110
|
+
`${c.brightGreen}K${c.reset} ${c.bright}${c.cyan}Memory ${c.reset} ${c.dim}/remember /memories /memory-vault /compress${c.reset}`,
|
|
111
|
+
`${c.brightGreen}V${c.reset} ${c.bright}${c.cyan}Visual ${c.reset} ${c.dim}/image /paste ^V img /designs /page-agent${c.reset}`,
|
|
112
|
+
`${c.brightGreen}S${c.reset} ${c.bright}${c.cyan}System ${c.reset} ${c.dim}/doctor full /stats /permissions /mcp /help${c.reset}`,
|
|
113
|
+
''
|
|
114
|
+
].join('\n');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function renderSplitPanel({ title, left = [], right = [], colors, width = 80 } = {}) {
|
|
118
|
+
const c = colors || {};
|
|
119
|
+
const bgBlue = '\x1b[48;5;236m';
|
|
120
|
+
const header = `${bgBlue}${c.white} ${(title || 'Info').toUpperCase()} ${c.reset}`;
|
|
121
|
+
|
|
122
|
+
const innerWidth = Math.max(56, width - 4);
|
|
123
|
+
const leftWidth = Math.floor(innerWidth * 0.5);
|
|
124
|
+
|
|
125
|
+
const rows = [];
|
|
126
|
+
const count = Math.max(left.length, right.length);
|
|
127
|
+
for (let i = 0; i < count; i++) {
|
|
128
|
+
const lText = String(left[i] || '');
|
|
129
|
+
const rText = String(right[i] || '');
|
|
130
|
+
const lPlain = lText.replace(/\x1b\[[0-9;]*m/g, '');
|
|
131
|
+
const lPad = Math.max(0, leftWidth - lPlain.length);
|
|
132
|
+
rows.push(`${lText}${' '.repeat(lPad)} ${rText}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return [
|
|
136
|
+
'',
|
|
137
|
+
header,
|
|
138
|
+
...rows,
|
|
139
|
+
''
|
|
140
|
+
].join('\n');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function renderHistoryPanel(entries = [], { colors, title = 'Recent Session' } = {}) {
|
|
144
|
+
const c = colors || {};
|
|
145
|
+
const bgBlue = '\x1b[48;5;236m';
|
|
146
|
+
const header = `${bgBlue}${c.white} ${title.toUpperCase()} ${c.reset}`;
|
|
147
|
+
|
|
148
|
+
const body = entries.length > 0
|
|
149
|
+
? entries.map(entry => {
|
|
150
|
+
const role = entry.role === 'assistant' ? 'Winter' : entry.role === 'user' ? 'You' : entry.role || 'event';
|
|
151
|
+
const roleColor = entry.role === 'assistant' ? c.cyan : c.green;
|
|
152
|
+
return `${c.bright}${roleColor}${role}${c.reset}: ${entry.content || entry.text || ''}`;
|
|
153
|
+
})
|
|
154
|
+
: [`${c.dim}No previous messages.${c.reset}`];
|
|
155
|
+
|
|
156
|
+
return [
|
|
157
|
+
'',
|
|
158
|
+
header,
|
|
159
|
+
...body,
|
|
160
|
+
''
|
|
161
|
+
].join('\n');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function renderAssistantPanel({ content = '', footer = '', colors, title = 'Assistant' } = {}) {
|
|
165
|
+
const c = colors || {};
|
|
166
|
+
const bgBlue = '\x1b[48;5;236m';
|
|
167
|
+
const header = `${bgBlue}${c.white} ${title.toUpperCase()} ${c.reset}`;
|
|
168
|
+
|
|
169
|
+
const parts = [];
|
|
170
|
+
if (title) parts.push('', header);
|
|
171
|
+
if (content) parts.push(content);
|
|
172
|
+
if (footer) parts.push(`${c.dim}${footer}${c.reset}`);
|
|
173
|
+
return parts.join('\n') + '\n';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function renderToolPanel({ toolName = 'Tool', summary = '', success = true, colors } = {}) {
|
|
177
|
+
const c = colors || {};
|
|
178
|
+
const status = success ? `${c.brightGreen}✓${c.reset}` : `${c.red}✖${c.reset}`;
|
|
179
|
+
const plainSummary = String(summary || '').replace(/\n/g, ' ');
|
|
180
|
+
return `${status} ${c.bright}${c.cyan}${toolName}${c.reset} ${c.dim}· ${plainSummary}${c.reset}`;
|
|
181
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
async function importCodeGraphModule() {
|
|
4
|
+
return quietCodeGraph(() => import('@colbymchenry/codegraph'));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async function quietCodeGraph(fn) {
|
|
8
|
+
if (process.env.WINTER_CODEGRAPH_DEBUG === '1') {
|
|
9
|
+
return fn();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const originalLog = console.log;
|
|
13
|
+
const originalInfo = console.info;
|
|
14
|
+
const originalWarn = console.warn;
|
|
15
|
+
const originalError = console.error;
|
|
16
|
+
try {
|
|
17
|
+
console.log = () => {};
|
|
18
|
+
console.info = () => {};
|
|
19
|
+
console.warn = () => {};
|
|
20
|
+
console.error = () => {};
|
|
21
|
+
return await fn();
|
|
22
|
+
} finally {
|
|
23
|
+
console.log = originalLog;
|
|
24
|
+
console.info = originalInfo;
|
|
25
|
+
console.warn = originalWarn;
|
|
26
|
+
console.error = originalError;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class CodeGraphAdapter {
|
|
31
|
+
constructor(options = {}) {
|
|
32
|
+
this.projectPath = path.resolve(options.projectPath || process.cwd());
|
|
33
|
+
this.enabled = options.enabled !== false;
|
|
34
|
+
this.instance = options.instance || null;
|
|
35
|
+
this.module = options.module || null;
|
|
36
|
+
this.available = Boolean(this.instance);
|
|
37
|
+
this.lastError = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async init() {
|
|
41
|
+
if (!this.enabled) return false;
|
|
42
|
+
if (this.instance) {
|
|
43
|
+
this.available = true;
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const mod = this.module || await importCodeGraphModule();
|
|
49
|
+
const CodeGraph = mod.CodeGraph || mod.default;
|
|
50
|
+
if (!CodeGraph) throw new Error('CodeGraph export not found');
|
|
51
|
+
|
|
52
|
+
this.instance = await quietCodeGraph(() => CodeGraph.isInitialized?.(this.projectPath)
|
|
53
|
+
? CodeGraph.open(this.projectPath, { sync: false })
|
|
54
|
+
: CodeGraph.init(this.projectPath, {
|
|
55
|
+
index: false,
|
|
56
|
+
config: {
|
|
57
|
+
exclude: [
|
|
58
|
+
'node_modules/**',
|
|
59
|
+
'.git/**',
|
|
60
|
+
'.winter/**',
|
|
61
|
+
'dist/**',
|
|
62
|
+
'build/**',
|
|
63
|
+
'resources/local/**',
|
|
64
|
+
'VSCode-win32-x64/**',
|
|
65
|
+
'vscode-main/**',
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
69
|
+
this.available = true;
|
|
70
|
+
return true;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
this.available = false;
|
|
73
|
+
this.lastError = error;
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async ensureIndexed() {
|
|
79
|
+
if (!await this.init()) return null;
|
|
80
|
+
const stats = this.safeStats();
|
|
81
|
+
if (!stats || stats.nodeCount === 0 || stats.fileCount === 0) {
|
|
82
|
+
await quietCodeGraph(() => this.instance.indexAll());
|
|
83
|
+
} else {
|
|
84
|
+
await quietCodeGraph(() => this.instance.sync?.());
|
|
85
|
+
}
|
|
86
|
+
return this.safeStats();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
safeStats() {
|
|
90
|
+
try {
|
|
91
|
+
return this.instance?.getStats?.() || null;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
this.lastError = error;
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async search(query, options = {}) {
|
|
99
|
+
await this.ensureIndexed();
|
|
100
|
+
if (!this.instance) return [];
|
|
101
|
+
try {
|
|
102
|
+
return await quietCodeGraph(() => this.instance.searchNodes(String(query || ''), {
|
|
103
|
+
limit: options.limit || 20,
|
|
104
|
+
}));
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.lastError = error;
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async buildContext(task, options = {}) {
|
|
112
|
+
await this.ensureIndexed();
|
|
113
|
+
if (!this.instance) return '';
|
|
114
|
+
try {
|
|
115
|
+
const result = await quietCodeGraph(() => this.instance.buildContext(String(task || ''), {
|
|
116
|
+
maxNodes: options.maxNodes || 24,
|
|
117
|
+
maxCodeBlocks: options.maxCodeBlocks || 8,
|
|
118
|
+
maxCodeBlockSize: options.maxCodeBlockSize || 1800,
|
|
119
|
+
includeCode: options.includeCode !== false,
|
|
120
|
+
format: 'markdown',
|
|
121
|
+
}));
|
|
122
|
+
return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
this.lastError = error;
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async findSymbol(name, options = {}) {
|
|
130
|
+
const results = await this.search(name, options);
|
|
131
|
+
return results.map(result => {
|
|
132
|
+
const node = result.node || result;
|
|
133
|
+
return {
|
|
134
|
+
name: node.name,
|
|
135
|
+
type: node.kind || 'symbol',
|
|
136
|
+
filePath: node.filePath,
|
|
137
|
+
line: node.startLine || 1,
|
|
138
|
+
endLine: node.endLine || node.startLine || 1,
|
|
139
|
+
qualifiedName: node.qualifiedName,
|
|
140
|
+
score: result.score,
|
|
141
|
+
content: node.signature || node.docstring || '',
|
|
142
|
+
node,
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
close() {
|
|
148
|
+
try {
|
|
149
|
+
this.instance?.close?.();
|
|
150
|
+
} catch {}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export default CodeGraphAdapter;
|
|
@@ -8,7 +8,7 @@ import path from 'path';
|
|
|
8
8
|
import crypto from 'crypto';
|
|
9
9
|
|
|
10
10
|
const DEFAULT_IGNORE = new Set([
|
|
11
|
-
'node_modules', '.git', 'dist', 'build', '.winter', '.claude',
|
|
11
|
+
'node_modules', '.git', 'dist', 'build', '.winter', '.codegraph', '.claude',
|
|
12
12
|
'.next', '.cache', 'coverage', '.nyc_output',
|
|
13
13
|
'__pycache__', '.venv', 'venv', 'env', '.env',
|
|
14
14
|
'VSCode-win32-x64', 'vscode-main',
|
|
@@ -7,20 +7,26 @@
|
|
|
7
7
|
* - Combined search
|
|
8
8
|
*/
|
|
9
9
|
import { CodebaseIndexer } from './indexer.js';
|
|
10
|
+
import { CodeGraphAdapter } from './codegraph-adapter.js';
|
|
10
11
|
import path from 'path';
|
|
11
12
|
|
|
12
13
|
export class CodebaseSearch {
|
|
13
14
|
constructor(options = {}) {
|
|
14
15
|
this.indexer = options.indexer || new CodebaseIndexer(options);
|
|
15
16
|
this.projectPath = options.projectPath || this.indexer.projectPath;
|
|
17
|
+
this.codeGraph = options.codeGraphAdapter || (
|
|
18
|
+
options.enableCodeGraph ? new CodeGraphAdapter({ projectPath: this.projectPath }) : null
|
|
19
|
+
);
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
async init() {
|
|
19
23
|
await this.indexer.init();
|
|
24
|
+
await this.codeGraph?.init?.();
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
async ensureIndexed() {
|
|
23
28
|
const stats = this.indexer.getStats();
|
|
29
|
+
await this.codeGraph?.ensureIndexed?.();
|
|
24
30
|
if (stats.totalChunks === 0) {
|
|
25
31
|
return await this.indexer.indexAll();
|
|
26
32
|
}
|
|
@@ -34,6 +40,9 @@ export class CodebaseSearch {
|
|
|
34
40
|
async query(query, options = {}) {
|
|
35
41
|
await this.ensureIndexed();
|
|
36
42
|
const results = this.indexer.search(query, options);
|
|
43
|
+
const graphResults = this.codeGraph
|
|
44
|
+
? await this.codeGraph.search(query, { limit: Math.min(options.limit || 20, 12) })
|
|
45
|
+
: [];
|
|
37
46
|
|
|
38
47
|
// Group by file for display
|
|
39
48
|
const byFile = new Map();
|
|
@@ -46,9 +55,10 @@ export class CodebaseSearch {
|
|
|
46
55
|
|
|
47
56
|
return {
|
|
48
57
|
query,
|
|
49
|
-
totalResults: results.length,
|
|
58
|
+
totalResults: results.length + graphResults.length,
|
|
50
59
|
totalFiles: byFile.size,
|
|
51
60
|
results,
|
|
61
|
+
graphResults,
|
|
52
62
|
byFile: [...byFile.entries()].map(([filePath, chunks]) => ({
|
|
53
63
|
filePath,
|
|
54
64
|
score: Math.max(...chunks.map(c => c.score)),
|
|
@@ -63,6 +73,9 @@ export class CodebaseSearch {
|
|
|
63
73
|
*/
|
|
64
74
|
async findSymbol(name, options = {}) {
|
|
65
75
|
await this.ensureIndexed();
|
|
76
|
+
const graphMatches = this.codeGraph
|
|
77
|
+
? await this.codeGraph.findSymbol(name, options)
|
|
78
|
+
: [];
|
|
66
79
|
const nameLower = name.toLowerCase();
|
|
67
80
|
const matches = [];
|
|
68
81
|
|
|
@@ -79,7 +92,12 @@ export class CodebaseSearch {
|
|
|
79
92
|
}
|
|
80
93
|
}
|
|
81
94
|
|
|
82
|
-
return matches.slice(0, options.limit || 20);
|
|
95
|
+
return [...graphMatches, ...matches].slice(0, options.limit || 20);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async buildGraphContext(task, options = {}) {
|
|
99
|
+
if (!this.codeGraph) return '';
|
|
100
|
+
return await this.codeGraph.buildContext(task, options);
|
|
83
101
|
}
|
|
84
102
|
|
|
85
103
|
/**
|
|
@@ -148,6 +166,13 @@ export class CodebaseSearch {
|
|
|
148
166
|
|
|
149
167
|
return {
|
|
150
168
|
...stats,
|
|
169
|
+
codeGraph: this.codeGraph
|
|
170
|
+
? {
|
|
171
|
+
available: this.codeGraph.available,
|
|
172
|
+
stats: this.codeGraph.safeStats?.() || null,
|
|
173
|
+
error: this.codeGraph.lastError?.message || null,
|
|
174
|
+
}
|
|
175
|
+
: null,
|
|
151
176
|
languages: [...languages.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10),
|
|
152
177
|
topSymbols,
|
|
153
178
|
};
|
|
@@ -161,6 +186,10 @@ export class CodebaseSearch {
|
|
|
161
186
|
return await this.indexer.clear();
|
|
162
187
|
}
|
|
163
188
|
|
|
189
|
+
close() {
|
|
190
|
+
this.codeGraph?.close?.();
|
|
191
|
+
}
|
|
192
|
+
|
|
164
193
|
// ── Private ────────────────────────────────────────
|
|
165
194
|
|
|
166
195
|
_extractRelevantLine(content, lineNumber) {
|
package/src/context/router.js
CHANGED
|
@@ -12,19 +12,8 @@ function flattenMessageText(messages) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
import { ReasoningConfig, REASONING_LEVELS } from '../ai/reasoning.js';
|
|
15
|
-
import { classifyModelTier
|
|
15
|
+
import { classifyModelTier } from '../ai/model-capabilities.js';
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
* Bump reasoning level by N steps.
|
|
19
|
-
*/
|
|
20
|
-
function bumpReasoningLevel(level, steps) {
|
|
21
|
-
const order = [REASONING_LEVELS.NONE, REASONING_LEVELS.LOW, REASONING_LEVELS.MEDIUM, REASONING_LEVELS.HIGH, REASONING_LEVELS.MAX];
|
|
22
|
-
const idx = order.indexOf(level);
|
|
23
|
-
if (idx === -1) return level;
|
|
24
|
-
const newIdx = Math.min(idx + steps, order.length - 1);
|
|
25
|
-
return order[newIdx];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
17
|
export function selectExecutionProfile({ messages = [], activeProvider = null, providers = {}, options = {} } = {}) {
|
|
29
18
|
const text = flattenMessageText(messages);
|
|
30
19
|
const providerNames = Object.keys(providers).filter(name => providers[name]?.ready || providers[name]?.model);
|
|
@@ -50,35 +39,9 @@ export function selectExecutionProfile({ messages = [], activeProvider = null, p
|
|
|
50
39
|
const providerConfig = providers[provider] || providers[activeProvider] || {};
|
|
51
40
|
const model = options.model || providerConfig.model || providers[activeProvider]?.model || null;
|
|
52
41
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const reasoningBump = getReasoningBump(modelTier);
|
|
57
|
-
|
|
58
|
-
// Determine reasoning level based on task complexity signals
|
|
59
|
-
// Default: HIGH for coding — all models must think deeply
|
|
60
|
-
let reasoningLevel = options.reasoningLevel || REASONING_LEVELS.HIGH;
|
|
61
|
-
if (!options.reasoningLevel) {
|
|
62
|
-
const hasDeepSignals = /\b(refactor|architecture|redesign|migrate|complex|full stack|e2e|end to end|security|optimize|performance|implement|build|create)\b/.test(text);
|
|
63
|
-
const hasComplexSignals = /\b(debug|fix|test|multiple|integrate|design|plan|review|analyze)\b/.test(text);
|
|
64
|
-
|
|
65
|
-
if (hasDeepSignals && text.length > 30) {
|
|
66
|
-
reasoningLevel = REASONING_LEVELS.MAX;
|
|
67
|
-
} else if (hasComplexSignals && text.length > 20) {
|
|
68
|
-
reasoningLevel = REASONING_LEVELS.MAX;
|
|
69
|
-
} else if (text.split(/\s+/).length > 10) {
|
|
70
|
-
reasoningLevel = REASONING_LEVELS.HIGH;
|
|
71
|
-
} else if (text.split(/\s+/).length < 3) {
|
|
72
|
-
reasoningLevel = REASONING_LEVELS.MEDIUM;
|
|
73
|
-
} else {
|
|
74
|
-
reasoningLevel = REASONING_LEVELS.HIGH;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// If small model, bump reasoning level even more to compensate
|
|
78
|
-
if (isSmall && reasoningBump > 0) {
|
|
79
|
-
reasoningLevel = bumpReasoningLevel(reasoningLevel, reasoningBump);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
42
|
+
// Keep the real tier for diagnostics, but Winter always pushes max reasoning.
|
|
43
|
+
const modelTier = classifyModelTier(model, provider);
|
|
44
|
+
const reasoningLevel = options.reasoningLevel || REASONING_LEVELS.MAX;
|
|
82
45
|
|
|
83
46
|
const reasoning = new ReasoningConfig({
|
|
84
47
|
level: reasoningLevel,
|
|
@@ -63,26 +63,52 @@ export function serializeToolResultToMarkdown(toolName, result = {}) {
|
|
|
63
63
|
export function extractKeyLines(text = '', limit = 18) {
|
|
64
64
|
const patterns = /(error|failed|exception|warning|todo|fixme|export |import |class |function |const |let |var |def |interface |type |=>)/i;
|
|
65
65
|
const selected = [];
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
const lines = String(text).split(/\r?\n/);
|
|
67
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
68
|
+
const line = lines[index];
|
|
69
|
+
if (patterns.test(line)) selected.push(`L${index + 1}: ${line.trim()}`);
|
|
68
70
|
if (selected.length >= limit) break;
|
|
69
71
|
}
|
|
70
72
|
return selected;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
function buildExcerpts(text = '', maxChars = 700) {
|
|
76
|
+
const value = String(text || '').trim();
|
|
77
|
+
if (!value) return [];
|
|
78
|
+
if (value.length <= maxChars) return [value];
|
|
79
|
+
const head = value.slice(0, Math.floor(maxChars * 0.58)).trimEnd();
|
|
80
|
+
const tail = value.slice(-Math.floor(maxChars * 0.32)).trimStart();
|
|
81
|
+
return [
|
|
82
|
+
`${head}\n[TokenJuice middle omitted from inline preview; full text is in memory]\n${tail}`,
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildChunkMap(saved) {
|
|
87
|
+
if (!saved?.files?.length) return [];
|
|
88
|
+
return saved.files.map(file => {
|
|
89
|
+
const label = `part ${file.part}/${saved.parts}`;
|
|
90
|
+
return `- ${label}: ${file.wikiLink} (~${file.tokensEst} tokens)`;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function buildCompressedPreview(toolName, result, markdown, savedOrLinks = []) {
|
|
95
|
+
const links = Array.isArray(savedOrLinks) ? savedOrLinks : (savedOrLinks?.links || []);
|
|
74
96
|
const source = [result?.error, result?.content, result?.stdout, result?.stderr, result?.diff, result?.message]
|
|
75
97
|
.filter(Boolean)
|
|
76
98
|
.join('\n');
|
|
77
99
|
const keyLines = extractKeyLines(source);
|
|
78
|
-
const
|
|
100
|
+
const excerpts = buildExcerpts(source || markdown);
|
|
101
|
+
const chunkMap = Array.isArray(savedOrLinks) ? [] : buildChunkMap(savedOrLinks);
|
|
79
102
|
const lines = [
|
|
80
|
-
`TokenJuice
|
|
81
|
-
links.length ? `Full
|
|
82
|
-
|
|
103
|
+
`TokenJuice losslessly stored a large ${toolName || 'tool'} result before model context.`,
|
|
104
|
+
links.length ? `Full detail chunks: ${links.join(', ')}` : '',
|
|
105
|
+
chunkMap.length ? 'Chunk map for exact detail retrieval:' : '',
|
|
106
|
+
...chunkMap,
|
|
107
|
+
'Instruction: use Read on the memory file behind a chunk link when exact omitted lines are needed.',
|
|
108
|
+
keyLines.length ? 'Key lines with original line numbers:' : '',
|
|
83
109
|
...keyLines.map(line => `- ${line}`),
|
|
84
|
-
|
|
85
|
-
|
|
110
|
+
excerpts.length ? 'Representative excerpts:' : '',
|
|
111
|
+
...excerpts.map(excerpt => compactText(excerpt, 900)),
|
|
86
112
|
].filter(Boolean);
|
|
87
113
|
return lines.join('\n');
|
|
88
114
|
}
|
|
@@ -129,7 +155,7 @@ export class TokenJuice {
|
|
|
129
155
|
source: result.path || result.url || result.command || '',
|
|
130
156
|
},
|
|
131
157
|
});
|
|
132
|
-
const preview = buildCompressedPreview(toolName, result, markdown, saved
|
|
158
|
+
const preview = buildCompressedPreview(toolName, result, markdown, saved);
|
|
133
159
|
const compressed = {
|
|
134
160
|
...basePromptResult,
|
|
135
161
|
content: preview,
|
|
@@ -143,6 +169,7 @@ export class TokenJuice {
|
|
|
143
169
|
memoryRoot: saved.rootDir,
|
|
144
170
|
memoryLinks: saved.links,
|
|
145
171
|
memoryFiles: saved.files.map(file => file.relativePath),
|
|
172
|
+
detailRetrieval: 'Read the listed memoryFiles for exact omitted content.',
|
|
146
173
|
parts: saved.parts,
|
|
147
174
|
},
|
|
148
175
|
};
|