thatgfsj-code 3.0.3 → 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 (212) 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/cache/smartModel.d.ts +29 -22
  7. package/dist/cache/smartModel.d.ts.map +1 -1
  8. package/dist/cache/smartModel.js +33 -40
  9. package/dist/cache/smartModel.js.map +1 -1
  10. package/dist/cmd/index.d.ts +12 -20
  11. package/dist/cmd/index.d.ts.map +1 -1
  12. package/dist/cmd/index.js +192 -146
  13. package/dist/cmd/index.js.map +1 -1
  14. package/dist/config/index.d.ts +4 -0
  15. package/dist/config/index.d.ts.map +1 -1
  16. package/dist/config/index.js +9 -6
  17. package/dist/config/index.js.map +1 -1
  18. package/dist/config/types.d.ts +5 -0
  19. package/dist/config/types.d.ts.map +1 -1
  20. package/dist/llm/anthropic.d.ts +1 -1
  21. package/dist/llm/anthropic.d.ts.map +1 -1
  22. package/dist/llm/anthropic.js +116 -84
  23. package/dist/llm/anthropic.js.map +1 -1
  24. package/dist/llm/gemini.d.ts +27 -3
  25. package/dist/llm/gemini.d.ts.map +1 -1
  26. package/dist/llm/gemini.js +196 -73
  27. package/dist/llm/gemini.js.map +1 -1
  28. package/dist/llm/index.d.ts +20 -1
  29. package/dist/llm/index.d.ts.map +1 -1
  30. package/dist/llm/index.js +71 -10
  31. package/dist/llm/index.js.map +1 -1
  32. package/dist/llm/openai.d.ts +5 -1
  33. package/dist/llm/openai.d.ts.map +1 -1
  34. package/dist/llm/openai.js +96 -56
  35. package/dist/llm/openai.js.map +1 -1
  36. package/dist/mcp/client.d.ts +84 -34
  37. package/dist/mcp/client.d.ts.map +1 -1
  38. package/dist/mcp/client.js +291 -126
  39. package/dist/mcp/client.js.map +1 -1
  40. package/dist/session/compactor.d.ts +27 -7
  41. package/dist/session/compactor.d.ts.map +1 -1
  42. package/dist/session/compactor.js +78 -23
  43. package/dist/session/compactor.js.map +1 -1
  44. package/dist/session/index.d.ts +96 -33
  45. package/dist/session/index.d.ts.map +1 -1
  46. package/dist/session/index.js +278 -60
  47. package/dist/session/index.js.map +1 -1
  48. package/dist/tools/file.d.ts +10 -2
  49. package/dist/tools/file.d.ts.map +1 -1
  50. package/dist/tools/file.js +54 -5
  51. package/dist/tools/file.js.map +1 -1
  52. package/dist/tools/git.d.ts +16 -2
  53. package/dist/tools/git.d.ts.map +1 -1
  54. package/dist/tools/git.js +61 -18
  55. package/dist/tools/git.js.map +1 -1
  56. package/dist/tools/nwt.d.ts.map +1 -1
  57. package/dist/tools/nwt.js +8 -1
  58. package/dist/tools/nwt.js.map +1 -1
  59. package/dist/tools/search.d.ts +8 -1
  60. package/dist/tools/search.d.ts.map +1 -1
  61. package/dist/tools/search.js +105 -34
  62. package/dist/tools/search.js.map +1 -1
  63. package/dist/tools/shell.d.ts +0 -4
  64. package/dist/tools/shell.d.ts.map +1 -1
  65. package/dist/tools/shell.js +11 -26
  66. package/dist/tools/shell.js.map +1 -1
  67. package/dist/tools/types.d.ts +7 -0
  68. package/dist/tools/types.d.ts.map +1 -1
  69. package/dist/tools/types.js.map +1 -1
  70. package/dist/tui/app.d.ts.map +1 -1
  71. package/dist/tui/app.js +90 -18
  72. package/dist/tui/app.js.map +1 -1
  73. package/dist/tui/components/ChatMessage.d.ts.map +1 -1
  74. package/dist/tui/components/ChatMessage.js +9 -2
  75. package/dist/tui/components/ChatMessage.js.map +1 -1
  76. package/dist/tui/components/ConfirmPrompt.d.ts +21 -0
  77. package/dist/tui/components/ConfirmPrompt.d.ts.map +1 -0
  78. package/dist/tui/components/ConfirmPrompt.js +45 -0
  79. package/dist/tui/components/ConfirmPrompt.js.map +1 -0
  80. package/dist/tui/components/Header.d.ts +10 -5
  81. package/dist/tui/components/Header.d.ts.map +1 -1
  82. package/dist/tui/components/Header.js +13 -5
  83. package/dist/tui/components/Header.js.map +1 -1
  84. package/dist/tui/components/InitWizard.d.ts.map +1 -1
  85. package/dist/tui/components/InitWizard.js +8 -13
  86. package/dist/tui/components/InitWizard.js.map +1 -1
  87. package/dist/tui/components/StatusBar.d.ts +7 -0
  88. package/dist/tui/components/StatusBar.d.ts.map +1 -1
  89. package/dist/tui/components/StatusBar.js +8 -3
  90. package/dist/tui/components/StatusBar.js.map +1 -1
  91. package/dist/tui/components/Thinking.d.ts +5 -0
  92. package/dist/tui/components/Thinking.d.ts.map +1 -1
  93. package/dist/tui/components/Thinking.js +24 -2
  94. package/dist/tui/components/Thinking.js.map +1 -1
  95. package/dist/tui/components/ToolCall.d.ts +6 -1
  96. package/dist/tui/components/ToolCall.d.ts.map +1 -1
  97. package/dist/tui/components/ToolCall.js +50 -22
  98. package/dist/tui/components/ToolCall.js.map +1 -1
  99. package/dist/tui/components/UserInput.d.ts +5 -0
  100. package/dist/tui/components/UserInput.d.ts.map +1 -1
  101. package/dist/tui/components/UserInput.js +9 -13
  102. package/dist/tui/components/UserInput.js.map +1 -1
  103. package/dist/tui/hooks/useChat.d.ts +1 -0
  104. package/dist/tui/hooks/useChat.d.ts.map +1 -1
  105. package/dist/tui/hooks/useChat.js +36 -16
  106. package/dist/tui/hooks/useChat.js.map +1 -1
  107. package/dist/tui/hooks/useCommands.d.ts +7 -1
  108. package/dist/tui/hooks/useCommands.d.ts.map +1 -1
  109. package/dist/tui/hooks/useCommands.js +83 -49
  110. package/dist/tui/hooks/useCommands.js.map +1 -1
  111. package/dist/tui/theme.d.ts +34 -0
  112. package/dist/tui/theme.d.ts.map +1 -0
  113. package/dist/tui/theme.js +34 -0
  114. package/dist/tui/theme.js.map +1 -0
  115. package/dist/tui/welcome.d.ts.map +1 -1
  116. package/dist/tui/welcome.js +9 -7
  117. package/dist/tui/welcome.js.map +1 -1
  118. package/dist/types.d.ts +16 -1
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/utils/diff.d.ts +5 -0
  121. package/dist/utils/diff.d.ts.map +1 -1
  122. package/dist/utils/diff.js +15 -0
  123. package/dist/utils/diff.js.map +1 -1
  124. package/dist/version.d.ts +12 -0
  125. package/dist/version.d.ts.map +1 -0
  126. package/dist/version.js +29 -0
  127. package/dist/version.js.map +1 -0
  128. package/package.json +19 -7
  129. package/CHANGELOG.md +0 -192
  130. package/DEVELOPMENT.md +0 -286
  131. package/ROADMAP.md +0 -91
  132. package/docs/API_KEY_GUIDE.md +0 -236
  133. package/docs/FAQ.md +0 -182
  134. package/install.bat +0 -63
  135. package/install.ps1 +0 -238
  136. package/install.sh +0 -113
  137. package/src/app/index.ts +0 -197
  138. package/src/cache/fingerprint.ts +0 -101
  139. package/src/cache/index.ts +0 -21
  140. package/src/cache/smartModel.ts +0 -145
  141. package/src/cache/stats.ts +0 -199
  142. package/src/cache/volatile.ts +0 -47
  143. package/src/cmd/index.tsx +0 -288
  144. package/src/config/index.ts +0 -158
  145. package/src/config/providers.ts +0 -234
  146. package/src/config/types.ts +0 -71
  147. package/src/hooks/index.ts +0 -111
  148. package/src/llm/anthropic.ts +0 -413
  149. package/src/llm/gemini.ts +0 -169
  150. package/src/llm/index.ts +0 -302
  151. package/src/llm/openai.ts +0 -243
  152. package/src/llm/provider.ts +0 -71
  153. package/src/mcp/client.ts +0 -330
  154. package/src/prompts/index.ts +0 -260
  155. package/src/session/compactor.ts +0 -103
  156. package/src/session/index.ts +0 -181
  157. package/src/session/message.ts +0 -42
  158. package/src/skills/brainstorming.ts +0 -43
  159. package/src/skills/code-review.ts +0 -45
  160. package/src/skills/executing-plans.ts +0 -27
  161. package/src/skills/frontend-design.ts +0 -35
  162. package/src/skills/git-workflow.ts +0 -36
  163. package/src/skills/improve-architecture.ts +0 -38
  164. package/src/skills/index.ts +0 -136
  165. package/src/skills/neuroweave.ts +0 -47
  166. package/src/skills/playwright.ts +0 -72
  167. package/src/skills/prototype.ts +0 -30
  168. package/src/skills/subagent.ts +0 -28
  169. package/src/skills/supabase.ts +0 -44
  170. package/src/skills/systematic-debugging.ts +0 -44
  171. package/src/skills/tdd.ts +0 -39
  172. package/src/skills/triage.ts +0 -35
  173. package/src/skills/verification.ts +0 -31
  174. package/src/skills/writing-plans.ts +0 -42
  175. package/src/tools/file.ts +0 -143
  176. package/src/tools/git.ts +0 -132
  177. package/src/tools/index.ts +0 -111
  178. package/src/tools/nwt.ts +0 -598
  179. package/src/tools/search.ts +0 -263
  180. package/src/tools/shell.ts +0 -136
  181. package/src/tools/types.ts +0 -122
  182. package/src/tui/app.tsx +0 -159
  183. package/src/tui/components/ChatList.tsx +0 -41
  184. package/src/tui/components/ChatMessage.tsx +0 -54
  185. package/src/tui/components/Header.tsx +0 -66
  186. package/src/tui/components/InitWizard.tsx +0 -251
  187. package/src/tui/components/Markdown.tsx +0 -35
  188. package/src/tui/components/ModelSelector.tsx +0 -107
  189. package/src/tui/components/StatusBar.tsx +0 -30
  190. package/src/tui/components/Thinking.tsx +0 -17
  191. package/src/tui/components/ToolCall.tsx +0 -102
  192. package/src/tui/components/UserInput.tsx +0 -151
  193. package/src/tui/hooks/useChat.ts +0 -287
  194. package/src/tui/hooks/useCommands.ts +0 -273
  195. package/src/tui/index.ts +0 -6
  196. package/src/tui/welcome.ts +0 -177
  197. package/src/types.ts +0 -104
  198. package/src/utils/diff.ts +0 -71
  199. package/src/utils/project.ts +0 -99
  200. package/src/utils/stableStringify.ts +0 -47
  201. package/src/utils/thinking.ts +0 -119
  202. package/tests/cache/fingerprint.test.ts +0 -75
  203. package/tests/cache/providerCatalog.test.ts +0 -31
  204. package/tests/cache/smartModel.test.ts +0 -112
  205. package/tests/cache/stableStringify.test.ts +0 -43
  206. package/tests/cache/stats.test.ts +0 -146
  207. package/tests/cache/volatile.test.ts +0 -75
  208. package/tests/smoke-pollution.mjs +0 -78
  209. package/tests/smoke-thinking.mjs +0 -110
  210. package/tests/smoke-tool-stream.mjs +0 -69
  211. package/tests/smoke-tool.mjs +0 -117
  212. package/tsconfig.json +0 -21
