dino-spec 13.6.0 → 13.6.1

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 (133) hide show
  1. package/README.md +5 -82
  2. package/bin/dino-hud.js +1 -1
  3. package/bin/dino.js +1 -1
  4. package/dist/commands/hud.d.ts +8 -1
  5. package/dist/commands/hud.d.ts.map +1 -1
  6. package/dist/commands/hud.js +18 -4
  7. package/dist/commands/hud.js.map +1 -1
  8. package/dist/core/agents/context-isolation.js +26 -26
  9. package/dist/core/config/feature-flags.d.ts +1 -25
  10. package/dist/core/config/feature-flags.d.ts.map +1 -1
  11. package/dist/core/config/feature-flags.js +1 -7
  12. package/dist/core/config/feature-flags.js.map +1 -1
  13. package/dist/core/context/auto-injection-engine.d.ts +1 -10
  14. package/dist/core/context/auto-injection-engine.d.ts.map +1 -1
  15. package/dist/core/context/auto-injection-engine.js +2 -45
  16. package/dist/core/context/auto-injection-engine.js.map +1 -1
  17. package/dist/core/context/context-health.d.ts +2 -27
  18. package/dist/core/context/context-health.d.ts.map +1 -1
  19. package/dist/core/context/context-health.js +12 -98
  20. package/dist/core/context/context-health.js.map +1 -1
  21. package/dist/core/context/index.d.ts +2 -3
  22. package/dist/core/context/index.d.ts.map +1 -1
  23. package/dist/core/context/index.js +2 -18
  24. package/dist/core/context/index.js.map +1 -1
  25. package/dist/core/context/lazy-loader.d.ts +1 -44
  26. package/dist/core/context/lazy-loader.d.ts.map +1 -1
  27. package/dist/core/context/lazy-loader.js +1 -59
  28. package/dist/core/context/lazy-loader.js.map +1 -1
  29. package/dist/core/context-repl/index.d.ts +3 -8
  30. package/dist/core/context-repl/index.d.ts.map +1 -1
  31. package/dist/core/context-repl/index.js +3 -11
  32. package/dist/core/context-repl/index.js.map +1 -1
  33. package/dist/core/context-repl/types.d.ts +1 -277
  34. package/dist/core/context-repl/types.d.ts.map +1 -1
  35. package/dist/core/context-repl/types.js +1 -52
  36. package/dist/core/context-repl/types.js.map +1 -1
  37. package/dist/core/generator/claude-md.js +1 -1
  38. package/dist/core/provider/storage.d.ts.map +1 -1
  39. package/dist/core/provider/storage.js +2 -1
  40. package/dist/core/provider/storage.js.map +1 -1
  41. package/dist/hooks/post-edit.js +0 -73
  42. package/dist/hooks/post-edit.js.map +1 -1
  43. package/dist/hooks/session-start.js +0 -115
  44. package/dist/hooks/session-start.js.map +1 -1
  45. package/dist/hooks/types.js +1 -1
  46. package/dist/hooks/user-prompt-submit.js +0 -100
  47. package/dist/hooks/user-prompt-submit.js.map +1 -1
  48. package/dist/hud/config-tui.d.ts +25 -0
  49. package/dist/hud/config-tui.d.ts.map +1 -0
  50. package/dist/hud/config-tui.js +199 -0
  51. package/dist/hud/config-tui.js.map +1 -0
  52. package/dist/hud/config.d.ts +28 -3
  53. package/dist/hud/config.d.ts.map +1 -1
  54. package/dist/hud/config.js +60 -8
  55. package/dist/hud/config.js.map +1 -1
  56. package/dist/hud/index.d.ts.map +1 -1
  57. package/dist/hud/index.js +1 -0
  58. package/dist/hud/index.js.map +1 -1
  59. package/dist/hud/models.d.ts +58 -0
  60. package/dist/hud/models.d.ts.map +1 -0
  61. package/dist/hud/models.js +124 -0
  62. package/dist/hud/models.js.map +1 -0
  63. package/dist/hud/render/budget-bar.d.ts +2 -0
  64. package/dist/hud/render/budget-bar.d.ts.map +1 -1
  65. package/dist/hud/render/budget-bar.js +8 -5
  66. package/dist/hud/render/budget-bar.js.map +1 -1
  67. package/dist/hud/stdin.d.ts +3 -0
  68. package/dist/hud/stdin.d.ts.map +1 -1
  69. package/dist/hud/stdin.js +29 -2
  70. package/dist/hud/stdin.js.map +1 -1
  71. package/dist/hud/token-estimator.d.ts +79 -0
  72. package/dist/hud/token-estimator.d.ts.map +1 -0
  73. package/dist/hud/token-estimator.js +126 -0
  74. package/dist/hud/token-estimator.js.map +1 -0
  75. package/dist/hud/types.d.ts +2 -0
  76. package/dist/hud/types.d.ts.map +1 -1
  77. package/dist/mcp/server.d.ts +1 -2
  78. package/dist/mcp/server.d.ts.map +1 -1
  79. package/dist/mcp/server.js +3 -12
  80. package/dist/mcp/server.js.map +1 -1
  81. package/dist/mcp/tool-tiers.d.ts.map +1 -1
  82. package/dist/mcp/tool-tiers.js +0 -5
  83. package/dist/mcp/tool-tiers.js.map +1 -1
  84. package/dist/mcp/tools/index.d.ts +2 -7
  85. package/dist/mcp/tools/index.d.ts.map +1 -1
  86. package/dist/mcp/tools/index.js +2 -25
  87. package/dist/mcp/tools/index.js.map +1 -1
  88. package/dist/mcp-server.d.ts.map +1 -1
  89. package/dist/mcp-server.js +5 -9
  90. package/dist/mcp-server.js.map +1 -1
  91. package/dist/rules/index.js +2 -2
  92. package/dist/utils/exec.js +2 -2
  93. package/dist/utils/exec.js.map +1 -1
  94. package/dist/utils/gitignore.d.ts.map +1 -1
  95. package/dist/utils/gitignore.js +0 -5
  96. package/dist/utils/gitignore.js.map +1 -1
  97. package/package.json +77 -76
  98. package/dist/core/context/focus-resource-loader.d.ts +0 -143
  99. package/dist/core/context/focus-resource-loader.d.ts.map +0 -1
  100. package/dist/core/context/focus-resource-loader.js +0 -305
  101. package/dist/core/context/focus-resource-loader.js.map +0 -1
  102. package/dist/core/context-repl/__tests__/repl-environment.test.d.ts +0 -7
  103. package/dist/core/context-repl/__tests__/repl-environment.test.d.ts.map +0 -1
  104. package/dist/core/context-repl/__tests__/repl-environment.test.js +0 -335
  105. package/dist/core/context-repl/__tests__/repl-environment.test.js.map +0 -1
  106. package/dist/core/context-repl/context-window-monitor.d.ts +0 -181
  107. package/dist/core/context-repl/context-window-monitor.d.ts.map +0 -1
  108. package/dist/core/context-repl/context-window-monitor.js +0 -309
  109. package/dist/core/context-repl/context-window-monitor.js.map +0 -1
  110. package/dist/core/context-repl/repl-environment.d.ts +0 -66
  111. package/dist/core/context-repl/repl-environment.d.ts.map +0 -1
  112. package/dist/core/context-repl/repl-environment.js +0 -795
  113. package/dist/core/context-repl/repl-environment.js.map +0 -1
  114. package/dist/mcp/focus-filter.d.ts +0 -74
  115. package/dist/mcp/focus-filter.d.ts.map +0 -1
  116. package/dist/mcp/focus-filter.js +0 -229
  117. package/dist/mcp/focus-filter.js.map +0 -1
  118. package/dist/mcp/tools/auto-inject.d.ts +0 -36
  119. package/dist/mcp/tools/auto-inject.d.ts.map +0 -1
  120. package/dist/mcp/tools/auto-inject.js +0 -143
  121. package/dist/mcp/tools/auto-inject.js.map +0 -1
  122. package/dist/mcp/tools/auto-unload.d.ts +0 -29
  123. package/dist/mcp/tools/auto-unload.d.ts.map +0 -1
  124. package/dist/mcp/tools/auto-unload.js +0 -151
  125. package/dist/mcp/tools/auto-unload.js.map +0 -1
  126. package/dist/mcp/tools/context-repl.d.ts +0 -48
  127. package/dist/mcp/tools/context-repl.d.ts.map +0 -1
  128. package/dist/mcp/tools/context-repl.js +0 -290
  129. package/dist/mcp/tools/context-repl.js.map +0 -1
  130. package/dist/mcp/tools/health.d.ts +0 -29
  131. package/dist/mcp/tools/health.d.ts.map +0 -1
  132. package/dist/mcp/tools/health.js +0 -171
  133. package/dist/mcp/tools/health.js.map +0 -1
