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,157 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Tool Types - Tool definitions, schemas, and execution
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import type { ToolCallId, ApprovalMode, ApprovalRequest, ApprovalResponse } from './session.js';
|
|
6
|
+
|
|
7
|
+
// --- Tool Categories ---
|
|
8
|
+
export type ToolCategory =
|
|
9
|
+
| 'file' // File read/write/edit operations
|
|
10
|
+
| 'search' // Search & grep operations
|
|
11
|
+
| 'execution' // Shell command execution
|
|
12
|
+
| 'web' // Web search & fetch
|
|
13
|
+
| 'memory' // Memory & context management
|
|
14
|
+
| 'orchestration' // Agent orchestration (task, plan, etc.)
|
|
15
|
+
| 'mcp'; // MCP server tools
|
|
16
|
+
|
|
17
|
+
// --- Tool Definition ---
|
|
18
|
+
export interface ToolParameter {
|
|
19
|
+
name: string;
|
|
20
|
+
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
21
|
+
description: string;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
default?: unknown;
|
|
24
|
+
enum?: string[];
|
|
25
|
+
items?: ToolParameter;
|
|
26
|
+
properties?: Record<string, ToolParameter>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ToolDefinition {
|
|
30
|
+
/** Unique tool name */
|
|
31
|
+
name: string;
|
|
32
|
+
/** Human-readable description for the LLM */
|
|
33
|
+
description: string;
|
|
34
|
+
/** Category for organizational purposes */
|
|
35
|
+
category: ToolCategory;
|
|
36
|
+
/** JSON Schema for input parameters */
|
|
37
|
+
inputSchema: Record<string, unknown>;
|
|
38
|
+
/** Whether this tool requires approval */
|
|
39
|
+
requiresApproval: boolean | ((input: Record<string, unknown>, mode: ApprovalMode) => boolean);
|
|
40
|
+
/** Risk level when tool is used */
|
|
41
|
+
riskLevel: 'low' | 'medium' | 'high' | 'critical';
|
|
42
|
+
/** Timeout in milliseconds */
|
|
43
|
+
timeout?: number;
|
|
44
|
+
/** Whether the tool can be cancelled mid-execution */
|
|
45
|
+
cancellable?: boolean;
|
|
46
|
+
/** Tags for searchability */
|
|
47
|
+
tags?: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- Tool Execution ---
|
|
51
|
+
export type ToolExecutionStatus = 'pending' | 'running' | 'completed' | 'error' | 'cancelled';
|
|
52
|
+
|
|
53
|
+
export interface ToolCall {
|
|
54
|
+
id: ToolCallId;
|
|
55
|
+
toolName: string;
|
|
56
|
+
input: Record<string, unknown>;
|
|
57
|
+
status: ToolExecutionStatus;
|
|
58
|
+
startTime?: number;
|
|
59
|
+
endTime?: number;
|
|
60
|
+
result?: ToolResult;
|
|
61
|
+
error?: ToolErrorData;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ToolResult {
|
|
65
|
+
output: string;
|
|
66
|
+
metadata?: Record<string, unknown>;
|
|
67
|
+
filesAffected?: string[];
|
|
68
|
+
duration: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ToolErrorData {
|
|
72
|
+
code: string;
|
|
73
|
+
message: string;
|
|
74
|
+
details?: unknown;
|
|
75
|
+
retryable: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// --- Tool Registry ---
|
|
79
|
+
export interface ToolRegistryEntry {
|
|
80
|
+
definition: ToolDefinition;
|
|
81
|
+
handler: ToolHandler;
|
|
82
|
+
enabled: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type ToolHandler = (input: ToolHandlerInput) => Promise<ToolHandlerOutput>;
|
|
86
|
+
|
|
87
|
+
export interface ToolHandlerInput {
|
|
88
|
+
params: Record<string, unknown>;
|
|
89
|
+
context: ToolExecutionContext;
|
|
90
|
+
abortSignal?: AbortSignal;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ToolExecutionContext {
|
|
94
|
+
sessionId: string;
|
|
95
|
+
workingDirectory: string;
|
|
96
|
+
environment: Record<string, string>;
|
|
97
|
+
model: string;
|
|
98
|
+
approvalMode: ApprovalMode;
|
|
99
|
+
metadata?: Record<string, unknown>;
|
|
100
|
+
/** Maximum tokens for the model */
|
|
101
|
+
maxTokens?: number;
|
|
102
|
+
/** Approval required callback */
|
|
103
|
+
onApprovalRequired?: (request: ApprovalRequest) => Promise<ApprovalResponse>;
|
|
104
|
+
/** Session manager instance */
|
|
105
|
+
sessionManager?: unknown;
|
|
106
|
+
/** Tool registry instance */
|
|
107
|
+
toolRegistry?: unknown;
|
|
108
|
+
/** Model client instance */
|
|
109
|
+
modelClient?: unknown;
|
|
110
|
+
/** Context compressor instance */
|
|
111
|
+
contextCompressor?: unknown;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface ToolHandlerOutput {
|
|
115
|
+
content: string;
|
|
116
|
+
isError?: boolean;
|
|
117
|
+
metadata?: Record<string, unknown>;
|
|
118
|
+
filesAffected?: string[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- Streaming Events ---
|
|
122
|
+
export type ToolStreamEvent =
|
|
123
|
+
| { type: 'stdout'; data: string }
|
|
124
|
+
| { type: 'stderr'; data: string }
|
|
125
|
+
| { type: 'progress'; current: number; total: number; message?: string }
|
|
126
|
+
| { type: 'status'; message: string }
|
|
127
|
+
| { type: 'complete' };
|
|
128
|
+
|
|
129
|
+
// --- Builtin Tool Names ---
|
|
130
|
+
export const BUILTIN_TOOLS = {
|
|
131
|
+
// File operations
|
|
132
|
+
READ_FILE: 'read_file',
|
|
133
|
+
WRITE_FILE: 'write_file',
|
|
134
|
+
EDIT_FILE: 'edit_file',
|
|
135
|
+
LIST_DIRECTORY: 'list_directory',
|
|
136
|
+
|
|
137
|
+
// Search operations
|
|
138
|
+
SEARCH_FILE: 'search_file',
|
|
139
|
+
SEARCH_CONTENT: 'search_content',
|
|
140
|
+
GLOB: 'glob',
|
|
141
|
+
|
|
142
|
+
// Execution
|
|
143
|
+
EXECUTE_COMMAND: 'execute_command',
|
|
144
|
+
|
|
145
|
+
// Web
|
|
146
|
+
WEB_SEARCH: 'web_search',
|
|
147
|
+
WEB_FETCH: 'web_fetch',
|
|
148
|
+
|
|
149
|
+
// Memory
|
|
150
|
+
MEMORY_READ: 'memory_read',
|
|
151
|
+
MEMORY_WRITE: 'memory_write',
|
|
152
|
+
|
|
153
|
+
// Orchestration
|
|
154
|
+
TASK: 'task',
|
|
155
|
+
} as const;
|
|
156
|
+
|
|
157
|
+
export type BuiltinToolName = (typeof BUILTIN_TOOLS)[keyof typeof BUILTIN_TOOLS];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CheckpointManager.d.ts","sourceRoot":"","sources":["CheckpointManager.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,UAAU;IACzB,2BAA2B;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oCAAoC;IACpC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAS;gBAEb,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU;IAQrD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA2D7F;;OAEG;IACG,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBlD;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IA2BnC;;OAEG;IACG,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAU5D;;OAEG;IACG,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUpD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,eAAe,CAAC;IAgBvC;;OAEG;IACG,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;IA2B5G;;OAEG;YACW,qBAAqB;IAsBnC;;OAEG;YACW,SAAS;IAiBvB;;OAEG;IACH,OAAO,CAAC,UAAU;CAKnB"}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// CheckpointManager - File snapshot and rollback system
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { promises as fs } from 'node:fs';
|
|
6
|
+
import { join, dirname, relative } from 'node:path';
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import type { NovaConfig } from '../types/config.js';
|
|
9
|
+
|
|
10
|
+
export interface Checkpoint {
|
|
11
|
+
/** Unique checkpoint ID */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Human-readable name */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Creation timestamp */
|
|
16
|
+
timestamp: number;
|
|
17
|
+
/** Files included in this checkpoint */
|
|
18
|
+
files: CheckpointFile[];
|
|
19
|
+
/** Description of changes */
|
|
20
|
+
description?: string;
|
|
21
|
+
/** Parent checkpoint ID (for branching) */
|
|
22
|
+
parentId?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CheckpointFile {
|
|
26
|
+
/** Relative path from workspace root */
|
|
27
|
+
path: string;
|
|
28
|
+
/** File hash for integrity checking */
|
|
29
|
+
hash: string;
|
|
30
|
+
/** File size in bytes */
|
|
31
|
+
size: number;
|
|
32
|
+
/** Modification timestamp */
|
|
33
|
+
mtime: number;
|
|
34
|
+
/** Whether this file was created new */
|
|
35
|
+
isNew?: boolean;
|
|
36
|
+
/** Whether this file was deleted */
|
|
37
|
+
isDeleted?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CheckpointStats {
|
|
41
|
+
totalCheckpoints: number;
|
|
42
|
+
totalSize: number;
|
|
43
|
+
oldest?: number;
|
|
44
|
+
newest?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* CheckpointManager - Manages file snapshots for rollback capability
|
|
49
|
+
*
|
|
50
|
+
* Features:
|
|
51
|
+
* - Create snapshots before destructive operations
|
|
52
|
+
* - Rollback to previous states
|
|
53
|
+
* - List and manage checkpoints
|
|
54
|
+
* - Automatic cleanup of old checkpoints
|
|
55
|
+
*/
|
|
56
|
+
export class CheckpointManager {
|
|
57
|
+
private workspaceRoot: string;
|
|
58
|
+
private checkpointsDir: string;
|
|
59
|
+
private config: NovaConfig;
|
|
60
|
+
private maxCheckpoints: number;
|
|
61
|
+
private maxAgeMs: number;
|
|
62
|
+
|
|
63
|
+
constructor(workspaceRoot: string, config: NovaConfig) {
|
|
64
|
+
this.workspaceRoot = workspaceRoot;
|
|
65
|
+
this.config = config;
|
|
66
|
+
this.checkpointsDir = join(workspaceRoot, '.nova', 'checkpoints');
|
|
67
|
+
this.maxCheckpoints = config.core.maxCheckpoints || 10;
|
|
68
|
+
this.maxAgeMs = (config.core.checkpointMaxAgeDays || 7) * 24 * 60 * 60 * 1000;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Initialize checkpoint directory
|
|
73
|
+
*/
|
|
74
|
+
async initialize(): Promise<void> {
|
|
75
|
+
try {
|
|
76
|
+
await fs.mkdir(this.checkpointsDir, { recursive: true });
|
|
77
|
+
} catch (err) {
|
|
78
|
+
// Directory might already exist
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a checkpoint of specified files
|
|
84
|
+
*/
|
|
85
|
+
async create(name: string, filePatterns: string[], description?: string): Promise<Checkpoint> {
|
|
86
|
+
await this.initialize();
|
|
87
|
+
|
|
88
|
+
const checkpointId = this.generateId();
|
|
89
|
+
const checkpointDir = join(this.checkpointsDir, checkpointId);
|
|
90
|
+
await fs.mkdir(checkpointDir, { recursive: true });
|
|
91
|
+
|
|
92
|
+
const files: CheckpointFile[] = [];
|
|
93
|
+
const matchedFiles = await this.findFiles(filePatterns);
|
|
94
|
+
|
|
95
|
+
for (const filePath of matchedFiles) {
|
|
96
|
+
const fullPath = join(this.workspaceRoot, filePath);
|
|
97
|
+
try {
|
|
98
|
+
const stats = await fs.stat(fullPath);
|
|
99
|
+
if (!stats.isFile()) continue;
|
|
100
|
+
|
|
101
|
+
// Read file content
|
|
102
|
+
const content = await fs.readFile(fullPath);
|
|
103
|
+
|
|
104
|
+
// Calculate hash
|
|
105
|
+
const hash = createHash('sha256').update(content).digest('hex');
|
|
106
|
+
|
|
107
|
+
// Copy file to checkpoint
|
|
108
|
+
const checkpointFilePath = join(checkpointDir, filePath);
|
|
109
|
+
await fs.mkdir(dirname(checkpointFilePath), { recursive: true });
|
|
110
|
+
await fs.writeFile(checkpointFilePath, content);
|
|
111
|
+
|
|
112
|
+
files.push({
|
|
113
|
+
path: filePath,
|
|
114
|
+
hash,
|
|
115
|
+
size: content.length,
|
|
116
|
+
mtime: stats.mtimeMs,
|
|
117
|
+
});
|
|
118
|
+
} catch (err) {
|
|
119
|
+
// File might not exist or be readable
|
|
120
|
+
console.warn(`Warning: Could not checkpoint ${filePath}: ${err}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const checkpoint: Checkpoint = {
|
|
125
|
+
id: checkpointId,
|
|
126
|
+
name,
|
|
127
|
+
timestamp: Date.now(),
|
|
128
|
+
files,
|
|
129
|
+
description,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Save checkpoint metadata
|
|
133
|
+
await fs.writeFile(
|
|
134
|
+
join(checkpointDir, 'metadata.json'),
|
|
135
|
+
JSON.stringify(checkpoint, null, 2)
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Cleanup old checkpoints
|
|
139
|
+
await this.cleanupOldCheckpoints();
|
|
140
|
+
|
|
141
|
+
return checkpoint;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Restore a checkpoint
|
|
146
|
+
*/
|
|
147
|
+
async restore(checkpointId: string): Promise<void> {
|
|
148
|
+
const checkpoint = await this.load(checkpointId);
|
|
149
|
+
if (!checkpoint) {
|
|
150
|
+
throw new Error(`Checkpoint not found: ${checkpointId}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const checkpointDir = join(this.checkpointsDir, checkpointId);
|
|
154
|
+
|
|
155
|
+
for (const file of checkpoint.files) {
|
|
156
|
+
const sourcePath = join(checkpointDir, file.path);
|
|
157
|
+
const destPath = join(this.workspaceRoot, file.path);
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
await fs.mkdir(dirname(destPath), { recursive: true });
|
|
161
|
+
const content = await fs.readFile(sourcePath);
|
|
162
|
+
await fs.writeFile(destPath, content);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
throw new Error(`Failed to restore ${file.path}: ${err}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* List all checkpoints
|
|
171
|
+
*/
|
|
172
|
+
async list(): Promise<Checkpoint[]> {
|
|
173
|
+
await this.initialize();
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const entries = await fs.readdir(this.checkpointsDir, { withFileTypes: true });
|
|
177
|
+
const checkpoints: Checkpoint[] = [];
|
|
178
|
+
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
if (entry.isDirectory()) {
|
|
181
|
+
try {
|
|
182
|
+
const metadataPath = join(this.checkpointsDir, entry.name, 'metadata.json');
|
|
183
|
+
const metadata = await fs.readFile(metadataPath, 'utf-8');
|
|
184
|
+
const checkpoint = JSON.parse(metadata) as Checkpoint;
|
|
185
|
+
checkpoints.push(checkpoint);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
// Skip invalid checkpoints
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Sort by timestamp (newest first)
|
|
193
|
+
return checkpoints.sort((a, b) => b.timestamp - a.timestamp);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get checkpoint by ID
|
|
201
|
+
*/
|
|
202
|
+
async load(checkpointId: string): Promise<Checkpoint | null> {
|
|
203
|
+
try {
|
|
204
|
+
const metadataPath = join(this.checkpointsDir, checkpointId, 'metadata.json');
|
|
205
|
+
const metadata = await fs.readFile(metadataPath, 'utf-8');
|
|
206
|
+
return JSON.parse(metadata) as Checkpoint;
|
|
207
|
+
} catch (err) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Delete a checkpoint
|
|
214
|
+
*/
|
|
215
|
+
async delete(checkpointId: string): Promise<boolean> {
|
|
216
|
+
try {
|
|
217
|
+
const checkpointDir = join(this.checkpointsDir, checkpointId);
|
|
218
|
+
await fs.rm(checkpointDir, { recursive: true, force: true });
|
|
219
|
+
return true;
|
|
220
|
+
} catch (err) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get checkpoint statistics
|
|
227
|
+
*/
|
|
228
|
+
async stats(): Promise<CheckpointStats> {
|
|
229
|
+
const checkpoints = await this.list();
|
|
230
|
+
let totalSize = 0;
|
|
231
|
+
|
|
232
|
+
for (const checkpoint of checkpoints) {
|
|
233
|
+
totalSize += checkpoint.files.reduce((sum, file) => sum + file.size, 0);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
totalCheckpoints: checkpoints.length,
|
|
238
|
+
totalSize,
|
|
239
|
+
oldest: checkpoints.length > 0 ? checkpoints[checkpoints.length - 1].timestamp : undefined,
|
|
240
|
+
newest: checkpoints.length > 0 ? checkpoints[0].timestamp : undefined,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Compare current files with checkpoint
|
|
246
|
+
*/
|
|
247
|
+
async diff(checkpointId: string): Promise<Array<{ path: string; status: 'modified' | 'deleted' | 'added' }>> {
|
|
248
|
+
const checkpoint = await this.load(checkpointId);
|
|
249
|
+
if (!checkpoint) {
|
|
250
|
+
throw new Error(`Checkpoint not found: ${checkpointId}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const differences: Array<{ path: string; status: 'modified' | 'deleted' | 'added' }> = [];
|
|
254
|
+
|
|
255
|
+
// Check files in checkpoint
|
|
256
|
+
for (const file of checkpoint.files) {
|
|
257
|
+
const currentPath = join(this.workspaceRoot, file.path);
|
|
258
|
+
try {
|
|
259
|
+
const currentContent = await fs.readFile(currentPath);
|
|
260
|
+
const currentHash = createHash('sha256').update(currentContent).digest('hex');
|
|
261
|
+
|
|
262
|
+
if (currentHash !== file.hash) {
|
|
263
|
+
differences.push({ path: file.path, status: 'modified' });
|
|
264
|
+
}
|
|
265
|
+
} catch (err) {
|
|
266
|
+
// File doesn't exist (deleted)
|
|
267
|
+
differences.push({ path: file.path, status: 'deleted' });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return differences;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Cleanup old checkpoints based on age and count limits
|
|
276
|
+
*/
|
|
277
|
+
private async cleanupOldCheckpoints(): Promise<void> {
|
|
278
|
+
const checkpoints = await this.list();
|
|
279
|
+
const now = Date.now();
|
|
280
|
+
const toDelete: string[] = [];
|
|
281
|
+
|
|
282
|
+
// Find checkpoints to delete
|
|
283
|
+
for (let i = 0; i < checkpoints.length; i++) {
|
|
284
|
+
const checkpoint = checkpoints[i];
|
|
285
|
+
const age = now - checkpoint.timestamp;
|
|
286
|
+
|
|
287
|
+
// Delete if too old or if we have too many
|
|
288
|
+
if (age > this.maxAgeMs || i >= this.maxCheckpoints) {
|
|
289
|
+
toDelete.push(checkpoint.id);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Delete old checkpoints
|
|
294
|
+
for (const id of toDelete) {
|
|
295
|
+
await this.delete(id);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Find files matching patterns
|
|
301
|
+
*/
|
|
302
|
+
private async findFiles(patterns: string[]): Promise<string[]> {
|
|
303
|
+
const files = new Set<string>();
|
|
304
|
+
|
|
305
|
+
for (const pattern of patterns) {
|
|
306
|
+
if (pattern.includes('*')) {
|
|
307
|
+
// Simple glob pattern - in real implementation use a glob library
|
|
308
|
+
// For now, just add the pattern as-is
|
|
309
|
+
files.add(pattern);
|
|
310
|
+
} else {
|
|
311
|
+
// Single file or directory
|
|
312
|
+
files.add(pattern);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return Array.from(files);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Generate unique checkpoint ID
|
|
321
|
+
*/
|
|
322
|
+
private generateId(): string {
|
|
323
|
+
const timestamp = Date.now().toString(36);
|
|
324
|
+
const random = Math.random().toString(36).substr(2, 6);
|
|
325
|
+
return `cp-${timestamp}-${random}`;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Logger.d.ts","sourceRoot":"","sources":["Logger.ts"],"names":[],"mappings":"AAIA,KAAK,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE/D,UAAU,QAAQ;IAChB,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,QAAiB,EAAE,UAAU,GAAE,MAAa;IAM/E,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI/B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI;IAI5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI;IAI3C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI;IAI3C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI;IAI5C,OAAO,CAAC,GAAG;IAiBX,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,MAAM;IAgBd,UAAU,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,QAAQ,EAAE;IAKxC,YAAY,IAAI,IAAI;CAGrB;AAED,4BAA4B;AAC5B,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,CAErE"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Logger - Simple structured logging
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
|
6
|
+
|
|
7
|
+
export interface LogEntry {
|
|
8
|
+
level: LogLevel;
|
|
9
|
+
message: string;
|
|
10
|
+
data?: unknown;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
source: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class Logger {
|
|
16
|
+
private level: LogLevel;
|
|
17
|
+
private source: string;
|
|
18
|
+
private history: LogEntry[] = [];
|
|
19
|
+
private maxHistory: number;
|
|
20
|
+
|
|
21
|
+
constructor(source: string, level: LogLevel = 'info', maxHistory: number = 1000) {
|
|
22
|
+
this.source = source;
|
|
23
|
+
this.level = level;
|
|
24
|
+
this.maxHistory = maxHistory;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setLevel(level: LogLevel): void {
|
|
28
|
+
this.level = level;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
debug(message: string, data?: unknown): void {
|
|
32
|
+
this.log('debug', message, data);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
info(message: string, data?: unknown): void {
|
|
36
|
+
this.log('info', message, data);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
warn(message: string, data?: unknown): void {
|
|
40
|
+
this.log('warn', message, data);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
error(message: string, data?: unknown): void {
|
|
44
|
+
this.log('error', message, data);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private log(level: LogLevel, message: string, data?: unknown): void {
|
|
48
|
+
if (!this.shouldLog(level)) return;
|
|
49
|
+
|
|
50
|
+
const entry: LogEntry = {
|
|
51
|
+
level,
|
|
52
|
+
message,
|
|
53
|
+
data,
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
source: this.source,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
this.history.push(entry);
|
|
59
|
+
if (this.history.length > this.maxHistory) this.history.shift();
|
|
60
|
+
|
|
61
|
+
this.output(entry);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private shouldLog(level: LogLevel): boolean {
|
|
65
|
+
const levels: LogLevel[] = ['debug', 'info', 'warn', 'error', 'silent'];
|
|
66
|
+
return levels.indexOf(level) >= levels.indexOf(this.level);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private output(entry: LogEntry): void {
|
|
70
|
+
const colors: Record<LogLevel, string> = {
|
|
71
|
+
debug: '\x1b[36m', info: '\x1b[37m', warn: '\x1b[33m', error: '\x1b[31m', silent: '',
|
|
72
|
+
};
|
|
73
|
+
const reset = '\x1b[0m';
|
|
74
|
+
const color = colors[entry.level];
|
|
75
|
+
const prefix = `${entry.timestamp} [${entry.level.toUpperCase()}] [${entry.source}]`;
|
|
76
|
+
|
|
77
|
+
if (entry.level === 'error') {
|
|
78
|
+
console.error(`${color}${prefix}${reset} ${entry.message}`);
|
|
79
|
+
if (entry.data) console.error(`${color} Data: ${JSON.stringify(entry.data)}${reset}`);
|
|
80
|
+
} else {
|
|
81
|
+
console.log(`${color}${prefix}${reset} ${entry.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getHistory(level?: LogLevel): LogEntry[] {
|
|
86
|
+
if (level) return this.history.filter((e) => e.level === level);
|
|
87
|
+
return [...this.history];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
clearHistory(): void {
|
|
91
|
+
this.history = [];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Create a named logger */
|
|
96
|
+
export function createLogger(source: string, level?: LogLevel): Logger {
|
|
97
|
+
return new Logger(source, level);
|
|
98
|
+
}
|