@within-7/minto 0.3.5 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/{cli.js → cli.cjs} +25 -23
  2. package/dist/commands/language.js +137 -0
  3. package/dist/commands/language.js.map +7 -0
  4. package/dist/commands/new.js +56 -0
  5. package/dist/commands/new.js.map +7 -0
  6. package/dist/commands/resume.js +251 -16
  7. package/dist/commands/resume.js.map +2 -2
  8. package/dist/commands/sessions.js +224 -0
  9. package/dist/commands/sessions.js.map +7 -0
  10. package/dist/commands/setup.js +3 -2
  11. package/dist/commands/setup.js.map +2 -2
  12. package/dist/commands/stats.js +235 -0
  13. package/dist/commands/stats.js.map +7 -0
  14. package/dist/commands/status.js +11 -5
  15. package/dist/commands/status.js.map +2 -2
  16. package/dist/commands/undo.js +26 -16
  17. package/dist/commands/undo.js.map +2 -2
  18. package/dist/commands.js +6 -0
  19. package/dist/commands.js.map +2 -2
  20. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js +3 -2
  21. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
  22. package/dist/components/Config.js +9 -8
  23. package/dist/components/Config.js.map +2 -2
  24. package/dist/components/HeaderBar.js +2 -1
  25. package/dist/components/HeaderBar.js.map +2 -2
  26. package/dist/components/Help.js +2 -1
  27. package/dist/components/Help.js.map +2 -2
  28. package/dist/components/HotkeyHelpPanel.js +46 -44
  29. package/dist/components/HotkeyHelpPanel.js.map +2 -2
  30. package/dist/components/Logo.js +5 -2
  31. package/dist/components/Logo.js.map +2 -2
  32. package/dist/components/MCPServerApprovalDialog.js +6 -5
  33. package/dist/components/MCPServerApprovalDialog.js.map +2 -2
  34. package/dist/components/MCPServerMultiselectDialog.js +5 -4
  35. package/dist/components/MCPServerMultiselectDialog.js.map +2 -2
  36. package/dist/components/MessageSelector.js +4 -3
  37. package/dist/components/MessageSelector.js.map +2 -2
  38. package/dist/components/ModelConfig.js +13 -12
  39. package/dist/components/ModelConfig.js.map +2 -2
  40. package/dist/components/ModelListManager.js +4 -3
  41. package/dist/components/ModelListManager.js.map +2 -2
  42. package/dist/components/PromptInput.js +72 -39
  43. package/dist/components/PromptInput.js.map +2 -2
  44. package/dist/components/SensitiveFileWarning.js +12 -8
  45. package/dist/components/SensitiveFileWarning.js.map +2 -2
  46. package/dist/components/TabbedListView/ScrollableList.js +91 -0
  47. package/dist/components/TabbedListView/ScrollableList.js.map +7 -0
  48. package/dist/components/TabbedListView/SearchInput.js +23 -0
  49. package/dist/components/TabbedListView/SearchInput.js.map +7 -0
  50. package/dist/components/TabbedListView/TabBar.js +20 -0
  51. package/dist/components/TabbedListView/TabBar.js.map +7 -0
  52. package/dist/components/TabbedListView/TabbedListView.js +171 -0
  53. package/dist/components/TabbedListView/TabbedListView.js.map +7 -0
  54. package/dist/components/TabbedListView/index.js +11 -0
  55. package/dist/components/TabbedListView/index.js.map +7 -0
  56. package/dist/components/TabbedListView/types.js +1 -0
  57. package/dist/components/TabbedListView/types.js.map +7 -0
  58. package/dist/components/TodoChangeBlock.js +6 -5
  59. package/dist/components/TodoChangeBlock.js.map +3 -3
  60. package/dist/components/TodoPanel.js +6 -3
  61. package/dist/components/TodoPanel.js.map +3 -3
  62. package/dist/components/TrustDialog.js +6 -5
  63. package/dist/components/TrustDialog.js.map +2 -2
  64. package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js +2 -1
  65. package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js.map +2 -2
  66. package/dist/constants/macros.js +1 -1
  67. package/dist/constants/macros.js.map +1 -1
  68. package/dist/constants/product.js +2 -2
  69. package/dist/constants/product.js.map +1 -1
  70. package/dist/constants/prompts.js +17 -0
  71. package/dist/constants/prompts.js.map +2 -2
  72. package/dist/constants/toolInputExamples.js +5 -1
  73. package/dist/constants/toolInputExamples.js.map +2 -2
  74. package/dist/core/tokenStatsManager.js +5 -0
  75. package/dist/core/tokenStatsManager.js.map +2 -2
  76. package/dist/entrypoints/bootstrap.js +54 -0
  77. package/dist/entrypoints/bootstrap.js.map +7 -0
  78. package/dist/entrypoints/cli.js +132 -23
  79. package/dist/entrypoints/cli.js.map +3 -3
  80. package/dist/history.js +75 -15
  81. package/dist/history.js.map +2 -2
  82. package/dist/i18n/index.js +2 -2
  83. package/dist/i18n/index.js.map +2 -2
  84. package/dist/i18n/locales/en.js +283 -1
  85. package/dist/i18n/locales/en.js.map +2 -2
  86. package/dist/i18n/locales/zh-CN.js +283 -1
  87. package/dist/i18n/locales/zh-CN.js.map +2 -2
  88. package/dist/i18n/types.js.map +1 -1
  89. package/dist/index.js +1 -1
  90. package/dist/index.js.map +2 -2
  91. package/dist/messages.js +11 -0
  92. package/dist/messages.js.map +2 -2
  93. package/dist/permissions.js.map +2 -2
  94. package/dist/query.js +9 -0
  95. package/dist/query.js.map +2 -2
  96. package/dist/screens/REPL.js +45 -7
  97. package/dist/screens/REPL.js.map +2 -2
  98. package/dist/services/customCommands.js +14 -8
  99. package/dist/services/customCommands.js.map +2 -2
  100. package/dist/tools/TaskTool/TaskTool.js +176 -1
  101. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  102. package/dist/tools/TodoWriteTool/prompt.js +21 -0
  103. package/dist/tools/TodoWriteTool/prompt.js.map +2 -2
  104. package/dist/tools/URLFetcherTool/prompt.js +14 -9
  105. package/dist/tools/URLFetcherTool/prompt.js.map +2 -2
  106. package/dist/tools/WebSearchTool/prompt.js +12 -6
  107. package/dist/tools/WebSearchTool/prompt.js.map +2 -2
  108. package/dist/types/PermissionMode.js +30 -1
  109. package/dist/types/PermissionMode.js.map +2 -2
  110. package/dist/types/plugin.js.map +2 -2
  111. package/dist/utils/agentHookExecutor.js +106 -0
  112. package/dist/utils/agentHookExecutor.js.map +7 -0
  113. package/dist/utils/agentLoader.js +212 -26
  114. package/dist/utils/agentLoader.js.map +2 -2
  115. package/dist/utils/agentMemory.js +134 -0
  116. package/dist/utils/agentMemory.js.map +7 -0
  117. package/dist/utils/config.js +51 -1
  118. package/dist/utils/config.js.map +2 -2
  119. package/dist/utils/configPaths.js +199 -0
  120. package/dist/utils/configPaths.js.map +7 -0
  121. package/dist/utils/historyManager.js +234 -0
  122. package/dist/utils/historyManager.js.map +7 -0
  123. package/dist/utils/messages.js +13 -8
  124. package/dist/utils/messages.js.map +2 -2
  125. package/dist/utils/migration/index.js +37 -0
  126. package/dist/utils/migration/index.js.map +7 -0
  127. package/dist/utils/migration/migrateHistory.js +273 -0
  128. package/dist/utils/migration/migrateHistory.js.map +7 -0
  129. package/dist/utils/migration/migrateTodos.js +323 -0
  130. package/dist/utils/migration/migrateTodos.js.map +7 -0
  131. package/dist/utils/pasteCache.js +309 -0
  132. package/dist/utils/pasteCache.js.map +7 -0
  133. package/dist/utils/pluginLoader.js +6 -3
  134. package/dist/utils/pluginLoader.js.map +2 -2
  135. package/dist/utils/sessionIndex.js +192 -0
  136. package/dist/utils/sessionIndex.js.map +7 -0
  137. package/dist/utils/sessionTracker.js +170 -0
  138. package/dist/utils/sessionTracker.js.map +7 -0
  139. package/dist/utils/skillLoader.js +91 -5
  140. package/dist/utils/skillLoader.js.map +2 -2
  141. package/dist/utils/stats.js +417 -0
  142. package/dist/utils/stats.js.map +7 -0
  143. package/dist/utils/stringSubstitution.js +107 -0
  144. package/dist/utils/stringSubstitution.js.map +7 -0
  145. package/dist/utils/teamConfig.js +3 -1
  146. package/dist/utils/teamConfig.js.map +2 -2
  147. package/dist/utils/todoStorage.js +51 -19
  148. package/dist/utils/todoStorage.js.map +2 -2
  149. package/dist/utils/tooling/safeRender.js.map +2 -2
  150. package/dist/version.js +2 -2
  151. package/dist/version.js.map +1 -1
  152. package/package.json +71 -28
  153. package/scripts/{postinstall.js → postinstall.cjs} +1 -1