@@ -1,795 +0,0 @@
1
- /**
2
- * REPL Environment - v13.6.0
3
- *
4
- * RLM-style REPL environment for context management.
5
- * Provides programmatic access to context with controlled window growth.
6
- *
7
- * Key features:
8
- * - Peek: Reconnaissance of context structure without full loading
9
- * - Grep: Pattern matching within loaded context
10
- * - Execute: Run queries with result truncation
11
- * - Partition: Segment context by strategy
12
- *
13
- * Reference: RLM Paper (arxiv.org/abs/2512.24601v1)
14
- */
15
- import { DEFAULT_REPL_MAX_TOKENS, REPL_OPERATION_TIMEOUT_MS, PEEK_SAMPLE_COUNT, PEEK_MAX_DEPTH, VALID_VARIABLE_PATHS, } from './types.js';
16
- import { parseQuery } from './query-parser.js';
17
- import { executeQuery } from './query-executor.js';
18
- import { loadVariable as loadContextVariable } from './variable-loader.js';
19
- import { estimateTokens } from './condensation.js';
20
- import { computePartitions } from './partitioning/index.js';
21
- import { isFeatureEnabled } from '../config/feature-flags.js';
22
- import * as fs from 'fs/promises';
23
- import * as path from 'path';
24
- // =============================================================================
25
- // Constants
26
- // =============================================================================
27
- const PREVIEW_LENGTH = 100;
28
- // =============================================================================
29
- // REPL Environment Implementation
30
- // =============================================================================
31
- /**
32
- * REPL Environment for context management
33
- *
34
- * Provides a programmatic interface for context exploration
35
- * with controlled context window growth.
36
- */
37
- export class REPLEnvironment {
38
- state;
39
- projectDir;
40
- maxTokens;
41
- constructor(options = {}) {
42
- this.projectDir = options.projectDir || process.cwd();
43
- this.maxTokens = options.maxTokens || DEFAULT_REPL_MAX_TOKENS;
44
- this.state = this.createInitialState();
45
- }
46
- // ---------------------------------------------------------------------------
47
- // State Management
48
- // ---------------------------------------------------------------------------
49
- createInitialState() {
50
- return {
51
- variables: new Map(),
52
- totalTokens: 0,
53
- startedAt: Date.now(),
54
- operationCount: 0,
55
- history: [],
56
- };
57
- }
58
- getState() {
59
- return { ...this.state };
60
- }
61
- clearState() {
62
- const tokensBefore = this.state.totalTokens;
63
- this.state = this.createInitialState();
64
- this.addHistoryEntry('clear', tokensBefore, 0, true, 'Cleared all state');
65
- }
66
- // ---------------------------------------------------------------------------
67
- // History Tracking
68
- // ---------------------------------------------------------------------------
69
- addHistoryEntry(operation, tokensBefore, tokensAfter, success, summary) {
70
- this.state.history.push({
71
- operation,
72
- timestamp: Date.now(),
73
- tokensBefore,
74
- tokensAfter,
75
- success,
76
- summary,
77
- });
78
- // Keep history limited
79
- if (this.state.history.length > 100) {
80
- this.state.history = this.state.history.slice(-100);
81
- }
82
- }
83
- // ---------------------------------------------------------------------------
84
- // Execute Operation
85
- // ---------------------------------------------------------------------------
86
- async execute(code) {
87
- const startTime = Date.now();
88
- const tokensBefore = this.state.totalTokens;
89
- this.state.operationCount++;
90
- // Check feature flag
91
- const enabled = await isFeatureEnabled('enableReplEnvironment', this.projectDir);
92
- if (!enabled) {
93
- return {
94
- success: false,
95
- data: null,
96
- tokensUsed: 0,
97
- executionTimeMs: Date.now() - startTime,
98
- error: 'REPL environment is disabled. Set enableReplEnvironment: true in .dino/config.json',
99
- };
100
- }
101
- try {
102
- // Parse query if string
103
- let query;
104
- if (typeof code === 'string') {
105
- const parsed = parseQuery(code);
106
- if (!parsed.success || !parsed.query) {
107
- this.addHistoryEntry('execute', tokensBefore, tokensBefore, false, `Parse error: ${parsed.error}`);
108
- return {
109
- success: false,
110
- data: null,
111
- tokensUsed: 0,
112
- executionTimeMs: Date.now() - startTime,
113
- error: parsed.error || 'Failed to parse query',
114
- };
115
- }
116
- query = parsed.query;
117
- }
118
- else {
119
- query = code;
120
- }
121
- // Execute with timeout
122
- const result = await this.executeWithTimeout(executeQuery(query, { projectDir: this.projectDir }), REPL_OPERATION_TIMEOUT_MS);
123
- if (!result.success) {
124
- this.addHistoryEntry('execute', tokensBefore, tokensBefore, false, `Execution error: ${result.error}`);
125
- return {
126
- success: false,
127
- data: null,
128
- tokensUsed: 0,
129
- executionTimeMs: Date.now() - startTime,
130
- error: result.error,
131
- };
132
- }
133
- // Estimate tokens and truncate if needed
134
- const dataStr = typeof result.data === 'string' ? result.data : JSON.stringify(result.data);
135
- const tokensUsed = estimateTokens(dataStr);
136
- let data = result.data;
137
- let warnings;
138
- if (tokensUsed > this.maxTokens) {
139
- data = this.truncateData(result.data, this.maxTokens);
140
- warnings = [`Result truncated from ~${tokensUsed} to ~${this.maxTokens} tokens`];
141
- }
142
- const finalTokens = warnings ? this.maxTokens : tokensUsed;
143
- this.state.totalTokens += finalTokens;
144
- this.addHistoryEntry('execute', tokensBefore, this.state.totalTokens, true, `Executed query, ${result.count} results`);
145
- return {
146
- success: true,
147
- data,
148
- tokensUsed: finalTokens,
149
- executionTimeMs: Date.now() - startTime,
150
- warnings,
151
- };
152
- }
153
- catch (error) {
154
- const errorMsg = error instanceof Error ? error.message : String(error);
155
- this.addHistoryEntry('execute', tokensBefore, tokensBefore, false, `Error: ${errorMsg}`);
156
- return {
157
- success: false,
158
- data: null,
159
- tokensUsed: 0,
160
- executionTimeMs: Date.now() - startTime,
161
- error: errorMsg,
162
- };
163
- }
164
- }
165
- // ---------------------------------------------------------------------------
166
- // Peek Operation
167
- // ---------------------------------------------------------------------------
168
- async peek(pattern) {
169
- const startTime = Date.now();
170
- const tokensBefore = this.state.totalTokens;
171
- this.state.operationCount++;
172
- // Check feature flag
173
- const enabled = await isFeatureEnabled('enableReplEnvironment', this.projectDir);
174
- if (!enabled) {
175
- return {
176
- success: false,
177
- data: null,
178
- tokensUsed: 0,
179
- executionTimeMs: Date.now() - startTime,
180
- error: 'REPL environment is disabled. Set enableReplEnvironment: true in .dino/config.json',
181
- };
182
- }
183
- try {
184
- const metadata = await this.gatherPeekMetadata(pattern);
185
- // Peek doesn't add to token count (read-only reconnaissance)
186
- this.addHistoryEntry('peek', tokensBefore, tokensBefore, true, `Peeked ${metadata.totalFiles} files`);
187
- return {
188
- success: true,
189
- data: metadata,
190
- tokensUsed: 0, // Peek is free - no context loaded
191
- executionTimeMs: Date.now() - startTime,
192
- };
193
- }
194
- catch (error) {
195
- const errorMsg = error instanceof Error ? error.message : String(error);
196
- this.addHistoryEntry('peek', tokensBefore, tokensBefore, false, `Error: ${errorMsg}`);
197
- return {
198
- success: false,
199
- data: null,
200
- tokensUsed: 0,
201
- executionTimeMs: Date.now() - startTime,
202
- error: errorMsg,
203
- };
204
- }
205
- }
206
- async gatherPeekMetadata(pattern) {
207
- const categories = {};
208
- const samples = [];
209
- let totalFiles = 0;
210
- let totalTokens = 0;
211
- // Check .dino directory for session state
212
- const dinoDir = path.join(this.projectDir, '.dino');
213
- try {
214
- const dinoFiles = await this.listFilesRecursive(dinoDir, pattern, 2);
215
- for (const file of dinoFiles) {
216
- totalFiles++;
217
- const category = this.categorizeFile(file);
218
- categories[category] = (categories[category] || 0) + 1;
219
- // Estimate tokens from file size
220
- try {
221
- const stats = await fs.stat(file);
222
- const tokenEstimate = Math.ceil(stats.size / 4); // Rough estimate
223
- totalTokens += tokenEstimate;
224
- // Add sample if under limit
225
- if (samples.length < PEEK_SAMPLE_COUNT) {
226
- const preview = await this.getFilePreview(file);
227
- samples.push({
228
- path: path.relative(this.projectDir, file),
229
- type: category,
230
- sizeEstimate: tokenEstimate,
231
- preview,
232
- });
233
- }
234
- }
235
- catch {
236
- // Skip files we can't stat
237
- }
238
- }
239
- }
240
- catch {
241
- // .dino directory might not exist
242
- }
243
- // Add memory variables info
244
- const memoryCategories = ['memories.decisions', 'memories.patterns', 'memories.learnings'];
245
- for (const varPath of memoryCategories) {
246
- try {
247
- const varResult = await loadContextVariable(varPath, this.projectDir);
248
- if (!varResult.error && Array.isArray(varResult.data)) {
249
- const count = varResult.data.length;
250
- const cat = varPath.split('.')[1];
251
- categories[`memory:${cat}`] = count;
252
- totalFiles += count;
253
- const dataStr = JSON.stringify(varResult.data);
254
- totalTokens += estimateTokens(dataStr);
255
- }
256
- }
257
- catch {
258
- // Memory might not be available
259
- }
260
- }
261
- // Build structure preview
262
- const structure = await this.buildStructurePreview(this.projectDir, pattern, PEEK_MAX_DEPTH);
263
- return {
264
- totalFiles,
265
- totalTokens,
266
- categories,
267
- samples,
268
- structure,
269
- filterPattern: pattern,
270
- };
271
- }
272
- async listFilesRecursive(dir, pattern, maxDepth = 3, depth = 0) {
273
- if (depth >= maxDepth)
274
- return [];
275
- const files = [];
276
- try {
277
- const entries = await fs.readdir(dir, { withFileTypes: true });
278
- for (const entry of entries) {
279
- const fullPath = path.join(dir, entry.name);
280
- if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
281
- const subFiles = await this.listFilesRecursive(fullPath, pattern, maxDepth, depth + 1);
282
- files.push(...subFiles);
283
- }
284
- else if (entry.isFile()) {
285
- if (!pattern || this.matchesPattern(fullPath, pattern)) {
286
- files.push(fullPath);
287
- }
288
- }
289
- }
290
- }
291
- catch {
292
- // Directory might not be readable
293
- }
294
- return files;
295
- }
296
- matchesPattern(filePath, pattern) {
297
- // Simple glob matching
298
- const regex = pattern
299
- .replace(/\./g, '\\.')
300
- .replace(/\*/g, '.*')
301
- .replace(/\?/g, '.');
302
- return new RegExp(regex, 'i').test(filePath);
303
- }
304
- categorizeFile(filePath) {
305
- const ext = path.extname(filePath).toLowerCase();
306
- const name = path.basename(filePath).toLowerCase();
307
- if (name === 'session.md')
308
- return 'session';
309
- if (name === 'config.json')
310
- return 'config';
311
- if (name.includes('handoff'))
312
- return 'handoff';
313
- if (name.includes('research'))
314
- return 'research';
315
- if (name.includes('plan'))
316
- return 'plan';
317
- if (ext === '.md')
318
- return 'documentation';
319
- if (ext === '.json')
320
- return 'data';
321
- return 'other';
322
- }
323
- async getFilePreview(filePath) {
324
- try {
325
- const content = await fs.readFile(filePath, 'utf-8');
326
- return content.substring(0, PREVIEW_LENGTH).replace(/\n/g, ' ');
327
- }
328
- catch {
329
- return '(unable to read)';
330
- }
331
- }
332
- async buildStructurePreview(dir, pattern, maxDepth = 3, depth = 0) {
333
- const name = path.basename(dir) || dir;
334
- const node = { name, isDirectory: true };
335
- if (depth >= maxDepth) {
336
- return node;
337
- }
338
- try {
339
- const entries = await fs.readdir(dir, { withFileTypes: true });
340
- const children = [];
341
- let childCount = 0;
342
- for (const entry of entries) {
343
- if (entry.name.startsWith('.') && entry.name !== '.dino')
344
- continue;
345
- if (entry.name === 'node_modules' || entry.name === 'dist')
346
- continue;
347
- childCount++;
348
- if (children.length < 10) { // Limit children shown
349
- if (entry.isDirectory()) {
350
- const fullPath = path.join(dir, entry.name);
351
- const childNode = await this.buildStructurePreview(fullPath, pattern, maxDepth, depth + 1);
352
- children.push(childNode);
353
- }
354
- else {
355
- children.push({ name: entry.name, isDirectory: false });
356
- }
357
- }
358
- }
359
- node.childCount = childCount;
360
- if (children.length > 0) {
361
- node.children = children;
362
- }
363
- }
364
- catch {
365
- // Can't read directory
366
- }
367
- return node;
368
- }
369
- // ---------------------------------------------------------------------------
370
- // Grep Operation
371
- // ---------------------------------------------------------------------------
372
- async grep(query, options = {}) {
373
- const startTime = Date.now();
374
- const tokensBefore = this.state.totalTokens;
375
- this.state.operationCount++;
376
- // Check feature flag
377
- const enabled = await isFeatureEnabled('enableReplEnvironment', this.projectDir);
378
- if (!enabled) {
379
- return {
380
- success: false,
381
- data: null,
382
- tokensUsed: 0,
383
- executionTimeMs: Date.now() - startTime,
384
- error: 'REPL environment is disabled. Set enableReplEnvironment: true in .dino/config.json',
385
- };
386
- }
387
- try {
388
- const { ignoreCase = true, filesOnly = false, maxMatches = 50, includeLineNumbers = true, } = options;
389
- const matches = [];
390
- const filesWithMatches = new Set();
391
- let totalMatches = 0;
392
- let truncated = false;
393
- // Search in loaded variables first
394
- for (const [name, variable] of this.state.variables) {
395
- const varMatches = this.searchInData(variable.data, query, ignoreCase);
396
- for (const match of varMatches) {
397
- totalMatches++;
398
- filesWithMatches.add(name);
399
- if (matches.length < maxMatches) {
400
- matches.push({
401
- file: name,
402
- line: match.line,
403
- column: match.column,
404
- match: match.text,
405
- context: match.context,
406
- });
407
- }
408
- else {
409
- truncated = true;
410
- }
411
- }
412
- }
413
- // Search in .dino files
414
- const dinoDir = path.join(this.projectDir, '.dino');
415
- try {
416
- const files = await this.listFilesRecursive(dinoDir, undefined, 3);
417
- for (const file of files) {
418
- if (matches.length >= maxMatches) {
419
- truncated = true;
420
- break;
421
- }
422
- try {
423
- const content = await fs.readFile(file, 'utf-8');
424
- const fileMatches = this.searchInText(content, query, ignoreCase, includeLineNumbers);
425
- for (const match of fileMatches) {
426
- totalMatches++;
427
- filesWithMatches.add(file);
428
- if (matches.length < maxMatches) {
429
- matches.push({
430
- file: path.relative(this.projectDir, file),
431
- line: match.line,
432
- column: match.column,
433
- match: match.text,
434
- context: match.context,
435
- });
436
- }
437
- else {
438
- truncated = true;
439
- break;
440
- }
441
- }
442
- }
443
- catch {
444
- // Skip files we can't read
445
- }
446
- }
447
- }
448
- catch {
449
- // .dino directory might not exist
450
- }
451
- const result = {
452
- pattern: query,
453
- totalMatches,
454
- filesWithMatches: filesWithMatches.size,
455
- matches: filesOnly ? [] : matches,
456
- truncated,
457
- };
458
- // Grep is mostly read-only, small token cost for results
459
- const resultStr = JSON.stringify(result);
460
- const tokensUsed = estimateTokens(resultStr);
461
- this.addHistoryEntry('grep', tokensBefore, tokensBefore, true, `Grep found ${totalMatches} matches in ${filesWithMatches.size} files`);
462
- return {
463
- success: true,
464
- data: result,
465
- tokensUsed,
466
- executionTimeMs: Date.now() - startTime,
467
- warnings: truncated ? [`Results truncated at ${maxMatches} matches`] : undefined,
468
- };
469
- }
470
- catch (error) {
471
- const errorMsg = error instanceof Error ? error.message : String(error);
472
- this.addHistoryEntry('grep', tokensBefore, tokensBefore, false, `Error: ${errorMsg}`);
473
- return {
474
- success: false,
475
- data: null,
476
- tokensUsed: 0,
477
- executionTimeMs: Date.now() - startTime,
478
- error: errorMsg,
479
- };
480
- }
481
- }
482
- searchInData(data, query, ignoreCase) {
483
- const results = [];
484
- const text = JSON.stringify(data, null, 2);
485
- return this.searchInText(text, query, ignoreCase, true);
486
- }
487
- searchInText(text, query, ignoreCase, includeLineNumbers) {
488
- const results = [];
489
- const lines = text.split('\n');
490
- const regex = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), ignoreCase ? 'gi' : 'g');
491
- for (let i = 0; i < lines.length; i++) {
492
- const line = lines[i];
493
- let match;
494
- while ((match = regex.exec(line)) !== null) {
495
- const contextStart = Math.max(0, match.index - 20);
496
- const contextEnd = Math.min(line.length, match.index + match[0].length + 20);
497
- const context = line.substring(contextStart, contextEnd);
498
- results.push({
499
- line: includeLineNumbers ? i + 1 : 0,
500
- column: match.index,
501
- text: match[0],
502
- context,
503
- });
504
- }
505
- }
506
- return results;
507
- }
508
- // ---------------------------------------------------------------------------
509
- // Partition Operation
510
- // ---------------------------------------------------------------------------
511
- async partition(strategy) {
512
- const startTime = Date.now();
513
- const tokensBefore = this.state.totalTokens;
514
- this.state.operationCount++;
515
- // Check feature flag
516
- const enabled = await isFeatureEnabled('enableReplEnvironment', this.projectDir);
517
- if (!enabled) {
518
- return {
519
- success: false,
520
- data: null,
521
- tokensUsed: 0,
522
- executionTimeMs: Date.now() - startTime,
523
- error: 'REPL environment is disabled. Set enableReplEnvironment: true in .dino/config.json',
524
- };
525
- }
526
- try {
527
- // Validate strategy
528
- const validStrategies = ['by-directory', 'by-feature', 'by-dependency', 'by-time'];
529
- if (!validStrategies.includes(strategy)) {
530
- return {
531
- success: false,
532
- data: null,
533
- tokensUsed: 0,
534
- executionTimeMs: Date.now() - startTime,
535
- error: `Invalid strategy: ${strategy}. Valid strategies: ${validStrategies.join(', ')}`,
536
- };
537
- }
538
- // Get files to partition
539
- const srcDir = path.join(this.projectDir, 'src');
540
- let files = [];
541
- try {
542
- files = await this.listFilesRecursive(srcDir, '*.ts', 5);
543
- }
544
- catch {
545
- // No src directory
546
- }
547
- if (files.length === 0) {
548
- return {
549
- success: true,
550
- data: { strategy, partitions: [], message: 'No files found to partition' },
551
- tokensUsed: 0,
552
- executionTimeMs: Date.now() - startTime,
553
- };
554
- }
555
- // Convert to relative paths
556
- const relativePaths = files.map(f => path.relative(this.projectDir, f));
557
- // Compute partitions
558
- const result = await computePartitions(strategy, relativePaths, { maxPartitions: 10 });
559
- const resultStr = JSON.stringify(result);
560
- const tokensUsed = estimateTokens(resultStr);
561
- this.addHistoryEntry('partition', tokensBefore, tokensBefore, true, `Partitioned ${files.length} files into ${result.partitions.length} groups`);
562
- return {
563
- success: result.success,
564
- data: result,
565
- tokensUsed,
566
- executionTimeMs: Date.now() - startTime,
567
- error: result.error,
568
- };
569
- }
570
- catch (error) {
571
- const errorMsg = error instanceof Error ? error.message : String(error);
572
- this.addHistoryEntry('partition', tokensBefore, tokensBefore, false, `Error: ${errorMsg}`);
573
- return {
574
- success: false,
575
- data: null,
576
- tokensUsed: 0,
577
- executionTimeMs: Date.now() - startTime,
578
- error: errorMsg,
579
- };
580
- }
581
- }
582
- // ---------------------------------------------------------------------------
583
- // Variable Management
584
- // ---------------------------------------------------------------------------
585
- async loadVariable(variablePath, name) {
586
- const startTime = Date.now();
587
- const tokensBefore = this.state.totalTokens;
588
- // Check feature flag
589
- const enabled = await isFeatureEnabled('enableReplEnvironment', this.projectDir);
590
- if (!enabled) {
591
- return {
592
- success: false,
593
- data: null,
594
- tokensUsed: 0,
595
- executionTimeMs: Date.now() - startTime,
596
- error: 'REPL environment is disabled',
597
- };
598
- }
599
- // Validate variable path
600
- if (!VALID_VARIABLE_PATHS.includes(variablePath)) {
601
- return {
602
- success: false,
603
- data: null,
604
- tokensUsed: 0,
605
- executionTimeMs: Date.now() - startTime,
606
- error: `Invalid variable path: ${variablePath}. Valid paths: ${VALID_VARIABLE_PATHS.join(', ')}`,
607
- };
608
- }
609
- try {
610
- const result = await loadContextVariable(variablePath, this.projectDir);
611
- if (result.error) {
612
- return {
613
- success: false,
614
- data: null,
615
- tokensUsed: 0,
616
- executionTimeMs: Date.now() - startTime,
617
- error: result.error || `Failed to load variable: ${variablePath}`,
618
- };
619
- }
620
- const varName = name || variablePath.split('.').pop() || variablePath;
621
- const dataStr = typeof result.data === 'string' ? result.data : JSON.stringify(result.data);
622
- const tokenEstimate = estimateTokens(dataStr);
623
- const variable = {
624
- name: varName,
625
- path: variablePath,
626
- data: result.data,
627
- loadedAt: Date.now(),
628
- tokenEstimate,
629
- source: 'loadVariable',
630
- };
631
- this.state.variables.set(varName, variable);
632
- this.state.totalTokens += tokenEstimate;
633
- return {
634
- success: true,
635
- data: { name: varName, path: variablePath, tokenEstimate },
636
- tokensUsed: tokenEstimate,
637
- executionTimeMs: Date.now() - startTime,
638
- };
639
- }
640
- catch (error) {
641
- const errorMsg = error instanceof Error ? error.message : String(error);
642
- return {
643
- success: false,
644
- data: null,
645
- tokensUsed: 0,
646
- executionTimeMs: Date.now() - startTime,
647
- error: errorMsg,
648
- };
649
- }
650
- }
651
- unloadVariable(name) {
652
- const variable = this.state.variables.get(name);
653
- if (!variable) {
654
- return false;
655
- }
656
- this.state.totalTokens -= variable.tokenEstimate;
657
- this.state.variables.delete(name);
658
- return true;
659
- }
660
- // ---------------------------------------------------------------------------
661
- // Utility Methods
662
- // ---------------------------------------------------------------------------
663
- async executeWithTimeout(promise, timeoutMs) {
664
- let timeoutId;
665
- const timeoutPromise = new Promise((_, reject) => {
666
- timeoutId = setTimeout(() => {
667
- reject(new Error(`Operation timed out after ${timeoutMs}ms`));
668
- }, timeoutMs);
669
- });
670
- try {
671
- const result = await Promise.race([promise, timeoutPromise]);
672
- clearTimeout(timeoutId);
673
- return result;
674
- }
675
- catch (error) {
676
- clearTimeout(timeoutId);
677
- throw error;
678
- }
679
- }
680
- truncateData(data, maxTokens) {
681
- if (Array.isArray(data)) {
682
- const items = [];
683
- let tokens = 0;
684
- for (const item of data) {
685
- const itemStr = JSON.stringify(item);
686
- const itemTokens = estimateTokens(itemStr);
687
- if (tokens + itemTokens > maxTokens) {
688
- break;
689
- }
690
- items.push(item);
691
- tokens += itemTokens;
692
- }
693
- return items;
694
- }
695
- if (typeof data === 'string') {
696
- const maxChars = maxTokens * 4;
697
- return data.substring(0, maxChars);
698
- }
699
- if (typeof data === 'object' && data !== null) {
700
- const truncated = {};
701
- let tokens = 0;
702
- for (const [key, value] of Object.entries(data)) {
703
- const entryStr = JSON.stringify({ [key]: value });
704
- const entryTokens = estimateTokens(entryStr);
705
- if (tokens + entryTokens > maxTokens) {
706
- break;
707
- }
708
- truncated[key] = value;
709
- tokens += entryTokens;
710
- }
711
- return truncated;
712
- }
713
- return data;
714
- }
715
- }
716
- // =============================================================================
717
- // Factory Function
718
- // =============================================================================
719
- /**
720
- * Create a new REPL environment instance
721
- */
722
- export function createREPLEnvironment(options = {}) {
723
- return new REPLEnvironment(options);
724
- }
725
- // =============================================================================
726
- // Formatting
727
- // =============================================================================
728
- /**
729
- * Format REPL result for display
730
- */
731
- export function formatREPLResult(result) {
732
- const lines = [];
733
- if (result.success) {
734
- lines.push('## REPL Result');
735
- lines.push('');
736
- lines.push(`**Success:** Yes`);
737
- lines.push(`**Tokens Used:** ~${result.tokensUsed}`);
738
- lines.push(`**Execution Time:** ${result.executionTimeMs}ms`);
739
- if (result.warnings && result.warnings.length > 0) {
740
- lines.push('');
741
- lines.push('### Warnings');
742
- for (const warning of result.warnings) {
743
- lines.push(`- ${warning}`);
744
- }
745
- }
746
- lines.push('');
747
- lines.push('### Data');
748
- lines.push('```json');
749
- const dataStr = JSON.stringify(result.data, null, 2);
750
- if (dataStr.length > 2000) {
751
- lines.push(dataStr.substring(0, 2000) + '\n... (truncated)');
752
- }
753
- else {
754
- lines.push(dataStr);
755
- }
756
- lines.push('```');
757
- }
758
- else {
759
- lines.push('## REPL Error');
760
- lines.push('');
761
- lines.push(`**Error:** ${result.error}`);
762
- lines.push(`**Execution Time:** ${result.executionTimeMs}ms`);
763
- }
764
- return lines.join('\n');
765
- }
766
- /**
767
- * Format REPL state summary
768
- */
769
- export function formatREPLState(state) {
770
- const lines = [];
771
- lines.push('## REPL State');
772
- lines.push('');
773
- lines.push(`**Variables Loaded:** ${state.variables.size}`);
774
- lines.push(`**Total Tokens:** ~${state.totalTokens}`);
775
- lines.push(`**Operations:** ${state.operationCount}`);
776
- lines.push(`**Uptime:** ${Math.round((Date.now() - state.startedAt) / 1000)}s`);
777
- if (state.variables.size > 0) {
778
- lines.push('');
779
- lines.push('### Loaded Variables');
780
- for (const [name, variable] of state.variables) {
781
- lines.push(`- **${name}** (${variable.path}): ~${variable.tokenEstimate} tokens`);
782
- }
783
- }
784
- if (state.history.length > 0) {
785
- lines.push('');
786
- lines.push('### Recent Operations');
787
- const recent = state.history.slice(-5);
788
- for (const entry of recent) {
789
- const status = entry.success ? 'OK' : 'FAIL';
790
- lines.push(`- [${status}] ${entry.operation}: ${entry.summary}`);
791
- }
792
- }
793
- return lines.join('\n');
794
- }
795
- //# sourceMappingURL=repl-environment.js.map