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.
- package/README.md +5 -82
- package/bin/dino-hud.js +1 -1
- package/bin/dino.js +1 -1
- package/dist/commands/hud.d.ts +8 -1
- package/dist/commands/hud.d.ts.map +1 -1
- package/dist/commands/hud.js +18 -4
- package/dist/commands/hud.js.map +1 -1
- package/dist/core/agents/context-isolation.js +26 -26
- package/dist/core/config/feature-flags.d.ts +1 -25
- package/dist/core/config/feature-flags.d.ts.map +1 -1
- package/dist/core/config/feature-flags.js +1 -7
- package/dist/core/config/feature-flags.js.map +1 -1
- package/dist/core/context/auto-injection-engine.d.ts +1 -10
- package/dist/core/context/auto-injection-engine.d.ts.map +1 -1
- package/dist/core/context/auto-injection-engine.js +2 -45
- package/dist/core/context/auto-injection-engine.js.map +1 -1
- package/dist/core/context/context-health.d.ts +2 -27
- package/dist/core/context/context-health.d.ts.map +1 -1
- package/dist/core/context/context-health.js +12 -98
- package/dist/core/context/context-health.js.map +1 -1
- package/dist/core/context/index.d.ts +2 -3
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +2 -18
- package/dist/core/context/index.js.map +1 -1
- package/dist/core/context/lazy-loader.d.ts +1 -44
- package/dist/core/context/lazy-loader.d.ts.map +1 -1
- package/dist/core/context/lazy-loader.js +1 -59
- package/dist/core/context/lazy-loader.js.map +1 -1
- package/dist/core/context-repl/index.d.ts +3 -8
- package/dist/core/context-repl/index.d.ts.map +1 -1
- package/dist/core/context-repl/index.js +3 -11
- package/dist/core/context-repl/index.js.map +1 -1
- package/dist/core/context-repl/types.d.ts +1 -277
- package/dist/core/context-repl/types.d.ts.map +1 -1
- package/dist/core/context-repl/types.js +1 -52
- package/dist/core/context-repl/types.js.map +1 -1
- package/dist/core/generator/claude-md.js +1 -1
- package/dist/core/provider/storage.d.ts.map +1 -1
- package/dist/core/provider/storage.js +2 -1
- package/dist/core/provider/storage.js.map +1 -1
- package/dist/hooks/post-edit.js +0 -73
- package/dist/hooks/post-edit.js.map +1 -1
- package/dist/hooks/session-start.js +0 -115
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/hooks/types.js +1 -1
- package/dist/hooks/user-prompt-submit.js +0 -100
- package/dist/hooks/user-prompt-submit.js.map +1 -1
- package/dist/hud/config-tui.d.ts +25 -0
- package/dist/hud/config-tui.d.ts.map +1 -0
- package/dist/hud/config-tui.js +199 -0
- package/dist/hud/config-tui.js.map +1 -0
- package/dist/hud/config.d.ts +28 -3
- package/dist/hud/config.d.ts.map +1 -1
- package/dist/hud/config.js +60 -8
- package/dist/hud/config.js.map +1 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +1 -0
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/models.d.ts +58 -0
- package/dist/hud/models.d.ts.map +1 -0
- package/dist/hud/models.js +124 -0
- package/dist/hud/models.js.map +1 -0
- package/dist/hud/render/budget-bar.d.ts +2 -0
- package/dist/hud/render/budget-bar.d.ts.map +1 -1
- package/dist/hud/render/budget-bar.js +8 -5
- package/dist/hud/render/budget-bar.js.map +1 -1
- package/dist/hud/stdin.d.ts +3 -0
- package/dist/hud/stdin.d.ts.map +1 -1
- package/dist/hud/stdin.js +29 -2
- package/dist/hud/stdin.js.map +1 -1
- package/dist/hud/token-estimator.d.ts +79 -0
- package/dist/hud/token-estimator.d.ts.map +1 -0
- package/dist/hud/token-estimator.js +126 -0
- package/dist/hud/token-estimator.js.map +1 -0
- package/dist/hud/types.d.ts +2 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/mcp/server.d.ts +1 -2
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +3 -12
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool-tiers.d.ts.map +1 -1
- package/dist/mcp/tool-tiers.js +0 -5
- package/dist/mcp/tool-tiers.js.map +1 -1
- package/dist/mcp/tools/index.d.ts +2 -7
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +2 -25
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +5 -9
- package/dist/mcp-server.js.map +1 -1
- package/dist/rules/index.js +2 -2
- package/dist/utils/exec.js +2 -2
- package/dist/utils/exec.js.map +1 -1
- package/dist/utils/gitignore.d.ts.map +1 -1
- package/dist/utils/gitignore.js +0 -5
- package/dist/utils/gitignore.js.map +1 -1
- package/package.json +77 -76
- package/dist/core/context/focus-resource-loader.d.ts +0 -143
- package/dist/core/context/focus-resource-loader.d.ts.map +0 -1
- package/dist/core/context/focus-resource-loader.js +0 -305
- package/dist/core/context/focus-resource-loader.js.map +0 -1
- package/dist/core/context-repl/__tests__/repl-environment.test.d.ts +0 -7
- package/dist/core/context-repl/__tests__/repl-environment.test.d.ts.map +0 -1
- package/dist/core/context-repl/__tests__/repl-environment.test.js +0 -335
- package/dist/core/context-repl/__tests__/repl-environment.test.js.map +0 -1
- package/dist/core/context-repl/context-window-monitor.d.ts +0 -181
- package/dist/core/context-repl/context-window-monitor.d.ts.map +0 -1
- package/dist/core/context-repl/context-window-monitor.js +0 -309
- package/dist/core/context-repl/context-window-monitor.js.map +0 -1
- package/dist/core/context-repl/repl-environment.d.ts +0 -66
- package/dist/core/context-repl/repl-environment.d.ts.map +0 -1
- package/dist/core/context-repl/repl-environment.js +0 -795
- package/dist/core/context-repl/repl-environment.js.map +0 -1
- package/dist/mcp/focus-filter.d.ts +0 -74
- package/dist/mcp/focus-filter.d.ts.map +0 -1
- package/dist/mcp/focus-filter.js +0 -229
- package/dist/mcp/focus-filter.js.map +0 -1
- package/dist/mcp/tools/auto-inject.d.ts +0 -36
- package/dist/mcp/tools/auto-inject.d.ts.map +0 -1
- package/dist/mcp/tools/auto-inject.js +0 -143
- package/dist/mcp/tools/auto-inject.js.map +0 -1
- package/dist/mcp/tools/auto-unload.d.ts +0 -29
- package/dist/mcp/tools/auto-unload.d.ts.map +0 -1
- package/dist/mcp/tools/auto-unload.js +0 -151
- package/dist/mcp/tools/auto-unload.js.map +0 -1
- package/dist/mcp/tools/context-repl.d.ts +0 -48
- package/dist/mcp/tools/context-repl.d.ts.map +0 -1
- package/dist/mcp/tools/context-repl.js +0 -290
- package/dist/mcp/tools/context-repl.js.map +0 -1
- package/dist/mcp/tools/health.d.ts +0 -29
- package/dist/mcp/tools/health.d.ts.map +0 -1
- package/dist/mcp/tools/health.js +0 -171
- 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
|