k0ntext 3.3.0 → 3.5.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.
Files changed (200) hide show
  1. package/README.md +240 -26
  2. package/dist/agents/cleanup-agent.d.ts.map +1 -1
  3. package/dist/agents/cleanup-agent.js +18 -6
  4. package/dist/agents/cleanup-agent.js.map +1 -1
  5. package/dist/agents/drift-agent.d.ts +7 -0
  6. package/dist/agents/drift-agent.d.ts.map +1 -1
  7. package/dist/agents/drift-agent.js +29 -8
  8. package/dist/agents/drift-agent.js.map +1 -1
  9. package/dist/cli/commands/cleanup.d.ts.map +1 -1
  10. package/dist/cli/commands/cleanup.js +8 -1
  11. package/dist/cli/commands/cleanup.js.map +1 -1
  12. package/dist/cli/commands/drift-detect.d.ts.map +1 -1
  13. package/dist/cli/commands/drift-detect.js +21 -1
  14. package/dist/cli/commands/drift-detect.js.map +1 -1
  15. package/dist/cli/commands/restore.d.ts +12 -0
  16. package/dist/cli/commands/restore.d.ts.map +1 -0
  17. package/dist/cli/commands/restore.js +261 -0
  18. package/dist/cli/commands/restore.js.map +1 -0
  19. package/dist/cli/commands/sync-templates.d.ts +15 -0
  20. package/dist/cli/commands/sync-templates.d.ts.map +1 -0
  21. package/dist/cli/commands/sync-templates.js +181 -0
  22. package/dist/cli/commands/sync-templates.js.map +1 -0
  23. package/dist/cli/commands/version-check.d.ts +12 -0
  24. package/dist/cli/commands/version-check.d.ts.map +1 -0
  25. package/dist/cli/commands/version-check.js +133 -0
  26. package/dist/cli/commands/version-check.js.map +1 -0
  27. package/dist/cli/generate.d.ts +5 -0
  28. package/dist/cli/generate.d.ts.map +1 -1
  29. package/dist/cli/generate.js +80 -16
  30. package/dist/cli/generate.js.map +1 -1
  31. package/dist/cli/index.js +145 -1
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/repl/index.d.ts +4 -0
  34. package/dist/cli/repl/index.d.ts.map +1 -1
  35. package/dist/cli/repl/index.js +113 -95
  36. package/dist/cli/repl/index.js.map +1 -1
  37. package/dist/cli/repl/tui/panels/config.d.ts +71 -0
  38. package/dist/cli/repl/tui/panels/config.d.ts.map +1 -0
  39. package/dist/cli/repl/tui/panels/config.js +392 -0
  40. package/dist/cli/repl/tui/panels/config.js.map +1 -0
  41. package/dist/cli/repl/tui/panels/drift.d.ts +95 -0
  42. package/dist/cli/repl/tui/panels/drift.d.ts.map +1 -0
  43. package/dist/cli/repl/tui/panels/drift.js +353 -0
  44. package/dist/cli/repl/tui/panels/drift.js.map +1 -0
  45. package/dist/cli/repl/tui/panels/indexing.d.ts +86 -0
  46. package/dist/cli/repl/tui/panels/indexing.d.ts.map +1 -0
  47. package/dist/cli/repl/tui/panels/indexing.js +254 -0
  48. package/dist/cli/repl/tui/panels/indexing.js.map +1 -0
  49. package/dist/cli/repl/tui/panels/search.d.ts +66 -0
  50. package/dist/cli/repl/tui/panels/search.d.ts.map +1 -0
  51. package/dist/cli/repl/tui/panels/search.js +215 -0
  52. package/dist/cli/repl/tui/panels/search.js.map +1 -0
  53. package/dist/cli/utils/backup-manager.d.ts +94 -0
  54. package/dist/cli/utils/backup-manager.d.ts.map +1 -0
  55. package/dist/cli/utils/backup-manager.js +230 -0
  56. package/dist/cli/utils/backup-manager.js.map +1 -0
  57. package/dist/cli/utils/file-detector.d.ts +87 -0
  58. package/dist/cli/utils/file-detector.d.ts.map +1 -0
  59. package/dist/cli/utils/file-detector.js +131 -0
  60. package/dist/cli/utils/file-detector.js.map +1 -0
  61. package/dist/cli/utils/index.d.ts +9 -0
  62. package/dist/cli/utils/index.d.ts.map +1 -0
  63. package/dist/cli/utils/index.js +9 -0
  64. package/dist/cli/utils/index.js.map +1 -0
  65. package/dist/cli/utils/modification-prompt.d.ts +41 -0
  66. package/dist/cli/utils/modification-prompt.d.ts.map +1 -0
  67. package/dist/cli/utils/modification-prompt.js +84 -0
  68. package/dist/cli/utils/modification-prompt.js.map +1 -0
  69. package/dist/cli/version/checker.d.ts +47 -0
  70. package/dist/cli/version/checker.d.ts.map +1 -0
  71. package/dist/cli/version/checker.js +143 -0
  72. package/dist/cli/version/checker.js.map +1 -0
  73. package/dist/cli/version/comparator.d.ts +46 -0
  74. package/dist/cli/version/comparator.d.ts.map +1 -0
  75. package/dist/cli/version/comparator.js +99 -0
  76. package/dist/cli/version/comparator.js.map +1 -0
  77. package/dist/cli/version/index.d.ts +11 -0
  78. package/dist/cli/version/index.d.ts.map +1 -0
  79. package/dist/cli/version/index.js +11 -0
  80. package/dist/cli/version/index.js.map +1 -0
  81. package/dist/cli/version/parser.d.ts +38 -0
  82. package/dist/cli/version/parser.d.ts.map +1 -0
  83. package/dist/cli/version/parser.js +90 -0
  84. package/dist/cli/version/parser.js.map +1 -0
  85. package/dist/cli/version/prompt.d.ts +40 -0
  86. package/dist/cli/version/prompt.d.ts.map +1 -0
  87. package/dist/cli/version/prompt.js +162 -0
  88. package/dist/cli/version/prompt.js.map +1 -0
  89. package/dist/cli/version/types.d.ts +89 -0
  90. package/dist/cli/version/types.d.ts.map +1 -0
  91. package/dist/cli/version/types.js +7 -0
  92. package/dist/cli/version/types.js.map +1 -0
  93. package/dist/db/client.d.ts +64 -2
  94. package/dist/db/client.d.ts.map +1 -1
  95. package/dist/db/client.js +148 -2
  96. package/dist/db/client.js.map +1 -1
  97. package/dist/db/schema.d.ts +41 -2
  98. package/dist/db/schema.d.ts.map +1 -1
  99. package/dist/db/schema.js +77 -2
  100. package/dist/db/schema.js.map +1 -1
  101. package/dist/mcp.js +2 -2
  102. package/dist/mcp.js.map +1 -1
  103. package/dist/template-engine/data-transformer.d.ts +17 -0
  104. package/dist/template-engine/data-transformer.d.ts.map +1 -0
  105. package/dist/template-engine/data-transformer.js +343 -0
  106. package/dist/template-engine/data-transformer.js.map +1 -0
  107. package/dist/template-engine/engine.d.ts +74 -0
  108. package/dist/template-engine/engine.d.ts.map +1 -0
  109. package/dist/template-engine/engine.js +183 -0
  110. package/dist/template-engine/engine.js.map +1 -0
  111. package/dist/template-engine/helpers.d.ts +81 -0
  112. package/dist/template-engine/helpers.d.ts.map +1 -0
  113. package/dist/template-engine/helpers.js +153 -0
  114. package/dist/template-engine/helpers.js.map +1 -0
  115. package/dist/template-engine/index.d.ts +10 -0
  116. package/dist/template-engine/index.d.ts.map +1 -0
  117. package/dist/template-engine/index.js +10 -0
  118. package/dist/template-engine/index.js.map +1 -0
  119. package/dist/template-engine/types.d.ts +147 -0
  120. package/dist/template-engine/types.d.ts.map +1 -0
  121. package/dist/template-engine/types.js +7 -0
  122. package/dist/template-engine/types.js.map +1 -0
  123. package/dist/template-sync/comparator.d.ts +138 -0
  124. package/dist/template-sync/comparator.d.ts.map +1 -0
  125. package/dist/template-sync/comparator.js +353 -0
  126. package/dist/template-sync/comparator.js.map +1 -0
  127. package/dist/template-sync/conflict-resolver.d.ts +112 -0
  128. package/dist/template-sync/conflict-resolver.d.ts.map +1 -0
  129. package/dist/template-sync/conflict-resolver.js +328 -0
  130. package/dist/template-sync/conflict-resolver.js.map +1 -0
  131. package/dist/template-sync/engine.d.ts +93 -0
  132. package/dist/template-sync/engine.d.ts.map +1 -0
  133. package/dist/template-sync/engine.js +350 -0
  134. package/dist/template-sync/engine.js.map +1 -0
  135. package/dist/template-sync/hasher.d.ts +67 -0
  136. package/dist/template-sync/hasher.d.ts.map +1 -0
  137. package/dist/template-sync/hasher.js +94 -0
  138. package/dist/template-sync/hasher.js.map +1 -0
  139. package/dist/template-sync/index.d.ts +20 -0
  140. package/dist/template-sync/index.d.ts.map +1 -0
  141. package/dist/template-sync/index.js +14 -0
  142. package/dist/template-sync/index.js.map +1 -0
  143. package/dist/template-sync/manifest.d.ts +131 -0
  144. package/dist/template-sync/manifest.d.ts.map +1 -0
  145. package/dist/template-sync/manifest.js +309 -0
  146. package/dist/template-sync/manifest.js.map +1 -0
  147. package/dist/template-sync/merger.d.ts +125 -0
  148. package/dist/template-sync/merger.d.ts.map +1 -0
  149. package/dist/template-sync/merger.js +371 -0
  150. package/dist/template-sync/merger.js.map +1 -0
  151. package/dist/template-sync/scanner.d.ts +106 -0
  152. package/dist/template-sync/scanner.d.ts.map +1 -0
  153. package/dist/template-sync/scanner.js +196 -0
  154. package/dist/template-sync/scanner.js.map +1 -0
  155. package/dist/template-sync/types.d.ts +199 -0
  156. package/dist/template-sync/types.d.ts.map +1 -0
  157. package/dist/template-sync/types.js +30 -0
  158. package/dist/template-sync/types.js.map +1 -0
  159. package/package.json +2 -1
  160. package/src/agents/cleanup-agent.ts +21 -6
  161. package/src/agents/drift-agent.ts +31 -8
  162. package/src/cli/commands/cleanup.ts +9 -1
  163. package/src/cli/commands/drift-detect.ts +24 -1
  164. package/src/cli/commands/restore.ts +318 -0
  165. package/src/cli/commands/sync-templates.ts +210 -0
  166. package/src/cli/commands/version-check.ts +158 -0
  167. package/src/cli/generate.ts +99 -17
  168. package/src/cli/index.ts +167 -1
  169. package/src/cli/repl/index.ts +118 -105
  170. package/src/cli/repl/tui/panels/config.ts +457 -0
  171. package/src/cli/repl/tui/panels/drift.ts +458 -0
  172. package/src/cli/repl/tui/panels/indexing.ts +324 -0
  173. package/src/cli/repl/tui/panels/search.ts +272 -0
  174. package/src/cli/utils/backup-manager.ts +275 -0
  175. package/src/cli/utils/file-detector.ts +181 -0
  176. package/src/cli/utils/index.ts +9 -0
  177. package/src/cli/utils/modification-prompt.ts +112 -0
  178. package/src/cli/version/checker.ts +172 -0
  179. package/src/cli/version/comparator.ts +106 -0
  180. package/src/cli/version/index.ts +11 -0
  181. package/src/cli/version/parser.ts +101 -0
  182. package/src/cli/version/prompt.ts +208 -0
  183. package/src/cli/version/types.ts +95 -0
  184. package/src/db/client.ts +220 -3
  185. package/src/db/schema.ts +109 -2
  186. package/src/mcp.ts +2 -2
  187. package/src/template-engine/data-transformer.ts +367 -0
  188. package/src/template-engine/engine.ts +213 -0
  189. package/src/template-engine/helpers.ts +163 -0
  190. package/src/template-engine/index.ts +10 -0
  191. package/src/template-engine/types.ts +158 -0
  192. package/src/template-sync/comparator.ts +452 -0
  193. package/src/template-sync/conflict-resolver.ts +401 -0
  194. package/src/template-sync/engine.ts +417 -0
  195. package/src/template-sync/hasher.ts +104 -0
  196. package/src/template-sync/index.ts +60 -0
  197. package/src/template-sync/manifest.ts +358 -0
  198. package/src/template-sync/merger.ts +454 -0
  199. package/src/template-sync/scanner.ts +254 -0
  200. package/src/template-sync/types.ts +247 -0