@@ -1,111 +0,0 @@
1
- /**
2
- * Tool Registry - Manages available tools
3
- * Migrated from old src/core/tool-registry.ts + src/tools/index.ts
4
- */
5
-
6
- import type { Tool, ToolResult, ToolContext, ToolInputSchema } from './types.js';
7
- import { FileTool } from './file.js';
8
- import { ShellTool } from './shell.js';
9
- import { GitTool } from './git.js';
10
- import { SearchTool } from './search.js';
11
- import { NwtTool } from './nwt.js';
12
-
13
- export class ToolRegistry {
14
- private tools: Map<string, Tool> = new Map();
15
- private context: ToolContext = {};
16
-
17
- constructor() {
18
- this.registerDefaults();
19
- }
20
-
21
- /**
22
- * Register default built-in tools
23
- */
24
- private registerDefaults(): void {
25
- this.register(new FileTool());
26
- this.register(new ShellTool());
27
- this.register(new GitTool());
28
- this.register(new SearchTool());
29
- this.register(new NwtTool());
30
- }
31
-
32
- setContext(ctx: Partial<ToolContext>): void {
33
- this.context = { ...this.context, ...ctx };
34
- }
35
-
36
- register(tool: Tool): void {
37
- this.tools.set(tool.name, tool);
38
- }
39
-
40
- unregister(name: string): boolean {
41
- return this.tools.delete(name);
42
- }
43
-
44
- get(name: string): Tool | undefined {
45
- return this.tools.get(name);
46
- }
47
-
48
- list(): Tool[] {
49
- return [...this.tools.values()];
50
- }
51
-
52
- listNames(): string[] {
53
- return [...this.tools.keys()];
54
- }
55
-
56
- /**
57
- * Get tools formatted for AI API
58
- */
59
- getToolsForAPI(): Array<{ type: 'function'; function: any }> {
60
- return [...this.tools.values()].map(tool => ({
61
- type: 'function' as const,
62
- function: {
63
- name: tool.name,
64
- description: tool.description,
65
- parameters: tool.inputSchema || this.buildInputSchema(tool),
66
- },
67
- }));
68
- }
69
-
70
- /**
71
- * Build JSON Schema from legacy parameters array
72
- */
73
- private buildInputSchema(tool: Tool): ToolInputSchema {
74
- return {
75
- type: 'object',
76
- properties: tool.parameters.reduce((acc, p) => {
77
- acc[p.name] = {
78
- type: p.type === 'number' ? 'number' : p.type === 'boolean' ? 'boolean' : 'string',
79
- description: p.description,
80
- };
81
- return acc;
82
- }, {} as Record<string, { type: string; description: string }>),
83
- required: tool.parameters.filter(p => p.required).map(p => p.name),
84
- };
85
- }
86
-
87
- /**
88
- * Execute a tool by name
89
- */
90
- async execute(name: string, params: Record<string, any>): Promise<ToolResult> {
91
- const tool = this.tools.get(name);
92
- if (!tool) {
93
- return { success: false, error: `Tool "${name}" not found` };
94
- }
95
-
96
- try {
97
- return await tool.execute(params, this.context);
98
- } catch (error: any) {
99
- return { success: false, error: error.message };
100
- }
101
- }
102
- }
103
-
104
- // Re-export types and tools
105
- export type { Tool, ToolResult, ToolContext, ToolParameter, ToolInputSchema, ToolMetadata } from './types.js';
106
- export { ToolBuilder } from './types.js';
107
- export { FileTool } from './file.js';
108
- export { ShellTool } from './shell.js';
109
- export { GitTool } from './git.js';
110
- export { SearchTool } from './search.js';
111
- export { NwtTool } from './nwt.js';
package/src/tools/nwt.ts DELETED
@@ -1,598 +0,0 @@
1
- /**
2
- * NeuroWeave Timeline (NWT) Tool - Project evolution memory
3
- * Built-in port of https://github.com/Thatgfsj/neuroweave-timeline
4
- *
5
- * Stores meaningful actions (decisions, refactors, bug fixes) as timeline events
6
- * in .nwt/ directory. Supports monthly archives (max 1 month per archive).
7
- */
8
-
9
- import type { Tool, ToolResult } from './types.js';
10
- import {
11
- readFileSync, writeFileSync, existsSync, mkdirSync,
12
- readdirSync, renameSync, unlinkSync
13
- } from 'fs';
14
- import { join } from 'path';
15
-
16
- interface TimelineEvent {
17
- id: string;
18
- timestamp: string;
19
- task: string;
20
- summary: string;
21
- reason?: string;
22
- files: string[];
23
- tags: string[];
24
- parent?: string;
25
- importance: 'low' | 'normal' | 'high' | 'milestone';
26
- }
27
-
28
- const NWT_DIR = '.nwt';
29
- const EVENTS_DIR = 'events';
30
- const ARCHIVE_DIR = 'archives';
31
- const MAX_EVENTS = 500;
32
- const ARCHIVE_AFTER_DAYS = 30;
33
-
34
- export class NwtTool implements Tool {
35
- name = 'nwt';
36
- description = `NeuroWeave Timeline - Project evolution memory.
37
- Actions: init, log, history, search, story, explain, archive, diff, compact
38
-
39
- AUTO-LOG TRIGGERS (call nwt log silently when):
40
- - Created, modified, or deleted 2+ files
41
- - Made an architectural decision (chose a library, pattern, or approach)
42
- - Fixed a bug
43
- - Added a new feature or capability
44
- - Refactored existing code
45
- - Set up a new project or module
46
- - Resolved a security issue
47
- - Completed a multi-step task
48
-
49
- DO NOT log:
50
- - Simple file reads or searches
51
- - Trivial single-line changes
52
- - Conversational questions with no code changes`;
53
-
54
- inputSchema = {
55
- type: 'object' as const,
56
- properties: {
57
- action: {
58
- type: 'string',
59
- description: 'Action: init, log, history, search, story, explain, archive, diff, compact',
60
- },
61
- task: { type: 'string', description: 'Short imperative title (for log)' },
62
- summary: { type: 'string', description: 'What was done (for log)' },
63
- reason: { type: 'string', description: 'Why it was done (for log)' },
64
- files: { type: 'string', description: 'Comma-separated file paths (for log)' },
65
- tags: { type: 'string', description: 'Comma-separated tags (for log)' },
66
- importance: { type: 'string', description: 'Importance: low, normal, high, milestone (for log)' },
67
- query: { type: 'string', description: 'Search query (for search/explain)' },
68
- limit: { type: 'number', description: 'Max results (for history/search)' },
69
- from_id: { type: 'string', description: 'Start event ID (for diff)' },
70
- to_id: { type: 'string', description: 'End event ID (for diff)' },
71
- },
72
- required: ['action'],
73
- };
74
-
75
- metadata = {
76
- permissions: ['read', 'write'] as ('read' | 'write' | 'execute' | 'network')[],
77
- tags: ['nwt', 'timeline', 'memory', 'history'],
78
- };
79
-
80
- parameters = [
81
- { name: 'action', type: 'string', description: 'Action to perform', required: true },
82
- { name: 'task', type: 'string', description: 'Short title', required: false },
83
- { name: 'summary', type: 'string', description: 'What was done', required: false },
84
- { name: 'reason', type: 'string', description: 'Why it was done', required: false },
85
- { name: 'files', type: 'string', description: 'Comma-separated file paths', required: false },
86
- { name: 'tags', type: 'string', description: 'Comma-separated tags', required: false },
87
- { name: 'importance', type: 'string', description: 'low/normal/high/milestone', required: false },
88
- { name: 'query', type: 'string', description: 'Search query', required: false },
89
- { name: 'limit', type: 'number', description: 'Max results', required: false },
90
- { name: 'from_id', type: 'string', description: 'Start event ID for diff', required: false },
91
- { name: 'to_id', type: 'string', description: 'End event ID for diff', required: false },
92
- ];
93
-
94
- async execute(params: Record<string, any>, ctx?: any): Promise<ToolResult> {
95
- const cwd = ctx?.workingDirectory || process.cwd();
96
- const action = params.action;
97
-
98
- try {
99
- switch (action) {
100
- case 'init':
101
- return this.init(cwd);
102
- case 'log':
103
- return this.log(cwd, params);
104
- case 'history':
105
- return this.history(cwd, params.limit || 20);
106
- case 'search':
107
- return this.search(cwd, params.query || '', params.limit || 20);
108
- case 'story':
109
- return this.story(cwd);
110
- case 'explain':
111
- return this.explain(cwd, params.query || '');
112
- case 'archive':
113
- return this.archive(cwd);
114
- case 'diff':
115
- return this.diff(cwd, params.from_id || '', params.to_id || '');
116
- case 'compact':
117
- return this.compact(cwd);
118
- default:
119
- return { success: false, error: `Unknown action: ${action}` };
120
- }
121
- } catch (error: any) {
122
- return { success: false, error: error.message };
123
- }
124
- }
125
-
126
- // ── Init ──────────────────────────────────────────────
127
-
128
- private init(cwd: string): ToolResult {
129
- const nwtDir = join(cwd, NWT_DIR);
130
- const eventsDir = join(nwtDir, EVENTS_DIR);
131
- const archiveDir = join(nwtDir, ARCHIVE_DIR);
132
-
133
- if (!existsSync(nwtDir)) mkdirSync(nwtDir, { recursive: true });
134
- if (!existsSync(eventsDir)) mkdirSync(eventsDir, { recursive: true });
135
- if (!existsSync(archiveDir)) mkdirSync(archiveDir, { recursive: true });
136
-
137
- // Auto-archive events older than 30 days on every init
138
- this.autoArchiveIfNeeded(cwd);
139
-
140
- // Write metadata
141
- const meta = {
142
- version: '2.0.0',
143
- created: new Date().toISOString(),
144
- description: 'NeuroWeave Timeline - Project evolution memory',
145
- };
146
- writeFileSync(join(nwtDir, 'meta.json'), JSON.stringify(meta, null, 2));
147
-
148
- return { success: true, output: 'NWT initialized in .nwt/' };
149
- }
150
-
151
- // ── Log Event ─────────────────────────────────────────
152
-
153
- private log(cwd: string, params: Record<string, any>): ToolResult {
154
- const nwtDir = join(cwd, NWT_DIR);
155
- if (!existsSync(nwtDir)) {
156
- this.init(cwd);
157
- }
158
-
159
- const { task, summary, reason, files, tags, importance } = params;
160
- if (!task || !summary) {
161
- return { success: false, error: 'task and summary are required' };
162
- }
163
-
164
- // Get next ID
165
- const eventsDir = join(nwtDir, EVENTS_DIR);
166
- const existing = existsSync(eventsDir)
167
- ? readdirSync(eventsDir).filter(f => f.endsWith('.json'))
168
- : [];
169
- const nextId = (existing.length + 1).toString().padStart(6, '0');
170
-
171
- // Get parent (last event)
172
- const parent = existing.length > 0
173
- ? existing.sort().pop()?.replace('.json', '')
174
- : undefined;
175
-
176
- // Validate importance
177
- const validImportance = ['low', 'normal', 'high', 'milestone'];
178
- const eventImportance = validImportance.includes(importance) ? importance : 'normal';
179
-
180
- const event: TimelineEvent = {
181
- id: nextId,
182
- timestamp: new Date().toISOString(),
183
- task: task.trim(),
184
- summary: summary.trim(),
185
- reason: reason?.trim() || undefined,
186
- files: files ? files.split(',').map((f: string) => f.trim()).filter(Boolean) : [],
187
- tags: tags ? tags.split(',').map((t: string) => t.trim().toLowerCase()).filter(Boolean) : [],
188
- parent: parent || undefined,
189
- importance: eventImportance as TimelineEvent['importance'],
190
- };
191
-
192
- writeFileSync(
193
- join(eventsDir, `${nextId}.json`),
194
- JSON.stringify(event, null, 2)
195
- );
196
-
197
- // Auto-archive if too many events
198
- if (existing.length >= MAX_EVENTS) {
199
- this.archive(cwd);
200
- }
201
-
202
- return {
203
- success: true,
204
- output: `Event [${nextId}] logged: ${event.task}`,
205
- };
206
- }
207
-
208
- // ── History ───────────────────────────────────────────
209
-
210
- private history(cwd: string, limit: number): ToolResult {
211
- const events = this.loadEvents(cwd);
212
- if (events.length === 0) {
213
- return { success: true, output: 'No events yet. Use nwt log to start tracking.' };
214
- }
215
-
216
- const recent = events.slice(-limit);
217
- const lines = recent.map(e => {
218
- const time = e.timestamp.split('T')[0];
219
- const files = e.files.length > 0 ? ` [${e.files.join(', ')}]` : '';
220
- const tags = e.tags.length > 0 ? ` {${e.tags.join(', ')}}` : '';
221
- const imp = e.importance && e.importance !== 'normal' ? ` (${e.importance})` : '';
222
- return `[${e.id}] ${time} ${e.task}${imp}${files}${tags}`;
223
- });
224
-
225
- return { success: true, output: lines.join('\n') };
226
- }
227
-
228
- // ── Search ────────────────────────────────────────────
229
-
230
- private search(cwd: string, query: string, limit: number): ToolResult {
231
- if (!query) return { success: false, error: 'query is required' };
232
-
233
- const events = this.loadEvents(cwd);
234
- const q = query.toLowerCase();
235
- const matches = events.filter(e =>
236
- e.task.toLowerCase().includes(q) ||
237
- e.summary.toLowerCase().includes(q) ||
238
- (e.reason || '').toLowerCase().includes(q) ||
239
- e.files.some(f => f.toLowerCase().includes(q)) ||
240
- e.tags.some(t => t.includes(q))
241
- ).slice(-limit);
242
-
243
- if (matches.length === 0) {
244
- return { success: true, output: `No events matching "${query}"` };
245
- }
246
-
247
- const lines = matches.map(e => {
248
- const time = e.timestamp.split('T')[0];
249
- return `[${e.id}] ${time} ${e.task}\n ${e.summary}`;
250
- });
251
-
252
- return { success: true, output: lines.join('\n\n') };
253
- }
254
-
255
- // ── Story ─────────────────────────────────────────────
256
-
257
- private story(cwd: string): ToolResult {
258
- const events = this.loadEvents(cwd);
259
- if (events.length === 0) {
260
- return { success: true, output: 'No events yet.' };
261
- }
262
-
263
- // Group by importance
264
- const milestones = events.filter(e => e.importance === 'milestone' || e.tags.includes('milestone'));
265
- const highImportance = events.filter(e => e.importance === 'high');
266
- const decisions = events.filter(e => e.tags.includes('decision'));
267
- const totalFiles = new Set(events.flatMap(e => e.files)).size;
268
-
269
- const lines = [
270
- `Project Story: ${events.length} events, ${totalFiles} files touched`,
271
- '',
272
- ];
273
-
274
- if (milestones.length > 0) {
275
- lines.push('Milestones:');
276
- for (const m of milestones.slice(-5)) {
277
- lines.push(` [${m.id}] ${m.task} - ${m.summary}`);
278
- }
279
- lines.push('');
280
- }
281
-
282
- if (highImportance.length > 0) {
283
- lines.push('High Importance:');
284
- for (const h of highImportance.slice(-5)) {
285
- lines.push(` [${h.id}] ${h.task} - ${h.summary}`);
286
- }
287
- lines.push('');
288
- }
289
-
290
- if (decisions.length > 0) {
291
- lines.push('Key Decisions:');
292
- for (const d of decisions.slice(-5)) {
293
- lines.push(` [${d.id}] ${d.task}${d.reason ? ` - ${d.reason}` : ''}`);
294
- }
295
- lines.push('');
296
- }
297
-
298
- lines.push('Recent Activity:');
299
- for (const e of events.slice(-5)) {
300
- const time = e.timestamp.split('T')[0];
301
- const imp = e.importance && e.importance !== 'normal' ? ` (${e.importance})` : '';
302
- lines.push(` ${time} ${e.task}${imp}`);
303
- }
304
-
305
- return { success: true, output: lines.join('\n') };
306
- }
307
-
308
- // ── Explain File ──────────────────────────────────────
309
-
310
- private explain(cwd: string, filePath: string): ToolResult {
311
- if (!filePath) return { success: false, error: 'file path is required' };
312
-
313
- const events = this.loadEvents(cwd);
314
- const matches = events.filter(e =>
315
- e.files.some(f => f === filePath || f.endsWith(filePath))
316
- );
317
-
318
- if (matches.length === 0) {
319
- return { success: true, output: `No history for "${filePath}"` };
320
- }
321
-
322
- const lines = matches.map(e => {
323
- const time = e.timestamp.split('T')[0];
324
- return `[${e.id}] ${time} ${e.task}\n ${e.summary}${e.reason ? `\n Reason: ${e.reason}` : ''}`;
325
- });
326
-
327
- return { success: true, output: `History of ${filePath}:\n\n${lines.join('\n\n')}` };
328
- }
329
-
330
- // ── Diff ──────────────────────────────────────────────
331
-
332
- private diff(cwd: string, fromId: string, toId: string): ToolResult {
333
- if (!fromId || !toId) {
334
- return { success: false, error: 'from_id and to_id are required' };
335
- }
336
-
337
- const events = this.loadEvents(cwd);
338
- const fromEvent = events.find(e => e.id === fromId.padStart(6, '0'));
339
- const toEvent = events.find(e => e.id === toId.padStart(6, '0'));
340
-
341
- if (!fromEvent) return { success: false, error: `Event ${fromId} not found` };
342
- if (!toEvent) return { success: false, error: `Event ${toId} not found` };
343
-
344
- // Get events between from and to
345
- const fromIdx = events.indexOf(fromEvent);
346
- const toIdx = events.indexOf(toEvent);
347
- const between = events.slice(fromIdx, toIdx + 1);
348
-
349
- // Collect all files touched
350
- const allFiles = new Set(between.flatMap(e => e.files));
351
- const fromFiles = new Set(fromEvent.files);
352
- const toFiles = new Set(toEvent.files);
353
-
354
- // Files added/removed/modified
355
- const added = [...toFiles].filter(f => !fromFiles.has(f));
356
- const removed = [...fromFiles].filter(f => !toFiles.has(f));
357
- const modified = [...allFiles].filter(f => fromFiles.has(f) && toFiles.has(f));
358
-
359
- const lines = [
360
- `Diff: [${fromEvent.id}] → [${toEvent.id}]`,
361
- `Events: ${between.length} between these points`,
362
- '',
363
- ];
364
-
365
- if (added.length > 0) lines.push(`Added: ${added.join(', ')}`);
366
- if (removed.length > 0) lines.push(`Removed: ${removed.join(', ')}`);
367
- if (modified.length > 0) lines.push(`Modified: ${modified.join(', ')}`);
368
-
369
- lines.push('', 'Events in range:');
370
- for (const e of between) {
371
- const time = e.timestamp.split('T')[0];
372
- lines.push(` [${e.id}] ${time} ${e.task}`);
373
- }
374
-
375
- return { success: true, output: lines.join('\n') };
376
- }
377
-
378
- // ── Compact ───────────────────────────────────────────
379
-
380
- private compact(cwd: string): ToolResult {
381
- const events = this.loadEvents(cwd);
382
- if (events.length < 10) {
383
- return { success: true, output: 'Not enough events to compact (need 10+).' };
384
- }
385
-
386
- const nwtDir = join(cwd, NWT_DIR);
387
- const eventsDir = join(nwtDir, EVENTS_DIR);
388
-
389
- // Group consecutive events with same tags
390
- const groups: TimelineEvent[][] = [];
391
- let currentGroup: TimelineEvent[] = [events[0]];
392
-
393
- for (let i = 1; i < events.length; i++) {
394
- const prev = events[i - 1];
395
- const curr = events[i];
396
-
397
- // Same tags and close in time (within 1 hour)
398
- const sameTags = curr.tags.join(',') === prev.tags.join(',');
399
- const timeDiff = new Date(curr.timestamp).getTime() - new Date(prev.timestamp).getTime();
400
- const closeInTime = timeDiff < 3600000; // 1 hour
401
-
402
- if (sameTags && closeInTime) {
403
- currentGroup.push(curr);
404
- } else {
405
- groups.push(currentGroup);
406
- currentGroup = [curr];
407
- }
408
- }
409
- groups.push(currentGroup);
410
-
411
- // Merge groups with 3+ events
412
- let merged = 0;
413
- const newEvents: TimelineEvent[] = [];
414
-
415
- for (const group of groups) {
416
- if (group.length >= 3) {
417
- // Merge into one summary event
418
- const first = group[0];
419
- const last = group[group.length - 1];
420
- const allFiles = [...new Set(group.flatMap(e => e.files))];
421
- const allTags = [...new Set(group.flatMap(e => e.tags))];
422
-
423
- newEvents.push({
424
- id: first.id,
425
- timestamp: first.timestamp,
426
- task: `${first.task} ... ${last.task}`,
427
- summary: `Compacted ${group.length} events: ${group.map(e => e.task).join(', ')}`,
428
- files: allFiles,
429
- tags: allTags,
430
- importance: first.importance,
431
- });
432
- merged += group.length - 1;
433
- } else {
434
- newEvents.push(...group);
435
- }
436
- }
437
-
438
- if (merged === 0) {
439
- return { success: true, output: 'No events to compact.' };
440
- }
441
-
442
- // Re-write events
443
- for (const file of readdirSync(eventsDir).filter(f => f.endsWith('.json'))) {
444
- unlinkSync(join(eventsDir, file));
445
- }
446
-
447
- for (let i = 0; i < newEvents.length; i++) {
448
- const event = newEvents[i];
449
- event.id = (i + 1).toString().padStart(6, '0');
450
- writeFileSync(join(eventsDir, `${event.id}.json`), JSON.stringify(event, null, 2));
451
- }
452
-
453
- return {
454
- success: true,
455
- output: `Compacted: ${events.length} → ${newEvents.length} events (merged ${merged})`,
456
- };
457
- }
458
-
459
- // ── Archive ───────────────────────────────────────────
460
-
461
- private archive(cwd: string): ToolResult {
462
- const nwtDir = join(cwd, NWT_DIR);
463
- const eventsDir = join(nwtDir, EVENTS_DIR);
464
- const archiveDir = join(nwtDir, ARCHIVE_DIR);
465
-
466
- if (!existsSync(eventsDir)) {
467
- return { success: true, output: 'No events to archive.' };
468
- }
469
-
470
- const cutoff = new Date();
471
- cutoff.setDate(cutoff.getDate() - ARCHIVE_AFTER_DAYS);
472
-
473
- const files = readdirSync(eventsDir).filter(f => f.endsWith('.json')).sort();
474
- const toArchive: string[] = [];
475
- const toKeep: string[] = [];
476
-
477
- for (const file of files) {
478
- try {
479
- const event = JSON.parse(readFileSync(join(eventsDir, file), 'utf-8'));
480
- const eventDate = new Date(event.timestamp);
481
- if (eventDate < cutoff) {
482
- toArchive.push(file);
483
- } else {
484
- toKeep.push(file);
485
- }
486
- } catch {
487
- toKeep.push(file);
488
- }
489
- }
490
-
491
- if (toArchive.length === 0) {
492
- return { success: true, output: 'No events old enough to archive.' };
493
- }
494
-
495
- // Create archive file
496
- const archiveName = `archive-${new Date().toISOString().split('T')[0]}.json`;
497
- const archiveEvents = toArchive.map(f => {
498
- return JSON.parse(readFileSync(join(eventsDir, f), 'utf-8'));
499
- });
500
-
501
- writeFileSync(
502
- join(archiveDir, archiveName),
503
- JSON.stringify(archiveEvents, null, 2)
504
- );
505
-
506
- // Remove archived events from events dir
507
- for (const file of toArchive) {
508
- const src = join(eventsDir, file);
509
- try { renameSync(src, join(archiveDir, file)); } catch {}
510
- }
511
-
512
- return {
513
- success: true,
514
- output: `Archived ${toArchive.length} events to archives/${archiveName}. ${toKeep.length} events remain.`,
515
- };
516
- }
517
-
518
- // ── Auto Archive ──────────────────────────────────────
519
-
520
- private autoArchiveIfNeeded(cwd: string): void {
521
- try {
522
- const nwtDir = join(cwd, NWT_DIR);
523
- const eventsDir = join(nwtDir, EVENTS_DIR);
524
- const archiveDir = join(nwtDir, ARCHIVE_DIR);
525
-
526
- if (!existsSync(eventsDir)) return;
527
-
528
- const files = readdirSync(eventsDir).filter(f => f.endsWith('.json'));
529
- if (files.length === 0) return;
530
-
531
- const cutoff = new Date();
532
- cutoff.setDate(cutoff.getDate() - ARCHIVE_AFTER_DAYS);
533
-
534
- const toArchive: string[] = [];
535
- const toKeep: string[] = [];
536
-
537
- for (const file of files) {
538
- try {
539
- const event = JSON.parse(readFileSync(join(eventsDir, file), 'utf-8'));
540
- const eventDate = new Date(event.timestamp);
541
- if (eventDate < cutoff) {
542
- toArchive.push(file);
543
- } else {
544
- toKeep.push(file);
545
- }
546
- } catch {
547
- toKeep.push(file);
548
- }
549
- }
550
-
551
- if (toArchive.length === 0) return;
552
-
553
- // Create monthly archive file
554
- const archiveName = `archive-${new Date().toISOString().split('T')[0]}.json`;
555
- const archiveEvents = toArchive.map(f => {
556
- return JSON.parse(readFileSync(join(eventsDir, f), 'utf-8'));
557
- });
558
-
559
- if (!existsSync(archiveDir)) mkdirSync(archiveDir, { recursive: true });
560
-
561
- // Append to existing archive or create new
562
- const archivePath = join(archiveDir, archiveName);
563
- let existing: any[] = [];
564
- if (existsSync(archivePath)) {
565
- try { existing = JSON.parse(readFileSync(archivePath, 'utf-8')); } catch {}
566
- }
567
- existing.push(...archiveEvents);
568
- writeFileSync(archivePath, JSON.stringify(existing, null, 2));
569
-
570
- // Remove archived files from events dir
571
- for (const file of toArchive) {
572
- try { renameSync(join(eventsDir, file), join(archiveDir, file)); } catch {}
573
- }
574
- } catch {
575
- // Silent fail - don't break init
576
- }
577
- }
578
-
579
- // ── Helpers ───────────────────────────────────────────
580
-
581
- private loadEvents(cwd: string): TimelineEvent[] {
582
- const eventsDir = join(cwd, NWT_DIR, EVENTS_DIR);
583
- if (!existsSync(eventsDir)) return [];
584
-
585
- const files = readdirSync(eventsDir).filter(f => f.endsWith('.json')).sort();
586
- const events: TimelineEvent[] = [];
587
-
588
- for (const file of files) {
589
- try {
590
- events.push(JSON.parse(readFileSync(join(eventsDir, file), 'utf-8')));
591
- } catch {
592
- // Skip corrupted files
593
- }
594
- }
595
-
596
- return events;
597
- }
598
- }