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.

Files changed (192) hide show
  1. package/README.md +358 -0
  2. package/bin/nova +38 -0
  3. package/bin/nova.js +12 -0
  4. package/package.json +67 -0
  5. package/src/cli/commands/SmartCompletion.ts +458 -0
  6. package/src/cli/index.ts +5 -0
  7. package/src/cli/startup/IFlowRepl.ts +212 -0
  8. package/src/cli/startup/InkBasedRepl.ts +1056 -0
  9. package/src/cli/startup/InteractiveRepl.ts +2833 -0
  10. package/src/cli/startup/NovaApp.ts +1861 -0
  11. package/src/cli/startup/index.ts +4 -0
  12. package/src/cli/startup/parseArgs.ts +293 -0
  13. package/src/cli/test-modules.ts +27 -0
  14. package/src/cli/ui/IFlowDropdown.ts +425 -0
  15. package/src/cli/ui/ModernReplUI.ts +276 -0
  16. package/src/cli/ui/SimpleSelector2.ts +215 -0
  17. package/src/cli/ui/components/ConfirmDialog.ts +176 -0
  18. package/src/cli/ui/components/ErrorPanel.ts +364 -0
  19. package/src/cli/ui/components/InkAppRunner.tsx +67 -0
  20. package/src/cli/ui/components/InkComponents.tsx +613 -0
  21. package/src/cli/ui/components/NovaInkApp.tsx +312 -0
  22. package/src/cli/ui/components/ProgressBar.ts +177 -0
  23. package/src/cli/ui/components/ProgressIndicator.ts +298 -0
  24. package/src/cli/ui/components/QuickActions.ts +396 -0
  25. package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
  26. package/src/cli/ui/components/StatusBar.ts +194 -0
  27. package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
  28. package/src/cli/ui/components/index.ts +27 -0
  29. package/src/cli/ui/ink-prototype.tsx +347 -0
  30. package/src/cli/utils/CliUI.ts +336 -0
  31. package/src/cli/utils/CompletionHelper.ts +388 -0
  32. package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
  33. package/src/cli/utils/EnhancedCompleter.ts +513 -0
  34. package/src/cli/utils/ErrorEnhancer.ts +429 -0
  35. package/src/cli/utils/OutputFormatter.ts +193 -0
  36. package/src/cli/utils/index.ts +9 -0
  37. package/src/core/agents/AgentOrchestrator.ts +515 -0
  38. package/src/core/agents/index.ts +17 -0
  39. package/src/core/audit/AuditLogger.ts +509 -0
  40. package/src/core/audit/index.ts +11 -0
  41. package/src/core/auth/AuthManager.d.ts.map +1 -0
  42. package/src/core/auth/AuthManager.ts +138 -0
  43. package/src/core/auth/index.d.ts.map +1 -0
  44. package/src/core/auth/index.ts +2 -0
  45. package/src/core/config/ConfigManager.d.ts.map +1 -0
  46. package/src/core/config/ConfigManager.test.ts +183 -0
  47. package/src/core/config/ConfigManager.ts +1219 -0
  48. package/src/core/config/index.d.ts.map +1 -0
  49. package/src/core/config/index.ts +1 -0
  50. package/src/core/context/ContextBuilder.d.ts.map +1 -0
  51. package/src/core/context/ContextBuilder.ts +171 -0
  52. package/src/core/context/ContextCompressor.d.ts.map +1 -0
  53. package/src/core/context/ContextCompressor.ts +642 -0
  54. package/src/core/context/LayeredMemoryManager.ts +657 -0
  55. package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
  56. package/src/core/context/MemoryDiscovery.ts +175 -0
  57. package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
  58. package/src/core/context/defaultSystemPrompt.ts +35 -0
  59. package/src/core/context/index.d.ts.map +1 -0
  60. package/src/core/context/index.ts +22 -0
  61. package/src/core/extensions/SkillGenerator.ts +421 -0
  62. package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
  63. package/src/core/extensions/SkillInstaller.ts +257 -0
  64. package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
  65. package/src/core/extensions/SkillRegistry.ts +361 -0
  66. package/src/core/extensions/SkillValidator.ts +525 -0
  67. package/src/core/extensions/index.ts +15 -0
  68. package/src/core/index.d.ts.map +1 -0
  69. package/src/core/index.ts +42 -0
  70. package/src/core/mcp/McpManager.d.ts.map +1 -0
  71. package/src/core/mcp/McpManager.ts +632 -0
  72. package/src/core/mcp/index.d.ts.map +1 -0
  73. package/src/core/mcp/index.ts +2 -0
  74. package/src/core/model/ModelClient.d.ts.map +1 -0
  75. package/src/core/model/ModelClient.ts +217 -0
  76. package/src/core/model/ModelConnectionTester.ts +363 -0
  77. package/src/core/model/ModelValidator.ts +348 -0
  78. package/src/core/model/index.d.ts.map +1 -0
  79. package/src/core/model/index.ts +6 -0
  80. package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
  81. package/src/core/model/providers/AnthropicProvider.ts +279 -0
  82. package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
  83. package/src/core/model/providers/CodingPlanProvider.ts +210 -0
  84. package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
  85. package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
  86. package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
  87. package/src/core/model/providers/OllamaManager.ts +201 -0
  88. package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
  89. package/src/core/model/providers/OllamaProvider.ts +73 -0
  90. package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  91. package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
  92. package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
  93. package/src/core/model/providers/OpenAIProvider.ts +29 -0
  94. package/src/core/model/providers/index.d.ts.map +1 -0
  95. package/src/core/model/providers/index.ts +12 -0
  96. package/src/core/model/types.d.ts.map +1 -0
  97. package/src/core/model/types.ts +77 -0
  98. package/src/core/security/ApprovalManager.d.ts.map +1 -0
  99. package/src/core/security/ApprovalManager.ts +174 -0
  100. package/src/core/security/FileFilter.d.ts.map +1 -0
  101. package/src/core/security/FileFilter.ts +141 -0
  102. package/src/core/security/HookExecutor.d.ts.map +1 -0
  103. package/src/core/security/HookExecutor.ts +178 -0
  104. package/src/core/security/SandboxExecutor.ts +447 -0
  105. package/src/core/security/index.d.ts.map +1 -0
  106. package/src/core/security/index.ts +8 -0
  107. package/src/core/session/AgentLoop.d.ts.map +1 -0
  108. package/src/core/session/AgentLoop.ts +501 -0
  109. package/src/core/session/SessionManager.d.ts.map +1 -0
  110. package/src/core/session/SessionManager.test.ts +183 -0
  111. package/src/core/session/SessionManager.ts +460 -0
  112. package/src/core/session/index.d.ts.map +1 -0
  113. package/src/core/session/index.ts +3 -0
  114. package/src/core/telemetry/Telemetry.d.ts.map +1 -0
  115. package/src/core/telemetry/Telemetry.ts +90 -0
  116. package/src/core/telemetry/TelemetryService.ts +531 -0
  117. package/src/core/telemetry/index.d.ts.map +1 -0
  118. package/src/core/telemetry/index.ts +12 -0
  119. package/src/core/testing/AutoFixer.ts +385 -0
  120. package/src/core/testing/ErrorAnalyzer.ts +499 -0
  121. package/src/core/testing/TestRunner.ts +265 -0
  122. package/src/core/testing/agent-cli-tests.ts +538 -0
  123. package/src/core/testing/index.ts +11 -0
  124. package/src/core/tools/ToolRegistry.d.ts.map +1 -0
  125. package/src/core/tools/ToolRegistry.test.ts +206 -0
  126. package/src/core/tools/ToolRegistry.ts +260 -0
  127. package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
  128. package/src/core/tools/impl/EditFileTool.ts +97 -0
  129. package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
  130. package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
  131. package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
  132. package/src/core/tools/impl/MemoryTool.ts +102 -0
  133. package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
  134. package/src/core/tools/impl/ReadFileTool.ts +58 -0
  135. package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
  136. package/src/core/tools/impl/SearchContentTool.ts +94 -0
  137. package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
  138. package/src/core/tools/impl/SearchFileTool.ts +61 -0
  139. package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
  140. package/src/core/tools/impl/ShellTool.ts +118 -0
  141. package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
  142. package/src/core/tools/impl/TaskTool.ts +207 -0
  143. package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
  144. package/src/core/tools/impl/TodoTool.ts +122 -0
  145. package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
  146. package/src/core/tools/impl/WebFetchTool.ts +103 -0
  147. package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
  148. package/src/core/tools/impl/WebSearchTool.ts +89 -0
  149. package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
  150. package/src/core/tools/impl/WriteFileTool.ts +49 -0
  151. package/src/core/tools/impl/index.d.ts.map +1 -0
  152. package/src/core/tools/impl/index.ts +16 -0
  153. package/src/core/tools/index.d.ts.map +1 -0
  154. package/src/core/tools/index.ts +7 -0
  155. package/src/core/tools/schemas/execution.d.ts.map +1 -0
  156. package/src/core/tools/schemas/execution.ts +42 -0
  157. package/src/core/tools/schemas/file.d.ts.map +1 -0
  158. package/src/core/tools/schemas/file.ts +119 -0
  159. package/src/core/tools/schemas/index.d.ts.map +1 -0
  160. package/src/core/tools/schemas/index.ts +11 -0
  161. package/src/core/tools/schemas/memory.d.ts.map +1 -0
  162. package/src/core/tools/schemas/memory.ts +52 -0
  163. package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
  164. package/src/core/tools/schemas/orchestration.ts +44 -0
  165. package/src/core/tools/schemas/search.d.ts.map +1 -0
  166. package/src/core/tools/schemas/search.ts +112 -0
  167. package/src/core/tools/schemas/todo.d.ts.map +1 -0
  168. package/src/core/tools/schemas/todo.ts +32 -0
  169. package/src/core/tools/schemas/web.d.ts.map +1 -0
  170. package/src/core/tools/schemas/web.ts +86 -0
  171. package/src/core/types/config.d.ts.map +1 -0
  172. package/src/core/types/config.ts +200 -0
  173. package/src/core/types/errors.d.ts.map +1 -0
  174. package/src/core/types/errors.ts +204 -0
  175. package/src/core/types/index.d.ts.map +1 -0
  176. package/src/core/types/index.ts +8 -0
  177. package/src/core/types/session.d.ts.map +1 -0
  178. package/src/core/types/session.ts +216 -0
  179. package/src/core/types/tools.d.ts.map +1 -0
  180. package/src/core/types/tools.ts +157 -0
  181. package/src/core/utils/CheckpointManager.d.ts.map +1 -0
  182. package/src/core/utils/CheckpointManager.ts +327 -0
  183. package/src/core/utils/Logger.d.ts.map +1 -0
  184. package/src/core/utils/Logger.ts +98 -0
  185. package/src/core/utils/RetryManager.ts +471 -0
  186. package/src/core/utils/TokenCounter.d.ts.map +1 -0
  187. package/src/core/utils/TokenCounter.ts +414 -0
  188. package/src/core/utils/VectorMemoryStore.ts +440 -0
  189. package/src/core/utils/helpers.d.ts.map +1 -0
  190. package/src/core/utils/helpers.ts +89 -0
  191. package/src/core/utils/index.d.ts.map +1 -0
  192. 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
+ }