@@ -0,0 +1,358 @@
1
+ /**
2
+ * Template Manifest Manager
3
+ *
4
+ * Manages template manifest in both database and file system.
5
+ * Dual storage ensures reliability and easy inspection.
6
+ */
7
+
8
+ import { promises as fs } from 'fs';
9
+ import path from 'path';
10
+ import type { DatabaseClient } from '../db/client.js';
11
+ import type { TemplateManifest, TemplateFileEntry } from './types.js';
12
+ import { TEMPLATE_SCHEMA_SQL } from '../db/schema.js';
13
+
14
+ /**
15
+ * Manifest file path in .claude directory
16
+ */
17
+ const MANIFEST_FILE_NAME = '.k0ntext-manifest.json';
18
+
19
+ /**
20
+ * Manifest ID for database storage
21
+ */
22
+ const MANIFEST_ID = 'current';
23
+
24
+ /**
25
+ * Manages template manifest in both database and file system
26
+ */
27
+ export class TemplateManifestManager {
28
+ private readonly manifestPath: string;
29
+
30
+ constructor(
31
+ private db: DatabaseClient,
32
+ private projectRoot: string = process.cwd()
33
+ ) {
34
+ this.manifestPath = path.join(projectRoot, '.claude', MANIFEST_FILE_NAME);
35
+ }
36
+
37
+ /**
38
+ * Load manifest from file system
39
+ *
40
+ * @returns Manifest object or null if doesn't exist
41
+ */
42
+ async loadManifest(): Promise<TemplateManifest | null> {
43
+ try {
44
+ const content = await fs.readFile(this.manifestPath, 'utf8');
45
+ const manifest = JSON.parse(content) as TemplateManifest;
46
+ this.validateManifest(manifest);
47
+ return manifest;
48
+ } catch (error) {
49
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
50
+ return null;
51
+ }
52
+ // Invalid JSON or validation error
53
+ console.warn(`Warning: Invalid manifest file at ${this.manifestPath}`);
54
+ return null;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Save manifest to file system
60
+ *
61
+ * @param manifest - Manifest to save
62
+ */
63
+ async saveManifest(manifest: TemplateManifest): Promise<void> {
64
+ this.validateManifest(manifest);
65
+
66
+ // Ensure directory exists
67
+ const manifestDir = path.dirname(this.manifestPath);
68
+ await fs.mkdir(manifestDir, { recursive: true });
69
+
70
+ // Update timestamp
71
+ manifest.updatedAt = new Date().toISOString();
72
+
73
+ // Write manifest with proper formatting
74
+ await fs.writeFile(
75
+ this.manifestPath,
76
+ JSON.stringify(manifest, null, 2),
77
+ 'utf8'
78
+ );
79
+ }
80
+
81
+ /**
82
+ * Get manifest from database
83
+ *
84
+ * @returns Manifest object or null if doesn't exist
85
+ */
86
+ getDbManifest(): TemplateManifest | null {
87
+ try {
88
+ const stmt = this.db.getRawDb().prepare(`
89
+ SELECT manifest
90
+ FROM template_manifests
91
+ WHERE id = ?
92
+ ORDER BY created_at DESC
93
+ LIMIT 1
94
+ `);
95
+
96
+ const row = stmt.get(MANIFEST_ID) as { manifest: string } | undefined;
97
+ if (!row) return null;
98
+
99
+ const manifest = JSON.parse(row.manifest) as TemplateManifest;
100
+ this.validateManifest(manifest);
101
+ return manifest;
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Save manifest to database
109
+ *
110
+ * @param manifest - Manifest to save
111
+ */
112
+ saveDbManifest(manifest: TemplateManifest): void {
113
+ this.validateManifest(manifest);
114
+
115
+ // Update timestamp
116
+ manifest.updatedAt = new Date().toISOString();
117
+
118
+ const stmt = this.db.getRawDb().prepare(`
119
+ INSERT OR REPLACE INTO template_manifests
120
+ (id, k0ntext_version, template_version, manifest, created_at, updated_at)
121
+ VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))
122
+ `);
123
+
124
+ stmt.run(
125
+ MANIFEST_ID,
126
+ manifest.k0ntextVersion,
127
+ manifest.templateVersion,
128
+ JSON.stringify(manifest)
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Sync manifest between DB and file system
134
+ * Uses DB as source of truth by default
135
+ *
136
+ * @param manifest - Manifest to sync
137
+ * @param source - 'db' or 'file' to determine source of truth
138
+ */
139
+ async syncManifest(manifest: TemplateManifest, source: 'db' | 'file' = 'db'): Promise<void> {
140
+ if (source === 'db') {
141
+ this.saveDbManifest(manifest);
142
+ await this.saveManifest(manifest);
143
+ } else {
144
+ await this.saveManifest(manifest);
145
+ this.saveDbManifest(manifest);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Load manifest from both sources and reconcile
151
+ * Prefers the most recently updated version
152
+ *
153
+ * @returns Reconciled manifest or null
154
+ */
155
+ async loadReconciledManifest(): Promise<TemplateManifest | null> {
156
+ const fileManifest = await this.loadManifest();
157
+ const dbManifest = this.getDbManifest();
158
+
159
+ if (!fileManifest && !dbManifest) {
160
+ return null;
161
+ }
162
+
163
+ if (!fileManifest) return dbManifest;
164
+ if (!dbManifest) return fileManifest;
165
+
166
+ // Both exist - prefer the most recently updated
167
+ const fileDate = new Date(fileManifest.updatedAt || fileManifest.createdAt);
168
+ const dbDate = new Date(dbManifest.updatedAt || dbManifest.createdAt);
169
+
170
+ return fileDate > dbDate ? fileManifest : dbManifest;
171
+ }
172
+
173
+ /**
174
+ * Get file entry from manifest
175
+ *
176
+ * @param relativePath - Relative path to file
177
+ * @param manifest - Optional manifest to use (otherwise loads)
178
+ * @returns File entry or null
179
+ */
180
+ async getFileEntry(relativePath: string, manifest?: TemplateManifest): Promise<TemplateFileEntry | null> {
181
+ const activeManifest = manifest ?? await this.loadReconciledManifest();
182
+ if (!activeManifest) return null;
183
+
184
+ return activeManifest.files[relativePath] || null;
185
+ }
186
+
187
+ /**
188
+ * Mark file as user-modified
189
+ *
190
+ * @param relativePath - Relative path to file
191
+ * @param originalHash - Original template hash before modification
192
+ */
193
+ async markUserModified(relativePath: string, originalHash?: string): Promise<void> {
194
+ const manifest = await this.loadReconciledManifest() ?? this.createEmptyManifest();
195
+
196
+ if (manifest.files[relativePath]) {
197
+ manifest.files[relativePath].userModified = true;
198
+ if (originalHash) {
199
+ manifest.files[relativePath].originalHash = originalHash;
200
+ }
201
+ await this.syncManifest(manifest);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Clear user-modified flag for a file
207
+ *
208
+ * @param relativePath - Relative path to file
209
+ */
210
+ async clearUserModified(relativePath: string): Promise<void> {
211
+ const manifest = await this.loadReconciledManifest() ?? this.createEmptyManifest();
212
+
213
+ if (manifest.files[relativePath]) {
214
+ manifest.files[relativePath].userModified = false;
215
+ manifest.files[relativePath].originalHash = undefined;
216
+ await this.syncManifest(manifest);
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Update file entry in manifest
222
+ *
223
+ * @param relativePath - Relative path to file
224
+ * @param entry - New file entry data
225
+ */
226
+ async updateFileEntry(relativePath: string, entry: Partial<TemplateFileEntry>): Promise<void> {
227
+ const manifest = await this.loadReconciledManifest() ?? this.createEmptyManifest();
228
+
229
+ manifest.files[relativePath] = {
230
+ ...manifest.files[relativePath],
231
+ ...entry
232
+ };
233
+
234
+ await this.syncManifest(manifest);
235
+ }
236
+
237
+ /**
238
+ * Remove file entry from manifest
239
+ *
240
+ * @param relativePath - Relative path to file
241
+ */
242
+ async removeFileEntry(relativePath: string): Promise<void> {
243
+ const manifest = await this.loadReconciledManifest();
244
+ if (!manifest) return;
245
+
246
+ delete manifest.files[relativePath];
247
+ await this.syncManifest(manifest);
248
+ }
249
+
250
+ /**
251
+ * Get all user-modified files
252
+ *
253
+ * @returns Array of relative paths to user-modified files
254
+ */
255
+ async getUserModifiedFiles(): Promise<string[]> {
256
+ const manifest = await this.loadReconciledManifest();
257
+ if (!manifest) return [];
258
+
259
+ return Object.entries(manifest.files)
260
+ .filter(([_, entry]) => entry.userModified)
261
+ .map(([path, _]) => path);
262
+ }
263
+
264
+ /**
265
+ * Check if manifest needs update based on version
266
+ *
267
+ * @param currentVersion - Current package version
268
+ * @returns true if manifest is outdated or missing
269
+ */
270
+ async needsUpdate(currentVersion: string): Promise<boolean> {
271
+ const manifest = await this.loadReconciledManifest();
272
+ if (!manifest) return true;
273
+
274
+ return manifest.templateVersion !== currentVersion;
275
+ }
276
+
277
+ /**
278
+ * Create empty manifest with current versions
279
+ *
280
+ * @param k0ntextVersion - k0ntext package version
281
+ * @param templateVersion - Template version (usually same as k0ntext version)
282
+ * @returns New empty manifest
283
+ */
284
+ createEmptyManifest(
285
+ k0ntextVersion: string = '0.0.0',
286
+ templateVersion: string = '0.0.0'
287
+ ): TemplateManifest {
288
+ return {
289
+ k0ntextVersion,
290
+ templateVersion,
291
+ createdAt: new Date().toISOString(),
292
+ updatedAt: new Date().toISOString(),
293
+ files: {}
294
+ };
295
+ }
296
+
297
+ /**
298
+ * Validate manifest structure
299
+ *
300
+ * @param manifest - Manifest to validate
301
+ * @throws Error if invalid
302
+ */
303
+ private validateManifest(manifest: unknown): asserts manifest is TemplateManifest {
304
+ if (!manifest || typeof manifest !== 'object') {
305
+ throw new Error('Invalid manifest: not an object');
306
+ }
307
+
308
+ const m = manifest as Partial<TemplateManifest>;
309
+
310
+ if (!m.k0ntextVersion || typeof m.k0ntextVersion !== 'string') {
311
+ throw new Error('Invalid manifest: missing or invalid k0ntextVersion');
312
+ }
313
+
314
+ if (!m.templateVersion || typeof m.templateVersion !== 'string') {
315
+ throw new Error('Invalid manifest: missing or invalid templateVersion');
316
+ }
317
+
318
+ if (!m.createdAt || typeof m.createdAt !== 'string') {
319
+ throw new Error('Invalid manifest: missing or invalid createdAt');
320
+ }
321
+
322
+ if (!m.files || typeof m.files !== 'object') {
323
+ throw new Error('Invalid manifest: missing or invalid files');
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Get manifest file path for display purposes
329
+ */
330
+ getManifestPath(): string {
331
+ return this.manifestPath;
332
+ }
333
+
334
+ /**
335
+ * Check if manifest file exists
336
+ */
337
+ async manifestExists(): Promise<boolean> {
338
+ try {
339
+ await fs.access(this.manifestPath);
340
+ return true;
341
+ } catch {
342
+ return false;
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Delete manifest file (for cleanup/testing)
348
+ */
349
+ async deleteManifest(): Promise<void> {
350
+ try {
351
+ await fs.unlink(this.manifestPath);
352
+ } catch (error) {
353
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
354
+ throw error;
355
+ }
356
+ }
357
+ }
358
+ }