nova-terminal-assistant 0.1.0
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.
Potentially problematic release.
This version of nova-terminal-assistant might be problematic. Click here for more details.
- package/README.md +358 -0
- package/bin/nova +38 -0
- package/bin/nova.js +12 -0
- package/package.json +67 -0
- package/src/cli/commands/SmartCompletion.ts +458 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/startup/IFlowRepl.ts +212 -0
- package/src/cli/startup/InkBasedRepl.ts +1056 -0
- package/src/cli/startup/InteractiveRepl.ts +2833 -0
- package/src/cli/startup/NovaApp.ts +1861 -0
- package/src/cli/startup/index.ts +4 -0
- package/src/cli/startup/parseArgs.ts +293 -0
- package/src/cli/test-modules.ts +27 -0
- package/src/cli/ui/IFlowDropdown.ts +425 -0
- package/src/cli/ui/ModernReplUI.ts +276 -0
- package/src/cli/ui/SimpleSelector2.ts +215 -0
- package/src/cli/ui/components/ConfirmDialog.ts +176 -0
- package/src/cli/ui/components/ErrorPanel.ts +364 -0
- package/src/cli/ui/components/InkAppRunner.tsx +67 -0
- package/src/cli/ui/components/InkComponents.tsx +613 -0
- package/src/cli/ui/components/NovaInkApp.tsx +312 -0
- package/src/cli/ui/components/ProgressBar.ts +177 -0
- package/src/cli/ui/components/ProgressIndicator.ts +298 -0
- package/src/cli/ui/components/QuickActions.ts +396 -0
- package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
- package/src/cli/ui/components/StatusBar.ts +194 -0
- package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
- package/src/cli/ui/components/index.ts +27 -0
- package/src/cli/ui/ink-prototype.tsx +347 -0
- package/src/cli/utils/CliUI.ts +336 -0
- package/src/cli/utils/CompletionHelper.ts +388 -0
- package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
- package/src/cli/utils/EnhancedCompleter.ts +513 -0
- package/src/cli/utils/ErrorEnhancer.ts +429 -0
- package/src/cli/utils/OutputFormatter.ts +193 -0
- package/src/cli/utils/index.ts +9 -0
- package/src/core/agents/AgentOrchestrator.ts +515 -0
- package/src/core/agents/index.ts +17 -0
- package/src/core/audit/AuditLogger.ts +509 -0
- package/src/core/audit/index.ts +11 -0
- package/src/core/auth/AuthManager.d.ts.map +1 -0
- package/src/core/auth/AuthManager.ts +138 -0
- package/src/core/auth/index.d.ts.map +1 -0
- package/src/core/auth/index.ts +2 -0
- package/src/core/config/ConfigManager.d.ts.map +1 -0
- package/src/core/config/ConfigManager.test.ts +183 -0
- package/src/core/config/ConfigManager.ts +1219 -0
- package/src/core/config/index.d.ts.map +1 -0
- package/src/core/config/index.ts +1 -0
- package/src/core/context/ContextBuilder.d.ts.map +1 -0
- package/src/core/context/ContextBuilder.ts +171 -0
- package/src/core/context/ContextCompressor.d.ts.map +1 -0
- package/src/core/context/ContextCompressor.ts +642 -0
- package/src/core/context/LayeredMemoryManager.ts +657 -0
- package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
- package/src/core/context/MemoryDiscovery.ts +175 -0
- package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
- package/src/core/context/defaultSystemPrompt.ts +35 -0
- package/src/core/context/index.d.ts.map +1 -0
- package/src/core/context/index.ts +22 -0
- package/src/core/extensions/SkillGenerator.ts +421 -0
- package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
- package/src/core/extensions/SkillInstaller.ts +257 -0
- package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
- package/src/core/extensions/SkillRegistry.ts +361 -0
- package/src/core/extensions/SkillValidator.ts +525 -0
- package/src/core/extensions/index.ts +15 -0
- package/src/core/index.d.ts.map +1 -0
- package/src/core/index.ts +42 -0
- package/src/core/mcp/McpManager.d.ts.map +1 -0
- package/src/core/mcp/McpManager.ts +632 -0
- package/src/core/mcp/index.d.ts.map +1 -0
- package/src/core/mcp/index.ts +2 -0
- package/src/core/model/ModelClient.d.ts.map +1 -0
- package/src/core/model/ModelClient.ts +217 -0
- package/src/core/model/ModelConnectionTester.ts +363 -0
- package/src/core/model/ModelValidator.ts +348 -0
- package/src/core/model/index.d.ts.map +1 -0
- package/src/core/model/index.ts +6 -0
- package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
- package/src/core/model/providers/AnthropicProvider.ts +279 -0
- package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
- package/src/core/model/providers/CodingPlanProvider.ts +210 -0
- package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
- package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
- package/src/core/model/providers/OllamaManager.ts +201 -0
- package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaProvider.ts +73 -0
- package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
- package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAIProvider.ts +29 -0
- package/src/core/model/providers/index.d.ts.map +1 -0
- package/src/core/model/providers/index.ts +12 -0
- package/src/core/model/types.d.ts.map +1 -0
- package/src/core/model/types.ts +77 -0
- package/src/core/security/ApprovalManager.d.ts.map +1 -0
- package/src/core/security/ApprovalManager.ts +174 -0
- package/src/core/security/FileFilter.d.ts.map +1 -0
- package/src/core/security/FileFilter.ts +141 -0
- package/src/core/security/HookExecutor.d.ts.map +1 -0
- package/src/core/security/HookExecutor.ts +178 -0
- package/src/core/security/SandboxExecutor.ts +447 -0
- package/src/core/security/index.d.ts.map +1 -0
- package/src/core/security/index.ts +8 -0
- package/src/core/session/AgentLoop.d.ts.map +1 -0
- package/src/core/session/AgentLoop.ts +501 -0
- package/src/core/session/SessionManager.d.ts.map +1 -0
- package/src/core/session/SessionManager.test.ts +183 -0
- package/src/core/session/SessionManager.ts +460 -0
- package/src/core/session/index.d.ts.map +1 -0
- package/src/core/session/index.ts +3 -0
- package/src/core/telemetry/Telemetry.d.ts.map +1 -0
- package/src/core/telemetry/Telemetry.ts +90 -0
- package/src/core/telemetry/TelemetryService.ts +531 -0
- package/src/core/telemetry/index.d.ts.map +1 -0
- package/src/core/telemetry/index.ts +12 -0
- package/src/core/testing/AutoFixer.ts +385 -0
- package/src/core/testing/ErrorAnalyzer.ts +499 -0
- package/src/core/testing/TestRunner.ts +265 -0
- package/src/core/testing/agent-cli-tests.ts +538 -0
- package/src/core/testing/index.ts +11 -0
- package/src/core/tools/ToolRegistry.d.ts.map +1 -0
- package/src/core/tools/ToolRegistry.test.ts +206 -0
- package/src/core/tools/ToolRegistry.ts +260 -0
- package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/EditFileTool.ts +97 -0
- package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
- package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/MemoryTool.ts +102 -0
- package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/ReadFileTool.ts +58 -0
- package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchContentTool.ts +94 -0
- package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchFileTool.ts +61 -0
- package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
- package/src/core/tools/impl/ShellTool.ts +118 -0
- package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
- package/src/core/tools/impl/TaskTool.ts +207 -0
- package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
- package/src/core/tools/impl/TodoTool.ts +122 -0
- package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebFetchTool.ts +103 -0
- package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebSearchTool.ts +89 -0
- package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/WriteFileTool.ts +49 -0
- package/src/core/tools/impl/index.d.ts.map +1 -0
- package/src/core/tools/impl/index.ts +16 -0
- package/src/core/tools/index.d.ts.map +1 -0
- package/src/core/tools/index.ts +7 -0
- package/src/core/tools/schemas/execution.d.ts.map +1 -0
- package/src/core/tools/schemas/execution.ts +42 -0
- package/src/core/tools/schemas/file.d.ts.map +1 -0
- package/src/core/tools/schemas/file.ts +119 -0
- package/src/core/tools/schemas/index.d.ts.map +1 -0
- package/src/core/tools/schemas/index.ts +11 -0
- package/src/core/tools/schemas/memory.d.ts.map +1 -0
- package/src/core/tools/schemas/memory.ts +52 -0
- package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
- package/src/core/tools/schemas/orchestration.ts +44 -0
- package/src/core/tools/schemas/search.d.ts.map +1 -0
- package/src/core/tools/schemas/search.ts +112 -0
- package/src/core/tools/schemas/todo.d.ts.map +1 -0
- package/src/core/tools/schemas/todo.ts +32 -0
- package/src/core/tools/schemas/web.d.ts.map +1 -0
- package/src/core/tools/schemas/web.ts +86 -0
- package/src/core/types/config.d.ts.map +1 -0
- package/src/core/types/config.ts +200 -0
- package/src/core/types/errors.d.ts.map +1 -0
- package/src/core/types/errors.ts +204 -0
- package/src/core/types/index.d.ts.map +1 -0
- package/src/core/types/index.ts +8 -0
- package/src/core/types/session.d.ts.map +1 -0
- package/src/core/types/session.ts +216 -0
- package/src/core/types/tools.d.ts.map +1 -0
- package/src/core/types/tools.ts +157 -0
- package/src/core/utils/CheckpointManager.d.ts.map +1 -0
- package/src/core/utils/CheckpointManager.ts +327 -0
- package/src/core/utils/Logger.d.ts.map +1 -0
- package/src/core/utils/Logger.ts +98 -0
- package/src/core/utils/RetryManager.ts +471 -0
- package/src/core/utils/TokenCounter.d.ts.map +1 -0
- package/src/core/utils/TokenCounter.ts +414 -0
- package/src/core/utils/VectorMemoryStore.ts +440 -0
- package/src/core/utils/helpers.d.ts.map +1 -0
- package/src/core/utils/helpers.ts +89 -0
- package/src/core/utils/index.d.ts.map +1 -0
- package/src/core/utils/index.ts +19 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Agent CLI Tests - Comprehensive test suite for Agent-friendly CLI features
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import * as fs from 'node:fs/promises';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const projectRoot = path.resolve(__dirname, '../../..');
|
|
13
|
+
|
|
14
|
+
interface TestResult {
|
|
15
|
+
name: string;
|
|
16
|
+
passed: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
duration: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface TestSuite {
|
|
22
|
+
name: string;
|
|
23
|
+
tests: TestResult[];
|
|
24
|
+
passed: number;
|
|
25
|
+
failed: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Helper to run CLI command
|
|
29
|
+
async function runNovaCommand(args: string[], stdin?: string): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const cliPath = path.join(projectRoot, 'packages/cli/bin/nova.js');
|
|
32
|
+
const child = spawn('node', [cliPath, ...args], {
|
|
33
|
+
cwd: projectRoot,
|
|
34
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
let stdout = '';
|
|
38
|
+
let stderr = '';
|
|
39
|
+
|
|
40
|
+
child.stdout.on('data', (data) => {
|
|
41
|
+
stdout += data.toString();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
child.stderr.on('data', (data) => {
|
|
45
|
+
stderr += data.toString();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
child.on('close', (exitCode) => {
|
|
49
|
+
resolve({ stdout, stderr, exitCode: exitCode || 0 });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
child.on('error', reject);
|
|
53
|
+
|
|
54
|
+
if (stdin) {
|
|
55
|
+
child.stdin.write(stdin);
|
|
56
|
+
}
|
|
57
|
+
child.stdin.end();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Test Suite 1: Non-interactive mode (--no-input)
|
|
62
|
+
async function testNonInteractiveMode(): Promise<TestSuite> {
|
|
63
|
+
const suite: TestSuite = {
|
|
64
|
+
name: 'Non-interactive Mode Tests',
|
|
65
|
+
tests: [],
|
|
66
|
+
passed: 0,
|
|
67
|
+
failed: 0,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Test 1: --no-input flag prevents interactive prompts
|
|
71
|
+
const start = Date.now();
|
|
72
|
+
try {
|
|
73
|
+
const result = await runNovaCommand(['auth', 'set', 'anthropic', '--no-input']);
|
|
74
|
+
const duration = Date.now() - start;
|
|
75
|
+
|
|
76
|
+
// Should exit quickly without hanging
|
|
77
|
+
if (duration < 5000 && result.exitCode !== 0) {
|
|
78
|
+
suite.tests.push({
|
|
79
|
+
name: 'API Key missing returns error (non-interactive)',
|
|
80
|
+
passed: true,
|
|
81
|
+
duration,
|
|
82
|
+
});
|
|
83
|
+
suite.passed++;
|
|
84
|
+
} else {
|
|
85
|
+
suite.tests.push({
|
|
86
|
+
name: 'API Key missing returns error (non-interactive)',
|
|
87
|
+
passed: false,
|
|
88
|
+
error: `Command took too long (${duration}ms) or exited with code 0`,
|
|
89
|
+
duration,
|
|
90
|
+
});
|
|
91
|
+
suite.failed++;
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const duration = Date.now() - start;
|
|
95
|
+
suite.tests.push({
|
|
96
|
+
name: 'API Key missing returns error (non-interactive)',
|
|
97
|
+
passed: false,
|
|
98
|
+
error: (err as Error).message,
|
|
99
|
+
duration,
|
|
100
|
+
});
|
|
101
|
+
suite.failed++;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Test 2: --no-input with --json returns structured error
|
|
105
|
+
const start2 = Date.now();
|
|
106
|
+
try {
|
|
107
|
+
const result = await runNovaCommand(['auth', 'set', 'anthropic', '--no-input', '--json']);
|
|
108
|
+
const duration = Date.now() - start2;
|
|
109
|
+
|
|
110
|
+
// Should return JSON error
|
|
111
|
+
const jsonOutput = JSON.parse(result.stdout || result.stderr);
|
|
112
|
+
if (jsonOutput.error && jsonOutput.suggestions) {
|
|
113
|
+
suite.tests.push({
|
|
114
|
+
name: 'JSON error format includes actionable suggestions',
|
|
115
|
+
passed: true,
|
|
116
|
+
duration,
|
|
117
|
+
});
|
|
118
|
+
suite.passed++;
|
|
119
|
+
} else {
|
|
120
|
+
suite.tests.push({
|
|
121
|
+
name: 'JSON error format includes actionable suggestions',
|
|
122
|
+
passed: false,
|
|
123
|
+
error: 'JSON output missing error or suggestions',
|
|
124
|
+
duration,
|
|
125
|
+
});
|
|
126
|
+
suite.failed++;
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
const duration = Date.now() - start2;
|
|
130
|
+
suite.tests.push({
|
|
131
|
+
name: 'JSON error format includes actionable suggestions',
|
|
132
|
+
passed: false,
|
|
133
|
+
error: (err as Error).message,
|
|
134
|
+
duration,
|
|
135
|
+
});
|
|
136
|
+
suite.failed++;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return suite;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Test Suite 2: Structured output (--json)
|
|
143
|
+
async function testStructuredOutput(): Promise<TestSuite> {
|
|
144
|
+
const suite: TestSuite = {
|
|
145
|
+
name: 'Structured Output Tests',
|
|
146
|
+
tests: [],
|
|
147
|
+
passed: 0,
|
|
148
|
+
failed: 0,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Test 1: model list --json returns valid JSON
|
|
152
|
+
const start = Date.now();
|
|
153
|
+
try {
|
|
154
|
+
const result = await runNovaCommand(['model', 'list', '--json']);
|
|
155
|
+
const duration = Date.now() - start;
|
|
156
|
+
|
|
157
|
+
const jsonOutput = JSON.parse(result.stdout);
|
|
158
|
+
if (Array.isArray(jsonOutput.models) && jsonOutput.models.length > 0) {
|
|
159
|
+
suite.tests.push({
|
|
160
|
+
name: 'model list --json returns valid JSON array',
|
|
161
|
+
passed: true,
|
|
162
|
+
duration,
|
|
163
|
+
});
|
|
164
|
+
suite.passed++;
|
|
165
|
+
} else {
|
|
166
|
+
suite.tests.push({
|
|
167
|
+
name: 'model list --json returns valid JSON array',
|
|
168
|
+
passed: false,
|
|
169
|
+
error: 'JSON output is not a valid array or is empty',
|
|
170
|
+
duration,
|
|
171
|
+
});
|
|
172
|
+
suite.failed++;
|
|
173
|
+
}
|
|
174
|
+
} catch (err) {
|
|
175
|
+
const duration = Date.now() - start;
|
|
176
|
+
suite.tests.push({
|
|
177
|
+
name: 'model list --json returns valid JSON array',
|
|
178
|
+
passed: false,
|
|
179
|
+
error: (err as Error).message,
|
|
180
|
+
duration,
|
|
181
|
+
});
|
|
182
|
+
suite.failed++;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Test 2: auth status --json returns structured data
|
|
186
|
+
const start2 = Date.now();
|
|
187
|
+
try {
|
|
188
|
+
const result = await runNovaCommand(['auth', 'status', '--json']);
|
|
189
|
+
const duration = Date.now() - start2;
|
|
190
|
+
|
|
191
|
+
const jsonOutput = JSON.parse(result.stdout);
|
|
192
|
+
if (jsonOutput.providers && typeof jsonOutput.providers === 'object') {
|
|
193
|
+
suite.tests.push({
|
|
194
|
+
name: 'auth status --json returns structured provider data',
|
|
195
|
+
passed: true,
|
|
196
|
+
duration,
|
|
197
|
+
});
|
|
198
|
+
suite.passed++;
|
|
199
|
+
} else {
|
|
200
|
+
suite.tests.push({
|
|
201
|
+
name: 'auth status --json returns structured provider data',
|
|
202
|
+
passed: false,
|
|
203
|
+
error: 'JSON output missing providers object',
|
|
204
|
+
duration,
|
|
205
|
+
});
|
|
206
|
+
suite.failed++;
|
|
207
|
+
}
|
|
208
|
+
} catch (err) {
|
|
209
|
+
const duration = Date.now() - start2;
|
|
210
|
+
suite.tests.push({
|
|
211
|
+
name: 'auth status --json returns structured provider data',
|
|
212
|
+
passed: false,
|
|
213
|
+
error: (err as Error).message,
|
|
214
|
+
duration,
|
|
215
|
+
});
|
|
216
|
+
suite.failed++;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return suite;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Test Suite 3: Actionable error messages
|
|
223
|
+
async function testActionableErrors(): Promise<TestSuite> {
|
|
224
|
+
const suite: TestSuite = {
|
|
225
|
+
name: 'Actionable Error Messages Tests',
|
|
226
|
+
tests: [],
|
|
227
|
+
passed: 0,
|
|
228
|
+
failed: 0,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Test 1: Missing API key shows environment variable suggestion
|
|
232
|
+
const start = Date.now();
|
|
233
|
+
try {
|
|
234
|
+
const result = await runNovaCommand(['-m', 'claude-3-5-sonnet', '-p', 'test', '--no-input']);
|
|
235
|
+
const duration = Date.now() - start;
|
|
236
|
+
|
|
237
|
+
if (result.stderr.includes('ANTHROPIC_API_KEY') || result.stderr.includes('export')) {
|
|
238
|
+
suite.tests.push({
|
|
239
|
+
name: 'Error message includes environment variable suggestion',
|
|
240
|
+
passed: true,
|
|
241
|
+
duration,
|
|
242
|
+
});
|
|
243
|
+
suite.passed++;
|
|
244
|
+
} else {
|
|
245
|
+
suite.tests.push({
|
|
246
|
+
name: 'Error message includes environment variable suggestion',
|
|
247
|
+
passed: false,
|
|
248
|
+
error: 'Error message missing environment variable suggestion',
|
|
249
|
+
duration,
|
|
250
|
+
});
|
|
251
|
+
suite.failed++;
|
|
252
|
+
}
|
|
253
|
+
} catch (err) {
|
|
254
|
+
const duration = Date.now() - start;
|
|
255
|
+
suite.tests.push({
|
|
256
|
+
name: 'Error message includes environment variable suggestion',
|
|
257
|
+
passed: false,
|
|
258
|
+
error: (err as Error).message,
|
|
259
|
+
duration,
|
|
260
|
+
});
|
|
261
|
+
suite.failed++;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Test 2: Invalid command shows usage examples
|
|
265
|
+
const start2 = Date.now();
|
|
266
|
+
try {
|
|
267
|
+
const result = await runNovaCommand(['invalid-command', '--no-input']);
|
|
268
|
+
const duration = Date.now() - start2;
|
|
269
|
+
|
|
270
|
+
if (result.stderr.includes('Usage:') || result.stderr.includes('Example:')) {
|
|
271
|
+
suite.tests.push({
|
|
272
|
+
name: 'Invalid command shows usage examples',
|
|
273
|
+
passed: true,
|
|
274
|
+
duration,
|
|
275
|
+
});
|
|
276
|
+
suite.passed++;
|
|
277
|
+
} else {
|
|
278
|
+
suite.tests.push({
|
|
279
|
+
name: 'Invalid command shows usage examples',
|
|
280
|
+
passed: false,
|
|
281
|
+
error: 'Error message missing usage examples',
|
|
282
|
+
duration,
|
|
283
|
+
});
|
|
284
|
+
suite.failed++;
|
|
285
|
+
}
|
|
286
|
+
} catch (err) {
|
|
287
|
+
const duration = Date.now() - start2;
|
|
288
|
+
suite.tests.push({
|
|
289
|
+
name: 'Invalid command shows usage examples',
|
|
290
|
+
passed: false,
|
|
291
|
+
error: (err as Error).message,
|
|
292
|
+
duration,
|
|
293
|
+
});
|
|
294
|
+
suite.failed++;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return suite;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Test Suite 4: Bounded output with truncation guidance
|
|
301
|
+
async function testBoundedOutput(): Promise<TestSuite> {
|
|
302
|
+
const suite: TestSuite = {
|
|
303
|
+
name: 'Bounded Output Tests',
|
|
304
|
+
tests: [],
|
|
305
|
+
passed: 0,
|
|
306
|
+
failed: 0,
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// Test 1: Large directory listing shows truncation message
|
|
310
|
+
const start = Date.now();
|
|
311
|
+
try {
|
|
312
|
+
// Create a test directory with many files
|
|
313
|
+
const testDir = path.join(projectRoot, 'test-large-dir');
|
|
314
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
315
|
+
|
|
316
|
+
// Create 100 test files
|
|
317
|
+
for (let i = 0; i < 100; i++) {
|
|
318
|
+
await fs.writeFile(path.join(testDir, `file${i}.txt`), `content ${i}`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const result = await runNovaCommand(['--json', '-p', `list_directory ${testDir} --recursive`]);
|
|
322
|
+
const duration = Date.now() - start;
|
|
323
|
+
|
|
324
|
+
// Cleanup
|
|
325
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
326
|
+
|
|
327
|
+
if (result.stdout.includes('TRUNCATED') || result.stdout.includes('limit')) {
|
|
328
|
+
suite.tests.push({
|
|
329
|
+
name: 'Large directory listing shows truncation message',
|
|
330
|
+
passed: true,
|
|
331
|
+
duration,
|
|
332
|
+
});
|
|
333
|
+
suite.passed++;
|
|
334
|
+
} else {
|
|
335
|
+
suite.tests.push({
|
|
336
|
+
name: 'Large directory listing shows truncation message',
|
|
337
|
+
passed: false,
|
|
338
|
+
error: 'Output missing truncation message',
|
|
339
|
+
duration,
|
|
340
|
+
});
|
|
341
|
+
suite.failed++;
|
|
342
|
+
}
|
|
343
|
+
} catch (err) {
|
|
344
|
+
const duration = Date.now() - start;
|
|
345
|
+
suite.tests.push({
|
|
346
|
+
name: 'Large directory listing shows truncation message',
|
|
347
|
+
passed: false,
|
|
348
|
+
error: (err as Error).message,
|
|
349
|
+
duration,
|
|
350
|
+
});
|
|
351
|
+
suite.failed++;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Test 2: Shell command with large output shows continuation guidance
|
|
355
|
+
const start2 = Date.now();
|
|
356
|
+
try {
|
|
357
|
+
const result = await runNovaCommand(['--json', '-p', 'execute_command "dir /s"']);
|
|
358
|
+
const duration = Date.now() - start2;
|
|
359
|
+
|
|
360
|
+
if (result.stdout.includes('TRUNCATED') || result.stdout.includes('redirect')) {
|
|
361
|
+
suite.tests.push({
|
|
362
|
+
name: 'Large command output shows continuation guidance',
|
|
363
|
+
passed: true,
|
|
364
|
+
duration,
|
|
365
|
+
});
|
|
366
|
+
suite.passed++;
|
|
367
|
+
} else {
|
|
368
|
+
suite.tests.push({
|
|
369
|
+
name: 'Large command output shows continuation guidance',
|
|
370
|
+
passed: false,
|
|
371
|
+
error: 'Output missing continuation guidance',
|
|
372
|
+
duration,
|
|
373
|
+
});
|
|
374
|
+
suite.failed++;
|
|
375
|
+
}
|
|
376
|
+
} catch (err) {
|
|
377
|
+
const duration = Date.now() - start2;
|
|
378
|
+
suite.tests.push({
|
|
379
|
+
name: 'Large command output shows continuation guidance',
|
|
380
|
+
passed: false,
|
|
381
|
+
error: (err as Error).message,
|
|
382
|
+
duration,
|
|
383
|
+
});
|
|
384
|
+
suite.failed++;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return suite;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Test Suite 5: Real-world Agent scenarios
|
|
391
|
+
async function testRealWorldScenarios(): Promise<TestSuite> {
|
|
392
|
+
const suite: TestSuite = {
|
|
393
|
+
name: 'Real-world Agent Scenarios',
|
|
394
|
+
tests: [],
|
|
395
|
+
passed: 0,
|
|
396
|
+
failed: 0,
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// Test 1: Agent analyzes codebase and generates report
|
|
400
|
+
const start = Date.now();
|
|
401
|
+
try {
|
|
402
|
+
const result = await runNovaCommand([
|
|
403
|
+
'--no-input',
|
|
404
|
+
'--json',
|
|
405
|
+
'-p',
|
|
406
|
+
'Analyze this codebase structure and list the main modules',
|
|
407
|
+
'-d',
|
|
408
|
+
projectRoot,
|
|
409
|
+
]);
|
|
410
|
+
const duration = Date.now() - start;
|
|
411
|
+
|
|
412
|
+
const jsonOutput = JSON.parse(result.stdout);
|
|
413
|
+
if (jsonOutput.content && !result.stderr.includes('error')) {
|
|
414
|
+
suite.tests.push({
|
|
415
|
+
name: 'Agent analyzes codebase without interactive prompts',
|
|
416
|
+
passed: true,
|
|
417
|
+
duration,
|
|
418
|
+
});
|
|
419
|
+
suite.passed++;
|
|
420
|
+
} else {
|
|
421
|
+
suite.tests.push({
|
|
422
|
+
name: 'Agent analyzes codebase without interactive prompts',
|
|
423
|
+
passed: false,
|
|
424
|
+
error: 'Analysis failed or produced errors',
|
|
425
|
+
duration,
|
|
426
|
+
});
|
|
427
|
+
suite.failed++;
|
|
428
|
+
}
|
|
429
|
+
} catch (err) {
|
|
430
|
+
const duration = Date.now() - start;
|
|
431
|
+
suite.tests.push({
|
|
432
|
+
name: 'Agent analyzes codebase without interactive prompts',
|
|
433
|
+
passed: false,
|
|
434
|
+
error: (err as Error).message,
|
|
435
|
+
duration,
|
|
436
|
+
});
|
|
437
|
+
suite.failed++;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Test 2: Agent runs test suite and parses results
|
|
441
|
+
const start2 = Date.now();
|
|
442
|
+
try {
|
|
443
|
+
const result = await runNovaCommand([
|
|
444
|
+
'--no-input',
|
|
445
|
+
'--json',
|
|
446
|
+
'-p',
|
|
447
|
+
'Run npm test and show me the results',
|
|
448
|
+
'-d',
|
|
449
|
+
projectRoot,
|
|
450
|
+
]);
|
|
451
|
+
const duration = Date.now() - start2;
|
|
452
|
+
|
|
453
|
+
const jsonOutput = JSON.parse(result.stdout);
|
|
454
|
+
if (jsonOutput.content || jsonOutput.metadata) {
|
|
455
|
+
suite.tests.push({
|
|
456
|
+
name: 'Agent runs tests and parses structured output',
|
|
457
|
+
passed: true,
|
|
458
|
+
duration,
|
|
459
|
+
});
|
|
460
|
+
suite.passed++;
|
|
461
|
+
} else {
|
|
462
|
+
suite.tests.push({
|
|
463
|
+
name: 'Agent runs tests and parses structured output',
|
|
464
|
+
passed: false,
|
|
465
|
+
error: 'No structured output found',
|
|
466
|
+
duration,
|
|
467
|
+
});
|
|
468
|
+
suite.failed++;
|
|
469
|
+
}
|
|
470
|
+
} catch (err) {
|
|
471
|
+
const duration = Date.now() - start2;
|
|
472
|
+
suite.tests.push({
|
|
473
|
+
name: 'Agent runs tests and parses structured output',
|
|
474
|
+
passed: false,
|
|
475
|
+
error: (err as Error).message,
|
|
476
|
+
duration,
|
|
477
|
+
});
|
|
478
|
+
suite.failed++;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return suite;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Main test runner
|
|
485
|
+
export async function runAgentCLITests(): Promise<void> {
|
|
486
|
+
console.log('š Running Agent-friendly CLI Tests\n');
|
|
487
|
+
|
|
488
|
+
const suites = await Promise.all([
|
|
489
|
+
testNonInteractiveMode(),
|
|
490
|
+
testStructuredOutput(),
|
|
491
|
+
testActionableErrors(),
|
|
492
|
+
testBoundedOutput(),
|
|
493
|
+
testRealWorldScenarios(),
|
|
494
|
+
]);
|
|
495
|
+
|
|
496
|
+
let totalPassed = 0;
|
|
497
|
+
let totalFailed = 0;
|
|
498
|
+
let totalDuration = 0;
|
|
499
|
+
|
|
500
|
+
for (const suite of suites) {
|
|
501
|
+
console.log(`\nš ${suite.name}`);
|
|
502
|
+
console.log('=' .repeat(60));
|
|
503
|
+
|
|
504
|
+
for (const test of suite.tests) {
|
|
505
|
+
const status = test.passed ? 'ā
' : 'ā';
|
|
506
|
+
console.log(`${status} ${test.name} (${test.duration}ms)`);
|
|
507
|
+
if (test.error) {
|
|
508
|
+
console.log(` Error: ${test.error}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
totalPassed += suite.passed;
|
|
513
|
+
totalFailed += suite.failed;
|
|
514
|
+
totalDuration += suite.tests.reduce((sum, t) => sum + t.duration, 0);
|
|
515
|
+
|
|
516
|
+
console.log(`\n Summary: ${suite.passed} passed, ${suite.failed} failed`);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
console.log('\n' + '='.repeat(60));
|
|
520
|
+
console.log(`š Overall Summary: ${totalPassed} passed, ${totalFailed} failed`);
|
|
521
|
+
console.log(`ā±ļø Total Duration: ${totalDuration}ms`);
|
|
522
|
+
|
|
523
|
+
if (totalFailed > 0) {
|
|
524
|
+
console.log('\nā ļø Some tests failed. Review the errors above.');
|
|
525
|
+
process.exit(1);
|
|
526
|
+
} else {
|
|
527
|
+
console.log('\nš All tests passed! CLI is fully Agent-friendly.');
|
|
528
|
+
process.exit(0);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Run tests if this file is executed directly
|
|
533
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
534
|
+
runAgentCLITests().catch((err) => {
|
|
535
|
+
console.error('Test runner failed:', err);
|
|
536
|
+
process.exit(1);
|
|
537
|
+
});
|
|
538
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// testing - Test runner, error analyzer, and auto-fixer for self-healing
|
|
3
|
+
// Reference: Aider-style test-fix cycle
|
|
4
|
+
// ============================================================================
|
|
5
|
+
|
|
6
|
+
export { TestRunner } from './TestRunner.js';
|
|
7
|
+
export type { TestResult, FailedTest, TestRunnerOptions } from './TestRunner.js';
|
|
8
|
+
export { ErrorAnalyzer } from './ErrorAnalyzer.js';
|
|
9
|
+
export type { FixSuggestion, ErrorType } from './ErrorAnalyzer.js';
|
|
10
|
+
export { AutoFixer } from './AutoFixer.js';
|
|
11
|
+
export type { FixResult, FixIteration, AutoFixerOptions } from './AutoFixer.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToolRegistry.d.ts","sourceRoot":"","sources":["ToolRegistry.ts"],"names":[],"mappings":"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"}
|