@mycontxt/cli 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.
package/dist/contxt.js ADDED
@@ -0,0 +1,3068 @@
1
+ #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/bin/contxt.ts
10
+ import { Command } from "commander";
11
+
12
+ // src/commands/init.ts
13
+ import { mkdirSync } from "fs";
14
+ import { basename } from "path";
15
+ import { SQLiteDatabase as SQLiteDatabase2 } from "@mycontxt/adapters/sqlite";
16
+
17
+ // src/utils/project.ts
18
+ import { existsSync } from "fs";
19
+ import { join } from "path";
20
+ import { SQLiteDatabase } from "@mycontxt/adapters/sqlite";
21
+ import { MemoryEngine } from "@mycontxt/core";
22
+ function getContxtDir(cwd = process.cwd()) {
23
+ return join(cwd, ".contxt");
24
+ }
25
+ function getDbPath(cwd = process.cwd()) {
26
+ return join(getContxtDir(cwd), "local.db");
27
+ }
28
+ function isContxtProject(cwd = process.cwd()) {
29
+ const dbPath = getDbPath(cwd);
30
+ return existsSync(dbPath);
31
+ }
32
+ async function loadProject(cwd = process.cwd()) {
33
+ if (!isContxtProject(cwd)) {
34
+ throw new Error(
35
+ 'Not a Contxt project. Run "contxt init" to initialize.'
36
+ );
37
+ }
38
+ const dbPath = getDbPath(cwd);
39
+ const db = new SQLiteDatabase(dbPath);
40
+ await db.initialize();
41
+ const project = await db.getProjectByPath(cwd);
42
+ if (!project) {
43
+ throw new Error("Project not found in database");
44
+ }
45
+ const engine = new MemoryEngine(db);
46
+ return {
47
+ db,
48
+ engine,
49
+ projectId: project.id,
50
+ projectPath: cwd
51
+ };
52
+ }
53
+ async function getProjectDb(cwd = process.cwd()) {
54
+ if (!isContxtProject(cwd)) {
55
+ throw new Error(
56
+ 'Not a Contxt project. Run "contxt init" to initialize.'
57
+ );
58
+ }
59
+ const dbPath = getDbPath(cwd);
60
+ const db = new SQLiteDatabase(dbPath);
61
+ await db.initialize();
62
+ return db;
63
+ }
64
+ function formatDate(date) {
65
+ const now = /* @__PURE__ */ new Date();
66
+ const diff = now.getTime() - date.getTime();
67
+ const seconds = Math.floor(diff / 1e3);
68
+ const minutes = Math.floor(seconds / 60);
69
+ const hours = Math.floor(minutes / 60);
70
+ const days = Math.floor(hours / 24);
71
+ if (days > 0) return `${days}d ago`;
72
+ if (hours > 0) return `${hours}h ago`;
73
+ if (minutes > 0) return `${minutes}m ago`;
74
+ return "just now";
75
+ }
76
+
77
+ // src/utils/output.ts
78
+ import chalk from "chalk";
79
+ function success(message) {
80
+ console.log(chalk.green("\u2713"), message);
81
+ }
82
+ function error(message) {
83
+ console.error(chalk.red("\u2717"), message);
84
+ }
85
+ function info(message) {
86
+ console.log(chalk.blue("\u2139"), message);
87
+ }
88
+ function formatEntry(entry) {
89
+ const lines = [];
90
+ lines.push(chalk.bold(entry.title));
91
+ lines.push(
92
+ chalk.dim(
93
+ `${entry.type} \u2022 ${entry.id.substring(0, 8)} \u2022 ${formatDate(entry.updatedAt)}`
94
+ )
95
+ );
96
+ lines.push("");
97
+ lines.push(entry.content);
98
+ if (Object.keys(entry.metadata).length > 0) {
99
+ lines.push("");
100
+ lines.push(chalk.dim("Metadata:"));
101
+ for (const [key, value] of Object.entries(entry.metadata)) {
102
+ if (Array.isArray(value) && value.length > 0) {
103
+ lines.push(chalk.dim(` ${key}: ${value.join(", ")}`));
104
+ } else if (value) {
105
+ lines.push(chalk.dim(` ${key}: ${value}`));
106
+ }
107
+ }
108
+ }
109
+ return lines.join("\n");
110
+ }
111
+ function formatEntryList(entries) {
112
+ if (entries.length === 0) {
113
+ return chalk.dim("No entries found");
114
+ }
115
+ return entries.map((entry) => {
116
+ const id = chalk.dim(entry.id.substring(0, 8));
117
+ const time = chalk.dim(formatDate(entry.updatedAt));
118
+ const title = entry.title;
119
+ return ` ${id} ${title} ${time}`;
120
+ }).join("\n");
121
+ }
122
+ function section(title) {
123
+ return chalk.bold.underline(title);
124
+ }
125
+
126
+ // src/commands/init.ts
127
+ async function initCommand(options) {
128
+ try {
129
+ const cwd = process.cwd();
130
+ if (isContxtProject(cwd)) {
131
+ error("This directory is already a Contxt project");
132
+ process.exit(1);
133
+ }
134
+ const contxtDir = getContxtDir(cwd);
135
+ mkdirSync(contxtDir, { recursive: true });
136
+ const dbPath = getDbPath(cwd);
137
+ const db = new SQLiteDatabase2(dbPath);
138
+ await db.initialize();
139
+ const projectName = options.name || basename(cwd);
140
+ const project = await db.initProject({
141
+ name: projectName,
142
+ path: cwd,
143
+ stack: []
144
+ // TODO: Auto-detect stack
145
+ });
146
+ success(`Initialized Contxt project: ${project.name}`);
147
+ info(`Project ID: ${project.id}`);
148
+ info(`Database: ${dbPath}`);
149
+ console.log();
150
+ console.log("Get started:");
151
+ console.log(' contxt decision add -t "..." -r "..."');
152
+ console.log(' contxt pattern add -t "..." -c "..."');
153
+ console.log(" contxt status");
154
+ await db.close();
155
+ } catch (err) {
156
+ error(`Failed to initialize project: ${err.message}`);
157
+ process.exit(1);
158
+ }
159
+ }
160
+
161
+ // src/commands/decision.ts
162
+ async function add(options) {
163
+ try {
164
+ const { engine, projectId, db } = await loadProject();
165
+ const input = {
166
+ title: options.title,
167
+ rationale: options.rationale,
168
+ alternatives: options.alternatives,
169
+ consequences: options.consequences,
170
+ tags: options.tags
171
+ };
172
+ const entry = await engine.addDecision(projectId, input);
173
+ success(`Added decision: ${entry.title}`);
174
+ console.log(`ID: ${entry.id}`);
175
+ await db.close();
176
+ } catch (err) {
177
+ error(err.message);
178
+ process.exit(1);
179
+ }
180
+ }
181
+ async function list(options) {
182
+ try {
183
+ const { engine, projectId, db } = await loadProject();
184
+ const decisions = await engine.listDecisions(projectId, options.branch);
185
+ console.log(section("Decisions"));
186
+ console.log();
187
+ console.log(formatEntryList(decisions));
188
+ await db.close();
189
+ } catch (err) {
190
+ error(err.message);
191
+ process.exit(1);
192
+ }
193
+ }
194
+ async function show(id) {
195
+ try {
196
+ const { engine, db } = await loadProject();
197
+ const decision2 = await engine.getDecision(id);
198
+ console.log(formatEntry(decision2));
199
+ await db.close();
200
+ } catch (err) {
201
+ error(err.message);
202
+ process.exit(1);
203
+ }
204
+ }
205
+ var decisionCommand = {
206
+ add,
207
+ list,
208
+ show
209
+ };
210
+
211
+ // src/commands/pattern.ts
212
+ async function add2(options) {
213
+ try {
214
+ const { engine, projectId, db } = await loadProject();
215
+ const input = {
216
+ title: options.title,
217
+ content: options.content,
218
+ category: options.category,
219
+ tags: options.tags
220
+ };
221
+ const entry = await engine.addPattern(projectId, input);
222
+ success(`Added pattern: ${entry.title}`);
223
+ console.log(`ID: ${entry.id}`);
224
+ await db.close();
225
+ } catch (err) {
226
+ error(err.message);
227
+ process.exit(1);
228
+ }
229
+ }
230
+ async function list2(options) {
231
+ try {
232
+ const { engine, projectId, db } = await loadProject();
233
+ const patterns = await engine.listPatterns(projectId, options.branch);
234
+ console.log(section("Patterns"));
235
+ console.log();
236
+ console.log(formatEntryList(patterns));
237
+ await db.close();
238
+ } catch (err) {
239
+ error(err.message);
240
+ process.exit(1);
241
+ }
242
+ }
243
+ async function show2(id) {
244
+ try {
245
+ const { engine, db } = await loadProject();
246
+ const pattern2 = await engine.getPattern(id);
247
+ console.log(formatEntry(pattern2));
248
+ await db.close();
249
+ } catch (err) {
250
+ error(err.message);
251
+ process.exit(1);
252
+ }
253
+ }
254
+ var patternCommand = {
255
+ add: add2,
256
+ list: list2,
257
+ show: show2
258
+ };
259
+
260
+ // src/commands/context.ts
261
+ async function set(options) {
262
+ try {
263
+ const { engine, projectId, db } = await loadProject();
264
+ const input = {
265
+ feature: options.feature,
266
+ blockers: options.blockers,
267
+ nextSteps: options.next,
268
+ activeFiles: options.files
269
+ };
270
+ const entry = await engine.setContext(projectId, input);
271
+ success("Updated project context");
272
+ await db.close();
273
+ } catch (err) {
274
+ error(err.message);
275
+ process.exit(1);
276
+ }
277
+ }
278
+ async function show3() {
279
+ try {
280
+ const { engine, projectId, db } = await loadProject();
281
+ const context2 = await engine.getContext(projectId);
282
+ if (!context2) {
283
+ info('No context set. Use "contxt context set" to set context.');
284
+ await db.close();
285
+ return;
286
+ }
287
+ console.log(formatEntry(context2));
288
+ await db.close();
289
+ } catch (err) {
290
+ error(err.message);
291
+ process.exit(1);
292
+ }
293
+ }
294
+ async function clear() {
295
+ try {
296
+ const { engine, projectId, db } = await loadProject();
297
+ const context2 = await engine.getContext(projectId);
298
+ if (context2) {
299
+ await engine.deleteEntry(context2.id);
300
+ success("Cleared project context");
301
+ } else {
302
+ info("No context to clear");
303
+ }
304
+ await db.close();
305
+ } catch (err) {
306
+ error(err.message);
307
+ process.exit(1);
308
+ }
309
+ }
310
+ var contextCommand = {
311
+ set,
312
+ show: show3,
313
+ clear
314
+ };
315
+
316
+ // src/commands/status.ts
317
+ import chalk2 from "chalk";
318
+ async function statusCommand() {
319
+ try {
320
+ const { engine, projectId, db } = await loadProject();
321
+ const project = await db.getProject(projectId);
322
+ if (!project) {
323
+ throw new Error("Project not found");
324
+ }
325
+ const activeBranch = await db.getActiveBranch(projectId);
326
+ const branches = await db.listBranches(projectId);
327
+ const decisions = await engine.listDecisions(projectId);
328
+ const patterns = await engine.listPatterns(projectId);
329
+ const documents = await engine.listDocuments(projectId);
330
+ const sessions = await engine.listSessions(projectId);
331
+ const context2 = await engine.getContext(projectId);
332
+ const unsynced = await db.getUnsyncedEntries(projectId);
333
+ console.log();
334
+ console.log(section("Project Status"));
335
+ console.log();
336
+ console.log(chalk2.bold("Name:"), project.name);
337
+ console.log(chalk2.bold("Path:"), project.path);
338
+ console.log(chalk2.bold("ID:"), project.id);
339
+ console.log();
340
+ console.log(section("Branches"));
341
+ console.log();
342
+ for (const branch2 of branches) {
343
+ const marker = branch2.name === activeBranch ? chalk2.green("\u25CF") : " ";
344
+ console.log(` ${marker} ${branch2.name}`);
345
+ }
346
+ console.log();
347
+ console.log(section("Memory Entries"));
348
+ console.log();
349
+ console.log(` Decisions: ${decisions.length}`);
350
+ console.log(` Patterns: ${patterns.length}`);
351
+ console.log(` Documents: ${documents.length}`);
352
+ console.log(` Sessions: ${sessions.length}`);
353
+ console.log(` Total: ${decisions.length + patterns.length + documents.length + sessions.length}`);
354
+ console.log();
355
+ if (context2) {
356
+ console.log(section("Current Context"));
357
+ console.log();
358
+ if (context2.metadata.feature) {
359
+ console.log(chalk2.bold("Feature:"), context2.metadata.feature);
360
+ }
361
+ if (context2.metadata.blockers?.length > 0) {
362
+ console.log(chalk2.bold("Blockers:"));
363
+ context2.metadata.blockers.forEach((b) => console.log(` - ${b}`));
364
+ }
365
+ if (context2.metadata.nextSteps?.length > 0) {
366
+ console.log(chalk2.bold("Next Steps:"));
367
+ context2.metadata.nextSteps.forEach((s) => console.log(` - ${s}`));
368
+ }
369
+ console.log();
370
+ }
371
+ if (unsynced.length > 0) {
372
+ console.log(chalk2.yellow(`\u26A0 ${unsynced.length} unsynced entries`));
373
+ console.log(chalk2.dim(' Run "contxt push" to sync'));
374
+ console.log();
375
+ }
376
+ await db.close();
377
+ } catch (err) {
378
+ error(err.message);
379
+ process.exit(1);
380
+ }
381
+ }
382
+
383
+ // src/commands/doc.ts
384
+ import { readFileSync } from "fs";
385
+ async function add3(options) {
386
+ try {
387
+ const { engine, projectId, db } = await loadProject();
388
+ let content = options.content || "";
389
+ if (options.file) {
390
+ try {
391
+ content = readFileSync(options.file, "utf-8");
392
+ } catch (err) {
393
+ error(`Failed to read file: ${err.message}`);
394
+ process.exit(1);
395
+ }
396
+ }
397
+ if (!content) {
398
+ error("Either --content or --file is required");
399
+ process.exit(1);
400
+ }
401
+ const input = {
402
+ title: options.title,
403
+ content,
404
+ url: options.url,
405
+ tags: options.tags
406
+ };
407
+ const entry = await engine.addDocument(projectId, input);
408
+ success(`Added document: ${entry.title}`);
409
+ console.log(`ID: ${entry.id}`);
410
+ await db.close();
411
+ } catch (err) {
412
+ error(err.message);
413
+ process.exit(1);
414
+ }
415
+ }
416
+ async function list3(options) {
417
+ try {
418
+ const { engine, projectId, db } = await loadProject();
419
+ const documents = await engine.listDocuments(projectId, options.branch);
420
+ console.log(section("Documents"));
421
+ console.log();
422
+ console.log(formatEntryList(documents));
423
+ await db.close();
424
+ } catch (err) {
425
+ error(err.message);
426
+ process.exit(1);
427
+ }
428
+ }
429
+ async function show4(id) {
430
+ try {
431
+ const { engine, db } = await loadProject();
432
+ const document = await engine.getDocument(id);
433
+ console.log(formatEntry(document));
434
+ await db.close();
435
+ } catch (err) {
436
+ error(err.message);
437
+ process.exit(1);
438
+ }
439
+ }
440
+ var docCommand = {
441
+ add: add3,
442
+ list: list3,
443
+ show: show4
444
+ };
445
+
446
+ // src/commands/session.ts
447
+ async function start(options) {
448
+ try {
449
+ const { engine, projectId, db } = await loadProject();
450
+ const input = {
451
+ feature: options.feature,
452
+ description: options.description
453
+ };
454
+ const session2 = await engine.startSession(projectId, input);
455
+ success(`Started session: ${session2.title}`);
456
+ console.log(`ID: ${session2.id}`);
457
+ info('Run "contxt session end" when done');
458
+ await db.close();
459
+ } catch (err) {
460
+ error(err.message);
461
+ process.exit(1);
462
+ }
463
+ }
464
+ async function end(options) {
465
+ try {
466
+ const { engine, projectId, db } = await loadProject();
467
+ const session2 = await engine.endSession(projectId, options.summary);
468
+ success(`Ended session: ${session2.title}`);
469
+ await db.close();
470
+ } catch (err) {
471
+ error(err.message);
472
+ process.exit(1);
473
+ }
474
+ }
475
+ async function list4(options) {
476
+ try {
477
+ const { engine, projectId, db } = await loadProject();
478
+ const sessions = await engine.listSessions(projectId, options.branch);
479
+ console.log(section("Sessions"));
480
+ console.log();
481
+ console.log(formatEntryList(sessions));
482
+ await db.close();
483
+ } catch (err) {
484
+ error(err.message);
485
+ process.exit(1);
486
+ }
487
+ }
488
+ async function current() {
489
+ try {
490
+ const { engine, projectId, db } = await loadProject();
491
+ const session2 = await engine.getActiveSession(projectId);
492
+ if (!session2) {
493
+ info("No active session");
494
+ await db.close();
495
+ return;
496
+ }
497
+ console.log(formatEntry(session2));
498
+ await db.close();
499
+ } catch (err) {
500
+ error(err.message);
501
+ process.exit(1);
502
+ }
503
+ }
504
+ var sessionCommand = {
505
+ start,
506
+ end,
507
+ list: list4,
508
+ current
509
+ };
510
+
511
+ // src/commands/search.ts
512
+ async function searchCommand(query, options) {
513
+ try {
514
+ const { engine, projectId, db } = await loadProject();
515
+ const results = await engine.searchEntries(projectId, query, {
516
+ branch: options.branch,
517
+ type: options.type
518
+ });
519
+ const limited = options.limit ? results.slice(0, options.limit) : results;
520
+ console.log(section(`Search Results for "${query}"`));
521
+ console.log();
522
+ console.log(formatEntryList(limited));
523
+ console.log();
524
+ console.log(`Found ${results.length} result(s)`);
525
+ await db.close();
526
+ } catch (err) {
527
+ error(err.message);
528
+ process.exit(1);
529
+ }
530
+ }
531
+
532
+ // src/commands/export.ts
533
+ import { writeFileSync, readFileSync as readFileSync2 } from "fs";
534
+ async function exportCommand(options) {
535
+ try {
536
+ const { engine, projectId, db } = await loadProject();
537
+ const entries = await engine.getAllEntries({
538
+ projectId,
539
+ branch: options.branch,
540
+ isArchived: false
541
+ });
542
+ const exportData = {
543
+ version: "1.0",
544
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
545
+ projectId,
546
+ branch: options.branch || "main",
547
+ entries: entries.map((e) => ({
548
+ id: e.id,
549
+ type: e.type,
550
+ title: e.title,
551
+ content: e.content,
552
+ metadata: e.metadata,
553
+ branch: e.branch,
554
+ createdAt: e.createdAt.toISOString(),
555
+ updatedAt: e.updatedAt.toISOString()
556
+ }))
557
+ };
558
+ const json = JSON.stringify(exportData, null, 2);
559
+ if (options.output) {
560
+ writeFileSync(options.output, json, "utf-8");
561
+ success(`Exported ${entries.length} entries to ${options.output}`);
562
+ } else {
563
+ console.log(json);
564
+ }
565
+ await db.close();
566
+ } catch (err) {
567
+ error(err.message);
568
+ process.exit(1);
569
+ }
570
+ }
571
+ async function importCommand(options) {
572
+ try {
573
+ const { engine, projectId, db } = await loadProject();
574
+ let data;
575
+ try {
576
+ const json = readFileSync2(options.file, "utf-8");
577
+ data = JSON.parse(json);
578
+ } catch (err) {
579
+ error(`Failed to read import file: ${err.message}`);
580
+ process.exit(1);
581
+ }
582
+ if (!data.entries || !Array.isArray(data.entries)) {
583
+ error("Invalid import file format");
584
+ process.exit(1);
585
+ }
586
+ info(`Importing ${data.entries.length} entries...`);
587
+ let imported = 0;
588
+ for (const entry of data.entries) {
589
+ try {
590
+ await db.createEntry({
591
+ projectId,
592
+ type: entry.type,
593
+ title: entry.title,
594
+ content: entry.content,
595
+ metadata: entry.metadata,
596
+ branch: entry.branch
597
+ });
598
+ imported++;
599
+ } catch (err) {
600
+ console.error(`Failed to import entry "${entry.title}": ${err.message}`);
601
+ }
602
+ }
603
+ success(`Imported ${imported} of ${data.entries.length} entries`);
604
+ await db.close();
605
+ } catch (err) {
606
+ error(err.message);
607
+ process.exit(1);
608
+ }
609
+ }
610
+
611
+ // src/commands/auth.ts
612
+ import { SupabaseAuth } from "@mycontxt/adapters/supabase-auth";
613
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync3 } from "fs";
614
+ import { homedir } from "os";
615
+ import { join as join2 } from "path";
616
+
617
+ // src/config.ts
618
+ var SUPABASE_URL = "";
619
+ var SUPABASE_ANON_KEY = "";
620
+ function getSupabaseConfig() {
621
+ if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
622
+ throw new Error(
623
+ "Contxt is not configured for cloud sync.\nIf you installed via npm, please reinstall the latest version.\nIf building locally, set CONTXT_SUPABASE_URL and CONTXT_SUPABASE_ANON_KEY before running `pnpm build`."
624
+ );
625
+ }
626
+ return { url: SUPABASE_URL, anonKey: SUPABASE_ANON_KEY };
627
+ }
628
+
629
+ // src/commands/auth.ts
630
+ var CONFIG_DIR = join2(homedir(), ".contxt");
631
+ var AUTH_FILE = join2(CONFIG_DIR, "auth.json");
632
+ function saveAuthData(data) {
633
+ if (!existsSync3(CONFIG_DIR)) {
634
+ mkdirSync2(CONFIG_DIR, { recursive: true });
635
+ }
636
+ writeFileSync2(AUTH_FILE, JSON.stringify(data, null, 2), "utf-8");
637
+ }
638
+ function loadAuthData() {
639
+ if (!existsSync3(AUTH_FILE)) {
640
+ return null;
641
+ }
642
+ try {
643
+ const content = readFileSync3(AUTH_FILE, "utf-8");
644
+ return JSON.parse(content);
645
+ } catch {
646
+ return null;
647
+ }
648
+ }
649
+ var authCommand = {
650
+ async login(options) {
651
+ try {
652
+ const config = getSupabaseConfig();
653
+ const auth2 = new SupabaseAuth(config);
654
+ console.log("\u{1F510} Contxt Authentication\n");
655
+ if (options.email) {
656
+ await auth2.loginWithMagicLink(options.email);
657
+ console.log("\n\u2705 Magic link sent! Check your email and click the link.");
658
+ console.log(" Then run `contxt auth status` to verify.");
659
+ } else {
660
+ console.log("Opening browser for GitHub authentication...\n");
661
+ const result = await auth2.loginWithGitHub();
662
+ saveAuthData({
663
+ accessToken: result.accessToken,
664
+ userId: result.user.id,
665
+ email: result.user.email,
666
+ githubUsername: result.user.githubUsername
667
+ });
668
+ console.log("\n\u2705 Successfully authenticated!");
669
+ console.log(` Email: ${result.user.email}`);
670
+ if (result.user.githubUsername) {
671
+ console.log(` GitHub: @${result.user.githubUsername}`);
672
+ }
673
+ console.log("\nYou can now use `contxt push` and `contxt pull` to sync your memory.");
674
+ }
675
+ } catch (error2) {
676
+ console.error(
677
+ "\u274C Authentication failed:",
678
+ error2 instanceof Error ? error2.message : error2
679
+ );
680
+ process.exit(1);
681
+ }
682
+ },
683
+ async logout() {
684
+ try {
685
+ const config = getSupabaseConfig();
686
+ const auth2 = new SupabaseAuth(config);
687
+ await auth2.logout();
688
+ if (existsSync3(AUTH_FILE)) {
689
+ const fs = await import("fs/promises");
690
+ await fs.unlink(AUTH_FILE);
691
+ }
692
+ console.log("\u2705 Logged out successfully");
693
+ } catch (error2) {
694
+ console.error(
695
+ "\u274C Logout failed:",
696
+ error2 instanceof Error ? error2.message : error2
697
+ );
698
+ process.exit(1);
699
+ }
700
+ },
701
+ async status() {
702
+ try {
703
+ const authData = loadAuthData();
704
+ if (!authData) {
705
+ console.log("\u274C Not authenticated");
706
+ console.log("\nRun `contxt auth login` to authenticate.");
707
+ process.exit(1);
708
+ }
709
+ console.log("\u2705 Authenticated");
710
+ console.log(` Email: ${authData.email}`);
711
+ if (authData.githubUsername) {
712
+ console.log(` GitHub: @${authData.githubUsername}`);
713
+ }
714
+ console.log(` User ID: ${authData.userId}`);
715
+ const config = getSupabaseConfig();
716
+ const auth2 = new SupabaseAuth(config);
717
+ try {
718
+ await auth2.refreshSession();
719
+ console.log("\n\u2705 Session is valid");
720
+ } catch {
721
+ console.log("\n\u26A0\uFE0F Session expired. Run `contxt auth login` to re-authenticate.");
722
+ }
723
+ } catch (error2) {
724
+ console.error(
725
+ "\u274C Status check failed:",
726
+ error2 instanceof Error ? error2.message : error2
727
+ );
728
+ process.exit(1);
729
+ }
730
+ }
731
+ };
732
+ function getAccessToken() {
733
+ const authData = loadAuthData();
734
+ return authData?.accessToken || null;
735
+ }
736
+
737
+ // src/commands/sync.ts
738
+ import { SQLiteDatabase as SQLiteDatabase3 } from "@mycontxt/adapters/sqlite";
739
+ import { SupabaseDatabase } from "@mycontxt/adapters/supabase";
740
+ import { SyncEngine } from "@mycontxt/core";
741
+ var syncCommand = {
742
+ /**
743
+ * Push local changes to cloud
744
+ */
745
+ async push(options) {
746
+ try {
747
+ const accessToken = getAccessToken();
748
+ if (!accessToken) {
749
+ console.error("\u274C Not authenticated. Run `contxt auth login` first.");
750
+ process.exit(1);
751
+ }
752
+ const dbPath = getDbPath();
753
+ const localDb = new SQLiteDatabase3(dbPath);
754
+ await localDb.initialize();
755
+ try {
756
+ const cwd = process.cwd();
757
+ const project = await localDb.getProjectByPath(cwd);
758
+ if (!project) {
759
+ console.error("\u274C No Contxt project found. Run `contxt init` first.");
760
+ process.exit(1);
761
+ }
762
+ const supabaseConfig = getSupabaseConfig();
763
+ const remoteDb = new SupabaseDatabase({
764
+ ...supabaseConfig,
765
+ accessToken
766
+ });
767
+ await remoteDb.initialize();
768
+ const syncEngine = new SyncEngine(localDb, remoteDb);
769
+ console.log("\u{1F504} Pushing local changes to cloud...\n");
770
+ const result = await syncEngine.push(project.id, {
771
+ force: options.force,
772
+ dryRun: options.dryRun
773
+ });
774
+ if (result.errors.length > 0) {
775
+ console.error("\u274C Push failed:");
776
+ result.errors.forEach((err) => console.error(` ${err}`));
777
+ process.exit(1);
778
+ }
779
+ if (options.dryRun) {
780
+ console.log(`\u{1F4CB} Dry run - would push ${result.pushed} entries`);
781
+ } else {
782
+ console.log(`\u2705 Successfully pushed ${result.pushed} entries`);
783
+ }
784
+ if (result.conflicts > 0) {
785
+ console.log(
786
+ `\u26A0\uFE0F ${result.conflicts} conflict(s) detected. Use --force to override.`
787
+ );
788
+ }
789
+ await remoteDb.close();
790
+ } finally {
791
+ await localDb.close();
792
+ }
793
+ } catch (error2) {
794
+ console.error(
795
+ "\u274C Push failed:",
796
+ error2 instanceof Error ? error2.message : error2
797
+ );
798
+ process.exit(1);
799
+ }
800
+ },
801
+ /**
802
+ * Pull remote changes to local
803
+ */
804
+ async pull(options) {
805
+ try {
806
+ const accessToken = getAccessToken();
807
+ if (!accessToken) {
808
+ console.error("\u274C Not authenticated. Run `contxt auth login` first.");
809
+ process.exit(1);
810
+ }
811
+ const dbPath = getDbPath();
812
+ const localDb = new SQLiteDatabase3(dbPath);
813
+ await localDb.initialize();
814
+ try {
815
+ const cwd = process.cwd();
816
+ const project = await localDb.getProjectByPath(cwd);
817
+ if (!project) {
818
+ console.error("\u274C No Contxt project found. Run `contxt init` first.");
819
+ process.exit(1);
820
+ }
821
+ const supabaseConfig = getSupabaseConfig();
822
+ const remoteDb = new SupabaseDatabase({
823
+ ...supabaseConfig,
824
+ accessToken
825
+ });
826
+ await remoteDb.initialize();
827
+ const syncEngine = new SyncEngine(localDb, remoteDb);
828
+ console.log("\u{1F504} Pulling remote changes to local...\n");
829
+ const result = await syncEngine.pull(project.id, {
830
+ force: options.force,
831
+ dryRun: options.dryRun
832
+ });
833
+ if (result.errors.length > 0) {
834
+ console.error("\u274C Pull failed:");
835
+ result.errors.forEach((err) => console.error(` ${err}`));
836
+ process.exit(1);
837
+ }
838
+ if (options.dryRun) {
839
+ console.log(`\u{1F4CB} Dry run - would pull ${result.pulled} entries`);
840
+ } else {
841
+ console.log(`\u2705 Successfully pulled ${result.pulled} entries`);
842
+ }
843
+ if (result.conflicts > 0) {
844
+ console.log(
845
+ `\u26A0\uFE0F ${result.conflicts} conflict(s) detected. Use --force to override.`
846
+ );
847
+ }
848
+ await remoteDb.close();
849
+ } finally {
850
+ await localDb.close();
851
+ }
852
+ } catch (error2) {
853
+ console.error(
854
+ "\u274C Pull failed:",
855
+ error2 instanceof Error ? error2.message : error2
856
+ );
857
+ process.exit(1);
858
+ }
859
+ },
860
+ /**
861
+ * Full bidirectional sync
862
+ */
863
+ async sync(options) {
864
+ try {
865
+ const accessToken = getAccessToken();
866
+ if (!accessToken) {
867
+ console.error("\u274C Not authenticated. Run `contxt auth login` first.");
868
+ process.exit(1);
869
+ }
870
+ const dbPath = getDbPath();
871
+ const localDb = new SQLiteDatabase3(dbPath);
872
+ await localDb.initialize();
873
+ try {
874
+ const cwd = process.cwd();
875
+ const project = await localDb.getProjectByPath(cwd);
876
+ if (!project) {
877
+ console.error("\u274C No Contxt project found. Run `contxt init` first.");
878
+ process.exit(1);
879
+ }
880
+ const supabaseConfig = getSupabaseConfig();
881
+ const remoteDb = new SupabaseDatabase({
882
+ ...supabaseConfig,
883
+ accessToken
884
+ });
885
+ await remoteDb.initialize();
886
+ const syncEngine = new SyncEngine(localDb, remoteDb);
887
+ console.log("\u{1F504} Syncing with cloud (pull + push)...\n");
888
+ const result = await syncEngine.sync(project.id, {
889
+ force: options.force,
890
+ dryRun: options.dryRun
891
+ });
892
+ if (result.errors.length > 0) {
893
+ console.error("\u274C Sync failed:");
894
+ result.errors.forEach((err) => console.error(` ${err}`));
895
+ process.exit(1);
896
+ }
897
+ if (options.dryRun) {
898
+ console.log(
899
+ `\u{1F4CB} Dry run - would pull ${result.pulled} and push ${result.pushed} entries`
900
+ );
901
+ } else {
902
+ console.log(`\u2705 Successfully synced`);
903
+ console.log(` Pulled: ${result.pulled} entries`);
904
+ console.log(` Pushed: ${result.pushed} entries`);
905
+ }
906
+ if (result.conflicts > 0) {
907
+ console.log(
908
+ `\u26A0\uFE0F ${result.conflicts} conflict(s) detected. Use --force to override.`
909
+ );
910
+ }
911
+ await remoteDb.close();
912
+ } finally {
913
+ await localDb.close();
914
+ }
915
+ } catch (error2) {
916
+ console.error(
917
+ "\u274C Sync failed:",
918
+ error2 instanceof Error ? error2.message : error2
919
+ );
920
+ process.exit(1);
921
+ }
922
+ }
923
+ };
924
+
925
+ // src/commands/branch.ts
926
+ import { SQLiteDatabase as SQLiteDatabase4 } from "@mycontxt/adapters/sqlite";
927
+ var branchCommand = {
928
+ /**
929
+ * Create a new branch
930
+ */
931
+ async create(name, options) {
932
+ try {
933
+ const dbPath = getDbPath();
934
+ const db = new SQLiteDatabase4(dbPath);
935
+ await db.initialize();
936
+ try {
937
+ const cwd = process.cwd();
938
+ const project = await db.getProjectByPath(cwd);
939
+ if (!project) {
940
+ console.error("\u274C No Contxt project found. Run `contxt init` first.");
941
+ process.exit(1);
942
+ }
943
+ const fromBranch = options.from || await db.getActiveBranch(project.id);
944
+ await db.createBranch(project.id, name, fromBranch);
945
+ console.log(`\u2705 Created branch '${name}' from '${fromBranch}'`);
946
+ } finally {
947
+ await db.close();
948
+ }
949
+ } catch (error2) {
950
+ console.error(
951
+ "\u274C Branch create failed:",
952
+ error2 instanceof Error ? error2.message : error2
953
+ );
954
+ process.exit(1);
955
+ }
956
+ },
957
+ /**
958
+ * List all branches
959
+ */
960
+ async list() {
961
+ try {
962
+ const dbPath = getDbPath();
963
+ const db = new SQLiteDatabase4(dbPath);
964
+ await db.initialize();
965
+ try {
966
+ const cwd = process.cwd();
967
+ const project = await db.getProjectByPath(cwd);
968
+ if (!project) {
969
+ console.error("\u274C No Contxt project found. Run `contxt init` first.");
970
+ process.exit(1);
971
+ }
972
+ const branches = await db.listBranches(project.id);
973
+ const activeBranch = await db.getActiveBranch(project.id);
974
+ console.log("Branches:");
975
+ for (const branch2 of branches) {
976
+ const prefix = branch2.name === activeBranch ? "* " : " ";
977
+ const parent = branch2.parentBranch ? ` (from ${branch2.parentBranch})` : "";
978
+ console.log(`${prefix}${branch2.name}${parent}`);
979
+ }
980
+ } finally {
981
+ await db.close();
982
+ }
983
+ } catch (error2) {
984
+ console.error(
985
+ "\u274C Branch list failed:",
986
+ error2 instanceof Error ? error2.message : error2
987
+ );
988
+ process.exit(1);
989
+ }
990
+ },
991
+ /**
992
+ * Switch to a different branch
993
+ */
994
+ async switch(name) {
995
+ try {
996
+ const dbPath = getDbPath();
997
+ const db = new SQLiteDatabase4(dbPath);
998
+ await db.initialize();
999
+ try {
1000
+ const cwd = process.cwd();
1001
+ const project = await db.getProjectByPath(cwd);
1002
+ if (!project) {
1003
+ console.error("\u274C No Contxt project found. Run `contxt init` first.");
1004
+ process.exit(1);
1005
+ }
1006
+ await db.switchBranch(project.id, name);
1007
+ console.log(`\u2705 Switched to branch '${name}'`);
1008
+ } finally {
1009
+ await db.close();
1010
+ }
1011
+ } catch (error2) {
1012
+ console.error(
1013
+ "\u274C Branch switch failed:",
1014
+ error2 instanceof Error ? error2.message : error2
1015
+ );
1016
+ process.exit(1);
1017
+ }
1018
+ },
1019
+ /**
1020
+ * Delete a branch
1021
+ */
1022
+ async delete(name) {
1023
+ try {
1024
+ const dbPath = getDbPath();
1025
+ const db = new SQLiteDatabase4(dbPath);
1026
+ await db.initialize();
1027
+ try {
1028
+ const cwd = process.cwd();
1029
+ const project = await db.getProjectByPath(cwd);
1030
+ if (!project) {
1031
+ console.error("\u274C No Contxt project found. Run `contxt init` first.");
1032
+ process.exit(1);
1033
+ }
1034
+ const activeBranch = await db.getActiveBranch(project.id);
1035
+ if (name === activeBranch) {
1036
+ console.error("\u274C Cannot delete active branch. Switch to another branch first.");
1037
+ process.exit(1);
1038
+ }
1039
+ if (name === "main") {
1040
+ console.error("\u274C Cannot delete main branch.");
1041
+ process.exit(1);
1042
+ }
1043
+ await db.deleteBranch(project.id, name);
1044
+ console.log(`\u2705 Deleted branch '${name}'`);
1045
+ } finally {
1046
+ await db.close();
1047
+ }
1048
+ } catch (error2) {
1049
+ console.error(
1050
+ "\u274C Branch delete failed:",
1051
+ error2 instanceof Error ? error2.message : error2
1052
+ );
1053
+ process.exit(1);
1054
+ }
1055
+ },
1056
+ /**
1057
+ * Merge a branch into current branch
1058
+ */
1059
+ async merge(sourceBranch) {
1060
+ try {
1061
+ const dbPath = getDbPath();
1062
+ const db = new SQLiteDatabase4(dbPath);
1063
+ await db.initialize();
1064
+ try {
1065
+ const cwd = process.cwd();
1066
+ const project = await db.getProjectByPath(cwd);
1067
+ if (!project) {
1068
+ console.error("\u274C No Contxt project found. Run `contxt init` first.");
1069
+ process.exit(1);
1070
+ }
1071
+ const targetBranch = await db.getActiveBranch(project.id);
1072
+ if (sourceBranch === targetBranch) {
1073
+ console.error("\u274C Cannot merge a branch into itself.");
1074
+ process.exit(1);
1075
+ }
1076
+ const sourceEntries = await db.listEntries({
1077
+ projectId: project.id,
1078
+ branch: sourceBranch,
1079
+ isArchived: false
1080
+ });
1081
+ if (sourceEntries.length === 0) {
1082
+ console.log(`No entries to merge from '${sourceBranch}'.`);
1083
+ process.exit(0);
1084
+ }
1085
+ let merged = 0;
1086
+ for (const entry of sourceEntries) {
1087
+ const existing = await db.getEntry(entry.id);
1088
+ if (!existing || existing.branch !== targetBranch) {
1089
+ await db.createEntry({
1090
+ id: entry.id,
1091
+ projectId: entry.projectId,
1092
+ type: entry.type,
1093
+ title: entry.title,
1094
+ content: entry.content,
1095
+ metadata: entry.metadata,
1096
+ branch: targetBranch
1097
+ });
1098
+ merged++;
1099
+ } else if (entry.updatedAt > existing.updatedAt) {
1100
+ await db.updateEntry(entry.id, {
1101
+ title: entry.title,
1102
+ content: entry.content,
1103
+ metadata: entry.metadata,
1104
+ updatedAt: entry.updatedAt
1105
+ });
1106
+ merged++;
1107
+ }
1108
+ }
1109
+ console.log(
1110
+ `\u2705 Merged ${merged} entries from '${sourceBranch}' into '${targetBranch}'`
1111
+ );
1112
+ } finally {
1113
+ await db.close();
1114
+ }
1115
+ } catch (error2) {
1116
+ console.error(
1117
+ "\u274C Branch merge failed:",
1118
+ error2 instanceof Error ? error2.message : error2
1119
+ );
1120
+ process.exit(1);
1121
+ }
1122
+ }
1123
+ };
1124
+
1125
+ // src/commands/history.ts
1126
+ import { SQLiteDatabase as SQLiteDatabase5 } from "@mycontxt/adapters/sqlite";
1127
+ var historyCommand = {
1128
+ /**
1129
+ * Show version history for an entry
1130
+ */
1131
+ async show(entryId) {
1132
+ try {
1133
+ const dbPath = getDbPath();
1134
+ const db = new SQLiteDatabase5(dbPath);
1135
+ await db.initialize();
1136
+ try {
1137
+ const entry = await db.getEntry(entryId);
1138
+ if (!entry) {
1139
+ console.error("\u274C Entry not found.");
1140
+ process.exit(1);
1141
+ }
1142
+ const versions = await db.getVersionHistory(entryId);
1143
+ console.log(`\u{1F4DC} Version History: ${entry.title}
1144
+ `);
1145
+ console.log(`Current (v${entry.version}):`);
1146
+ console.log(` Updated: ${entry.updatedAt.toLocaleString()}`);
1147
+ console.log(` Title: ${entry.title}`);
1148
+ console.log(` Content: ${entry.content.substring(0, 100)}${entry.content.length > 100 ? "..." : ""}
1149
+ `);
1150
+ if (versions.length > 0) {
1151
+ console.log("Previous versions:");
1152
+ for (const version of versions) {
1153
+ console.log(`
1154
+ v${version.version}:`);
1155
+ console.log(` Updated: ${version.updatedAt.toLocaleString()}`);
1156
+ console.log(` Title: ${version.title}`);
1157
+ console.log(
1158
+ ` Content: ${version.content.substring(0, 100)}${version.content.length > 100 ? "..." : ""}`
1159
+ );
1160
+ }
1161
+ } else {
1162
+ console.log("No previous versions.");
1163
+ }
1164
+ } finally {
1165
+ await db.close();
1166
+ }
1167
+ } catch (error2) {
1168
+ console.error(
1169
+ "\u274C History failed:",
1170
+ error2 instanceof Error ? error2.message : error2
1171
+ );
1172
+ process.exit(1);
1173
+ }
1174
+ },
1175
+ /**
1176
+ * Restore an entry to a previous version
1177
+ */
1178
+ async restore(entryId, options) {
1179
+ try {
1180
+ const dbPath = getDbPath();
1181
+ const db = new SQLiteDatabase5(dbPath);
1182
+ await db.initialize();
1183
+ try {
1184
+ const restored = await db.restoreVersion(entryId, options.version);
1185
+ console.log(`\u2705 Restored '${restored.title}' to version ${options.version}`);
1186
+ console.log(` Current version is now: v${restored.version}`);
1187
+ } finally {
1188
+ await db.close();
1189
+ }
1190
+ } catch (error2) {
1191
+ console.error(
1192
+ "\u274C Restore failed:",
1193
+ error2 instanceof Error ? error2.message : error2
1194
+ );
1195
+ process.exit(1);
1196
+ }
1197
+ }
1198
+ };
1199
+
1200
+ // src/commands/load.ts
1201
+ import { SQLiteDatabase as SQLiteDatabase6 } from "@mycontxt/adapters/sqlite";
1202
+ import { buildContextPayload, buildContextSummary } from "@mycontxt/core";
1203
+ async function loadCommand(options) {
1204
+ try {
1205
+ const dbPath = getDbPath();
1206
+ const db = new SQLiteDatabase6(dbPath);
1207
+ await db.initialize();
1208
+ try {
1209
+ const cwd = process.cwd();
1210
+ const project = await db.getProjectByPath(cwd);
1211
+ if (!project) {
1212
+ console.error("\u274C No Contxt project found. Run `contxt init` first.");
1213
+ process.exit(1);
1214
+ }
1215
+ const branch2 = await db.getActiveBranch(project.id);
1216
+ const entries = await db.listEntries({
1217
+ projectId: project.id,
1218
+ branch: branch2,
1219
+ isArchived: false
1220
+ });
1221
+ if (entries.length === 0) {
1222
+ console.log("No memory entries found. Add some context first!");
1223
+ process.exit(0);
1224
+ }
1225
+ if (options.summary) {
1226
+ const summary = buildContextSummary(entries);
1227
+ console.log(`\u{1F4CA} Context Summary
1228
+ `);
1229
+ console.log(`Total entries: ${summary.totalEntries}`);
1230
+ console.log(`Branch: ${branch2}
1231
+ `);
1232
+ console.log("By type:");
1233
+ for (const [type, count] of Object.entries(summary.byType)) {
1234
+ console.log(` ${type}: ${count}`);
1235
+ }
1236
+ if (summary.recentActivity.length > 0) {
1237
+ console.log(`
1238
+ Recent activity:`);
1239
+ summary.recentActivity.forEach((activity) => {
1240
+ console.log(` \u2022 ${activity}`);
1241
+ });
1242
+ }
1243
+ if (summary.oldestEntry && summary.newestEntry) {
1244
+ console.log(
1245
+ `
1246
+ Date range: ${summary.oldestEntry.toLocaleDateString()} - ${summary.newestEntry.toLocaleDateString()}`
1247
+ );
1248
+ }
1249
+ process.exit(0);
1250
+ }
1251
+ let mode = "all";
1252
+ if (options.task) {
1253
+ mode = "task";
1254
+ } else if (options.files && options.files.length > 0) {
1255
+ mode = "files";
1256
+ } else if (options.all) {
1257
+ mode = "all";
1258
+ }
1259
+ const result = buildContextPayload(entries, {
1260
+ projectId: project.id,
1261
+ type: mode,
1262
+ taskDescription: options.task,
1263
+ activeFiles: options.files,
1264
+ maxTokens: options.maxTokens || 4e3,
1265
+ includeTypes: options.type ? [options.type] : void 0
1266
+ });
1267
+ console.log(result.context);
1268
+ console.error(
1269
+ `
1270
+ \u{1F4E6} Context: ${result.entriesIncluded} entries, ${result.tokensUsed}/${result.budget} tokens`
1271
+ );
1272
+ } finally {
1273
+ await db.close();
1274
+ }
1275
+ } catch (error2) {
1276
+ console.error(
1277
+ "\u274C Load failed:",
1278
+ error2 instanceof Error ? error2.message : error2
1279
+ );
1280
+ process.exit(1);
1281
+ }
1282
+ }
1283
+
1284
+ // src/commands/scan.ts
1285
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
1286
+ import { join as join3, relative } from "path";
1287
+ import chalk3 from "chalk";
1288
+ import ora from "ora";
1289
+ import { parseFile, scanCommentToEntry } from "@mycontxt/core";
1290
+ import { glob } from "glob";
1291
+ async function scanCommand(options = {}) {
1292
+ const spinner = ora("Scanning project...").start();
1293
+ try {
1294
+ const db = await getProjectDb();
1295
+ const project = await db.getProjectByPath(process.cwd());
1296
+ if (!project) {
1297
+ spinner.fail("Not a Contxt project. Run `contxt init` first.");
1298
+ return;
1299
+ }
1300
+ const patterns = [
1301
+ "**/*.ts",
1302
+ "**/*.tsx",
1303
+ "**/*.js",
1304
+ "**/*.jsx",
1305
+ "**/*.py",
1306
+ "**/*.rb",
1307
+ "**/*.go",
1308
+ "**/*.rs",
1309
+ "**/*.sql",
1310
+ "**/*.sh"
1311
+ ];
1312
+ const ignore = [
1313
+ "**/node_modules/**",
1314
+ "**/.git/**",
1315
+ "**/dist/**",
1316
+ "**/build/**",
1317
+ "**/.next/**",
1318
+ "**/*.min.js",
1319
+ "**/*.lock",
1320
+ ".contxt/**"
1321
+ ];
1322
+ const contxtIgnorePath = join3(process.cwd(), ".contxtignore");
1323
+ if (existsSync4(contxtIgnorePath)) {
1324
+ const contxtIgnore = readFileSync4(contxtIgnorePath, "utf-8").split("\n").filter((line) => line.trim() && !line.startsWith("#"));
1325
+ ignore.push(...contxtIgnore);
1326
+ }
1327
+ const searchPath = options.path || process.cwd();
1328
+ const files = await glob(patterns, {
1329
+ cwd: searchPath,
1330
+ ignore,
1331
+ absolute: true,
1332
+ nodir: true
1333
+ });
1334
+ spinner.text = `Scanning ${files.length} files...`;
1335
+ const allComments = [];
1336
+ for (const file of files) {
1337
+ const content = readFileSync4(file, "utf-8");
1338
+ const comments = parseFile(content, relative(process.cwd(), file));
1339
+ allComments.push(...comments);
1340
+ }
1341
+ spinner.succeed(`Found ${allComments.length} tagged comments across ${files.length} files`);
1342
+ if (allComments.length === 0) {
1343
+ console.log(chalk3.gray("\nNo tagged comments found. Try adding @decision, @pattern, or @context tags to your code."));
1344
+ return;
1345
+ }
1346
+ const existingEntries = await db.listEntries({
1347
+ projectId: project.id,
1348
+ branch: await db.getActiveBranch(project.id)
1349
+ });
1350
+ const existingHashes = new Map(
1351
+ existingEntries.filter((e) => e.metadata.source === "scan" && e.metadata.hash).map((e) => [e.metadata.hash, e])
1352
+ );
1353
+ const newComments = [];
1354
+ const updatedComments = [];
1355
+ const unchangedComments = [];
1356
+ for (const comment of allComments) {
1357
+ const existing = existingHashes.get(comment.hash);
1358
+ if (!existing) {
1359
+ newComments.push(comment);
1360
+ } else {
1361
+ const entry = scanCommentToEntry(comment, project.id);
1362
+ if (existing.title !== entry.title || existing.content !== entry.content) {
1363
+ updatedComments.push(comment);
1364
+ } else {
1365
+ unchangedComments.push(comment);
1366
+ }
1367
+ existingHashes.delete(comment.hash);
1368
+ }
1369
+ }
1370
+ const staleEntries = Array.from(existingHashes.values());
1371
+ console.log("");
1372
+ if (newComments.length > 0) {
1373
+ console.log(chalk3.bold("NEW"));
1374
+ for (const comment of newComments) {
1375
+ const icon = getTypeIcon(comment.tag);
1376
+ console.log(
1377
+ ` ${chalk3.green("+")} ${icon} ${chalk3.bold(comment.title.substring(0, 50))} ${chalk3.gray(comment.file + ":" + comment.line)}`
1378
+ );
1379
+ }
1380
+ console.log("");
1381
+ }
1382
+ if (updatedComments.length > 0) {
1383
+ console.log(chalk3.bold("UPDATED"));
1384
+ for (const comment of updatedComments) {
1385
+ const icon = getTypeIcon(comment.tag);
1386
+ console.log(
1387
+ ` ${chalk3.yellow("~")} ${icon} ${chalk3.bold(comment.title.substring(0, 50))} ${chalk3.gray(comment.file + ":" + comment.line)}`
1388
+ );
1389
+ }
1390
+ console.log("");
1391
+ }
1392
+ if (unchangedComments.length > 0) {
1393
+ console.log(chalk3.bold("UNCHANGED"));
1394
+ for (const comment of unchangedComments.slice(0, 3)) {
1395
+ const icon = getTypeIcon(comment.tag);
1396
+ console.log(
1397
+ ` ${chalk3.gray("\xB7")} ${icon} ${chalk3.gray(comment.title.substring(0, 50))} ${chalk3.gray(comment.file + ":" + comment.line)}`
1398
+ );
1399
+ }
1400
+ if (unchangedComments.length > 3) {
1401
+ console.log(chalk3.gray(` ... and ${unchangedComments.length - 3} more`));
1402
+ }
1403
+ console.log("");
1404
+ }
1405
+ if (staleEntries.length > 0) {
1406
+ console.log(chalk3.bold("STALE (source comment removed)"));
1407
+ for (const entry of staleEntries) {
1408
+ const icon = getTypeIcon(entry.type);
1409
+ console.log(
1410
+ ` ${chalk3.red("?")} ${icon} ${chalk3.gray(entry.title.substring(0, 50))} ${chalk3.gray("was: " + entry.metadata.file + ":" + entry.metadata.line)}`
1411
+ );
1412
+ }
1413
+ console.log("");
1414
+ }
1415
+ if (options.dryRun) {
1416
+ console.log(chalk3.yellow("Dry run - no changes saved."));
1417
+ return;
1418
+ }
1419
+ const toSave = [...newComments, ...updatedComments];
1420
+ if (toSave.length > 0) {
1421
+ const saveSpinner = ora("Saving entries...").start();
1422
+ for (const comment of toSave) {
1423
+ const entry = scanCommentToEntry(comment, project.id);
1424
+ const existing = existingHashes.get(comment.hash);
1425
+ if (existing) {
1426
+ await db.updateEntry(existing.id, {
1427
+ title: entry.title,
1428
+ content: entry.content,
1429
+ metadata: entry.metadata
1430
+ });
1431
+ } else {
1432
+ await db.createEntry({
1433
+ projectId: project.id,
1434
+ type: entry.type,
1435
+ title: entry.title,
1436
+ content: entry.content,
1437
+ metadata: entry.metadata,
1438
+ status: options.autoConfirm ? "active" : "draft"
1439
+ });
1440
+ }
1441
+ }
1442
+ saveSpinner.succeed(
1443
+ options.autoConfirm ? `Saved ${toSave.length} entries.` : `${toSave.length} new entries saved as drafts.`
1444
+ );
1445
+ }
1446
+ if (staleEntries.length > 0) {
1447
+ for (const entry of staleEntries) {
1448
+ await db.updateEntry(entry.id, { status: "stale" });
1449
+ }
1450
+ console.log(chalk3.gray(`Marked ${staleEntries.length} entries as stale.`));
1451
+ }
1452
+ if (!options.autoConfirm && toSave.length > 0) {
1453
+ console.log("");
1454
+ console.log(chalk3.cyan(`Run ${chalk3.bold("contxt review")} to confirm drafts.`));
1455
+ }
1456
+ await db.close();
1457
+ } catch (error2) {
1458
+ spinner.fail("Scan failed");
1459
+ console.error(chalk3.red(error2 instanceof Error ? error2.message : String(error2)));
1460
+ process.exit(1);
1461
+ }
1462
+ }
1463
+ function getTypeIcon(type) {
1464
+ const icons = {
1465
+ decision: chalk3.blue("DECISION"),
1466
+ pattern: chalk3.magenta("PATTERN"),
1467
+ context: chalk3.green("CONTEXT")
1468
+ };
1469
+ return icons[type] || type.toUpperCase();
1470
+ }
1471
+
1472
+ // src/commands/review.ts
1473
+ import chalk4 from "chalk";
1474
+ import inquirer from "inquirer";
1475
+ import ora2 from "ora";
1476
+ async function reviewCommand(options = {}) {
1477
+ try {
1478
+ const db = await getProjectDb();
1479
+ const project = await db.getProjectByPath(process.cwd());
1480
+ if (!project) {
1481
+ console.log(chalk4.red("Not a Contxt project. Run `contxt init` first."));
1482
+ return;
1483
+ }
1484
+ let drafts = await db.listEntries({
1485
+ projectId: project.id,
1486
+ branch: await db.getActiveBranch(project.id)
1487
+ });
1488
+ drafts = drafts.filter((e) => e.status === "draft");
1489
+ if (options.source) {
1490
+ drafts = drafts.filter((e) => {
1491
+ const source = e.metadata.source || "";
1492
+ return source.includes(options.source);
1493
+ });
1494
+ }
1495
+ if (drafts.length === 0) {
1496
+ console.log(chalk4.green("\u2713 No drafts pending review"));
1497
+ return;
1498
+ }
1499
+ if (options.count) {
1500
+ console.log(`${drafts.length} drafts pending review`);
1501
+ return;
1502
+ }
1503
+ console.log(chalk4.bold(`
1504
+ ${drafts.length} drafts pending review
1505
+ `));
1506
+ if (options.confirmAll) {
1507
+ const spinner = ora2("Confirming all drafts...").start();
1508
+ for (const draft of drafts) {
1509
+ await db.updateEntry(draft.id, { status: "active" });
1510
+ }
1511
+ spinner.succeed(`Confirmed ${drafts.length} drafts`);
1512
+ await db.close();
1513
+ return;
1514
+ }
1515
+ if (options.discardAll) {
1516
+ const { confirm } = await inquirer.prompt([
1517
+ {
1518
+ type: "confirm",
1519
+ name: "confirm",
1520
+ message: `Discard all ${drafts.length} drafts?`,
1521
+ default: false
1522
+ }
1523
+ ]);
1524
+ if (confirm) {
1525
+ const spinner = ora2("Discarding all drafts...").start();
1526
+ for (const draft of drafts) {
1527
+ await db.deleteEntry(draft.id);
1528
+ }
1529
+ spinner.succeed(`Discarded ${drafts.length} drafts`);
1530
+ }
1531
+ await db.close();
1532
+ return;
1533
+ }
1534
+ let confirmed = 0;
1535
+ let discarded = 0;
1536
+ let skipped = 0;
1537
+ for (const draft of drafts) {
1538
+ console.log(chalk4.gray("\u2500".repeat(50)));
1539
+ console.log("");
1540
+ displayDraft(draft);
1541
+ console.log("");
1542
+ const { action } = await inquirer.prompt([
1543
+ {
1544
+ type: "list",
1545
+ name: "action",
1546
+ message: "Action?",
1547
+ choices: [
1548
+ { name: "Confirm", value: "confirm" },
1549
+ { name: "Edit then confirm", value: "edit" },
1550
+ { name: "Discard", value: "discard" },
1551
+ { name: "Skip (review later)", value: "skip" }
1552
+ ]
1553
+ }
1554
+ ]);
1555
+ if (action === "confirm") {
1556
+ await db.updateEntry(draft.id, { status: "active" });
1557
+ console.log(chalk4.green("\u2713 Confirmed"));
1558
+ confirmed++;
1559
+ } else if (action === "edit") {
1560
+ const edited = await editDraft(draft);
1561
+ await db.updateEntry(draft.id, {
1562
+ title: edited.title,
1563
+ content: edited.content,
1564
+ metadata: edited.metadata,
1565
+ status: "active"
1566
+ });
1567
+ console.log(chalk4.green("\u2713 Edited and confirmed"));
1568
+ confirmed++;
1569
+ } else if (action === "discard") {
1570
+ await db.deleteEntry(draft.id);
1571
+ console.log(chalk4.red("\u2717 Discarded"));
1572
+ discarded++;
1573
+ } else {
1574
+ console.log(chalk4.gray("\u25CB Skipped"));
1575
+ skipped++;
1576
+ }
1577
+ console.log("");
1578
+ }
1579
+ console.log(chalk4.gray("\u2500".repeat(50)));
1580
+ console.log("");
1581
+ console.log(chalk4.bold("Review complete"));
1582
+ console.log(` ${chalk4.green(confirmed)} confirmed`);
1583
+ console.log(` ${chalk4.red(discarded)} discarded`);
1584
+ console.log(` ${chalk4.gray(skipped)} skipped`);
1585
+ console.log("");
1586
+ await db.close();
1587
+ } catch (error2) {
1588
+ console.error(chalk4.red(error2 instanceof Error ? error2.message : String(error2)));
1589
+ process.exit(1);
1590
+ }
1591
+ }
1592
+ function displayDraft(draft) {
1593
+ const icon = getTypeIcon2(draft.type);
1594
+ const source = draft.metadata.source || "unknown";
1595
+ const file = draft.metadata.file ? ` \xB7 ${draft.metadata.file}:${draft.metadata.line}` : "";
1596
+ const timeAgo = getTimeAgo(draft.createdAt);
1597
+ console.log(` ${icon} ${chalk4.bold(draft.title)}`);
1598
+ console.log(` ${chalk4.gray(`Source: ${source}${file} \xB7 ${timeAgo}`)}`);
1599
+ const contentPreview = draft.content.split("\n")[0].substring(0, 80);
1600
+ if (contentPreview) {
1601
+ console.log(` ${chalk4.gray(contentPreview)}${draft.content.length > 80 ? "..." : ""}`);
1602
+ }
1603
+ if (draft.type === "decision") {
1604
+ if (draft.metadata.rationale) {
1605
+ console.log(` ${chalk4.dim("Rationale:")} ${draft.metadata.rationale.substring(0, 60)}...`);
1606
+ }
1607
+ if (draft.metadata.alternatives) {
1608
+ console.log(` ${chalk4.dim("Alternatives:")} ${draft.metadata.alternatives}`);
1609
+ }
1610
+ }
1611
+ if (draft.type === "pattern") {
1612
+ if (draft.metadata.when) {
1613
+ console.log(` ${chalk4.dim("When:")} ${draft.metadata.when}`);
1614
+ }
1615
+ }
1616
+ }
1617
+ async function editDraft(draft) {
1618
+ console.log(chalk4.cyan("\nEdit mode (press Enter to keep current value):\n"));
1619
+ const { title, content } = await inquirer.prompt([
1620
+ {
1621
+ type: "input",
1622
+ name: "title",
1623
+ message: "Title:",
1624
+ default: draft.title
1625
+ },
1626
+ {
1627
+ type: "input",
1628
+ name: "content",
1629
+ message: "Content:",
1630
+ default: draft.content
1631
+ }
1632
+ ]);
1633
+ return {
1634
+ title: title || draft.title,
1635
+ content: content || draft.content,
1636
+ metadata: draft.metadata
1637
+ };
1638
+ }
1639
+ function getTypeIcon2(type) {
1640
+ const icons = {
1641
+ decision: chalk4.blue("DECISION"),
1642
+ pattern: chalk4.magenta("PATTERN"),
1643
+ context: chalk4.green("CONTEXT"),
1644
+ document: chalk4.yellow("DOCUMENT"),
1645
+ session: chalk4.cyan("SESSION")
1646
+ };
1647
+ return icons[type] || type.toUpperCase();
1648
+ }
1649
+ function getTimeAgo(date) {
1650
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
1651
+ if (seconds < 60) return "just now";
1652
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
1653
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
1654
+ return `${Math.floor(seconds / 86400)}d ago`;
1655
+ }
1656
+
1657
+ // src/commands/rules.ts
1658
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
1659
+ import { join as join4 } from "path";
1660
+ import chalk5 from "chalk";
1661
+ import ora3 from "ora";
1662
+ import { parseRulesFile, generateRulesFile } from "@mycontxt/core";
1663
+ async function syncCommand2(options = {}) {
1664
+ const spinner = ora3("Reading rules.md...").start();
1665
+ try {
1666
+ const db = await getProjectDb();
1667
+ const project = await db.getProjectByPath(process.cwd());
1668
+ if (!project) {
1669
+ spinner.fail("Not a Contxt project. Run `contxt init` first.");
1670
+ return;
1671
+ }
1672
+ const rulesPath = join4(process.cwd(), ".contxt", "rules.md");
1673
+ if (!existsSync5(rulesPath)) {
1674
+ spinner.fail("No rules.md file found. Run `contxt rules generate` to create one.");
1675
+ return;
1676
+ }
1677
+ const content = readFileSync5(rulesPath, "utf-8");
1678
+ const parsed = parseRulesFile(content);
1679
+ spinner.text = "Processing entries...";
1680
+ let added = 0;
1681
+ let updated = 0;
1682
+ let unchanged = 0;
1683
+ const existingEntries = await db.listEntries({
1684
+ projectId: project.id,
1685
+ branch: await db.getActiveBranch(project.id)
1686
+ });
1687
+ const existingByTitle = new Map(
1688
+ existingEntries.map((e) => [e.title, e])
1689
+ );
1690
+ for (const decision2 of parsed.decisions) {
1691
+ const existing = existingByTitle.get(decision2.title);
1692
+ if (!existing) {
1693
+ if (!options.dryRun) {
1694
+ await db.createEntry({
1695
+ projectId: project.id,
1696
+ type: "decision",
1697
+ title: decision2.title,
1698
+ content: decision2.content,
1699
+ metadata: decision2.metadata,
1700
+ status: "active"
1701
+ });
1702
+ }
1703
+ added++;
1704
+ } else if (existing.content !== decision2.content) {
1705
+ if (!options.dryRun) {
1706
+ await db.updateEntry(existing.id, {
1707
+ content: decision2.content,
1708
+ metadata: decision2.metadata
1709
+ });
1710
+ }
1711
+ updated++;
1712
+ } else {
1713
+ unchanged++;
1714
+ }
1715
+ }
1716
+ for (const pattern2 of parsed.patterns) {
1717
+ const existing = existingByTitle.get(pattern2.title);
1718
+ if (!existing) {
1719
+ if (!options.dryRun) {
1720
+ await db.createEntry({
1721
+ projectId: project.id,
1722
+ type: "pattern",
1723
+ title: pattern2.title,
1724
+ content: pattern2.content,
1725
+ metadata: pattern2.metadata,
1726
+ status: "active"
1727
+ });
1728
+ }
1729
+ added++;
1730
+ } else if (existing.content !== pattern2.content) {
1731
+ if (!options.dryRun) {
1732
+ await db.updateEntry(existing.id, {
1733
+ content: pattern2.content,
1734
+ metadata: pattern2.metadata
1735
+ });
1736
+ }
1737
+ updated++;
1738
+ } else {
1739
+ unchanged++;
1740
+ }
1741
+ }
1742
+ if (parsed.context) {
1743
+ const existing = existingEntries.find((e) => e.type === "context" && e.metadata.source === "rules");
1744
+ if (!existing) {
1745
+ if (!options.dryRun) {
1746
+ await db.createEntry({
1747
+ projectId: project.id,
1748
+ type: "context",
1749
+ title: parsed.context.title,
1750
+ content: parsed.context.content,
1751
+ metadata: parsed.context.metadata,
1752
+ status: "active"
1753
+ });
1754
+ }
1755
+ added++;
1756
+ } else if (existing.content !== parsed.context.content) {
1757
+ if (!options.dryRun) {
1758
+ await db.updateEntry(existing.id, {
1759
+ content: parsed.context.content
1760
+ });
1761
+ }
1762
+ updated++;
1763
+ } else {
1764
+ unchanged++;
1765
+ }
1766
+ }
1767
+ for (const doc2 of parsed.documents) {
1768
+ const existing = existingByTitle.get(doc2.title);
1769
+ if (!existing) {
1770
+ if (!options.dryRun) {
1771
+ await db.createEntry({
1772
+ projectId: project.id,
1773
+ type: "document",
1774
+ title: doc2.title,
1775
+ content: doc2.content,
1776
+ metadata: doc2.metadata,
1777
+ status: "active"
1778
+ });
1779
+ }
1780
+ added++;
1781
+ } else if (existing.content !== doc2.content) {
1782
+ if (!options.dryRun) {
1783
+ await db.updateEntry(existing.id, {
1784
+ content: doc2.content,
1785
+ metadata: doc2.metadata
1786
+ });
1787
+ }
1788
+ updated++;
1789
+ } else {
1790
+ unchanged++;
1791
+ }
1792
+ }
1793
+ if (parsed.stack.length > 0 && !options.dryRun) {
1794
+ await db.updateProject(project.id, {
1795
+ stack: parsed.stack
1796
+ });
1797
+ }
1798
+ spinner.succeed(
1799
+ options.dryRun ? `Dry run: Would add ${added}, update ${updated}, leave ${unchanged} unchanged` : `Synced rules.md: ${added} added, ${updated} updated, ${unchanged} unchanged`
1800
+ );
1801
+ await db.close();
1802
+ } catch (error2) {
1803
+ spinner.fail("Sync failed");
1804
+ console.error(chalk5.red(error2 instanceof Error ? error2.message : String(error2)));
1805
+ process.exit(1);
1806
+ }
1807
+ }
1808
+ async function generateCommand(options = {}) {
1809
+ const spinner = ora3("Loading memory entries...").start();
1810
+ try {
1811
+ const db = await getProjectDb();
1812
+ const project = await db.getProjectByPath(process.cwd());
1813
+ if (!project) {
1814
+ spinner.fail("Not a Contxt project. Run `contxt init` first.");
1815
+ return;
1816
+ }
1817
+ const rulesPath = join4(process.cwd(), ".contxt", "rules.md");
1818
+ if (existsSync5(rulesPath) && !options.force) {
1819
+ spinner.fail("rules.md already exists. Use --force to overwrite.");
1820
+ return;
1821
+ }
1822
+ const branch2 = await db.getActiveBranch(project.id);
1823
+ const allEntries = await db.listEntries({
1824
+ projectId: project.id,
1825
+ branch: branch2
1826
+ });
1827
+ const decisions = allEntries.filter((e) => e.type === "decision" && e.status === "active");
1828
+ const patterns = allEntries.filter((e) => e.type === "pattern" && e.status === "active");
1829
+ const context2 = allEntries.filter((e) => e.type === "context" && e.status === "active");
1830
+ const documents = allEntries.filter((e) => e.type === "document" && e.status === "active");
1831
+ spinner.text = "Generating rules.md...";
1832
+ const rulesContent = generateRulesFile({
1833
+ stack: project.stack || [],
1834
+ decisions: decisions.map((d) => ({
1835
+ title: d.title,
1836
+ content: d.content,
1837
+ metadata: d.metadata
1838
+ })),
1839
+ patterns: patterns.map((p) => ({
1840
+ title: p.title,
1841
+ content: p.content,
1842
+ metadata: p.metadata
1843
+ })),
1844
+ context: context2.map((c) => ({
1845
+ content: c.content
1846
+ })),
1847
+ documents: documents.map((d) => ({
1848
+ title: d.title,
1849
+ content: d.content
1850
+ }))
1851
+ });
1852
+ if (options.dryRun) {
1853
+ spinner.succeed("Generated rules.md (dry run):");
1854
+ console.log("");
1855
+ console.log(chalk5.gray(rulesContent));
1856
+ } else {
1857
+ writeFileSync3(rulesPath, rulesContent, "utf-8");
1858
+ spinner.succeed(`Generated ${rulesPath}`);
1859
+ }
1860
+ await db.close();
1861
+ } catch (error2) {
1862
+ spinner.fail("Generate failed");
1863
+ console.error(chalk5.red(error2 instanceof Error ? error2.message : String(error2)));
1864
+ process.exit(1);
1865
+ }
1866
+ }
1867
+ async function diffCommand() {
1868
+ const spinner = ora3("Comparing rules.md with memory...").start();
1869
+ try {
1870
+ const db = await getProjectDb();
1871
+ const project = await db.getProjectByPath(process.cwd());
1872
+ if (!project) {
1873
+ spinner.fail("Not a Contxt project. Run `contxt init` first.");
1874
+ return;
1875
+ }
1876
+ const rulesPath = join4(process.cwd(), ".contxt", "rules.md");
1877
+ if (!existsSync5(rulesPath)) {
1878
+ spinner.fail("No rules.md file found.");
1879
+ return;
1880
+ }
1881
+ const content = readFileSync5(rulesPath, "utf-8");
1882
+ const parsed = parseRulesFile(content);
1883
+ const branch2 = await db.getActiveBranch(project.id);
1884
+ const allEntries = await db.listEntries({
1885
+ projectId: project.id,
1886
+ branch: branch2
1887
+ });
1888
+ const existingByTitle = new Map(
1889
+ allEntries.map((e) => [e.title, e])
1890
+ );
1891
+ spinner.stop();
1892
+ const toAdd = [];
1893
+ const toUpdate = [];
1894
+ const inSync = [];
1895
+ for (const decision2 of parsed.decisions) {
1896
+ const existing = existingByTitle.get(decision2.title);
1897
+ if (!existing) {
1898
+ toAdd.push(`${chalk5.blue("DECISION")} ${decision2.title}`);
1899
+ } else if (existing.content !== decision2.content) {
1900
+ toUpdate.push(`${chalk5.blue("DECISION")} ${decision2.title}`);
1901
+ } else {
1902
+ inSync.push(`${chalk5.blue("DECISION")} ${decision2.title}`);
1903
+ }
1904
+ }
1905
+ for (const pattern2 of parsed.patterns) {
1906
+ const existing = existingByTitle.get(pattern2.title);
1907
+ if (!existing) {
1908
+ toAdd.push(`${chalk5.magenta("PATTERN")} ${pattern2.title}`);
1909
+ } else if (existing.content !== pattern2.content) {
1910
+ toUpdate.push(`${chalk5.magenta("PATTERN")} ${pattern2.title}`);
1911
+ } else {
1912
+ inSync.push(`${chalk5.magenta("PATTERN")} ${pattern2.title}`);
1913
+ }
1914
+ }
1915
+ for (const doc2 of parsed.documents) {
1916
+ const existing = existingByTitle.get(doc2.title);
1917
+ if (!existing) {
1918
+ toAdd.push(`${chalk5.cyan("DOCUMENT")} ${doc2.title}`);
1919
+ } else if (existing.content !== doc2.content) {
1920
+ toUpdate.push(`${chalk5.cyan("DOCUMENT")} ${doc2.title}`);
1921
+ } else {
1922
+ inSync.push(`${chalk5.cyan("DOCUMENT")} ${doc2.title}`);
1923
+ }
1924
+ }
1925
+ console.log("");
1926
+ if (toAdd.length > 0) {
1927
+ console.log(chalk5.bold("TO ADD (in rules.md, not in memory):"));
1928
+ for (const item of toAdd) {
1929
+ console.log(` ${chalk5.green("+")} ${item}`);
1930
+ }
1931
+ console.log("");
1932
+ }
1933
+ if (toUpdate.length > 0) {
1934
+ console.log(chalk5.bold("TO UPDATE (content differs):"));
1935
+ for (const item of toUpdate) {
1936
+ console.log(` ${chalk5.yellow("~")} ${item}`);
1937
+ }
1938
+ console.log("");
1939
+ }
1940
+ if (inSync.length > 0) {
1941
+ console.log(chalk5.bold("IN SYNC:"));
1942
+ for (const item of inSync.slice(0, 5)) {
1943
+ console.log(` ${chalk5.gray("\xB7")} ${chalk5.gray(item)}`);
1944
+ }
1945
+ if (inSync.length > 5) {
1946
+ console.log(chalk5.gray(` ... and ${inSync.length - 5} more`));
1947
+ }
1948
+ console.log("");
1949
+ }
1950
+ if (toAdd.length > 0 || toUpdate.length > 0) {
1951
+ console.log(chalk5.cyan(`Run ${chalk5.bold("contxt rules sync")} to apply changes.`));
1952
+ } else {
1953
+ console.log(chalk5.green("\u2713 Everything in sync!"));
1954
+ }
1955
+ await db.close();
1956
+ } catch (error2) {
1957
+ spinner.fail("Diff failed");
1958
+ console.error(chalk5.red(error2 instanceof Error ? error2.message : String(error2)));
1959
+ process.exit(1);
1960
+ }
1961
+ }
1962
+ var rulesCommand = {
1963
+ sync: syncCommand2,
1964
+ generate: generateCommand,
1965
+ diff: diffCommand
1966
+ };
1967
+
1968
+ // src/commands/capture.ts
1969
+ import { readFileSync as readFileSync6, existsSync as existsSync6, readdirSync } from "fs";
1970
+ import { join as join5, basename as basename2 } from "path";
1971
+ import chalk6 from "chalk";
1972
+ import ora4 from "ora";
1973
+ async function captureCommand(options = {}) {
1974
+ const spinner = ora4("Scanning project files...").start();
1975
+ try {
1976
+ const db = await getProjectDb();
1977
+ const project = await db.getProjectByPath(process.cwd());
1978
+ if (!project) {
1979
+ spinner.fail("Not a Contxt project. Run `contxt init` first.");
1980
+ return;
1981
+ }
1982
+ const entries = [];
1983
+ const sources = options.source === "all" || !options.source ? ["readme", "cursor", "claude", "adr", "commits", "package"] : [options.source];
1984
+ for (const source of sources) {
1985
+ spinner.text = `Importing from ${source}...`;
1986
+ switch (source) {
1987
+ case "readme":
1988
+ entries.push(...importReadme());
1989
+ break;
1990
+ case "cursor":
1991
+ entries.push(...importCursor());
1992
+ break;
1993
+ case "claude":
1994
+ entries.push(...importClaude());
1995
+ break;
1996
+ case "adr":
1997
+ entries.push(...importADR());
1998
+ break;
1999
+ case "commits":
2000
+ entries.push(...importCommits(options.limit || 50));
2001
+ break;
2002
+ case "package":
2003
+ entries.push(...importPackageFiles());
2004
+ break;
2005
+ }
2006
+ }
2007
+ spinner.succeed(`Found ${entries.length} entries across ${sources.length} source(s)`);
2008
+ if (entries.length === 0) {
2009
+ console.log(chalk6.gray("\\nNo entries found to import."));
2010
+ return;
2011
+ }
2012
+ console.log("");
2013
+ const grouped = groupBy(entries, "source");
2014
+ for (const [source, items] of Object.entries(grouped)) {
2015
+ console.log(chalk6.bold(`${source.toUpperCase()} (${items.length})`));
2016
+ for (const item of items.slice(0, 3)) {
2017
+ const icon = getTypeIcon3(item.type);
2018
+ console.log(` ${icon} ${chalk6.bold(item.title.substring(0, 60))}`);
2019
+ }
2020
+ if (items.length > 3) {
2021
+ console.log(chalk6.gray(` ... and ${items.length - 3} more`));
2022
+ }
2023
+ console.log("");
2024
+ }
2025
+ if (options.dryRun) {
2026
+ console.log(chalk6.yellow("Dry run - no entries saved."));
2027
+ return;
2028
+ }
2029
+ const saveSpinner = ora4("Saving entries...").start();
2030
+ let saved = 0;
2031
+ for (const entry of entries) {
2032
+ await db.createEntry({
2033
+ projectId: project.id,
2034
+ type: entry.type,
2035
+ title: entry.title,
2036
+ content: entry.content,
2037
+ metadata: {
2038
+ ...entry.metadata,
2039
+ source: `import:${entry.source}`
2040
+ },
2041
+ status: options.autoConfirm ? "active" : "draft"
2042
+ });
2043
+ saved++;
2044
+ }
2045
+ saveSpinner.succeed(
2046
+ options.autoConfirm ? `Saved ${saved} entries.` : `${saved} entries saved as drafts.`
2047
+ );
2048
+ if (!options.autoConfirm) {
2049
+ console.log("");
2050
+ console.log(chalk6.cyan(`Run ${chalk6.bold("contxt review")} to confirm drafts.`));
2051
+ }
2052
+ await db.close();
2053
+ } catch (error2) {
2054
+ spinner.fail("Import failed");
2055
+ console.error(chalk6.red(error2 instanceof Error ? error2.message : String(error2)));
2056
+ process.exit(1);
2057
+ }
2058
+ }
2059
+ function importReadme() {
2060
+ const readmePath = join5(process.cwd(), "README.md");
2061
+ if (!existsSync6(readmePath)) return [];
2062
+ try {
2063
+ const content = readFileSync6(readmePath, "utf-8");
2064
+ const lines = content.split("\\n");
2065
+ const entries = [];
2066
+ let currentSection = null;
2067
+ let currentContent = [];
2068
+ for (const line of lines) {
2069
+ const headingMatch = line.match(/^#+\\s+(.+)/);
2070
+ if (headingMatch) {
2071
+ if (currentSection && currentContent.length > 0) {
2072
+ const sectionContent = currentContent.join("\\n").trim();
2073
+ if (sectionContent.length > 50) {
2074
+ entries.push({
2075
+ type: "document",
2076
+ title: `README: ${currentSection}`,
2077
+ content: sectionContent,
2078
+ metadata: { section: currentSection },
2079
+ source: "readme"
2080
+ });
2081
+ }
2082
+ }
2083
+ currentSection = headingMatch[1].trim();
2084
+ currentContent = [];
2085
+ continue;
2086
+ }
2087
+ if (currentSection) {
2088
+ currentContent.push(line);
2089
+ }
2090
+ }
2091
+ if (currentSection && currentContent.length > 0) {
2092
+ const sectionContent = currentContent.join("\\n").trim();
2093
+ if (sectionContent.length > 50) {
2094
+ entries.push({
2095
+ type: "document",
2096
+ title: `README: ${currentSection}`,
2097
+ content: sectionContent,
2098
+ metadata: { section: currentSection },
2099
+ source: "readme"
2100
+ });
2101
+ }
2102
+ }
2103
+ return entries;
2104
+ } catch {
2105
+ return [];
2106
+ }
2107
+ }
2108
+ function importCursor() {
2109
+ const entries = [];
2110
+ const cursorrulesPath = join5(process.cwd(), ".cursorrules");
2111
+ if (existsSync6(cursorrulesPath)) {
2112
+ try {
2113
+ const content = readFileSync6(cursorrulesPath, "utf-8").trim();
2114
+ if (content.length > 0) {
2115
+ entries.push({
2116
+ type: "document",
2117
+ title: "Cursor Rules",
2118
+ content,
2119
+ metadata: { file: ".cursorrules" },
2120
+ source: "cursor"
2121
+ });
2122
+ }
2123
+ } catch {
2124
+ }
2125
+ }
2126
+ const cursorRulesPath = join5(process.cwd(), ".cursor", "rules");
2127
+ if (existsSync6(cursorRulesPath)) {
2128
+ try {
2129
+ const content = readFileSync6(cursorRulesPath, "utf-8").trim();
2130
+ if (content.length > 0) {
2131
+ entries.push({
2132
+ type: "document",
2133
+ title: "Cursor Rules",
2134
+ content,
2135
+ metadata: { file: ".cursor/rules" },
2136
+ source: "cursor"
2137
+ });
2138
+ }
2139
+ } catch {
2140
+ }
2141
+ }
2142
+ return entries;
2143
+ }
2144
+ function importClaude() {
2145
+ const entries = [];
2146
+ const claudePath = join5(process.cwd(), ".claude", "CLAUDE.md");
2147
+ if (existsSync6(claudePath)) {
2148
+ try {
2149
+ const content = readFileSync6(claudePath, "utf-8");
2150
+ const lines = content.split("\\n");
2151
+ let currentSection = null;
2152
+ let currentContent = [];
2153
+ for (const line of lines) {
2154
+ const headingMatch = line.match(/^#+\\s+(.+)/);
2155
+ if (headingMatch) {
2156
+ if (currentSection && currentContent.length > 0) {
2157
+ const sectionContent = currentContent.join("\\n").trim();
2158
+ if (sectionContent.length > 50) {
2159
+ entries.push({
2160
+ type: "document",
2161
+ title: `Claude: ${currentSection}`,
2162
+ content: sectionContent,
2163
+ metadata: { section: currentSection },
2164
+ source: "claude"
2165
+ });
2166
+ }
2167
+ }
2168
+ currentSection = headingMatch[1].trim();
2169
+ currentContent = [];
2170
+ continue;
2171
+ }
2172
+ if (currentSection) {
2173
+ currentContent.push(line);
2174
+ }
2175
+ }
2176
+ if (currentSection && currentContent.length > 0) {
2177
+ const sectionContent = currentContent.join("\\n").trim();
2178
+ if (sectionContent.length > 50) {
2179
+ entries.push({
2180
+ type: "document",
2181
+ title: `Claude: ${currentSection}`,
2182
+ content: sectionContent,
2183
+ metadata: { section: currentSection },
2184
+ source: "claude"
2185
+ });
2186
+ }
2187
+ }
2188
+ } catch {
2189
+ }
2190
+ }
2191
+ return entries;
2192
+ }
2193
+ function importADR() {
2194
+ const entries = [];
2195
+ const adrDirs = [
2196
+ join5(process.cwd(), "docs", "adr"),
2197
+ join5(process.cwd(), "docs", "architecture"),
2198
+ join5(process.cwd(), "adr")
2199
+ ];
2200
+ for (const adrDir of adrDirs) {
2201
+ if (!existsSync6(adrDir)) continue;
2202
+ try {
2203
+ const files = readdirSync(adrDir).filter((f) => f.endsWith(".md")).map((f) => join5(adrDir, f));
2204
+ for (const file of files) {
2205
+ try {
2206
+ const content = readFileSync6(file, "utf-8");
2207
+ const title = extractADRTitle(content, basename2(file, ".md"));
2208
+ entries.push({
2209
+ type: "decision",
2210
+ title,
2211
+ content: content.trim(),
2212
+ metadata: { file: file.replace(process.cwd(), ".") },
2213
+ source: "adr"
2214
+ });
2215
+ } catch {
2216
+ }
2217
+ }
2218
+ } catch {
2219
+ }
2220
+ }
2221
+ return entries;
2222
+ }
2223
+ function extractADRTitle(content, fallback) {
2224
+ const titleMatch = content.match(/^#\\s+(.+)/m);
2225
+ if (titleMatch) return titleMatch[1].trim();
2226
+ const titleLineMatch = content.match(/^Title:\\s+(.+)/m);
2227
+ if (titleLineMatch) return titleLineMatch[1].trim();
2228
+ return fallback.replace(/^\\d+-/, "").replace(/-/g, " ").replace(/\\b\\w/g, (l) => l.toUpperCase());
2229
+ }
2230
+ function importCommits(limit) {
2231
+ try {
2232
+ const { execSync: execSync4 } = __require("child_process");
2233
+ const output = execSync4(
2234
+ `git log --pretty=format:"%H|%an|%ai|%s|%b" -n ${limit}`,
2235
+ { cwd: process.cwd(), encoding: "utf-8" }
2236
+ ).trim();
2237
+ if (!output) return [];
2238
+ const entries = [];
2239
+ const commits = output.split("\\n");
2240
+ for (const commit of commits) {
2241
+ const [hash, author, date, subject, body] = commit.split("|");
2242
+ if (subject.startsWith("Merge ") || subject.length < 10) continue;
2243
+ const keywords = ["feat", "feature", "add", "implement", "refactor", "breaking", "major"];
2244
+ const isSignificant = keywords.some(
2245
+ (kw) => subject.toLowerCase().includes(kw)
2246
+ );
2247
+ if (isSignificant) {
2248
+ entries.push({
2249
+ type: "session",
2250
+ title: subject.trim(),
2251
+ content: body?.trim() || subject.trim(),
2252
+ metadata: {
2253
+ commit: hash.substring(0, 8),
2254
+ author,
2255
+ date
2256
+ },
2257
+ source: "commits"
2258
+ });
2259
+ }
2260
+ }
2261
+ return entries;
2262
+ } catch {
2263
+ return [];
2264
+ }
2265
+ }
2266
+ function importPackageFiles() {
2267
+ const entries = [];
2268
+ const packagePath = join5(process.cwd(), "package.json");
2269
+ if (existsSync6(packagePath)) {
2270
+ try {
2271
+ const pkg = JSON.parse(readFileSync6(packagePath, "utf-8"));
2272
+ if (pkg.description) {
2273
+ entries.push({
2274
+ type: "document",
2275
+ title: "Project Description",
2276
+ content: pkg.description,
2277
+ metadata: { source_file: "package.json" },
2278
+ source: "package"
2279
+ });
2280
+ }
2281
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2282
+ const majorDeps = Object.keys(deps).filter(
2283
+ (dep) => ["react", "vue", "angular", "express", "fastify", "next", "nuxt"].includes(dep)
2284
+ );
2285
+ if (majorDeps.length > 0) {
2286
+ entries.push({
2287
+ type: "context",
2288
+ title: "Tech Stack",
2289
+ content: `Using: ${majorDeps.join(", ")}`,
2290
+ metadata: { dependencies: majorDeps },
2291
+ source: "package"
2292
+ });
2293
+ }
2294
+ } catch {
2295
+ }
2296
+ }
2297
+ const requirementsPath = join5(process.cwd(), "requirements.txt");
2298
+ if (existsSync6(requirementsPath)) {
2299
+ try {
2300
+ const content = readFileSync6(requirementsPath, "utf-8");
2301
+ const packages = content.split("\\n").filter((line) => line.trim() && !line.startsWith("#")).map((line) => line.split("==")[0].split(">=")[0].trim());
2302
+ if (packages.length > 0) {
2303
+ entries.push({
2304
+ type: "context",
2305
+ title: "Python Dependencies",
2306
+ content: `Python packages: ${packages.slice(0, 10).join(", ")}${packages.length > 10 ? "..." : ""}`,
2307
+ metadata: { packages: packages.slice(0, 20) },
2308
+ source: "package"
2309
+ });
2310
+ }
2311
+ } catch {
2312
+ }
2313
+ }
2314
+ const cargoPath = join5(process.cwd(), "Cargo.toml");
2315
+ if (existsSync6(cargoPath)) {
2316
+ try {
2317
+ const content = readFileSync6(cargoPath, "utf-8");
2318
+ const nameMatch = content.match(/name\\s*=\\s*"([^"]+)"/);
2319
+ const descMatch = content.match(/description\\s*=\\s*"([^"]+)"/);
2320
+ if (descMatch) {
2321
+ entries.push({
2322
+ type: "document",
2323
+ title: `Rust Project: ${nameMatch?.[1] || "Unknown"}`,
2324
+ content: descMatch[1],
2325
+ metadata: { source_file: "Cargo.toml" },
2326
+ source: "package"
2327
+ });
2328
+ }
2329
+ } catch {
2330
+ }
2331
+ }
2332
+ return entries;
2333
+ }
2334
+ function groupBy(arr, key) {
2335
+ return arr.reduce((acc, item) => {
2336
+ const group = String(item[key]);
2337
+ if (!acc[group]) acc[group] = [];
2338
+ acc[group].push(item);
2339
+ return acc;
2340
+ }, {});
2341
+ }
2342
+ function getTypeIcon3(type) {
2343
+ const icons = {
2344
+ decision: chalk6.blue("DECISION"),
2345
+ pattern: chalk6.magenta("PATTERN"),
2346
+ context: chalk6.green("CONTEXT"),
2347
+ document: chalk6.cyan("DOCUMENT"),
2348
+ session: chalk6.yellow("SESSION")
2349
+ };
2350
+ return icons[type] || type.toUpperCase();
2351
+ }
2352
+
2353
+ // src/commands/hook.ts
2354
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync8, chmodSync, mkdirSync as mkdirSync3 } from "fs";
2355
+ import { join as join7 } from "path";
2356
+ import chalk7 from "chalk";
2357
+
2358
+ // src/hooks/post-commit.ts
2359
+ import { execSync } from "child_process";
2360
+ var DECISION_KEYWORDS = [
2361
+ "decided",
2362
+ "decision",
2363
+ "switched to",
2364
+ "migrated to",
2365
+ "migrated from",
2366
+ "replaced",
2367
+ "chose",
2368
+ "using .+ instead of",
2369
+ "instead of",
2370
+ "over .+ because",
2371
+ "picked"
2372
+ ];
2373
+ var DECISION_PATTERN = new RegExp(DECISION_KEYWORDS.join("|"), "i");
2374
+ var CONTEXT_PREFIXES = ["feat", "fix", "refactor", "arch", "build", "ci", "chore"];
2375
+ var CONTEXT_PATTERN = new RegExp(`^(${CONTEXT_PREFIXES.join("|")})(\\(.+\\))?:`, "i");
2376
+ async function runPostCommit() {
2377
+ try {
2378
+ const db = await getProjectDb();
2379
+ const project = await db.getProjectByPath(process.cwd());
2380
+ if (!project) return;
2381
+ const message = execSync("git log -1 --pretty=%B", {
2382
+ encoding: "utf-8",
2383
+ timeout: 1e3
2384
+ }).trim();
2385
+ if (!message) return;
2386
+ const firstLine = message.split("\n")[0].trim();
2387
+ let changedFiles = [];
2388
+ try {
2389
+ changedFiles = execSync("git diff-tree --no-commit-id -r --name-only HEAD", {
2390
+ encoding: "utf-8",
2391
+ timeout: 1e3
2392
+ }).trim().split("\n").filter(Boolean);
2393
+ } catch {
2394
+ }
2395
+ const commitHash = execSync("git rev-parse --short HEAD", {
2396
+ encoding: "utf-8",
2397
+ timeout: 1e3
2398
+ }).trim();
2399
+ const branch2 = await db.getActiveBranch(project.id);
2400
+ if (DECISION_PATTERN.test(firstLine)) {
2401
+ await db.createEntry({
2402
+ projectId: project.id,
2403
+ type: "decision",
2404
+ title: stripConventionalPrefix(firstLine),
2405
+ content: message,
2406
+ metadata: {
2407
+ source: "hooks:post-commit",
2408
+ commit: commitHash,
2409
+ files: changedFiles
2410
+ },
2411
+ status: "draft"
2412
+ });
2413
+ process.stdout.write(`contxt: draft saved \u2014 "${firstLine}" (decision)
2414
+ `);
2415
+ } else if (CONTEXT_PATTERN.test(firstLine)) {
2416
+ const existing = await db.listEntries({
2417
+ projectId: project.id,
2418
+ branch: branch2,
2419
+ type: "context"
2420
+ });
2421
+ const activeContext = existing.find((e) => e.status === "active");
2422
+ if (activeContext && changedFiles.length > 0) {
2423
+ const currentFiles = activeContext.metadata.files || [];
2424
+ const mergedFiles = Array.from(/* @__PURE__ */ new Set([...currentFiles, ...changedFiles])).slice(0, 20);
2425
+ await db.updateEntry(activeContext.id, {
2426
+ metadata: {
2427
+ ...activeContext.metadata,
2428
+ files: mergedFiles,
2429
+ lastCommit: commitHash
2430
+ }
2431
+ });
2432
+ }
2433
+ }
2434
+ await db.close();
2435
+ } catch {
2436
+ }
2437
+ }
2438
+ function stripConventionalPrefix(msg) {
2439
+ return msg.replace(/^(feat|fix|refactor|arch|build|ci|chore)(\(.+\))?:\s*/i, "").trim();
2440
+ }
2441
+
2442
+ // src/hooks/pre-push.ts
2443
+ import { execSync as execSync2 } from "child_process";
2444
+ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
2445
+ import { join as join6 } from "path";
2446
+ async function runPrePush() {
2447
+ try {
2448
+ const db = await getProjectDb();
2449
+ const project = await db.getProjectByPath(process.cwd());
2450
+ if (!project) return;
2451
+ let commitCount = 0;
2452
+ let changedFiles = [];
2453
+ try {
2454
+ const unpushed = execSync2("git cherry -v @{upstream} 2>/dev/null || git log --oneline origin/HEAD..HEAD 2>/dev/null", {
2455
+ encoding: "utf-8",
2456
+ timeout: 2e3
2457
+ }).trim();
2458
+ commitCount = unpushed ? unpushed.split("\n").filter(Boolean).length : 0;
2459
+ } catch {
2460
+ try {
2461
+ commitCount = parseInt(
2462
+ execSync2("git rev-list --count HEAD", { encoding: "utf-8", timeout: 1e3 }).trim(),
2463
+ 10
2464
+ );
2465
+ } catch {
2466
+ commitCount = 0;
2467
+ }
2468
+ }
2469
+ try {
2470
+ changedFiles = execSync2("git diff --name-only @{upstream}..HEAD 2>/dev/null", {
2471
+ encoding: "utf-8",
2472
+ timeout: 2e3
2473
+ }).trim().split("\n").filter(Boolean);
2474
+ } catch {
2475
+ }
2476
+ const branch2 = await db.getActiveBranch(project.id);
2477
+ const existing = await db.listEntries({
2478
+ projectId: project.id,
2479
+ branch: branch2,
2480
+ type: "context"
2481
+ });
2482
+ const activeContext = existing.find((e) => e.status === "active");
2483
+ if (activeContext) {
2484
+ await db.updateEntry(activeContext.id, {
2485
+ metadata: {
2486
+ ...activeContext.metadata,
2487
+ lastPush: (/* @__PURE__ */ new Date()).toISOString(),
2488
+ commitsPushed: (activeContext.metadata.commitsPushed || 0) + commitCount,
2489
+ filesPushed: changedFiles
2490
+ }
2491
+ });
2492
+ }
2493
+ process.stdout.write(
2494
+ `contxt: session updated \u2014 ${commitCount} commit${commitCount !== 1 ? "s" : ""}, ${changedFiles.length} files changed
2495
+ `
2496
+ );
2497
+ const configPath = join6(process.cwd(), ".contxt", "config.json");
2498
+ if (existsSync7(configPath)) {
2499
+ try {
2500
+ const config = JSON.parse(readFileSync7(configPath, "utf-8"));
2501
+ if (config.hooks?.auto_push_on_push) {
2502
+ process.stdout.write("contxt: syncing to cloud...\n");
2503
+ execSync2("contxt push --quiet 2>/dev/null", {
2504
+ timeout: 1e4,
2505
+ encoding: "utf-8"
2506
+ });
2507
+ process.stdout.write("contxt: \u2713 synced\n");
2508
+ }
2509
+ } catch {
2510
+ }
2511
+ }
2512
+ await db.close();
2513
+ } catch {
2514
+ }
2515
+ }
2516
+
2517
+ // src/hooks/post-checkout.ts
2518
+ import { execSync as execSync3 } from "child_process";
2519
+ async function runPostCheckout() {
2520
+ try {
2521
+ const isBranchCheckout = process.argv[4] === "1";
2522
+ if (!isBranchCheckout) return;
2523
+ const db = await getProjectDb();
2524
+ const project = await db.getProjectByPath(process.cwd());
2525
+ if (!project) return;
2526
+ const gitBranch = execSync3("git rev-parse --abbrev-ref HEAD", {
2527
+ encoding: "utf-8",
2528
+ timeout: 1e3
2529
+ }).trim();
2530
+ const branches = await db.listBranches(project.id);
2531
+ const contxtBranch = branches.find((b) => b.name === gitBranch);
2532
+ if (contxtBranch) {
2533
+ await db.switchBranch(project.id, gitBranch);
2534
+ const entryCount = await db.countEntries({ projectId: project.id, branch: gitBranch });
2535
+ process.stdout.write(`contxt: switched to branch ${gitBranch} (${entryCount} entries)
2536
+ `);
2537
+ } else {
2538
+ const currentBranch = await db.getActiveBranch(project.id);
2539
+ process.stdout.write(`contxt: no memory branch for ${gitBranch}, using ${currentBranch}
2540
+ `);
2541
+ }
2542
+ await db.close();
2543
+ } catch {
2544
+ }
2545
+ }
2546
+
2547
+ // src/hooks/prepare-commit-msg.ts
2548
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
2549
+ async function runPrepareCommitMsg() {
2550
+ try {
2551
+ const commitMsgFile = process.argv[4];
2552
+ const source = process.argv[5];
2553
+ if (source && source !== "template" && source !== "") return;
2554
+ if (!commitMsgFile) return;
2555
+ const db = await getProjectDb();
2556
+ const project = await db.getProjectByPath(process.cwd());
2557
+ if (!project) return;
2558
+ const branch2 = await db.getActiveBranch(project.id);
2559
+ const entries = await db.listEntries({
2560
+ projectId: project.id,
2561
+ branch: branch2
2562
+ });
2563
+ const context2 = entries.find((e) => e.type === "context" && e.status === "active");
2564
+ const recentDecisions = entries.filter((e) => e.type === "decision" && e.status === "active").sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()).slice(0, 3);
2565
+ if (!context2 && recentDecisions.length === 0) {
2566
+ await db.close();
2567
+ return;
2568
+ }
2569
+ const lines = [
2570
+ "",
2571
+ "# --- Contxt ---"
2572
+ ];
2573
+ if (context2) {
2574
+ const feature = context2.metadata.feature || context2.content.split("\n")[0];
2575
+ lines.push(`# Feature: ${feature}`);
2576
+ if (context2.metadata.blockers?.length) {
2577
+ lines.push(`# Blockers: ${context2.metadata.blockers.join(", ")}`);
2578
+ }
2579
+ }
2580
+ if (recentDecisions.length > 0) {
2581
+ const decisionList = recentDecisions.map((d) => d.title).join(", ");
2582
+ lines.push(`# Recent decisions: ${decisionList}`);
2583
+ }
2584
+ const draftCount = entries.filter((e) => e.status === "draft").length;
2585
+ if (draftCount > 0) {
2586
+ lines.push(`# ${draftCount} drafts pending \u2014 run \`contxt review\``);
2587
+ }
2588
+ lines.push("#");
2589
+ const existing = readFileSync8(commitMsgFile, "utf-8");
2590
+ writeFileSync4(commitMsgFile, existing + lines.join("\n") + "\n", "utf-8");
2591
+ await db.close();
2592
+ } catch {
2593
+ }
2594
+ }
2595
+
2596
+ // src/commands/hook.ts
2597
+ var CONTXT_BLOCK_START = "# --- contxt hook start ---";
2598
+ var CONTXT_BLOCK_END = "# --- contxt hook end ---";
2599
+ var ALL_HOOKS = ["post-commit", "pre-push", "post-checkout", "prepare-commit-msg"];
2600
+ async function installCommand(options = {}) {
2601
+ const gitHooksDir = join7(process.cwd(), ".git", "hooks");
2602
+ if (!existsSync8(join7(process.cwd(), ".git"))) {
2603
+ console.error(chalk7.red("Not a git repository."));
2604
+ process.exit(1);
2605
+ }
2606
+ mkdirSync3(gitHooksDir, { recursive: true });
2607
+ const hooksToInstall = options.hooks ? options.hooks.split(",").map((h) => h.trim()) : [...ALL_HOOKS];
2608
+ let installed = 0;
2609
+ let updated = 0;
2610
+ for (const hookName of hooksToInstall) {
2611
+ const hookPath = join7(gitHooksDir, hookName);
2612
+ const contxtLine = `contxt hook run ${hookName} "$@"`;
2613
+ const contxtBlock = `${CONTXT_BLOCK_START}
2614
+ ${contxtLine}
2615
+ ${CONTXT_BLOCK_END}`;
2616
+ if (existsSync8(hookPath)) {
2617
+ const content = readFileSync9(hookPath, "utf-8");
2618
+ if (content.includes(CONTXT_BLOCK_START)) {
2619
+ const updated_content = content.replace(
2620
+ new RegExp(`${escapeRegex(CONTXT_BLOCK_START)}[\\s\\S]*?${escapeRegex(CONTXT_BLOCK_END)}`),
2621
+ contxtBlock
2622
+ );
2623
+ writeFileSync5(hookPath, updated_content, "utf-8");
2624
+ updated++;
2625
+ } else {
2626
+ const newContent = content.trimEnd() + "\n\n" + contxtBlock + "\n";
2627
+ writeFileSync5(hookPath, newContent, "utf-8");
2628
+ installed++;
2629
+ }
2630
+ } else {
2631
+ const newContent = `#!/bin/sh
2632
+
2633
+ ${contxtBlock}
2634
+ `;
2635
+ writeFileSync5(hookPath, newContent, "utf-8");
2636
+ installed++;
2637
+ }
2638
+ chmodSync(hookPath, "755");
2639
+ console.log(chalk7.green("\u2713"), `${hookName}`);
2640
+ }
2641
+ console.log("");
2642
+ if (installed > 0) console.log(chalk7.green(`Installed ${installed} hook${installed !== 1 ? "s" : ""}.`));
2643
+ if (updated > 0) console.log(chalk7.yellow(`Updated ${updated} existing hook${updated !== 1 ? "s" : ""}.`));
2644
+ console.log("");
2645
+ console.log(chalk7.gray("Hooks will capture context from your git workflow automatically."));
2646
+ }
2647
+ async function uninstallCommand(options = {}) {
2648
+ const gitHooksDir = join7(process.cwd(), ".git", "hooks");
2649
+ if (!existsSync8(join7(process.cwd(), ".git"))) {
2650
+ console.error(chalk7.red("Not a git repository."));
2651
+ process.exit(1);
2652
+ }
2653
+ const hooksToRemove = options.hooks ? options.hooks.split(",").map((h) => h.trim()) : [...ALL_HOOKS];
2654
+ let removed = 0;
2655
+ for (const hookName of hooksToRemove) {
2656
+ const hookPath = join7(gitHooksDir, hookName);
2657
+ if (!existsSync8(hookPath)) continue;
2658
+ const content = readFileSync9(hookPath, "utf-8");
2659
+ if (!content.includes(CONTXT_BLOCK_START)) continue;
2660
+ const cleaned = content.replace(
2661
+ new RegExp(`\\n*${escapeRegex(CONTXT_BLOCK_START)}[\\s\\S]*?${escapeRegex(CONTXT_BLOCK_END)}\\n*`),
2662
+ "\n"
2663
+ ).trim();
2664
+ if (cleaned === "#!/bin/sh" || cleaned === "") {
2665
+ writeFileSync5(hookPath, "#!/bin/sh\n", "utf-8");
2666
+ } else {
2667
+ writeFileSync5(hookPath, cleaned + "\n", "utf-8");
2668
+ }
2669
+ removed++;
2670
+ console.log(chalk7.gray("\u2717"), hookName);
2671
+ }
2672
+ if (removed > 0) {
2673
+ console.log("");
2674
+ console.log(chalk7.green(`Removed ${removed} hook${removed !== 1 ? "s" : ""}.`));
2675
+ } else {
2676
+ console.log(chalk7.gray("No Contxt hooks found to remove."));
2677
+ }
2678
+ }
2679
+ async function statusCommand2() {
2680
+ const gitHooksDir = join7(process.cwd(), ".git", "hooks");
2681
+ if (!existsSync8(join7(process.cwd(), ".git"))) {
2682
+ console.error(chalk7.red("Not a git repository."));
2683
+ process.exit(1);
2684
+ }
2685
+ console.log("");
2686
+ console.log(chalk7.bold("Git Hook Status"));
2687
+ console.log("");
2688
+ for (const hookName of ALL_HOOKS) {
2689
+ const hookPath = join7(gitHooksDir, hookName);
2690
+ const fileExists = existsSync8(hookPath);
2691
+ let isInstalled = false;
2692
+ if (fileExists) {
2693
+ const content = readFileSync9(hookPath, "utf-8");
2694
+ isInstalled = content.includes(CONTXT_BLOCK_START);
2695
+ }
2696
+ const statusIcon = isInstalled ? chalk7.green("\u2713") : chalk7.gray("\u25CB");
2697
+ const label = isInstalled ? chalk7.green(hookName) : chalk7.gray(hookName);
2698
+ const note = !fileExists ? chalk7.gray(" (no hook file)") : isInstalled ? chalk7.gray(" installed") : chalk7.yellow(" not installed");
2699
+ console.log(` ${statusIcon} ${label}${note}`);
2700
+ }
2701
+ console.log("");
2702
+ const installedCount = ALL_HOOKS.filter((h) => {
2703
+ const hookPath = join7(gitHooksDir, h);
2704
+ return existsSync8(hookPath) && readFileSync9(hookPath, "utf-8").includes(CONTXT_BLOCK_START);
2705
+ }).length;
2706
+ if (installedCount === 0) {
2707
+ console.log(chalk7.cyan(`Run ${chalk7.bold("contxt hook install")} to enable automatic context capture.`));
2708
+ }
2709
+ }
2710
+ async function runCommand(hookName) {
2711
+ switch (hookName) {
2712
+ case "post-commit":
2713
+ await runPostCommit();
2714
+ break;
2715
+ case "pre-push":
2716
+ await runPrePush();
2717
+ break;
2718
+ case "post-checkout":
2719
+ await runPostCheckout();
2720
+ break;
2721
+ case "prepare-commit-msg":
2722
+ await runPrepareCommitMsg();
2723
+ break;
2724
+ default:
2725
+ }
2726
+ }
2727
+ function escapeRegex(str) {
2728
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2729
+ }
2730
+ var hookCommand = {
2731
+ install: installCommand,
2732
+ uninstall: uninstallCommand,
2733
+ status: statusCommand2,
2734
+ run: runCommand
2735
+ };
2736
+
2737
+ // src/commands/watch.ts
2738
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync9, unlinkSync } from "fs";
2739
+ import { join as join8, relative as relative2 } from "path";
2740
+ import { spawn } from "child_process";
2741
+ import chalk8 from "chalk";
2742
+ import chokidar from "chokidar";
2743
+ import { parseFile as parseFile2, scanCommentToEntry as scanCommentToEntry2 } from "@mycontxt/core";
2744
+ var PID_FILE = ".contxt/.watch.pid";
2745
+ var LOG_FILE = ".contxt/watch.log";
2746
+ var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
2747
+ var DEBOUNCE_MS = 3e4;
2748
+ async function startCommand(options = {}) {
2749
+ if (options.daemon) {
2750
+ return startDaemon();
2751
+ }
2752
+ return runWatcher();
2753
+ }
2754
+ async function stopCommand() {
2755
+ const pidFile = join8(process.cwd(), PID_FILE);
2756
+ if (!existsSync9(pidFile)) {
2757
+ console.log(chalk8.gray("No watch daemon running."));
2758
+ return;
2759
+ }
2760
+ const pid = parseInt(readFileSync10(pidFile, "utf-8").trim(), 10);
2761
+ try {
2762
+ process.kill(pid, "SIGTERM");
2763
+ unlinkSync(pidFile);
2764
+ console.log(chalk8.green("Watch daemon stopped."));
2765
+ } catch {
2766
+ console.log(chalk8.yellow("Daemon not found \u2014 removing stale PID file."));
2767
+ unlinkSync(pidFile);
2768
+ }
2769
+ }
2770
+ async function statusCommand3() {
2771
+ const pidFile = join8(process.cwd(), PID_FILE);
2772
+ if (!existsSync9(pidFile)) {
2773
+ console.log(chalk8.gray("Watch daemon: not running"));
2774
+ return;
2775
+ }
2776
+ const pid = parseInt(readFileSync10(pidFile, "utf-8").trim(), 10);
2777
+ try {
2778
+ process.kill(pid, 0);
2779
+ console.log(chalk8.green(`Watch daemon: running (PID ${pid})`));
2780
+ } catch {
2781
+ console.log(chalk8.yellow("Watch daemon: stale PID file (process not found)"));
2782
+ unlinkSync(pidFile);
2783
+ }
2784
+ }
2785
+ function startDaemon() {
2786
+ const pidFile = join8(process.cwd(), PID_FILE);
2787
+ if (existsSync9(pidFile)) {
2788
+ const pid = parseInt(readFileSync10(pidFile, "utf-8").trim(), 10);
2789
+ try {
2790
+ process.kill(pid, 0);
2791
+ console.log(chalk8.yellow(`Watch daemon already running (PID ${pid}).`));
2792
+ return;
2793
+ } catch {
2794
+ }
2795
+ }
2796
+ const logPath = join8(process.cwd(), LOG_FILE);
2797
+ const logStream = __require("fs").openSync(logPath, "a");
2798
+ const child = spawn(process.execPath, [process.argv[1], "watch"], {
2799
+ env: { ...process.env, CONTXT_WATCH_DAEMON: "1" },
2800
+ detached: true,
2801
+ stdio: ["ignore", logStream, logStream],
2802
+ cwd: process.cwd()
2803
+ });
2804
+ child.unref();
2805
+ writeFileSync6(pidFile, String(child.pid), "utf-8");
2806
+ console.log(chalk8.green(`Watch daemon started (PID ${child.pid}).`));
2807
+ console.log(chalk8.gray(`Logs: ${logPath}`));
2808
+ }
2809
+ async function runWatcher() {
2810
+ const cwd = process.cwd();
2811
+ const isDaemon = process.env.CONTXT_WATCH_DAEMON === "1";
2812
+ const db = await getProjectDb(cwd);
2813
+ const project = await db.getProjectByPath(cwd);
2814
+ if (!project) {
2815
+ if (!isDaemon) console.error(chalk8.red("Not a Contxt project."));
2816
+ return;
2817
+ }
2818
+ const branch2 = await db.getActiveBranch(project.id);
2819
+ if (!isDaemon) {
2820
+ console.log(chalk8.bold(`contxt watch`) + ` \u2014 monitoring ${project.name} (${branch2})`);
2821
+ console.log("");
2822
+ }
2823
+ const pendingFiles = /* @__PURE__ */ new Set();
2824
+ let flushTimer = null;
2825
+ let sessionStart = null;
2826
+ let lastActivityAt = null;
2827
+ let sessionTimer = null;
2828
+ const watchPatterns = [
2829
+ "**/*.ts",
2830
+ "**/*.tsx",
2831
+ "**/*.js",
2832
+ "**/*.jsx",
2833
+ "**/*.py",
2834
+ "**/*.rb",
2835
+ "**/*.go",
2836
+ "**/*.rs",
2837
+ "**/*.sql"
2838
+ ];
2839
+ const ignored = [
2840
+ /(^|[/\\])\../,
2841
+ // dotfiles
2842
+ /node_modules/,
2843
+ /\.contxt/,
2844
+ /dist/,
2845
+ /build/,
2846
+ /\.next/,
2847
+ /\.min\.js$/
2848
+ ];
2849
+ const watcher = chokidar.watch(watchPatterns, {
2850
+ cwd,
2851
+ ignored,
2852
+ ignoreInitial: true,
2853
+ persistent: true,
2854
+ usePolling: false,
2855
+ interval: 1e3
2856
+ });
2857
+ const rulesPath = join8(cwd, ".contxt", "rules.md");
2858
+ const rulesWatcher = chokidar.watch(rulesPath, { ignoreInitial: true });
2859
+ const gitHeadPath = join8(cwd, ".git", "HEAD");
2860
+ const gitWatcher = chokidar.watch(gitHeadPath, { ignoreInitial: true });
2861
+ watcher.on("change", (filePath) => {
2862
+ pendingFiles.add(filePath);
2863
+ touchSession();
2864
+ scheduleFlush();
2865
+ });
2866
+ watcher.on("add", (filePath) => {
2867
+ pendingFiles.add(filePath);
2868
+ touchSession();
2869
+ scheduleFlush();
2870
+ });
2871
+ rulesWatcher.on("change", async () => {
2872
+ log("rules", "rules.md changed \u2014 syncing...");
2873
+ try {
2874
+ const { parseRulesFile: parseRulesFile2 } = await import("@mycontxt/core");
2875
+ const content = readFileSync10(rulesPath, "utf-8");
2876
+ const parsed = parseRulesFile2(content);
2877
+ let synced = 0;
2878
+ const existing = await db.listEntries({ projectId: project.id, branch: branch2 });
2879
+ const byTitle = new Map(existing.map((e) => [e.title, e]));
2880
+ for (const decision2 of parsed.decisions) {
2881
+ const ex = byTitle.get(decision2.title);
2882
+ if (!ex) {
2883
+ await db.createEntry({ projectId: project.id, type: "decision", title: decision2.title, content: decision2.content, metadata: decision2.metadata, status: "active" });
2884
+ synced++;
2885
+ } else if (ex.content !== decision2.content) {
2886
+ await db.updateEntry(ex.id, { content: decision2.content });
2887
+ synced++;
2888
+ }
2889
+ }
2890
+ log("rules", `synced ${synced} update${synced !== 1 ? "s" : ""}`);
2891
+ } catch {
2892
+ log("rules", "sync failed");
2893
+ }
2894
+ });
2895
+ gitWatcher.on("change", async () => {
2896
+ try {
2897
+ const headContent = readFileSync10(gitHeadPath, "utf-8").trim();
2898
+ const branchMatch = headContent.match(/^ref: refs\/heads\/(.+)$/);
2899
+ if (!branchMatch) return;
2900
+ const newBranch = branchMatch[1];
2901
+ const currentBranch = await db.getActiveBranch(project.id);
2902
+ if (newBranch === currentBranch) return;
2903
+ const branches = await db.listBranches(project.id);
2904
+ const has = branches.some((b) => b.name === newBranch);
2905
+ if (has) {
2906
+ await db.switchBranch(project.id, newBranch);
2907
+ const count = await db.countEntries({ projectId: project.id, branch: newBranch });
2908
+ log("branch", `switched to ${newBranch} (${count} entries)`);
2909
+ }
2910
+ } catch {
2911
+ }
2912
+ });
2913
+ async function flush() {
2914
+ if (pendingFiles.size === 0) return;
2915
+ const files = Array.from(pendingFiles);
2916
+ pendingFiles.clear();
2917
+ try {
2918
+ const entries = await db.listEntries({ projectId: project.id, branch: branch2, type: "context" });
2919
+ const activeCtx = entries.find((e) => e.status === "active");
2920
+ if (activeCtx) {
2921
+ const currentFiles = activeCtx.metadata.files || [];
2922
+ const relFiles = files.map((f) => relative2(cwd, join8(cwd, f)));
2923
+ const merged = Array.from(/* @__PURE__ */ new Set([...currentFiles, ...relFiles])).slice(0, 30);
2924
+ await db.updateEntry(activeCtx.id, { metadata: { ...activeCtx.metadata, files: merged } });
2925
+ }
2926
+ } catch {
2927
+ }
2928
+ let newDrafts = 0;
2929
+ for (const file of files) {
2930
+ try {
2931
+ const absPath = join8(cwd, file);
2932
+ if (!existsSync9(absPath)) continue;
2933
+ const content = readFileSync10(absPath, "utf-8");
2934
+ const comments = parseFile2(content, file);
2935
+ if (comments.length === 0) continue;
2936
+ const existing = await db.listEntries({ projectId: project.id, branch: branch2 });
2937
+ const hashes = new Set(existing.filter((e) => e.metadata.hash).map((e) => e.metadata.hash));
2938
+ for (const comment of comments) {
2939
+ if (!hashes.has(comment.hash)) {
2940
+ const entry = scanCommentToEntry2(comment, project.id);
2941
+ await db.createEntry({ projectId: project.id, type: entry.type, title: entry.title, content: entry.content, metadata: entry.metadata, status: "draft" });
2942
+ newDrafts++;
2943
+ }
2944
+ }
2945
+ } catch {
2946
+ }
2947
+ }
2948
+ log("files", `${files.length} file${files.length !== 1 ? "s" : ""} ${newDrafts > 0 ? `\xB7 +${newDrafts} draft${newDrafts !== 1 ? "s" : ""}` : ""}`);
2949
+ }
2950
+ function scheduleFlush() {
2951
+ if (flushTimer) clearTimeout(flushTimer);
2952
+ flushTimer = setTimeout(flush, DEBOUNCE_MS);
2953
+ }
2954
+ function touchSession() {
2955
+ const now = /* @__PURE__ */ new Date();
2956
+ lastActivityAt = now;
2957
+ if (!sessionStart) {
2958
+ sessionStart = now;
2959
+ log("session", `started`);
2960
+ }
2961
+ if (sessionTimer) clearTimeout(sessionTimer);
2962
+ sessionTimer = setTimeout(endSession, SESSION_TIMEOUT_MS);
2963
+ }
2964
+ function endSession() {
2965
+ if (!sessionStart || !lastActivityAt) return;
2966
+ const durationMin = Math.round((lastActivityAt.getTime() - sessionStart.getTime()) / 6e4);
2967
+ log("session", `ended \u2014 ${durationMin} min`);
2968
+ sessionStart = null;
2969
+ lastActivityAt = null;
2970
+ }
2971
+ function log(type, message) {
2972
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
2973
+ const line = `${time} ${type.padEnd(8)} ${message}`;
2974
+ if (!isDaemon) {
2975
+ console.log(` ${chalk8.gray(time)} ${chalk8.cyan(type.padEnd(8))} ${message}`);
2976
+ } else {
2977
+ try {
2978
+ __require("fs").appendFileSync(join8(cwd, LOG_FILE), line + "\n");
2979
+ } catch {
2980
+ }
2981
+ }
2982
+ }
2983
+ const shutdown = async () => {
2984
+ if (flushTimer) clearTimeout(flushTimer);
2985
+ await flush();
2986
+ watcher.close();
2987
+ rulesWatcher.close();
2988
+ gitWatcher.close();
2989
+ await db.close();
2990
+ const pidFile = join8(cwd, PID_FILE);
2991
+ if (existsSync9(pidFile)) unlinkSync(pidFile);
2992
+ process.exit(0);
2993
+ };
2994
+ process.on("SIGTERM", shutdown);
2995
+ process.on("SIGINT", shutdown);
2996
+ if (!isDaemon) {
2997
+ console.log(chalk8.gray("Watching for file changes. Ctrl+C to stop.\n"));
2998
+ }
2999
+ }
3000
+ var watchCommand = {
3001
+ start: startCommand,
3002
+ stop: stopCommand,
3003
+ status: statusCommand3
3004
+ };
3005
+
3006
+ // src/bin/contxt.ts
3007
+ var program = new Command();
3008
+ program.name("contxt").description("GitHub for AI Context - Persistent memory for AI coding agents").version("0.1.0");
3009
+ program.command("init").description("Initialize a Contxt project in the current directory").option("-n, --name <name>", "Project name").action(initCommand);
3010
+ program.command("status").description("Show project status and memory summary").action(statusCommand);
3011
+ var decision = program.command("decision").description("Manage architectural decisions");
3012
+ decision.command("add").description("Add a new decision").requiredOption("-t, --title <title>", "Decision title").requiredOption("-r, --rationale <rationale>", "Decision rationale").option("-a, --alternatives <alternatives...>", "Alternative approaches considered").option("-c, --consequences <consequences...>", "Consequences of this decision").option("--tags <tags...>", "Tags for categorization").action(decisionCommand.add);
3013
+ decision.command("list").description("List all decisions").option("-b, --branch <branch>", "Filter by branch").action(decisionCommand.list);
3014
+ decision.command("show <id>").description("Show a specific decision").action(decisionCommand.show);
3015
+ var pattern = program.command("pattern").description("Manage code patterns and conventions");
3016
+ pattern.command("add").description("Add a new pattern").requiredOption("-t, --title <title>", "Pattern title").requiredOption("-c, --content <content>", "Pattern content/code").option("--category <category>", "Pattern category").option("--tags <tags...>", "Tags for categorization").action(patternCommand.add);
3017
+ pattern.command("list").description("List all patterns").option("-b, --branch <branch>", "Filter by branch").action(patternCommand.list);
3018
+ pattern.command("show <id>").description("Show a specific pattern").action(patternCommand.show);
3019
+ var context = program.command("context").description("Manage project working context");
3020
+ context.command("set").description("Set current working context").option("-f, --feature <feature>", "Current feature being worked on").option("-b, --blockers <blockers...>", "Current blockers").option("-n, --next <steps...>", "Next steps").option("--files <files...>", "Active files").action(contextCommand.set);
3021
+ context.command("show").description("Show current context").action(contextCommand.show);
3022
+ context.command("clear").description("Clear current context").action(contextCommand.clear);
3023
+ var doc = program.command("doc").description("Manage documentation and reference materials");
3024
+ doc.command("add").description("Add a new document").requiredOption("-t, --title <title>", "Document title").option("-c, --content <content>", "Document content").option("-f, --file <file>", "Read content from file").option("-u, --url <url>", "Source URL").option("--tags <tags...>", "Tags for categorization").action(docCommand.add);
3025
+ doc.command("list").description("List all documents").option("-b, --branch <branch>", "Filter by branch").action(docCommand.list);
3026
+ doc.command("show <id>").description("Show a specific document").action(docCommand.show);
3027
+ var session = program.command("session").description("Manage development sessions");
3028
+ session.command("start").description("Start a new development session").requiredOption("-f, --feature <feature>", "Feature being worked on").option("-d, --description <description>", "Session description").action(sessionCommand.start);
3029
+ session.command("end").description("End the current session").option("-s, --summary <summary>", "Session summary").action(sessionCommand.end);
3030
+ session.command("list").description("List all sessions").option("-b, --branch <branch>", "Filter by branch").action(sessionCommand.list);
3031
+ session.command("current").description("Show current active session").action(sessionCommand.current);
3032
+ program.command("search <query>").description("Search memory entries").option("-b, --branch <branch>", "Filter by branch").option("-t, --type <type>", "Filter by type (decision, pattern, context, document, session)").option("-l, --limit <limit>", "Limit number of results", parseInt).action(searchCommand);
3033
+ program.command("export").description("Export memory entries to JSON").option("-o, --output <file>", "Output file (defaults to stdout)").option("-b, --branch <branch>", "Export specific branch").action(exportCommand);
3034
+ program.command("import").description("Import memory entries from JSON").requiredOption("-f, --file <file>", "Input file to import").option("-m, --merge", "Merge with existing entries (default: replace)").action(importCommand);
3035
+ var auth = program.command("auth").description("Manage authentication");
3036
+ auth.command("login").description("Authenticate with GitHub or email").option("-e, --email <email>", "Login with magic link (email)").action(authCommand.login);
3037
+ auth.command("logout").description("Logout from Contxt").action(authCommand.logout);
3038
+ auth.command("status").description("Check authentication status").action(authCommand.status);
3039
+ program.command("push").description("Push local changes to cloud").option("-f, --force", "Force push (override conflicts)").option("-d, --dry-run", "Show what would be pushed without actually pushing").action(syncCommand.push);
3040
+ program.command("pull").description("Pull remote changes to local").option("-f, --force", "Force pull (override conflicts)").option("-d, --dry-run", "Show what would be pulled without actually pulling").action(syncCommand.pull);
3041
+ program.command("sync").description("Full bidirectional sync (pull + push)").option("-f, --force", "Force sync (override conflicts)").option("-d, --dry-run", "Show what would be synced without actually syncing").action(syncCommand.sync);
3042
+ var branch = program.command("branch").description("Manage branches");
3043
+ branch.command("create <name>").description("Create a new branch").option("-f, --from <branch>", "Create from specific branch").action(branchCommand.create);
3044
+ branch.command("list").description("List all branches").action(branchCommand.list);
3045
+ branch.command("switch <name>").description("Switch to a different branch").action(branchCommand.switch);
3046
+ branch.command("delete <name>").description("Delete a branch").action(branchCommand.delete);
3047
+ branch.command("merge <source>").description("Merge a branch into current branch").action(branchCommand.merge);
3048
+ var history = program.command("history").description("View and restore version history");
3049
+ history.command("show <entry-id>").description("Show version history for an entry").action(historyCommand.show);
3050
+ history.command("restore <entry-id>").description("Restore an entry to a previous version").requiredOption("-v, --version <version>", "Version number to restore", parseInt).action(historyCommand.restore);
3051
+ program.command("load").description("Generate context payload for AI prompts").option("-t, --task <description>", "Task-based context (smart relevance ranking)").option("-f, --files <files...>", "File-based context (filter by file mentions)").option("-a, --all", "All context (everything, sorted by recency)").option("--max-tokens <tokens>", "Maximum tokens for context", parseInt).option("--type <type>", "Filter by entry type (decision, pattern, etc.)").option("-s, --summary", "Show context summary instead of full context").action(loadCommand);
3052
+ program.command("scan").description("Scan codebase for tagged comments (@decision, @pattern, @context)").option("--path <path>", "Specific directory to scan").option("--dry-run", "Show what would be captured without saving").option("--auto-confirm", "Skip draft queue, save directly as active").action(scanCommand);
3053
+ program.command("review").description("Review and confirm draft entries").option("--source <source>", "Filter by source (scan, hooks, mcp, import)").option("--confirm-all", "Confirm all pending drafts").option("--discard-all", "Discard all pending drafts").option("--count", "Show draft count only").action(reviewCommand);
3054
+ var rules = program.command("rules").description("Manage .contxt/rules.md file");
3055
+ rules.command("sync").description("Sync rules.md into memory store").option("--dry-run", "Show what would be synced without saving").option("-f, --force", "Force sync even if conflicts exist").action(rulesCommand.sync);
3056
+ rules.command("generate").description("Generate rules.md from memory store").option("--dry-run", "Show what would be generated without writing").option("-f, --force", "Overwrite existing rules.md").action(rulesCommand.generate);
3057
+ rules.command("diff").description("Show differences between rules.md and memory").action(rulesCommand.diff);
3058
+ program.command("capture").description("Extract context from existing project files").option("--source <source>", "Source to capture from (readme, cursor, claude, adr, commits, package, all)").option("--dry-run", "Show what would be captured without saving").option("--auto-confirm", "Skip draft queue, save directly as active").option("--limit <limit>", "Limit number of commits to scan (default: 50)", parseInt).action(captureCommand);
3059
+ var hook = program.command("hook").description("Manage git hooks for automatic context capture");
3060
+ hook.command("install").description("Install git hooks").option("--hooks <hooks>", "Comma-separated hooks to install (post-commit, pre-push, post-checkout, prepare-commit-msg)").action(hookCommand.install);
3061
+ hook.command("uninstall").description("Uninstall git hooks").option("--hooks <hooks>", "Comma-separated hooks to remove").action(hookCommand.uninstall);
3062
+ hook.command("status").description("Show installed hook status").action(hookCommand.status);
3063
+ hook.command("run <hook-name>").description("Run a specific hook (called internally by git)").action(hookCommand.run);
3064
+ program.command("watch").description("Start background file watcher for passive context capture").option("--daemon", "Run as background process").action(watchCommand.start);
3065
+ program.command("watch:stop").description("Stop the background watch daemon").action(watchCommand.stop);
3066
+ program.command("watch:status").description("Show watch daemon status").action(watchCommand.status);
3067
+ program.parse();
3068
+ //# sourceMappingURL=contxt.js.map