thatgfsj-code 3.0.4 → 3.0.6

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 (205) hide show
  1. package/README.md +64 -21
  2. package/dist/app/index.d.ts +93 -22
  3. package/dist/app/index.d.ts.map +1 -1
  4. package/dist/app/index.js +232 -40
  5. package/dist/app/index.js.map +1 -1
  6. package/dist/cmd/index.d.ts +12 -20
  7. package/dist/cmd/index.d.ts.map +1 -1
  8. package/dist/cmd/index.js +192 -146
  9. package/dist/cmd/index.js.map +1 -1
  10. package/dist/config/index.d.ts +4 -0
  11. package/dist/config/index.d.ts.map +1 -1
  12. package/dist/config/index.js +5 -0
  13. package/dist/config/index.js.map +1 -1
  14. package/dist/config/types.d.ts +5 -0
  15. package/dist/config/types.d.ts.map +1 -1
  16. package/dist/llm/anthropic.d.ts +1 -1
  17. package/dist/llm/anthropic.d.ts.map +1 -1
  18. package/dist/llm/anthropic.js +116 -84
  19. package/dist/llm/anthropic.js.map +1 -1
  20. package/dist/llm/gemini.d.ts +27 -3
  21. package/dist/llm/gemini.d.ts.map +1 -1
  22. package/dist/llm/gemini.js +196 -73
  23. package/dist/llm/gemini.js.map +1 -1
  24. package/dist/llm/index.d.ts +20 -1
  25. package/dist/llm/index.d.ts.map +1 -1
  26. package/dist/llm/index.js +66 -6
  27. package/dist/llm/index.js.map +1 -1
  28. package/dist/llm/openai.d.ts +5 -1
  29. package/dist/llm/openai.d.ts.map +1 -1
  30. package/dist/llm/openai.js +96 -56
  31. package/dist/llm/openai.js.map +1 -1
  32. package/dist/mcp/client.d.ts +84 -34
  33. package/dist/mcp/client.d.ts.map +1 -1
  34. package/dist/mcp/client.js +291 -126
  35. package/dist/mcp/client.js.map +1 -1
  36. package/dist/session/compactor.d.ts +27 -7
  37. package/dist/session/compactor.d.ts.map +1 -1
  38. package/dist/session/compactor.js +78 -23
  39. package/dist/session/compactor.js.map +1 -1
  40. package/dist/session/index.d.ts +96 -33
  41. package/dist/session/index.d.ts.map +1 -1
  42. package/dist/session/index.js +278 -60
  43. package/dist/session/index.js.map +1 -1
  44. package/dist/tools/file.d.ts +10 -2
  45. package/dist/tools/file.d.ts.map +1 -1
  46. package/dist/tools/file.js +54 -5
  47. package/dist/tools/file.js.map +1 -1
  48. package/dist/tools/git.d.ts +16 -2
  49. package/dist/tools/git.d.ts.map +1 -1
  50. package/dist/tools/git.js +61 -18
  51. package/dist/tools/git.js.map +1 -1
  52. package/dist/tools/nwt.d.ts.map +1 -1
  53. package/dist/tools/nwt.js +8 -1
  54. package/dist/tools/nwt.js.map +1 -1
  55. package/dist/tools/search.d.ts +8 -1
  56. package/dist/tools/search.d.ts.map +1 -1
  57. package/dist/tools/search.js +105 -34
  58. package/dist/tools/search.js.map +1 -1
  59. package/dist/tools/shell.d.ts +0 -4
  60. package/dist/tools/shell.d.ts.map +1 -1
  61. package/dist/tools/shell.js +11 -26
  62. package/dist/tools/shell.js.map +1 -1
  63. package/dist/tools/types.d.ts +7 -0
  64. package/dist/tools/types.d.ts.map +1 -1
  65. package/dist/tools/types.js.map +1 -1
  66. package/dist/tui/app.d.ts.map +1 -1
  67. package/dist/tui/app.js +90 -18
  68. package/dist/tui/app.js.map +1 -1
  69. package/dist/tui/components/ChatMessage.d.ts.map +1 -1
  70. package/dist/tui/components/ChatMessage.js +9 -2
  71. package/dist/tui/components/ChatMessage.js.map +1 -1
  72. package/dist/tui/components/ConfirmPrompt.d.ts +21 -0
  73. package/dist/tui/components/ConfirmPrompt.d.ts.map +1 -0
  74. package/dist/tui/components/ConfirmPrompt.js +45 -0
  75. package/dist/tui/components/ConfirmPrompt.js.map +1 -0
  76. package/dist/tui/components/Header.d.ts +10 -5
  77. package/dist/tui/components/Header.d.ts.map +1 -1
  78. package/dist/tui/components/Header.js +13 -5
  79. package/dist/tui/components/Header.js.map +1 -1
  80. package/dist/tui/components/StatusBar.d.ts +7 -0
  81. package/dist/tui/components/StatusBar.d.ts.map +1 -1
  82. package/dist/tui/components/StatusBar.js +8 -3
  83. package/dist/tui/components/StatusBar.js.map +1 -1
  84. package/dist/tui/components/Thinking.d.ts +5 -0
  85. package/dist/tui/components/Thinking.d.ts.map +1 -1
  86. package/dist/tui/components/Thinking.js +24 -2
  87. package/dist/tui/components/Thinking.js.map +1 -1
  88. package/dist/tui/components/ToolCall.d.ts +6 -1
  89. package/dist/tui/components/ToolCall.d.ts.map +1 -1
  90. package/dist/tui/components/ToolCall.js +50 -22
  91. package/dist/tui/components/ToolCall.js.map +1 -1
  92. package/dist/tui/components/UserInput.d.ts +5 -0
  93. package/dist/tui/components/UserInput.d.ts.map +1 -1
  94. package/dist/tui/components/UserInput.js +9 -13
  95. package/dist/tui/components/UserInput.js.map +1 -1
  96. package/dist/tui/hooks/useChat.d.ts +1 -0
  97. package/dist/tui/hooks/useChat.d.ts.map +1 -1
  98. package/dist/tui/hooks/useChat.js +36 -16
  99. package/dist/tui/hooks/useChat.js.map +1 -1
  100. package/dist/tui/hooks/useCommands.d.ts +7 -1
  101. package/dist/tui/hooks/useCommands.d.ts.map +1 -1
  102. package/dist/tui/hooks/useCommands.js +77 -41
  103. package/dist/tui/hooks/useCommands.js.map +1 -1
  104. package/dist/tui/theme.d.ts +34 -0
  105. package/dist/tui/theme.d.ts.map +1 -0
  106. package/dist/tui/theme.js +34 -0
  107. package/dist/tui/theme.js.map +1 -0
  108. package/dist/tui/welcome.d.ts.map +1 -1
  109. package/dist/tui/welcome.js +9 -7
  110. package/dist/tui/welcome.js.map +1 -1
  111. package/dist/types.d.ts +16 -1
  112. package/dist/types.d.ts.map +1 -1
  113. package/dist/utils/diff.d.ts +5 -0
  114. package/dist/utils/diff.d.ts.map +1 -1
  115. package/dist/utils/diff.js +15 -0
  116. package/dist/utils/diff.js.map +1 -1
  117. package/dist/version.d.ts +12 -0
  118. package/dist/version.d.ts.map +1 -0
  119. package/dist/version.js +29 -0
  120. package/dist/version.js.map +1 -0
  121. package/package.json +19 -7
  122. package/CHANGELOG.md +0 -192
  123. package/DEVELOPMENT.md +0 -286
  124. package/ROADMAP.md +0 -91
  125. package/docs/API_KEY_GUIDE.md +0 -236
  126. package/docs/FAQ.md +0 -182
  127. package/install.bat +0 -63
  128. package/install.ps1 +0 -238
  129. package/install.sh +0 -113
  130. package/src/app/index.ts +0 -197
  131. package/src/cache/fingerprint.ts +0 -101
  132. package/src/cache/index.ts +0 -21
  133. package/src/cache/smartModel.ts +0 -133
  134. package/src/cache/stats.ts +0 -199
  135. package/src/cache/volatile.ts +0 -47
  136. package/src/cmd/index.tsx +0 -288
  137. package/src/config/index.ts +0 -156
  138. package/src/config/providers.ts +0 -234
  139. package/src/config/types.ts +0 -71
  140. package/src/hooks/index.ts +0 -111
  141. package/src/llm/anthropic.ts +0 -413
  142. package/src/llm/gemini.ts +0 -169
  143. package/src/llm/index.ts +0 -303
  144. package/src/llm/openai.ts +0 -243
  145. package/src/llm/provider.ts +0 -71
  146. package/src/mcp/client.ts +0 -330
  147. package/src/prompts/index.ts +0 -260
  148. package/src/session/compactor.ts +0 -103
  149. package/src/session/index.ts +0 -181
  150. package/src/session/message.ts +0 -42
  151. package/src/skills/brainstorming.ts +0 -43
  152. package/src/skills/code-review.ts +0 -45
  153. package/src/skills/executing-plans.ts +0 -27
  154. package/src/skills/frontend-design.ts +0 -35
  155. package/src/skills/git-workflow.ts +0 -36
  156. package/src/skills/improve-architecture.ts +0 -38
  157. package/src/skills/index.ts +0 -136
  158. package/src/skills/neuroweave.ts +0 -47
  159. package/src/skills/playwright.ts +0 -72
  160. package/src/skills/prototype.ts +0 -30
  161. package/src/skills/subagent.ts +0 -28
  162. package/src/skills/supabase.ts +0 -44
  163. package/src/skills/systematic-debugging.ts +0 -44
  164. package/src/skills/tdd.ts +0 -39
  165. package/src/skills/triage.ts +0 -35
  166. package/src/skills/verification.ts +0 -31
  167. package/src/skills/writing-plans.ts +0 -42
  168. package/src/tools/file.ts +0 -143
  169. package/src/tools/git.ts +0 -132
  170. package/src/tools/index.ts +0 -111
  171. package/src/tools/nwt.ts +0 -598
  172. package/src/tools/search.ts +0 -263
  173. package/src/tools/shell.ts +0 -136
  174. package/src/tools/types.ts +0 -122
  175. package/src/tui/app.tsx +0 -159
  176. package/src/tui/components/ChatList.tsx +0 -41
  177. package/src/tui/components/ChatMessage.tsx +0 -54
  178. package/src/tui/components/Header.tsx +0 -66
  179. package/src/tui/components/InitWizard.tsx +0 -247
  180. package/src/tui/components/Markdown.tsx +0 -35
  181. package/src/tui/components/ModelSelector.tsx +0 -107
  182. package/src/tui/components/StatusBar.tsx +0 -30
  183. package/src/tui/components/Thinking.tsx +0 -17
  184. package/src/tui/components/ToolCall.tsx +0 -102
  185. package/src/tui/components/UserInput.tsx +0 -151
  186. package/src/tui/hooks/useChat.ts +0 -287
  187. package/src/tui/hooks/useCommands.ts +0 -271
  188. package/src/tui/index.ts +0 -6
  189. package/src/tui/welcome.ts +0 -177
  190. package/src/types.ts +0 -104
  191. package/src/utils/diff.ts +0 -71
  192. package/src/utils/project.ts +0 -99
  193. package/src/utils/stableStringify.ts +0 -47
  194. package/src/utils/thinking.ts +0 -119
  195. package/tests/cache/fingerprint.test.ts +0 -75
  196. package/tests/cache/providerCatalog.test.ts +0 -31
  197. package/tests/cache/smartModel.test.ts +0 -100
  198. package/tests/cache/stableStringify.test.ts +0 -43
  199. package/tests/cache/stats.test.ts +0 -146
  200. package/tests/cache/volatile.test.ts +0 -75
  201. package/tests/smoke-pollution.mjs +0 -78
  202. package/tests/smoke-thinking.mjs +0 -110
  203. package/tests/smoke-tool-stream.mjs +0 -69
  204. package/tests/smoke-tool.mjs +0 -117
  205. package/tsconfig.json +0 -21
