k0ntext 3.7.0 → 3.8.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.
- package/README.md +281 -382
- package/dist/agent-system/timestamp-tracker.d.ts +159 -0
- package/dist/agent-system/timestamp-tracker.d.ts.map +1 -0
- package/dist/agent-system/timestamp-tracker.js +405 -0
- package/dist/agent-system/timestamp-tracker.js.map +1 -0
- package/dist/agent-system/todolist-manager.d.ts +244 -0
- package/dist/agent-system/todolist-manager.d.ts.map +1 -0
- package/dist/agent-system/todolist-manager.js +580 -0
- package/dist/agent-system/todolist-manager.js.map +1 -0
- package/dist/cli/commands/snapshot.d.ts +28 -0
- package/dist/cli/commands/snapshot.d.ts.map +1 -0
- package/dist/cli/commands/snapshot.js +408 -0
- package/dist/cli/commands/snapshot.js.map +1 -0
- package/dist/cli/version/comparator.d.ts +1 -0
- package/dist/cli/version/comparator.d.ts.map +1 -1
- package/dist/cli/version/comparator.js +1 -0
- package/dist/cli/version/comparator.js.map +1 -1
- package/dist/db/client.d.ts +5 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +7 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/schema.d.ts +1 -1
- package/dist/db/schema.js +1 -1
- package/dist/services/snapshot-manager.d.ts +251 -0
- package/dist/services/snapshot-manager.d.ts.map +1 -0
- package/dist/services/snapshot-manager.js +541 -0
- package/dist/services/snapshot-manager.js.map +1 -0
- package/docs/QUICKSTART.md +1 -1
- package/docs/TROUBLESHOOTING.md +51 -76
- package/docs/plans/2026-02-11-context-engineering-enhancement.md +1402 -0
- package/package.json +8 -2
- package/src/agent-system/timestamp-tracker.ts +520 -0
- package/src/agent-system/todolist-manager.ts +753 -0
- package/src/cli/commands/snapshot.ts +471 -0
- package/src/cli/version/comparator.ts +1 -0
- package/src/db/client.ts +8 -0
- package/src/db/migrations/0016_add_context_system_tables.sql +38 -0
- package/src/db/schema.ts +1 -1
- package/src/services/snapshot-manager.ts +719 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot Commands
|
|
3
|
+
*
|
|
4
|
+
* CLI commands for managing database snapshots.
|
|
5
|
+
* Supports create, restore, list, and diff operations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import ora from 'ora';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import fs from 'fs/promises';
|
|
13
|
+
import { confirm, input, select } from '@inquirer/prompts';
|
|
14
|
+
import type { DatabaseClient } from '../../db/client.js';
|
|
15
|
+
import { SnapshotManager } from '../../services/snapshot-manager.js';
|
|
16
|
+
import type { SnapshotMetadata, SnapshotListEntry, SnapshotDiffResult } from '../../services/snapshot-manager.js';
|
|
17
|
+
import { compareVersions, needsUpdate, getUpdateType } from '../version/comparator.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Format bytes for display
|
|
21
|
+
*/
|
|
22
|
+
function formatBytes(bytes: number): string {
|
|
23
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
24
|
+
let size = bytes;
|
|
25
|
+
let unitIndex = 0;
|
|
26
|
+
|
|
27
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
28
|
+
size /= 1024;
|
|
29
|
+
unitIndex++;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Format date for display
|
|
37
|
+
*/
|
|
38
|
+
function formatDate(isoString: string): string {
|
|
39
|
+
const date = new Date(isoString);
|
|
40
|
+
const now = new Date();
|
|
41
|
+
const diffMs = now.getTime() - date.getTime();
|
|
42
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
43
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
44
|
+
|
|
45
|
+
if (diffHours > 24) {
|
|
46
|
+
return `${date.toLocaleDateString()} (${Math.floor(diffHours / 24)}d ago)`;
|
|
47
|
+
} else if (diffHours > 0) {
|
|
48
|
+
return `${date.toLocaleDateString()} (${diffHours}h ago)`;
|
|
49
|
+
} else if (diffMins > 0) {
|
|
50
|
+
return `${date.toLocaleDateString()} (${diffMins}m ago)`;
|
|
51
|
+
} else {
|
|
52
|
+
return date.toLocaleTimeString();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Format snapshot metadata for display
|
|
58
|
+
*/
|
|
59
|
+
function formatSnapshot(snapshot: SnapshotMetadata): string {
|
|
60
|
+
const tags = snapshot.tags ? snapshot.tags.map((t: string) => chalk.cyan(`#${t}`)).join(' ') : '';
|
|
61
|
+
const tagStr = tags ? ` ${tags}` : '';
|
|
62
|
+
const auto = snapshot.automatic ? chalk.dim('[auto]') : '';
|
|
63
|
+
|
|
64
|
+
return `${chalk.bold(snapshot.name)} ${auto}${tagStr}
|
|
65
|
+
${chalk.dim(`ID: ${snapshot.id}`)}
|
|
66
|
+
${chalk.dim(`Created: ${formatDate(snapshot.createdAt)}`)}
|
|
67
|
+
${chalk.dim(`Size: ${formatBytes(snapshot.size)}`)}
|
|
68
|
+
${chalk.dim(`Items: ${snapshot.itemCount}`)}
|
|
69
|
+
${snapshot.gitCommit ? chalk.dim(`Git: ${snapshot.gitCommit.substring(0, 8)}`) : ''}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Snapshot create command
|
|
74
|
+
*/
|
|
75
|
+
export const snapshotCreateCommand = new Command('snapshot')
|
|
76
|
+
.alias('snap')
|
|
77
|
+
.description('Create a database snapshot')
|
|
78
|
+
.option('-n, --name <name>', 'Snapshot name')
|
|
79
|
+
.option('-d, --description <text>', 'Snapshot description')
|
|
80
|
+
.option('-t, --tags <tags>', 'Comma-separated tags')
|
|
81
|
+
.option('--no-compress', 'Do not compress snapshot')
|
|
82
|
+
.option('--dry-run', 'Show what would be saved without creating')
|
|
83
|
+
.option('--no-git', 'Do not include git commit in metadata')
|
|
84
|
+
.action(async (options) => {
|
|
85
|
+
const spinner = ora();
|
|
86
|
+
const projectRoot = process.cwd();
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Load database
|
|
90
|
+
const { DatabaseClient } = await import('../../db/client.js');
|
|
91
|
+
const versionModule = await import('../../cli/version/comparator.js');
|
|
92
|
+
const K0NTEXT_VERSION = versionModule.version;
|
|
93
|
+
const db = new DatabaseClient(projectRoot);
|
|
94
|
+
const manager = new SnapshotManager(db, projectRoot, K0NTEXT_VERSION);
|
|
95
|
+
|
|
96
|
+
// Get snapshot name if not provided
|
|
97
|
+
let name = options.name;
|
|
98
|
+
if (!name) {
|
|
99
|
+
const now = new Date();
|
|
100
|
+
name = `Snapshot ${now.toLocaleDateString()} ${now.toLocaleTimeString()}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Parse tags
|
|
104
|
+
const tags = options.tags ? options.tags.split(',').map((t: string) => t.trim()) : undefined;
|
|
105
|
+
|
|
106
|
+
spinner.start('Creating snapshot...');
|
|
107
|
+
|
|
108
|
+
// Create snapshot
|
|
109
|
+
const metadata = await manager.createSnapshot({
|
|
110
|
+
name,
|
|
111
|
+
description: options.description,
|
|
112
|
+
tags,
|
|
113
|
+
dryRun: !!options.dryRun,
|
|
114
|
+
includeGitCommit: options.git !== false,
|
|
115
|
+
compress: options.noCompress !== true
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
spinner.stop();
|
|
119
|
+
|
|
120
|
+
if (options.dryRun) {
|
|
121
|
+
console.log(chalk.bold('\nDry run - snapshot would be created:'));
|
|
122
|
+
console.log(formatSnapshot(metadata));
|
|
123
|
+
console.log(chalk.dim('\nRun without --dry-run to create the snapshot.'));
|
|
124
|
+
} else {
|
|
125
|
+
console.log(chalk.bold('\nSnapshot created successfully!'));
|
|
126
|
+
console.log(formatSnapshot(metadata));
|
|
127
|
+
console.log(chalk.dim(`\nRun 'k0ntext snapshot restore ${metadata.id}' to restore.`));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
db.close();
|
|
131
|
+
|
|
132
|
+
} catch (error) {
|
|
133
|
+
spinner.fail('Failed to create snapshot');
|
|
134
|
+
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : error}`));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Snapshot restore command
|
|
141
|
+
*/
|
|
142
|
+
export const snapshotRestoreCommand = new Command('snapshot')
|
|
143
|
+
.alias('snap-restore')
|
|
144
|
+
.description('Restore a database snapshot')
|
|
145
|
+
.argument('[snapshot]', 'Snapshot ID or path to restore')
|
|
146
|
+
.option('-f, --force', 'Restore without confirmation')
|
|
147
|
+
.option('--no-backup', 'Do not backup current database')
|
|
148
|
+
.option('--no-verify', 'Skip database verification after restore')
|
|
149
|
+
.action(async (snapshotId, options) => {
|
|
150
|
+
const spinner = ora();
|
|
151
|
+
const projectRoot = process.cwd();
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
// Load database
|
|
155
|
+
const { DatabaseClient } = await import('../../db/client.js');
|
|
156
|
+
const { version: K0NTEXT_VERSION } = await import('../../cli/version/comparator.js');
|
|
157
|
+
const db = new DatabaseClient(projectRoot);
|
|
158
|
+
const manager = new SnapshotManager(db, projectRoot, K0NTEXT_VERSION);
|
|
159
|
+
|
|
160
|
+
// List snapshots if no ID provided
|
|
161
|
+
if (!snapshotId) {
|
|
162
|
+
spinner.start('Loading snapshots...');
|
|
163
|
+
const snapshots = await manager.listSnapshots();
|
|
164
|
+
spinner.stop();
|
|
165
|
+
|
|
166
|
+
if (snapshots.length === 0) {
|
|
167
|
+
console.log(chalk.yellow('\nNo snapshots found.'));
|
|
168
|
+
console.log(chalk.dim('Run "k0ntext snapshot create" to create one.\n'));
|
|
169
|
+
db.close();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(chalk.bold('\nAvailable Snapshots:'));
|
|
174
|
+
console.log(chalk.dim('─'.repeat(60)));
|
|
175
|
+
|
|
176
|
+
for (const snap of snapshots.slice(0, 20)) {
|
|
177
|
+
console.log(formatSnapshot({
|
|
178
|
+
id: snap.id,
|
|
179
|
+
name: snap.name,
|
|
180
|
+
createdAt: snap.createdAt,
|
|
181
|
+
size: snap.size,
|
|
182
|
+
itemCount: snap.itemCount,
|
|
183
|
+
k0ntextVersion: K0NTEXT_VERSION,
|
|
184
|
+
automatic: false,
|
|
185
|
+
tags: snap.tags
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (snapshots.length > 20) {
|
|
190
|
+
console.log(chalk.dim(`\n... and ${snapshots.length - 20} more`));
|
|
191
|
+
console.log(chalk.dim(`Run "k0ntext snapshot restore <id>" to restore a snapshot.`));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
db.close();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Resolve snapshot path
|
|
199
|
+
let snapshotPath = snapshotId;
|
|
200
|
+
if (!path.isAbsolute(snapshotId)) {
|
|
201
|
+
// Try to find snapshot by ID
|
|
202
|
+
const snapshots = await manager.listSnapshots();
|
|
203
|
+
const found = snapshots.find(s => s.id === snapshotId);
|
|
204
|
+
if (found) {
|
|
205
|
+
snapshotPath = found.path;
|
|
206
|
+
} else {
|
|
207
|
+
spinner.fail(chalk.red(`Snapshot not found: ${snapshotId}`));
|
|
208
|
+
console.log(chalk.dim('\nRun "k0ntext snapshot list" to see available snapshots.\n'));
|
|
209
|
+
db.close();
|
|
210
|
+
process.exit(1);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Confirm restore
|
|
216
|
+
if (!options.force) {
|
|
217
|
+
const confirmed = await confirm({
|
|
218
|
+
message: `Restore snapshot ${snapshotId}? This will replace your current database.`,
|
|
219
|
+
default: false
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (!confirmed) {
|
|
223
|
+
console.log(chalk.dim('\nRestore cancelled.\n'));
|
|
224
|
+
db.close();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
spinner.start('Restoring snapshot...');
|
|
230
|
+
|
|
231
|
+
await manager.restoreSnapshot({
|
|
232
|
+
snapshotPath,
|
|
233
|
+
force: true,
|
|
234
|
+
backupBeforeRestore: options.backup !== false,
|
|
235
|
+
verify: options.verify !== false
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
spinner.succeed(chalk.green('Snapshot restored successfully!'));
|
|
239
|
+
console.log(chalk.dim('\nDatabase has been restored to the selected snapshot state.'));
|
|
240
|
+
|
|
241
|
+
db.close();
|
|
242
|
+
|
|
243
|
+
} catch (error) {
|
|
244
|
+
spinner.fail('Failed to restore snapshot');
|
|
245
|
+
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : error}`));
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Snapshot list command
|
|
252
|
+
*/
|
|
253
|
+
export const snapshotListCommand = new Command('snapshot')
|
|
254
|
+
.alias('snapshots')
|
|
255
|
+
.description('List all database snapshots')
|
|
256
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
257
|
+
.option('--json', 'Output in JSON format')
|
|
258
|
+
.action(async (options) => {
|
|
259
|
+
const spinner = ora();
|
|
260
|
+
const projectRoot = process.cwd();
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
// Load database
|
|
264
|
+
const { DatabaseClient } = await import('../../db/client.js');
|
|
265
|
+
const db = new DatabaseClient(projectRoot);
|
|
266
|
+
const manager = new SnapshotManager(db, projectRoot, 'unknown');
|
|
267
|
+
|
|
268
|
+
spinner.start('Loading snapshots...');
|
|
269
|
+
|
|
270
|
+
const snapshots = await manager.listSnapshots();
|
|
271
|
+
const usage = await manager.getStorageUsage();
|
|
272
|
+
|
|
273
|
+
spinner.stop();
|
|
274
|
+
|
|
275
|
+
if (snapshots.length === 0) {
|
|
276
|
+
console.log(chalk.yellow('\nNo snapshots found.'));
|
|
277
|
+
console.log(chalk.dim('Run "k0ntext snapshot create" to create one.\n'));
|
|
278
|
+
db.close();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// JSON output
|
|
283
|
+
if (options.json) {
|
|
284
|
+
console.log(JSON.stringify({
|
|
285
|
+
total: snapshots.length,
|
|
286
|
+
usage: {
|
|
287
|
+
totalSize: formatBytes(usage.totalSize),
|
|
288
|
+
oldest: usage.oldestSnapshot,
|
|
289
|
+
newest: usage.newestSnapshot
|
|
290
|
+
},
|
|
291
|
+
snapshots: snapshots.map(s => ({
|
|
292
|
+
id: s.id,
|
|
293
|
+
name: s.name,
|
|
294
|
+
createdAt: s.createdAt,
|
|
295
|
+
size: s.size,
|
|
296
|
+
itemCount: s.itemCount,
|
|
297
|
+
tags: s.tags
|
|
298
|
+
}))
|
|
299
|
+
}, null, 2));
|
|
300
|
+
db.close();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Regular output
|
|
305
|
+
console.log(chalk.bold('\nSnapshots'));
|
|
306
|
+
console.log(chalk.dim('─'.repeat(60)));
|
|
307
|
+
|
|
308
|
+
console.log(`Total: ${chalk.cyan(snapshots.length.toString())} snapshots`);
|
|
309
|
+
console.log(`Storage: ${chalk.cyan(formatBytes(usage.totalSize))}`);
|
|
310
|
+
console.log(`Oldest: ${chalk.dim(usage.oldestSnapshot ? formatDate(usage.oldestSnapshot) : 'N/A')}`);
|
|
311
|
+
console.log(`Newest: ${chalk.dim(usage.newestSnapshot ? formatDate(usage.newestSnapshot) : 'N/A')}`);
|
|
312
|
+
|
|
313
|
+
console.log(chalk.dim('\n' + '─'.repeat(60) + '\n'));
|
|
314
|
+
|
|
315
|
+
for (const snap of snapshots) {
|
|
316
|
+
const tags = snap.tags ? snap.tags.map(t => chalk.cyan(`#${t}`)).join(' ') : '';
|
|
317
|
+
console.log(`${chalk.bold(snap.name)} ${chalk.dim(`(${snap.id})`)}`);
|
|
318
|
+
console.log(` Created: ${chalk.dim(formatDate(snap.createdAt))}`);
|
|
319
|
+
console.log(` Size: ${chalk.cyan(formatBytes(snap.size))}`);
|
|
320
|
+
console.log(` Items: ${chalk.cyan(snap.itemCount.toString())}`);
|
|
321
|
+
if (tags.length > 0) {
|
|
322
|
+
console.log(` Tags: ${tags}`);
|
|
323
|
+
}
|
|
324
|
+
if (options.verbose && snap.description) {
|
|
325
|
+
console.log(` Description: ${chalk.dim(snap.description)}`);
|
|
326
|
+
}
|
|
327
|
+
console.log('');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
db.close();
|
|
331
|
+
|
|
332
|
+
} catch (error) {
|
|
333
|
+
spinner.fail('Failed to list snapshots');
|
|
334
|
+
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : error}`));
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Snapshot diff command
|
|
341
|
+
*/
|
|
342
|
+
export const snapshotDiffCommand = new Command('snapshot')
|
|
343
|
+
.alias('snap-diff')
|
|
344
|
+
.description('Compare two snapshots')
|
|
345
|
+
.argument('<snapshot-a>', 'First snapshot ID or path')
|
|
346
|
+
.argument('<snapshot-b>', 'Second snapshot ID or path')
|
|
347
|
+
.option('-v, --verbose', 'Show detailed differences')
|
|
348
|
+
.option('--json', 'Output in JSON format')
|
|
349
|
+
.action(async (snapshotA, snapshotB, options) => {
|
|
350
|
+
const spinner = ora();
|
|
351
|
+
const projectRoot = process.cwd();
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
// Load database
|
|
355
|
+
const { DatabaseClient } = await import('../../db/client.js');
|
|
356
|
+
const db = new DatabaseClient(projectRoot);
|
|
357
|
+
const manager = new SnapshotManager(db, projectRoot, 'unknown');
|
|
358
|
+
|
|
359
|
+
spinner.start('Comparing snapshots...');
|
|
360
|
+
|
|
361
|
+
const diff = await manager.diffSnapshots(snapshotA, snapshotB);
|
|
362
|
+
|
|
363
|
+
spinner.stop();
|
|
364
|
+
|
|
365
|
+
// JSON output
|
|
366
|
+
if (options.json) {
|
|
367
|
+
console.log(JSON.stringify(diff, null, 2));
|
|
368
|
+
db.close();
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Regular output
|
|
373
|
+
console.log(chalk.bold('\nSnapshot Comparison'));
|
|
374
|
+
console.log(chalk.dim('─'.repeat(60)));
|
|
375
|
+
console.log(`Snapshot A: ${chalk.cyan(diff.snapshotA)}`);
|
|
376
|
+
console.log(`Snapshot B: ${chalk.cyan(diff.snapshotB)}`);
|
|
377
|
+
|
|
378
|
+
console.log(chalk.bold('\nSummary:'));
|
|
379
|
+
console.log(` Only in A: ${chalk.yellow(diff.onlyInA.length.toString())}`);
|
|
380
|
+
console.log(` Only in B: ${chalk.yellow(diff.onlyInB.length.toString())}`);
|
|
381
|
+
console.log(` Changed: ${chalk.yellow(diff.differences.filter(d => d.changeType !== 'same').length.toString())}`);
|
|
382
|
+
|
|
383
|
+
if (options.verbose || diff.differences.filter(d => d.changeType !== 'same').length <= 20) {
|
|
384
|
+
console.log(chalk.bold('\nChanges:'));
|
|
385
|
+
|
|
386
|
+
const changes = diff.differences.filter(d => d.changeType !== 'same');
|
|
387
|
+
|
|
388
|
+
for (const change of changes.slice(0, 50)) {
|
|
389
|
+
const icon = change.changeType === 'added' ? chalk.green('+') :
|
|
390
|
+
change.changeType === 'removed' ? chalk.red('-') :
|
|
391
|
+
change.changeType === 'modified' ? chalk.yellow('~') :
|
|
392
|
+
chalk.dim('=');
|
|
393
|
+
|
|
394
|
+
const typeLabel = change.changeType === 'added' ? chalk.green('added') :
|
|
395
|
+
change.changeType === 'removed' ? chalk.red('removed') :
|
|
396
|
+
change.changeType === 'modified' ? chalk.yellow('modified') :
|
|
397
|
+
'';
|
|
398
|
+
|
|
399
|
+
console.log(` ${icon} ${chalk.cyan(change.id)} ${chalk.dim(`[${change.type}]`)}: ${change.name}`);
|
|
400
|
+
if (typeLabel) {
|
|
401
|
+
console.log(` ${typeLabel}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (changes.length > 50) {
|
|
406
|
+
console.log(chalk.dim(`\n... and ${changes.length - 50} more changes`));
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
console.log(chalk.dim('\nRun with --verbose to see detailed changes.'));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
db.close();
|
|
413
|
+
|
|
414
|
+
} catch (error) {
|
|
415
|
+
spinner.fail('Failed to compare snapshots');
|
|
416
|
+
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : error}`));
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Snapshot delete command
|
|
423
|
+
*/
|
|
424
|
+
export const snapshotDeleteCommand = new Command('snapshot')
|
|
425
|
+
.alias('snap-delete')
|
|
426
|
+
.description('Delete a snapshot')
|
|
427
|
+
.argument('<snapshot-id>', 'Snapshot ID to delete')
|
|
428
|
+
.option('-f, --force', 'Delete without confirmation')
|
|
429
|
+
.action(async (snapshotId, options) => {
|
|
430
|
+
const spinner = ora();
|
|
431
|
+
const projectRoot = process.cwd();
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
// Load database
|
|
435
|
+
const { DatabaseClient } = await import('../../db/client.js');
|
|
436
|
+
const { version: K0NTEXT_VERSION } = await import('../../cli/version/comparator.js');
|
|
437
|
+
const db = new DatabaseClient(projectRoot);
|
|
438
|
+
const manager = new SnapshotManager(db, projectRoot, K0NTEXT_VERSION);
|
|
439
|
+
|
|
440
|
+
// Confirm deletion
|
|
441
|
+
if (!options.force) {
|
|
442
|
+
const confirmed = await confirm({
|
|
443
|
+
message: `Delete snapshot ${snapshotId}? This cannot be undone.`,
|
|
444
|
+
default: false
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
if (!confirmed) {
|
|
448
|
+
console.log(chalk.dim('\nDeletion cancelled.\n'));
|
|
449
|
+
db.close();
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
spinner.start('Deleting snapshot...');
|
|
455
|
+
|
|
456
|
+
const deleted = await manager.deleteSnapshot(snapshotId);
|
|
457
|
+
|
|
458
|
+
if (deleted) {
|
|
459
|
+
spinner.succeed(chalk.green('Snapshot deleted successfully!'));
|
|
460
|
+
} else {
|
|
461
|
+
spinner.fail(chalk.red('Snapshot not found'));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
db.close();
|
|
465
|
+
|
|
466
|
+
} catch (error) {
|
|
467
|
+
spinner.fail('Failed to delete snapshot');
|
|
468
|
+
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : error}`));
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
});
|
package/src/db/client.ts
CHANGED
|
@@ -1210,6 +1210,14 @@ export class DatabaseClient {
|
|
|
1210
1210
|
}
|
|
1211
1211
|
}
|
|
1212
1212
|
|
|
1213
|
+
/**
|
|
1214
|
+
* Prepare a SQL statement
|
|
1215
|
+
* Exposes the underlying Database.prepare() method for external use
|
|
1216
|
+
*/
|
|
1217
|
+
prepare(sql: string): Database.Statement {
|
|
1218
|
+
return this.db.prepare(sql);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1213
1221
|
/**
|
|
1214
1222
|
* Close database connection
|
|
1215
1223
|
*/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
-- Migration 0016: Context System and TodoList Support
|
|
2
|
+
-- From: 1.5.0
|
|
3
|
+
-- To: 1.6.0
|
|
4
|
+
|
|
5
|
+
-- Todo sessions (for tracking across compactions)
|
|
6
|
+
CREATE TABLE IF NOT EXISTS todo_sessions (
|
|
7
|
+
id TEXT PRIMARY KEY,
|
|
8
|
+
name TEXT NOT NULL,
|
|
9
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
10
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
11
|
+
parent_session TEXT,
|
|
12
|
+
metadata JSON
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
-- Todo tasks
|
|
16
|
+
CREATE TABLE IF NOT EXISTS todo_tasks (
|
|
17
|
+
id TEXT PRIMARY KEY,
|
|
18
|
+
session_id TEXT NOT NULL,
|
|
19
|
+
subject TEXT NOT NULL,
|
|
20
|
+
description TEXT,
|
|
21
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
22
|
+
dependencies TEXT,
|
|
23
|
+
assigned_to TEXT,
|
|
24
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
25
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
26
|
+
completed_at TEXT,
|
|
27
|
+
FOREIGN KEY (session_id) REFERENCES todo_sessions(id) ON DELETE CASCADE
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
-- File timestamps for sync tracking
|
|
31
|
+
CREATE TABLE IF NOT EXISTS file_timestamps (
|
|
32
|
+
path TEXT PRIMARY KEY,
|
|
33
|
+
modified_time TEXT NOT NULL,
|
|
34
|
+
size INTEGER NOT NULL,
|
|
35
|
+
hash TEXT NOT NULL,
|
|
36
|
+
last_checked TEXT NOT NULL DEFAULT (datetime('now')),
|
|
37
|
+
git_commit TEXT
|
|
38
|
+
);
|