@@ -0,0 +1,323 @@
1
+ import {
2
+ existsSync,
3
+ readFileSync,
4
+ writeFileSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ renameSync,
8
+ unlinkSync
9
+ } from "fs";
10
+ import { join, resolve } from "path";
11
+ import crypto from "crypto";
12
+ import { CONFIG_PATHS, ensureConfigDirs } from "../configPaths.js";
13
+ import { debug as debugLogger } from "../debugLogger.js";
14
+ const MIGRATION_MARKER_FILE = ".todos-migration-completed";
15
+ const MIGRATION_VERSION = "1.0.0";
16
+ const LEGACY_FILE_PATTERN = /^default-session-agent-([a-f0-9-]{36})\.json$/;
17
+ const ORPHAN_PROJECT_HASH = "_orphaned";
18
+ function getMigrationMarkerPath() {
19
+ return join(CONFIG_PATHS.base, MIGRATION_MARKER_FILE);
20
+ }
21
+ function checkMigrationNeeded() {
22
+ const markerPath = getMigrationMarkerPath();
23
+ return !existsSync(markerPath);
24
+ }
25
+ function getMigrationStatus() {
26
+ const markerPath = getMigrationMarkerPath();
27
+ if (!existsSync(markerPath)) {
28
+ return {
29
+ completed: false,
30
+ version: MIGRATION_VERSION,
31
+ migratedFiles: 0,
32
+ errors: []
33
+ };
34
+ }
35
+ try {
36
+ const content = readFileSync(markerPath, "utf-8");
37
+ return JSON.parse(content);
38
+ } catch {
39
+ return {
40
+ completed: false,
41
+ version: MIGRATION_VERSION,
42
+ migratedFiles: 0,
43
+ errors: []
44
+ };
45
+ }
46
+ }
47
+ function writeMigrationMarker(result) {
48
+ const markerPath = getMigrationMarkerPath();
49
+ const status = {
50
+ completed: true,
51
+ timestamp: Date.now(),
52
+ version: MIGRATION_VERSION,
53
+ migratedFiles: result.migrated,
54
+ errors: result.errors.slice(0, 10)
55
+ // Keep only first 10 errors
56
+ };
57
+ try {
58
+ writeFileSync(markerPath, JSON.stringify(status, null, 2), "utf-8");
59
+ debugLogger.info("MIGRATION_MARKER_WRITTEN", {
60
+ path: markerPath,
61
+ migrated: result.migrated
62
+ });
63
+ } catch (error) {
64
+ debugLogger.error("MIGRATION_MARKER_WRITE_FAILED", {
65
+ error: error instanceof Error ? error.message : String(error)
66
+ });
67
+ }
68
+ }
69
+ function scanLegacyTodoFiles() {
70
+ const configDir = CONFIG_PATHS.base;
71
+ const files = [];
72
+ if (!existsSync(configDir)) {
73
+ return files;
74
+ }
75
+ try {
76
+ const entries = readdirSync(configDir);
77
+ for (const entry of entries) {
78
+ const match = entry.match(LEGACY_FILE_PATTERN);
79
+ if (match) {
80
+ const sessionId = match[1];
81
+ if (sessionId) {
82
+ files.push({
83
+ filename: entry,
84
+ filepath: join(configDir, entry),
85
+ sessionId
86
+ });
87
+ }
88
+ }
89
+ }
90
+ debugLogger.info("MIGRATION_SCAN_COMPLETE", {
91
+ found: files.length,
92
+ directory: configDir
93
+ });
94
+ } catch (error) {
95
+ debugLogger.error("MIGRATION_SCAN_FAILED", {
96
+ error: error instanceof Error ? error.message : String(error)
97
+ });
98
+ }
99
+ return files;
100
+ }
101
+ function findTranscriptForSession(sessionId) {
102
+ const transcriptsDir = join(CONFIG_PATHS.base, "transcripts");
103
+ if (!existsSync(transcriptsDir)) {
104
+ return null;
105
+ }
106
+ try {
107
+ const files = readdirSync(transcriptsDir);
108
+ const exactMatch = `${sessionId}.json`;
109
+ if (files.includes(exactMatch)) {
110
+ const content = readFileSync(join(transcriptsDir, exactMatch), "utf-8");
111
+ return JSON.parse(content);
112
+ }
113
+ for (const file of files) {
114
+ if (!file.endsWith(".json")) continue;
115
+ try {
116
+ const content = readFileSync(join(transcriptsDir, file), "utf-8");
117
+ const transcript = JSON.parse(content);
118
+ if (transcript.metadata?.cwd || transcript.metadata?.projectPath) {
119
+ return transcript;
120
+ }
121
+ } catch {
122
+ continue;
123
+ }
124
+ }
125
+ } catch {
126
+ }
127
+ return null;
128
+ }
129
+ function hashPath(path) {
130
+ const normalized = resolve(path).toLowerCase();
131
+ return crypto.createHash("sha256").update(normalized).digest("hex").slice(0, 16);
132
+ }
133
+ function getProjectHashFromTranscript(transcript) {
134
+ if (!transcript) {
135
+ return ORPHAN_PROJECT_HASH;
136
+ }
137
+ const projectPath = transcript.metadata?.projectPath || transcript.metadata?.cwd || null;
138
+ if (!projectPath) {
139
+ return ORPHAN_PROJECT_HASH;
140
+ }
141
+ return hashPath(projectPath);
142
+ }
143
+ function migrateSingleFile(file, log) {
144
+ try {
145
+ const content = readFileSync(file.filepath, "utf-8");
146
+ const todoData = JSON.parse(content);
147
+ if (!Array.isArray(todoData)) {
148
+ return {
149
+ success: false,
150
+ error: `Invalid todo file format: ${file.filename} (not an array)`
151
+ };
152
+ }
153
+ if (todoData.length === 0) {
154
+ log.push(`Skipped empty file: ${file.filename}`);
155
+ try {
156
+ unlinkSync(file.filepath);
157
+ log.push(`Deleted empty file: ${file.filename}`);
158
+ } catch {
159
+ }
160
+ return { success: true };
161
+ }
162
+ const transcript = findTranscriptForSession(file.sessionId);
163
+ const projectHash = getProjectHashFromTranscript(transcript);
164
+ const targetDir = join(CONFIG_PATHS.sessions, projectHash, file.sessionId);
165
+ const targetPath = join(targetDir, "todos.json");
166
+ if (existsSync(targetPath)) {
167
+ log.push(`Target exists, skipping: ${file.filename} -> ${targetPath}`);
168
+ const backupPath2 = file.filepath + ".migrated";
169
+ renameSync(file.filepath, backupPath2);
170
+ return { success: true };
171
+ }
172
+ if (!existsSync(targetDir)) {
173
+ mkdirSync(targetDir, { recursive: true });
174
+ }
175
+ writeFileSync(targetPath, JSON.stringify(todoData, null, 2), "utf-8");
176
+ const backupPath = file.filepath + ".migrated";
177
+ renameSync(file.filepath, backupPath);
178
+ log.push(
179
+ `Migrated: ${file.filename} -> sessions/${projectHash}/${file.sessionId}/todos.json (${todoData.length} items)`
180
+ );
181
+ debugLogger.trace("MIGRATION_FILE_SUCCESS", {
182
+ source: file.filename,
183
+ target: targetPath,
184
+ items: todoData.length,
185
+ projectHash
186
+ });
187
+ return { success: true };
188
+ } catch (error) {
189
+ const errorMsg = error instanceof Error ? error.message : String(error);
190
+ return {
191
+ success: false,
192
+ error: `Failed to migrate ${file.filename}: ${errorMsg}`
193
+ };
194
+ }
195
+ }
196
+ async function migrateTodos() {
197
+ const result = {
198
+ migrated: 0,
199
+ skipped: 0,
200
+ errors: [],
201
+ log: []
202
+ };
203
+ if (!checkMigrationNeeded()) {
204
+ const status = getMigrationStatus();
205
+ result.log.push(
206
+ `Migration already completed at ${status.timestamp ? new Date(status.timestamp).toISOString() : "unknown"}`
207
+ );
208
+ result.log.push(`Previously migrated ${status.migratedFiles} files`);
209
+ debugLogger.info("MIGRATION_ALREADY_COMPLETE", { status });
210
+ return result;
211
+ }
212
+ debugLogger.info("MIGRATION_START", { version: MIGRATION_VERSION });
213
+ result.log.push(`Starting todo migration (version ${MIGRATION_VERSION})`);
214
+ ensureConfigDirs();
215
+ const legacyFiles = scanLegacyTodoFiles();
216
+ if (legacyFiles.length === 0) {
217
+ result.log.push("No legacy todo files found");
218
+ debugLogger.info("MIGRATION_NO_FILES", {});
219
+ writeMigrationMarker(result);
220
+ return result;
221
+ }
222
+ result.log.push(`Found ${legacyFiles.length} legacy todo files to migrate`);
223
+ for (const file of legacyFiles) {
224
+ const migrationResult = migrateSingleFile(file, result.log);
225
+ if (migrationResult.success) {
226
+ result.migrated++;
227
+ } else if (migrationResult.error) {
228
+ result.errors.push(migrationResult.error);
229
+ result.log.push(`ERROR: ${migrationResult.error}`);
230
+ debugLogger.error("MIGRATION_FILE_FAILED", {
231
+ file: file.filename,
232
+ error: migrationResult.error
233
+ });
234
+ } else {
235
+ result.skipped++;
236
+ }
237
+ }
238
+ writeMigrationMarker(result);
239
+ debugLogger.info("MIGRATION_COMPLETE", {
240
+ migrated: result.migrated,
241
+ skipped: result.skipped,
242
+ errors: result.errors.length
243
+ });
244
+ result.log.push("");
245
+ result.log.push("=== Migration Summary ===");
246
+ result.log.push(`Migrated: ${result.migrated}`);
247
+ result.log.push(`Skipped: ${result.skipped}`);
248
+ result.log.push(`Errors: ${result.errors.length}`);
249
+ return result;
250
+ }
251
+ function cleanupMigrationBackups() {
252
+ const configDir = CONFIG_PATHS.base;
253
+ let cleaned = 0;
254
+ if (!existsSync(configDir)) {
255
+ return 0;
256
+ }
257
+ try {
258
+ const entries = readdirSync(configDir);
259
+ for (const entry of entries) {
260
+ if (entry.endsWith(".migrated")) {
261
+ const filepath = join(configDir, entry);
262
+ try {
263
+ unlinkSync(filepath);
264
+ cleaned++;
265
+ debugLogger.trace("MIGRATION_BACKUP_CLEANED", { file: entry });
266
+ } catch {
267
+ }
268
+ }
269
+ }
270
+ debugLogger.info("MIGRATION_CLEANUP_COMPLETE", { cleaned });
271
+ } catch (error) {
272
+ debugLogger.error("MIGRATION_CLEANUP_FAILED", {
273
+ error: error instanceof Error ? error.message : String(error)
274
+ });
275
+ }
276
+ return cleaned;
277
+ }
278
+ async function forceMigration() {
279
+ const markerPath = getMigrationMarkerPath();
280
+ if (existsSync(markerPath)) {
281
+ try {
282
+ unlinkSync(markerPath);
283
+ debugLogger.info("MIGRATION_MARKER_REMOVED", { path: markerPath });
284
+ } catch {
285
+ }
286
+ }
287
+ return migrateTodos();
288
+ }
289
+ async function runStartupMigration() {
290
+ if (!checkMigrationNeeded()) {
291
+ debugLogger.trace("STARTUP_MIGRATION_SKIPPED", {
292
+ reason: "already completed"
293
+ });
294
+ return;
295
+ }
296
+ debugLogger.info("STARTUP_MIGRATION_RUNNING", {});
297
+ try {
298
+ const result = await migrateTodos();
299
+ if (result.errors.length > 0) {
300
+ debugLogger.warn("STARTUP_MIGRATION_ERRORS", {
301
+ errors: result.errors.slice(0, 5),
302
+ totalErrors: result.errors.length
303
+ });
304
+ }
305
+ debugLogger.info("STARTUP_MIGRATION_DONE", {
306
+ migrated: result.migrated,
307
+ skipped: result.skipped
308
+ });
309
+ } catch (error) {
310
+ debugLogger.error("STARTUP_MIGRATION_FAILED", {
311
+ error: error instanceof Error ? error.message : String(error)
312
+ });
313
+ }
314
+ }
315
+ export {
316
+ checkMigrationNeeded,
317
+ cleanupMigrationBackups,
318
+ forceMigration,
319
+ getMigrationStatus,
320
+ migrateTodos,
321
+ runStartupMigration
322
+ };
323
+ //# sourceMappingURL=migrateTodos.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/migration/migrateTodos.ts"],
4
+ "sourcesContent": ["/**\n * Todo Files Migration Tool\n *\n * Migrates legacy todo files from ~/.minto/ root directory to the new\n * structured sessions directory hierarchy.\n *\n * Legacy format: ~/.minto/default-session-agent-{uuid}.json\n * New format: ~/.minto/sessions/{project-hash}/{session-id}/todos.json\n *\n * Migration process:\n * 1. Scan for legacy todo files matching the pattern\n * 2. Extract session ID from filename\n * 3. Attempt to find associated transcript to get project context\n * 4. Move to new location or use fallback directory\n * 5. Write migration log\n */\n\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n readdirSync,\n renameSync,\n unlinkSync,\n} from 'fs'\nimport { join, resolve } from 'path'\nimport crypto from 'crypto'\nimport { CONFIG_PATHS, ensureConfigDirs } from '../configPaths'\nimport { debug as debugLogger } from '../debugLogger'\n\n/**\n * Result of a migration operation\n */\nexport interface MigrationResult {\n /** Number of files successfully migrated */\n migrated: number\n /** Number of files skipped (already migrated or invalid) */\n skipped: number\n /** Error messages for failed migrations */\n errors: string[]\n /** Detailed log of all operations */\n log: string[]\n}\n\n/**\n * Migration status stored in marker file\n */\nexport interface MigrationStatus {\n completed: boolean\n timestamp?: number\n version: string\n migratedFiles: number\n errors: string[]\n}\n\n/**\n * Legacy todo file info\n */\ninterface LegacyTodoFile {\n filename: string\n filepath: string\n sessionId: string\n}\n\n/**\n * Transcript metadata used for project association\n */\ninterface TranscriptMetadata {\n agentId: string\n prompt?: string\n metadata?: {\n cwd?: string\n projectPath?: string\n }\n}\n\n// Migration marker file path\nconst MIGRATION_MARKER_FILE = '.todos-migration-completed'\nconst MIGRATION_VERSION = '1.0.0'\n\n// Legacy file pattern: default-session-agent-{uuid}.json\nconst LEGACY_FILE_PATTERN = /^default-session-agent-([a-f0-9-]{36})\\.json$/\n\n// Fallback project hash for orphan todos (no associated transcript)\nconst ORPHAN_PROJECT_HASH = '_orphaned'\n\n/**\n * Get the path to the migration marker file\n */\nfunction getMigrationMarkerPath(): string {\n return join(CONFIG_PATHS.base, MIGRATION_MARKER_FILE)\n}\n\n/**\n * Check if migration has already been completed\n */\nexport function checkMigrationNeeded(): boolean {\n const markerPath = getMigrationMarkerPath()\n return !existsSync(markerPath)\n}\n\n/**\n * Get the current migration status\n */\nexport function getMigrationStatus(): MigrationStatus {\n const markerPath = getMigrationMarkerPath()\n\n if (!existsSync(markerPath)) {\n return {\n completed: false,\n version: MIGRATION_VERSION,\n migratedFiles: 0,\n errors: [],\n }\n }\n\n try {\n const content = readFileSync(markerPath, 'utf-8')\n return JSON.parse(content) as MigrationStatus\n } catch {\n return {\n completed: false,\n version: MIGRATION_VERSION,\n migratedFiles: 0,\n errors: [],\n }\n }\n}\n\n/**\n * Write migration completion marker\n */\nfunction writeMigrationMarker(result: MigrationResult): void {\n const markerPath = getMigrationMarkerPath()\n const status: MigrationStatus = {\n completed: true,\n timestamp: Date.now(),\n version: MIGRATION_VERSION,\n migratedFiles: result.migrated,\n errors: result.errors.slice(0, 10), // Keep only first 10 errors\n }\n\n try {\n writeFileSync(markerPath, JSON.stringify(status, null, 2), 'utf-8')\n debugLogger.info('MIGRATION_MARKER_WRITTEN', {\n path: markerPath,\n migrated: result.migrated,\n })\n } catch (error) {\n debugLogger.error('MIGRATION_MARKER_WRITE_FAILED', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n}\n\n/**\n * Scan for legacy todo files in ~/.minto/\n */\nfunction scanLegacyTodoFiles(): LegacyTodoFile[] {\n const configDir = CONFIG_PATHS.base\n const files: LegacyTodoFile[] = []\n\n if (!existsSync(configDir)) {\n return files\n }\n\n try {\n const entries = readdirSync(configDir)\n\n for (const entry of entries) {\n const match = entry.match(LEGACY_FILE_PATTERN)\n if (match) {\n const sessionId = match[1]\n if (sessionId) {\n files.push({\n filename: entry,\n filepath: join(configDir, entry),\n sessionId,\n })\n }\n }\n }\n\n debugLogger.info('MIGRATION_SCAN_COMPLETE', {\n found: files.length,\n directory: configDir,\n })\n } catch (error) {\n debugLogger.error('MIGRATION_SCAN_FAILED', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n return files\n}\n\n/**\n * Try to find transcript file for a session ID\n */\nfunction findTranscriptForSession(\n sessionId: string,\n): TranscriptMetadata | null {\n const transcriptsDir = join(CONFIG_PATHS.base, 'transcripts')\n\n if (!existsSync(transcriptsDir)) {\n return null\n }\n\n try {\n const files = readdirSync(transcriptsDir)\n\n // Try exact match first (agent ID might be the session ID)\n const exactMatch = `${sessionId}.json`\n if (files.includes(exactMatch)) {\n const content = readFileSync(join(transcriptsDir, exactMatch), 'utf-8')\n return JSON.parse(content) as TranscriptMetadata\n }\n\n // Search through transcripts for matching metadata\n for (const file of files) {\n if (!file.endsWith('.json')) continue\n\n try {\n const content = readFileSync(join(transcriptsDir, file), 'utf-8')\n const transcript = JSON.parse(content) as TranscriptMetadata\n\n // Check if this transcript is related to the session\n if (transcript.metadata?.cwd || transcript.metadata?.projectPath) {\n // Return any transcript with project info as a reasonable default\n return transcript\n }\n } catch {\n // Skip invalid transcript files\n continue\n }\n }\n } catch {\n // Silently fail if we can't read transcripts\n }\n\n return null\n}\n\n/**\n * Hash a path to create a safe directory name\n * Same algorithm as SessionMemoryManager\n */\nfunction hashPath(path: string): string {\n const normalized = resolve(path).toLowerCase()\n return crypto\n .createHash('sha256')\n .update(normalized)\n .digest('hex')\n .slice(0, 16)\n}\n\n/**\n * Get project hash from transcript or return orphan hash\n */\nfunction getProjectHashFromTranscript(\n transcript: TranscriptMetadata | null,\n): string {\n if (!transcript) {\n return ORPHAN_PROJECT_HASH\n }\n\n const projectPath =\n transcript.metadata?.projectPath || transcript.metadata?.cwd || null\n\n if (!projectPath) {\n return ORPHAN_PROJECT_HASH\n }\n\n return hashPath(projectPath)\n}\n\n/**\n * Migrate a single todo file to the new location\n */\nfunction migrateSingleFile(\n file: LegacyTodoFile,\n log: string[],\n): { success: boolean; error?: string } {\n try {\n // Read the legacy todo file\n const content = readFileSync(file.filepath, 'utf-8')\n const todoData = JSON.parse(content)\n\n // Validate that it's actually a todo file (array of todo items)\n if (!Array.isArray(todoData)) {\n return {\n success: false,\n error: `Invalid todo file format: ${file.filename} (not an array)`,\n }\n }\n\n // Skip empty todo files\n if (todoData.length === 0) {\n log.push(`Skipped empty file: ${file.filename}`)\n // Delete empty files\n try {\n unlinkSync(file.filepath)\n log.push(`Deleted empty file: ${file.filename}`)\n } catch {\n // Ignore delete errors\n }\n return { success: true }\n }\n\n // Try to find associated transcript for project context\n const transcript = findTranscriptForSession(file.sessionId)\n const projectHash = getProjectHashFromTranscript(transcript)\n\n // Create target directory\n const targetDir = join(CONFIG_PATHS.sessions, projectHash, file.sessionId)\n const targetPath = join(targetDir, 'todos.json')\n\n // Check if target already exists\n if (existsSync(targetPath)) {\n log.push(`Target exists, skipping: ${file.filename} -> ${targetPath}`)\n // Rename old file as backup\n const backupPath = file.filepath + '.migrated'\n renameSync(file.filepath, backupPath)\n return { success: true }\n }\n\n // Create directory structure\n if (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true })\n }\n\n // Write to new location\n writeFileSync(targetPath, JSON.stringify(todoData, null, 2), 'utf-8')\n\n // Remove original file (or rename to .migrated for safety)\n const backupPath = file.filepath + '.migrated'\n renameSync(file.filepath, backupPath)\n\n log.push(\n `Migrated: ${file.filename} -> sessions/${projectHash}/${file.sessionId}/todos.json (${todoData.length} items)`,\n )\n\n debugLogger.trace('MIGRATION_FILE_SUCCESS', {\n source: file.filename,\n target: targetPath,\n items: todoData.length,\n projectHash,\n })\n\n return { success: true }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error)\n return {\n success: false,\n error: `Failed to migrate ${file.filename}: ${errorMsg}`,\n }\n }\n}\n\n/**\n * Main migration function\n *\n * Scans for legacy todo files and migrates them to the new\n * structured sessions directory.\n *\n * @returns Migration result with statistics and logs\n */\nexport async function migrateTodos(): Promise<MigrationResult> {\n const result: MigrationResult = {\n migrated: 0,\n skipped: 0,\n errors: [],\n log: [],\n }\n\n // Check if migration is needed\n if (!checkMigrationNeeded()) {\n const status = getMigrationStatus()\n result.log.push(\n `Migration already completed at ${status.timestamp ? new Date(status.timestamp).toISOString() : 'unknown'}`,\n )\n result.log.push(`Previously migrated ${status.migratedFiles} files`)\n debugLogger.info('MIGRATION_ALREADY_COMPLETE', { status })\n return result\n }\n\n debugLogger.info('MIGRATION_START', { version: MIGRATION_VERSION })\n result.log.push(`Starting todo migration (version ${MIGRATION_VERSION})`)\n\n // Ensure required directories exist\n ensureConfigDirs()\n\n // Scan for legacy files\n const legacyFiles = scanLegacyTodoFiles()\n\n if (legacyFiles.length === 0) {\n result.log.push('No legacy todo files found')\n debugLogger.info('MIGRATION_NO_FILES', {})\n // Still write marker to prevent future scans\n writeMigrationMarker(result)\n return result\n }\n\n result.log.push(`Found ${legacyFiles.length} legacy todo files to migrate`)\n\n // Migrate each file\n for (const file of legacyFiles) {\n const migrationResult = migrateSingleFile(file, result.log)\n\n if (migrationResult.success) {\n result.migrated++\n } else if (migrationResult.error) {\n result.errors.push(migrationResult.error)\n result.log.push(`ERROR: ${migrationResult.error}`)\n debugLogger.error('MIGRATION_FILE_FAILED', {\n file: file.filename,\n error: migrationResult.error,\n })\n } else {\n result.skipped++\n }\n }\n\n // Write completion marker\n writeMigrationMarker(result)\n\n debugLogger.info('MIGRATION_COMPLETE', {\n migrated: result.migrated,\n skipped: result.skipped,\n errors: result.errors.length,\n })\n\n result.log.push('')\n result.log.push('=== Migration Summary ===')\n result.log.push(`Migrated: ${result.migrated}`)\n result.log.push(`Skipped: ${result.skipped}`)\n result.log.push(`Errors: ${result.errors.length}`)\n\n return result\n}\n\n/**\n * Clean up backup files created during migration\n * Call this after verifying migration was successful\n */\nexport function cleanupMigrationBackups(): number {\n const configDir = CONFIG_PATHS.base\n let cleaned = 0\n\n if (!existsSync(configDir)) {\n return 0\n }\n\n try {\n const entries = readdirSync(configDir)\n\n for (const entry of entries) {\n if (entry.endsWith('.migrated')) {\n const filepath = join(configDir, entry)\n try {\n unlinkSync(filepath)\n cleaned++\n debugLogger.trace('MIGRATION_BACKUP_CLEANED', { file: entry })\n } catch {\n // Ignore individual cleanup errors\n }\n }\n }\n\n debugLogger.info('MIGRATION_CLEANUP_COMPLETE', { cleaned })\n } catch (error) {\n debugLogger.error('MIGRATION_CLEANUP_FAILED', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n return cleaned\n}\n\n/**\n * Force re-run migration (for debugging/recovery)\n * Removes the migration marker and runs migration again\n */\nexport async function forceMigration(): Promise<MigrationResult> {\n const markerPath = getMigrationMarkerPath()\n\n if (existsSync(markerPath)) {\n try {\n unlinkSync(markerPath)\n debugLogger.info('MIGRATION_MARKER_REMOVED', { path: markerPath })\n } catch {\n // Ignore errors\n }\n }\n\n return migrateTodos()\n}\n\n/**\n * Check and run migration at application startup\n * Should be called early in the application lifecycle\n */\nexport async function runStartupMigration(): Promise<void> {\n if (!checkMigrationNeeded()) {\n debugLogger.trace('STARTUP_MIGRATION_SKIPPED', {\n reason: 'already completed',\n })\n return\n }\n\n debugLogger.info('STARTUP_MIGRATION_RUNNING', {})\n\n try {\n const result = await migrateTodos()\n\n if (result.errors.length > 0) {\n debugLogger.warn('STARTUP_MIGRATION_ERRORS', {\n errors: result.errors.slice(0, 5),\n totalErrors: result.errors.length,\n })\n }\n\n debugLogger.info('STARTUP_MIGRATION_DONE', {\n migrated: result.migrated,\n skipped: result.skipped,\n })\n } catch (error) {\n debugLogger.error('STARTUP_MIGRATION_FAILED', {\n error: error instanceof Error ? error.message : String(error),\n })\n // Don't throw - migration failures shouldn't block application startup\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["backupPath"]
7
+ }
@@ -0,0 +1,309 @@
1
+ import {
2
+ existsSync,
3
+ readFileSync,
4
+ writeFileSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ unlinkSync
8
+ } from "fs";
9
+ import { join } from "path";
10
+ import crypto from "crypto";
11
+ import { CONFIG_PATHS } from "./configPaths.js";
12
+ import { debug as debugLogger } from "./debugLogger.js";
13
+ const DEFAULT_THRESHOLD = 1e3;
14
+ const DEFAULT_TTL = 7 * 24 * 60 * 60 * 1e3;
15
+ const MAX_CACHE_SIZE = 100;
16
+ function getPasteCacheDir() {
17
+ return CONFIG_PATHS.pasteCache;
18
+ }
19
+ function ensureCacheDir() {
20
+ const cacheDir = getPasteCacheDir();
21
+ if (!existsSync(cacheDir)) {
22
+ mkdirSync(cacheDir, { recursive: true });
23
+ }
24
+ }
25
+ function hashContent(content) {
26
+ return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
27
+ }
28
+ function getCacheFilePath(hash) {
29
+ return join(getPasteCacheDir(), `${hash}.txt`);
30
+ }
31
+ function getMetadataFilePath(hash) {
32
+ return join(getPasteCacheDir(), `${hash}.meta.json`);
33
+ }
34
+ function countLines(content) {
35
+ return (content.match(/\r\n|\r|\n/g) || []).length + 1;
36
+ }
37
+ function cachePaste(content, options = {}) {
38
+ const { threshold = DEFAULT_THRESHOLD } = options;
39
+ if (content.length < threshold) {
40
+ return null;
41
+ }
42
+ try {
43
+ ensureCacheDir();
44
+ const hash = hashContent(content);
45
+ const filePath = getCacheFilePath(hash);
46
+ const metadataPath = getMetadataFilePath(hash);
47
+ const entry = {
48
+ hash,
49
+ length: content.length,
50
+ lineCount: countLines(content),
51
+ timestamp: Date.now()
52
+ };
53
+ if (!existsSync(filePath)) {
54
+ writeFileSync(filePath, content, "utf-8");
55
+ writeFileSync(metadataPath, JSON.stringify(entry, null, 2), "utf-8");
56
+ debugLogger.trace("PASTE_CACHE_STORED", {
57
+ hash: hash.slice(0, 16),
58
+ length: content.length,
59
+ lineCount: entry.lineCount
60
+ });
61
+ } else {
62
+ const existingMeta = getMetadata(hash);
63
+ if (existingMeta) {
64
+ existingMeta.timestamp = Date.now();
65
+ writeFileSync(
66
+ metadataPath,
67
+ JSON.stringify(existingMeta, null, 2),
68
+ "utf-8"
69
+ );
70
+ }
71
+ debugLogger.trace("PASTE_CACHE_HIT", {
72
+ hash: hash.slice(0, 16)
73
+ });
74
+ }
75
+ return entry;
76
+ } catch (error) {
77
+ debugLogger.error("PASTE_CACHE_STORE_FAILED", {
78
+ error: error instanceof Error ? error.message : String(error),
79
+ contentLength: content.length
80
+ });
81
+ return null;
82
+ }
83
+ }
84
+ function getPaste(hash) {
85
+ const filePath = getCacheFilePath(hash);
86
+ if (!existsSync(filePath)) {
87
+ debugLogger.trace("PASTE_CACHE_MISS", { hash: hash.slice(0, 16) });
88
+ return null;
89
+ }
90
+ try {
91
+ const content = readFileSync(filePath, "utf-8");
92
+ debugLogger.trace("PASTE_CACHE_RETRIEVED", {
93
+ hash: hash.slice(0, 16),
94
+ length: content.length
95
+ });
96
+ return content;
97
+ } catch (error) {
98
+ debugLogger.error("PASTE_CACHE_READ_FAILED", {
99
+ error: error instanceof Error ? error.message : String(error),
100
+ hash: hash.slice(0, 16)
101
+ });
102
+ return null;
103
+ }
104
+ }
105
+ function getMetadata(hash) {
106
+ const metadataPath = getMetadataFilePath(hash);
107
+ if (!existsSync(metadataPath)) {
108
+ return null;
109
+ }
110
+ try {
111
+ const content = readFileSync(metadataPath, "utf-8");
112
+ return JSON.parse(content);
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+ function isCached(content) {
118
+ const hash = hashContent(content);
119
+ return existsSync(getCacheFilePath(hash));
120
+ }
121
+ function getContentHash(content) {
122
+ return hashContent(content);
123
+ }
124
+ function createPlaceholder(entry) {
125
+ return `[Cached paste: ${entry.lineCount} lines, ${entry.length} chars, hash:${entry.hash.slice(0, 8)}]`;
126
+ }
127
+ function parsePlaceholder(placeholder) {
128
+ const match = placeholder.match(/hash:([a-f0-9]{8,64})\]/);
129
+ return match ? match[1] : null;
130
+ }
131
+ function expandPlaceholder(placeholder) {
132
+ const hash = parsePlaceholder(placeholder);
133
+ if (!hash) {
134
+ return placeholder;
135
+ }
136
+ const cacheDir = getPasteCacheDir();
137
+ if (!existsSync(cacheDir)) {
138
+ return placeholder;
139
+ }
140
+ try {
141
+ const files = readdirSync(cacheDir);
142
+ const matchingFile = files.find(
143
+ (f) => f.startsWith(hash) && f.endsWith(".txt")
144
+ );
145
+ if (matchingFile) {
146
+ const fullHash = matchingFile.replace(".txt", "");
147
+ const content = getPaste(fullHash);
148
+ if (content) {
149
+ return content;
150
+ }
151
+ }
152
+ } catch {
153
+ }
154
+ return placeholder;
155
+ }
156
+ function listCachedEntries() {
157
+ const cacheDir = getPasteCacheDir();
158
+ if (!existsSync(cacheDir)) {
159
+ return [];
160
+ }
161
+ const entries = [];
162
+ try {
163
+ const files = readdirSync(cacheDir);
164
+ for (const file of files) {
165
+ if (file.endsWith(".meta.json")) {
166
+ const metadataPath = join(cacheDir, file);
167
+ try {
168
+ const content = readFileSync(metadataPath, "utf-8");
169
+ const entry = JSON.parse(content);
170
+ entries.push(entry);
171
+ } catch {
172
+ }
173
+ }
174
+ }
175
+ } catch {
176
+ }
177
+ return entries.sort((a, b) => b.timestamp - a.timestamp);
178
+ }
179
+ function cleanupExpiredEntries(options = {}) {
180
+ const { ttl = DEFAULT_TTL } = options;
181
+ const cacheDir = getPasteCacheDir();
182
+ const now = Date.now();
183
+ let removed = 0;
184
+ if (!existsSync(cacheDir)) {
185
+ return 0;
186
+ }
187
+ try {
188
+ const entries = listCachedEntries();
189
+ for (const entry of entries) {
190
+ if (now - entry.timestamp > ttl) {
191
+ try {
192
+ const filePath = getCacheFilePath(entry.hash);
193
+ const metadataPath = getMetadataFilePath(entry.hash);
194
+ if (existsSync(filePath)) {
195
+ unlinkSync(filePath);
196
+ }
197
+ if (existsSync(metadataPath)) {
198
+ unlinkSync(metadataPath);
199
+ }
200
+ removed++;
201
+ debugLogger.trace("PASTE_CACHE_EXPIRED_REMOVED", {
202
+ hash: entry.hash.slice(0, 16),
203
+ age: now - entry.timestamp
204
+ });
205
+ } catch {
206
+ }
207
+ }
208
+ }
209
+ if (removed > 0) {
210
+ debugLogger.info("PASTE_CACHE_CLEANUP_COMPLETE", {
211
+ removed,
212
+ remaining: entries.length - removed
213
+ });
214
+ }
215
+ } catch (error) {
216
+ debugLogger.error("PASTE_CACHE_CLEANUP_FAILED", {
217
+ error: error instanceof Error ? error.message : String(error)
218
+ });
219
+ }
220
+ return removed;
221
+ }
222
+ function enforceMaxCacheSize() {
223
+ const entries = listCachedEntries();
224
+ if (entries.length <= MAX_CACHE_SIZE) {
225
+ return 0;
226
+ }
227
+ const toRemove = entries.sort((a, b) => a.timestamp - b.timestamp).slice(0, entries.length - MAX_CACHE_SIZE);
228
+ let removed = 0;
229
+ for (const entry of toRemove) {
230
+ try {
231
+ const filePath = getCacheFilePath(entry.hash);
232
+ const metadataPath = getMetadataFilePath(entry.hash);
233
+ if (existsSync(filePath)) {
234
+ unlinkSync(filePath);
235
+ }
236
+ if (existsSync(metadataPath)) {
237
+ unlinkSync(metadataPath);
238
+ }
239
+ removed++;
240
+ } catch {
241
+ }
242
+ }
243
+ if (removed > 0) {
244
+ debugLogger.info("PASTE_CACHE_SIZE_ENFORCED", {
245
+ removed,
246
+ maxSize: MAX_CACHE_SIZE
247
+ });
248
+ }
249
+ return removed;
250
+ }
251
+ function getCacheStats() {
252
+ const entries = listCachedEntries();
253
+ if (entries.length === 0) {
254
+ return {
255
+ entryCount: 0,
256
+ totalSize: 0,
257
+ oldestEntry: null,
258
+ newestEntry: null
259
+ };
260
+ }
261
+ const totalSize = entries.reduce((sum, e) => sum + e.length, 0);
262
+ const timestamps = entries.map((e) => e.timestamp);
263
+ return {
264
+ entryCount: entries.length,
265
+ totalSize,
266
+ oldestEntry: Math.min(...timestamps),
267
+ newestEntry: Math.max(...timestamps)
268
+ };
269
+ }
270
+ function clearCache() {
271
+ const cacheDir = getPasteCacheDir();
272
+ let removed = 0;
273
+ if (!existsSync(cacheDir)) {
274
+ return 0;
275
+ }
276
+ try {
277
+ const files = readdirSync(cacheDir);
278
+ for (const file of files) {
279
+ try {
280
+ unlinkSync(join(cacheDir, file));
281
+ removed++;
282
+ } catch {
283
+ }
284
+ }
285
+ debugLogger.info("PASTE_CACHE_CLEARED", { removed });
286
+ } catch (error) {
287
+ debugLogger.error("PASTE_CACHE_CLEAR_FAILED", {
288
+ error: error instanceof Error ? error.message : String(error)
289
+ });
290
+ }
291
+ return removed;
292
+ }
293
+ export {
294
+ cachePaste,
295
+ cleanupExpiredEntries,
296
+ clearCache,
297
+ createPlaceholder,
298
+ enforceMaxCacheSize,
299
+ expandPlaceholder,
300
+ getCacheStats,
301
+ getContentHash,
302
+ getMetadata,
303
+ getPaste,
304
+ getPasteCacheDir,
305
+ isCached,
306
+ listCachedEntries,
307
+ parsePlaceholder
308
+ };
309
+ //# sourceMappingURL=pasteCache.js.map