package/src/mcp/client.ts DELETED
@@ -1,330 +0,0 @@
1
- /**
2
- * S07: MCP Client (Model Context Protocol)
3
- * Connect to MCP servers and expose their tools as agent tools
4
- *
5
- * MCP turns the agent from a closed system into an open platform.
6
- * Any server can expose tools via the MCP JSON-RPC 2.0 protocol.
7
- */
8
-
9
- import { spawn, ChildProcess } from 'child_process';
10
- import type { Tool, ToolResult } from '../tools/types.js';
11
-
12
- // ==================== MCP Types ====================
13
-
14
- interface MCPJsonRPCRequest {
15
- jsonrpc: '2.0';
16
- id: number | string;
17
- method: string;
18
- params?: any;
19
- }
20
-
21
- interface MCPJsonRPCResponse {
22
- jsonrpc: '2.0';
23
- id: number | string;
24
- result?: any;
25
- error?: { code: number; message: string; data?: any };
26
- }
27
-
28
- interface MCPTool {
29
- name: string;
30
- description: string;
31
- inputSchema: {
32
- type: 'object';
33
- properties: Record<string, any>;
34
- required?: string[];
35
- };
36
- }
37
-
38
- // ==================== MCP Client ====================
39
-
40
- export class MCPClient {
41
- private process: ChildProcess | null = null;
42
- private tools: Map<string, MCPTool> = new Map();
43
- private requestId = 0;
44
- private pendingRequests: Map<number | string, {
45
- resolve: (value: any) => void;
46
- reject: (reason: any) => void;
47
- }> = new Map();
48
- private name: string;
49
- private connected = false;
50
-
51
- constructor(name: string) {
52
- this.name = name;
53
- }
54
-
55
- /**
56
- * S07: Connect to an MCP server
57
- * @param command Command to run (e.g., 'npx', 'node')
58
- * @param args Arguments (e.g., ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/dir'])
59
- */
60
- async connect(command: string, args: string[]): Promise<void> {
61
- return new Promise((resolve, reject) => {
62
- this.process = spawn(command, args, {
63
- stdio: ['pipe', 'pipe', 'pipe']
64
- });
65
-
66
- let buffer = '';
67
-
68
- this.process.stdout?.on('data', (data: Buffer) => {
69
- buffer += data.toString();
70
- const lines = buffer.split('\n');
71
- buffer = lines.pop() || '';
72
-
73
- for (const line of lines) {
74
- if (line.trim()) {
75
- this.handleMessage(line);
76
- }
77
- }
78
- });
79
-
80
- this.process.stderr?.on('data', (data: Buffer) => {
81
- console.error(`[MCP ${this.name}] stderr:`, data.toString().trim());
82
- });
83
-
84
- this.process.on('error', (err) => {
85
- reject(err);
86
- });
87
-
88
- this.process.on('close', (code) => {
89
- this.connected = false;
90
- console.log(`[MCP ${this.name}] exited with code ${code}`);
91
- });
92
-
93
- // Initialize MCP connection
94
- this.sendRequest('initialize', {
95
- protocolVersion: '2024-11-05',
96
- capabilities: { tools: {} },
97
- clientInfo: { name: 'thatgfsj-code', version: '1.0.0' }
98
- }).then(() => {
99
- this.connected = true;
100
- // Send initialized notification
101
- this.sendNotification('initialized', {});
102
- // List available tools
103
- return this.listTools();
104
- }).then(() => {
105
- resolve();
106
- }).catch(reject);
107
-
108
- // Timeout
109
- setTimeout(() => reject(new Error('MCP connection timeout')), 30000);
110
- });
111
- }
112
-
113
- /**
114
- * S07: Handle incoming JSON-RPC message
115
- */
116
- private handleMessage(raw: string): void {
117
- try {
118
- const msg: MCPJsonRPCResponse = JSON.parse(raw);
119
-
120
- // Handle response
121
- const pending = this.pendingRequests.get(msg.id);
122
- if (pending) {
123
- this.pendingRequests.delete(msg.id);
124
- if (msg.error) {
125
- pending.reject(new Error(msg.error.message));
126
- } else {
127
- pending.resolve(msg.result);
128
- }
129
- return;
130
- }
131
- } catch {
132
- // Not JSON, ignore
133
- }
134
- }
135
-
136
- /**
137
- * S07: Send a JSON-RPC request
138
- */
139
- private sendRequest(method: string, params: any): Promise<any> {
140
- return new Promise((resolve, reject) => {
141
- if (!this.process?.connected) {
142
- reject(new Error('MCP process not connected'));
143
- return;
144
- }
145
-
146
- const id = ++this.requestId;
147
- this.pendingRequests.set(id, { resolve, reject });
148
-
149
- const request: MCPJsonRPCRequest = {
150
- jsonrpc: '2.0',
151
- id,
152
- method,
153
- params
154
- };
155
-
156
- this.process.stdin?.write(JSON.stringify(request) + '\n');
157
-
158
- // Timeout
159
- setTimeout(() => {
160
- if (this.pendingRequests.has(id)) {
161
- this.pendingRequests.delete(id);
162
- reject(new Error('MCP request timeout: ' + method));
163
- }
164
- }, 30000);
165
- });
166
- }
167
-
168
- /**
169
- * S07: Send a notification (no response expected)
170
- */
171
- private sendNotification(method: string, params: any): void {
172
- if (!this.process?.connected) return;
173
-
174
- const notification = {
175
- jsonrpc: '2.0',
176
- method,
177
- params
178
- };
179
-
180
- this.process.stdin?.write(JSON.stringify(notification) + '\n');
181
- }
182
-
183
- /**
184
- * S07: Call an MCP tool
185
- */
186
- async callTool(name: string, arguments_: Record<string, any>): Promise<ToolResult> {
187
- try {
188
- const result = await this.sendRequest('tools/call', {
189
- name,
190
- arguments: arguments_
191
- });
192
-
193
- return {
194
- success: true,
195
- output: typeof result === 'string' ? result : JSON.stringify(result)
196
- };
197
- } catch (error: any) {
198
- return {
199
- success: false,
200
- error: error.message
201
- };
202
- }
203
- }
204
-
205
- /**
206
- * S07: List tools from MCP server
207
- */
208
- private async listTools(): Promise<void> {
209
- try {
210
- const result = await this.sendRequest('tools/list', {});
211
- const tools: MCPTool[] = result.tools || [];
212
-
213
- this.tools.clear();
214
- for (const tool of tools) {
215
- this.tools.set(tool.name, tool);
216
- }
217
-
218
- console.log(`[MCP ${this.name}] Loaded ${tools.length} tools:`, [...this.tools.keys()].join(', '));
219
- } catch (error: any) {
220
- console.error(`[MCP ${this.name}] Failed to list tools:`, error.message);
221
- }
222
- }
223
-
224
- /**
225
- * S07: Get registered tools as agent Tool[]
226
- */
227
- getTools(): Tool[] {
228
- return [...this.tools.values()].map(mcpTool => ({
229
- name: this.name + ':' + mcpTool.name,
230
- description: mcpTool.description,
231
- parameters: Object.entries(mcpTool.inputSchema.properties || {}).map(([name, schema]: [string, any]) => ({
232
- name,
233
- type: schema.type || 'string',
234
- description: schema.description || '',
235
- required: (mcpTool.inputSchema.required || []).includes(name)
236
- })),
237
- execute: async (params: Record<string, any>): Promise<ToolResult> => {
238
- return this.callTool(mcpTool.name, params);
239
- }
240
- }));
241
- }
242
-
243
- /**
244
- * S07: Get tool names
245
- */
246
- getToolNames(): string[] {
247
- return [...this.tools.keys()];
248
- }
249
-
250
- /**
251
- * S07: Check if connected
252
- */
253
- isConnected(): boolean {
254
- return this.connected;
255
- }
256
-
257
- /**
258
- * S07: Disconnect from MCP server
259
- */
260
- disconnect(): void {
261
- if (this.process) {
262
- this.process.kill();
263
- this.process = null;
264
- }
265
- this.connected = false;
266
- this.tools.clear();
267
- this.pendingRequests.clear();
268
- }
269
- }
270
-
271
- // ==================== MCP Server Manager ====================
272
-
273
- export class MCPServerManager {
274
- private clients: Map<string, MCPClient> = new Map();
275
-
276
- /**
277
- * S07: Add and connect an MCP server
278
- */
279
- async addServer(name: string, command: string, args: string[]): Promise<void> {
280
- const client = new MCPClient(name);
281
- await client.connect(command, args);
282
- this.clients.set(name, client);
283
- }
284
-
285
- /**
286
- * S07: Get all tools from all servers
287
- */
288
- getAllTools(): Tool[] {
289
- const allTools: Tool[] = [];
290
- for (const client of this.clients.values()) {
291
- allTools.push(...client.getTools());
292
- }
293
- return allTools;
294
- }
295
-
296
- /**
297
- * S07: Get tool names from all servers
298
- */
299
- getAllToolNames(): string[] {
300
- const names: string[] = [];
301
- for (const [serverName, client] of this.clients) {
302
- for (const toolName of client.getToolNames()) {
303
- names.push(serverName + ':' + toolName);
304
- }
305
- }
306
- return names;
307
- }
308
-
309
- /**
310
- * S07: Disconnect a server
311
- */
312
- removeServer(name: string): void {
313
- const client = this.clients.get(name);
314
- if (client) {
315
- client.disconnect();
316
- this.clients.delete(name);
317
- }
318
- }
319
-
320
- /**
321
- * S07: Get server status
322
- */
323
- getStatus(): Record<string, boolean> {
324
- const status: Record<string, boolean> = {};
325
- for (const [name, client] of this.clients) {
326
- status[name] = client.isConnected();
327
- }
328
- return status;
329
- }
330
- }
@@ -1,260 +0,0 @@
1
- /**
2
- * System Prompt Builder - Dynamic prompt construction
3
- */
4
-
5
- import { readFileSync, existsSync, readdirSync } from 'fs';
6
- import { join } from 'path';
7
- import type { Tool } from '../tools/types.js';
8
-
9
- export interface SystemPromptConfig {
10
- cwd?: string;
11
- tools?: Tool[];
12
- includeProjectMd?: boolean;
13
- permissionMode?: 'accept' | 'deny' | 'ask';
14
- date?: Date;
15
- skillsPrompt?: string;
16
- }
17
-
18
- /**
19
- * v3.0.0: a single named fragment of the system prompt. The provider layer
20
- * attaches cache_control markers to volatile=false segments (Anthropic) and
21
- * the fingerprint module hashes only the non-volatile portion.
22
- */
23
- export interface SystemSegment {
24
- /** Stable identifier, used in fingerprints and logging. */
25
- name: string;
26
- /** Rendered text content. May be empty string if the section was a no-op. */
27
- content: string;
28
- /**
29
- * volatile=false: content never changes between requests within a session.
30
- * volatile=true: content may change (NWT events, current time).
31
- */
32
- volatile: boolean;
33
- }
34
-
35
- export class SystemPromptBuilder {
36
- private config: Required<SystemPromptConfig>;
37
-
38
- constructor(config: SystemPromptConfig = {}) {
39
- this.config = {
40
- cwd: config.cwd ?? process.cwd(),
41
- tools: config.tools ?? [],
42
- includeProjectMd: config.includeProjectMd ?? true,
43
- permissionMode: config.permissionMode ?? 'ask',
44
- date: config.date ?? new Date(),
45
- skillsPrompt: config.skillsPrompt ?? '',
46
- };
47
- }
48
-
49
- build(): string {
50
- return this.buildSegments()
51
- .map(s => s.content)
52
- .filter(Boolean)
53
- .join('\n\n');
54
- }
55
-
56
- /**
57
- * v3.0.0: build the system prompt as named segments instead of a flat
58
- * string. The provider layer (Anthropic) can attach cache_control markers
59
- * to the segments it caches, and the fingerprint module can hash the
60
- * static portion to detect when the immutable prefix changed.
61
- *
62
- * Ordering rule (Reasonix-style): immutable prefix first, volatile tail
63
- * last. NWT history and Date move to the tail because both can change
64
- * between rounds (Date: every build; NWT: when a new event is logged).
65
- * Keeping them out of the prefix means the upstream cache keeps its
66
- * prefix checkpoint stable across those changes.
67
- *
68
- * Segments with empty content are filtered out — they neither contribute
69
- * to the string output nor to any cache fingerprint.
70
- */
71
- buildSegments(): SystemSegment[] {
72
- return [
73
- // Immutable prefix (cacheable as a single block on Anthropic)
74
- { name: 'project-instructions', content: this.buildProjectInstructions(), volatile: false },
75
- { name: 'identity', content: this.buildIdentity(), volatile: false },
76
- { name: 'tool-instructions', content: this.buildToolInstructions(), volatile: false },
77
- { name: 'environment', content: this.buildEnvironment(), volatile: false },
78
- { name: 'permission-mode', content: this.buildPermissionMode(), volatile: false },
79
- { name: 'skills', content: this.buildSkills(), volatile: false },
80
- // Volatile tail — not part of the cache prefix
81
- { name: 'nwt-history', content: this.buildNwtHistory(), volatile: true },
82
- { name: 'date-info', content: this.buildDateInfo(), volatile: true },
83
- ].filter(s => s.content);
84
- }
85
-
86
- private buildIdentity(): string {
87
- return [
88
- 'You are Thatgfsj Code, an interactive coding agent.',
89
- '',
90
- 'IMPORTANT: You MUST follow the user configuration above (CLAUDE.md, SKILLS.md, etc).',
91
- 'At the start of each task, read SKILLS.md to check for relevant skills.',
92
- '',
93
- 'Tools available: file, shell, git, search, nwt.',
94
- '',
95
- 'Rules:',
96
- '- Follow the user\'s technical preferences from their config',
97
- '- Be concise but thorough',
98
- '- Verify changes after making them',
99
- '',
100
- '## NWT Auto-Logging (IMPORTANT)',
101
- '',
102
- 'After completing any meaningful task, you MUST evaluate whether to log it to NWT.',
103
- 'Call `nwt log` automatically when ANY of these conditions are true:',
104
- '',
105
- '1. You created, modified, or deleted 2+ files',
106
- '2. You made an architectural decision (chose a library, pattern, or approach)',
107
- '3. You fixed a bug',
108
- '4. You added a new feature or capability',
109
- '5. You refactored existing code',
110
- '6. You set up a new project or module',
111
- '7. You resolved a security issue',
112
- '8. You completed a multi-step task',
113
- '',
114
- 'Do NOT log:',
115
- '- Simple file reads or searches',
116
- '- Trivial single-line changes',
117
- '- Conversational questions with no code changes',
118
- '',
119
- 'When logging, use:',
120
- ' nwt log task="<imperative verb> <what>" summary="<what was done>" files="<files>" tags="<tags>"',
121
- '',
122
- 'Tags: feature, fix, refactor, decision, milestone, test, docs, config, security, init',
123
- '',
124
- 'IMPORTANT: Log silently without mentioning it to the user unless they ask.',
125
- ].join('\n');
126
- }
127
-
128
- private buildToolInstructions(): string {
129
- const tools = this.config.tools;
130
- if (tools.length === 0) return '## Tools\n\nNo tools are currently registered.';
131
-
132
- const toolDescs = tools.map(t => {
133
- const params = t.parameters
134
- .map(p => ` - ${p.name} (${p.type}${p.required ? ', required' : ''}): ${p.description}`)
135
- .join('\n');
136
- return `### ${t.name}\n${t.description}\n\nParameters:\n${params}`;
137
- }).join('\n\n');
138
-
139
- return `## Tools\n\nYou have access to the following tools:\n\n${toolDescs}\n\nTo use a tool, respond with a tool call.`;
140
- }
141
-
142
- private buildEnvironment(): string {
143
- return `## Environment\n\nWorking directory: ${this.config.cwd}`;
144
- }
145
-
146
- private buildPermissionMode(): string {
147
- const mode = this.config.permissionMode;
148
- const explanations: Record<string, string> = {
149
- accept: 'All tool calls are automatically allowed without confirmation.',
150
- deny: 'All tool calls are blocked. You may only read and discuss.',
151
- ask: 'Dangerous or destructive commands require user confirmation before execution.',
152
- };
153
- return `## Permission Mode\n\nCurrent mode: ${mode}\n\n${explanations[mode] || ''}`;
154
- }
155
-
156
- /**
157
- * Read project instruction files (generic, works for any user)
158
- */
159
- private buildProjectInstructions(): string {
160
- if (!this.config.includeProjectMd) return '';
161
-
162
- const cwd = this.config.cwd;
163
- const home = process.env.USERPROFILE || process.env.HOME || '';
164
-
165
- // Project-level files (any project can have these)
166
- const projectFiles = [
167
- 'CLAUDE.md', '.claude.md', 'AGENTS.md',
168
- 'Codex.md', '.Codex.md', 'CODEX.md', '.codex.md',
169
- 'CONVENTIONS.md', 'CONTRIBUTING.md',
170
- ];
171
-
172
- // User-level files (in home directory)
173
- const userDirs = ['.claude', '.Codex', '.agents'];
174
- const userFiles = ['CLAUDE.md', 'SKILLS.md', 'AGENTS.md', 'CONVENTIONS.md'];
175
-
176
- const paths: string[] = [];
177
-
178
- // Project-level
179
- for (const f of projectFiles) {
180
- paths.push(join(cwd, f));
181
- }
182
-
183
- // User-level
184
- for (const dir of userDirs) {
185
- for (const f of userFiles) {
186
- paths.push(join(home, dir, f));
187
- }
188
- }
189
-
190
- const sections: string[] = [];
191
- const MAX_LEN = 3000;
192
-
193
- for (const path of paths) {
194
- if (existsSync(path)) {
195
- try {
196
- let content = readFileSync(path, 'utf-8').trim();
197
- if (content) {
198
- const filename = path.split(/[/\\]/).pop();
199
- if (content.length > MAX_LEN) {
200
- content = content.slice(0, MAX_LEN) + '\n... (truncated)';
201
- }
202
- sections.push(`[${filename}]\n${content}`);
203
- }
204
- } catch {}
205
- }
206
- }
207
-
208
- if (sections.length === 0) return '';
209
- return `## User Configuration\n\n${sections.join('\n\n')}`;
210
- }
211
-
212
- /**
213
- * Auto-inject recent NWT history into system prompt
214
- */
215
- private buildNwtHistory(): string {
216
- try {
217
- const nwtDir = join(this.config.cwd, '.nwt', 'events');
218
- if (!existsSync(nwtDir)) return '';
219
-
220
- const files = readdirSync(nwtDir)
221
- .filter(f => f.endsWith('.json'))
222
- .sort()
223
- .slice(-10); // Last 10 events
224
-
225
- if (files.length === 0) return '';
226
-
227
- const events = files.map(f => {
228
- try {
229
- const ev = JSON.parse(readFileSync(join(nwtDir, f), 'utf-8'));
230
- const time = ev.timestamp?.split('T')[0] || '';
231
- const files = ev.files?.length > 0 ? ` [${ev.files.join(', ')}]` : '';
232
- return `- [${ev.id}] ${time} ${ev.task}: ${ev.summary}${files}`;
233
- } catch {
234
- return null;
235
- }
236
- }).filter(Boolean);
237
-
238
- if (events.length === 0) return '';
239
-
240
- return `## Project History (NWT)\n\nRecent evolution events:\n\n${events.join('\n')}`;
241
- } catch {
242
- return '';
243
- }
244
- }
245
-
246
- private buildSkills(): string {
247
- if (!this.config.skillsPrompt) return '';
248
- return `## Active Skills\n\n${this.config.skillsPrompt}`;
249
- }
250
-
251
- private buildDateInfo(): string {
252
- const now = this.config.date;
253
- const iso = now.toISOString().replace('T', ' ').split('.')[0];
254
- return `## Current Time\n\n${iso}`;
255
- }
256
-
257
- setTools(tools: Tool[]): this { this.config.tools = tools; return this; }
258
- setCwd(cwd: string): this { this.config.cwd = cwd; return this; }
259
- setPermissionMode(mode: 'accept' | 'deny' | 'ask'): this { this.config.permissionMode = mode; return this; }
260
- }
@@ -1,103 +0,0 @@
1
- /**
2
- * Context Compactor - Progressive compression for conversation history
3
- * Migrated from old src/core/context-compactor.ts
4
- *
5
- * Strategy:
6
- * 1. Within limit: no compression
7
- * 2. Beyond limit: keep system + recent N turns + summarize middle
8
- */
9
-
10
- import type { ChatMessage } from '../types.js';
11
-
12
- export interface CompactorConfig {
13
- /** Max messages before compression kicks in */
14
- maxMessages?: number;
15
- /** How many recent turns to always preserve */
16
- preserveRecent?: number;
17
- }
18
-
19
- export interface CompressionResult {
20
- originalCount: number;
21
- compactedCount: number;
22
- removedCount: number;
23
- }
24
-
25
- export class ContextCompactor {
26
- private maxMessages: number;
27
- private preserveRecent: number;
28
-
29
- constructor(config: CompactorConfig = {}) {
30
- this.maxMessages = config.maxMessages ?? 50;
31
- this.preserveRecent = config.preserveRecent ?? 10;
32
- }
33
-
34
- /**
35
- * Compact messages if needed
36
- */
37
- compact(messages: ChatMessage[]): { compacted: ChatMessage[]; result: CompressionResult } {
38
- if (messages.length <= this.maxMessages) {
39
- return {
40
- compacted: messages,
41
- result: { originalCount: messages.length, compactedCount: messages.length, removedCount: 0 },
42
- };
43
- }
44
-
45
- const systemMsg = messages.find(m => m.role === 'system');
46
- const others = messages.filter(m => m.role !== 'system');
47
-
48
- if (others.length <= this.preserveRecent) {
49
- return {
50
- compacted: messages,
51
- result: { originalCount: messages.length, compactedCount: messages.length, removedCount: 0 },
52
- };
53
- }
54
-
55
- // Keep recent N turns
56
- const recent = others.slice(-this.preserveRecent);
57
-
58
- // Summarize the middle portion
59
- const middle = others.slice(0, -this.preserveRecent);
60
- const summary: ChatMessage = {
61
- role: 'system',
62
- content: `[Earlier conversation summary: ${middle.length} messages covering ${this.extractTopics(middle)}]`,
63
- };
64
-
65
- const compacted = systemMsg
66
- ? [systemMsg, summary, ...recent]
67
- : [summary, ...recent];
68
-
69
- return {
70
- compacted,
71
- result: {
72
- originalCount: messages.length,
73
- compactedCount: compacted.length,
74
- removedCount: messages.length - compacted.length,
75
- },
76
- };
77
- }
78
-
79
- /**
80
- * Auto-compact if messages exceed limit
81
- */
82
- autoCompact(messages: ChatMessage[]): { compacted: ChatMessage[]; result: CompressionResult } {
83
- return this.compact(messages);
84
- }
85
-
86
- /**
87
- * Estimate token count (rough: ~4 chars per token)
88
- */
89
- estimateTokens(messages: ChatMessage[]): number {
90
- return messages.reduce((sum, m) => sum + Math.ceil(m.content.length / 4) + 10, 0);
91
- }
92
-
93
- /**
94
- * Extract topic summary from messages
95
- */
96
- private extractTopics(msgs: ChatMessage[]): string {
97
- const topics = msgs
98
- .filter(m => m.role === 'user')
99
- .map(m => m.content.slice(0, 30))
100
- .slice(0, 3);
101
- return topics.join(', ') || 'various topics';
102
- }
103
- }