@mattli/dotmd 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.
Files changed (78) hide show
  1. package/README.md +77 -0
  2. package/dist/cli/commands/init.d.ts +4 -0
  3. package/dist/cli/commands/init.d.ts.map +1 -0
  4. package/dist/cli/commands/init.js +23 -0
  5. package/dist/cli/commands/init.js.map +1 -0
  6. package/dist/cli/commands/install-hook.d.ts +2 -0
  7. package/dist/cli/commands/install-hook.d.ts.map +1 -0
  8. package/dist/cli/commands/install-hook.js +31 -0
  9. package/dist/cli/commands/install-hook.js.map +1 -0
  10. package/dist/cli/commands/scan.d.ts +5 -0
  11. package/dist/cli/commands/scan.d.ts.map +1 -0
  12. package/dist/cli/commands/scan.js +75 -0
  13. package/dist/cli/commands/scan.js.map +1 -0
  14. package/dist/cli/commands/serve.d.ts +4 -0
  15. package/dist/cli/commands/serve.d.ts.map +1 -0
  16. package/dist/cli/commands/serve.js +8 -0
  17. package/dist/cli/commands/serve.js.map +1 -0
  18. package/dist/cli/commands/status.d.ts +2 -0
  19. package/dist/cli/commands/status.d.ts.map +1 -0
  20. package/dist/cli/commands/status.js +70 -0
  21. package/dist/cli/commands/status.js.map +1 -0
  22. package/dist/cli/index.d.ts +3 -0
  23. package/dist/cli/index.d.ts.map +1 -0
  24. package/dist/cli/index.js +38 -0
  25. package/dist/cli/index.js.map +1 -0
  26. package/dist/config/defaults.d.ts +13 -0
  27. package/dist/config/defaults.d.ts.map +1 -0
  28. package/dist/config/defaults.js +36 -0
  29. package/dist/config/defaults.js.map +1 -0
  30. package/dist/config/loader.d.ts +4 -0
  31. package/dist/config/loader.d.ts.map +1 -0
  32. package/dist/config/loader.js +28 -0
  33. package/dist/config/loader.js.map +1 -0
  34. package/dist/dashboard/layout.d.ts +6 -0
  35. package/dist/dashboard/layout.d.ts.map +1 -0
  36. package/dist/dashboard/layout.js +47 -0
  37. package/dist/dashboard/layout.js.map +1 -0
  38. package/dist/dashboard/server.d.ts +5 -0
  39. package/dist/dashboard/server.d.ts.map +1 -0
  40. package/dist/dashboard/server.js +305 -0
  41. package/dist/dashboard/server.js.map +1 -0
  42. package/dist/dashboard/settings-client.d.ts +2 -0
  43. package/dist/dashboard/settings-client.d.ts.map +1 -0
  44. package/dist/dashboard/settings-client.js +310 -0
  45. package/dist/dashboard/settings-client.js.map +1 -0
  46. package/dist/dashboard/settings.d.ts +3 -0
  47. package/dist/dashboard/settings.d.ts.map +1 -0
  48. package/dist/dashboard/settings.js +99 -0
  49. package/dist/dashboard/settings.js.map +1 -0
  50. package/dist/dashboard/views.d.ts +8 -0
  51. package/dist/dashboard/views.d.ts.map +1 -0
  52. package/dist/dashboard/views.js +694 -0
  53. package/dist/dashboard/views.js.map +1 -0
  54. package/dist/dashboard/wizard-client.d.ts +2 -0
  55. package/dist/dashboard/wizard-client.d.ts.map +1 -0
  56. package/dist/dashboard/wizard-client.js +266 -0
  57. package/dist/dashboard/wizard-client.js.map +1 -0
  58. package/dist/dashboard/wizard.d.ts +9 -0
  59. package/dist/dashboard/wizard.d.ts.map +1 -0
  60. package/dist/dashboard/wizard.js +236 -0
  61. package/dist/dashboard/wizard.js.map +1 -0
  62. package/dist/scanner/git.d.ts +8 -0
  63. package/dist/scanner/git.d.ts.map +1 -0
  64. package/dist/scanner/git.js +34 -0
  65. package/dist/scanner/git.js.map +1 -0
  66. package/dist/scanner/index.d.ts +10 -0
  67. package/dist/scanner/index.d.ts.map +1 -0
  68. package/dist/scanner/index.js +60 -0
  69. package/dist/scanner/index.js.map +1 -0
  70. package/dist/storage/db.d.ts +3 -0
  71. package/dist/storage/db.d.ts.map +1 -0
  72. package/dist/storage/db.js +52 -0
  73. package/dist/storage/db.js.map +1 -0
  74. package/dist/storage/snapshots.d.ts +43 -0
  75. package/dist/storage/snapshots.d.ts.map +1 -0
  76. package/dist/storage/snapshots.js +102 -0
  77. package/dist/storage/snapshots.js.map +1 -0
  78. package/package.json +60 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":"AAEA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,WAAW,EAAc,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAiB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG7E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,EAAE,CAiC3D;AAED,wBAAgB,SAAS,CACvB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE,WAAW,GAClB,UAAU,EAAE,CAyBd"}
@@ -0,0 +1,60 @@
1
+ import fs from "fs";
2
+ import fg from "fast-glob";
3
+ import { expandHome } from "../config/defaults.js";
4
+ import { storeSnapshot } from "../storage/snapshots.js";
5
+ export function discoverFiles(config) {
6
+ const files = [];
7
+ for (const root of config.scan_roots) {
8
+ const expandedRoot = expandHome(root);
9
+ if (!fs.existsSync(expandedRoot)) {
10
+ continue;
11
+ }
12
+ for (let pattern of config.patterns) {
13
+ // Prepend **/ so patterns match at any depth
14
+ if (!pattern.startsWith("**/") && !pattern.startsWith("/")) {
15
+ pattern = `**/${pattern}`;
16
+ }
17
+ try {
18
+ const matches = fg.sync(pattern, {
19
+ cwd: expandedRoot,
20
+ absolute: true,
21
+ ignore: config.exclude,
22
+ dot: true,
23
+ followSymbolicLinks: false,
24
+ caseSensitiveMatch: false,
25
+ });
26
+ files.push(...matches);
27
+ }
28
+ catch {
29
+ // skip patterns that error
30
+ }
31
+ }
32
+ }
33
+ // deduplicate
34
+ return [...new Set(files)].sort();
35
+ }
36
+ export function scanFiles(db, config) {
37
+ const files = discoverFiles(config);
38
+ const discoveredSet = new Set(files);
39
+ const results = [];
40
+ for (const filePath of files) {
41
+ try {
42
+ const content = fs.readFileSync(filePath, "utf-8");
43
+ const result = storeSnapshot(db, filePath, content);
44
+ results.push({ path: filePath, result });
45
+ }
46
+ catch {
47
+ // skip unreadable files
48
+ }
49
+ }
50
+ // Remove tracked files that no longer exist on disk or aren't in the current config
51
+ const allTracked = db.prepare("SELECT id, path FROM tracked_files").all();
52
+ for (const tracked of allTracked) {
53
+ if (!discoveredSet.has(tracked.path) || !fs.existsSync(tracked.path)) {
54
+ db.prepare("DELETE FROM snapshots WHERE file_id = ?").run(tracked.id);
55
+ db.prepare("DELETE FROM tracked_files WHERE id = ?").run(tracked.id);
56
+ }
57
+ }
58
+ return results;
59
+ }
60
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,WAAW,CAAC;AAE3B,OAAO,EAAe,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAuB,MAAM,yBAAyB,CAAC;AAQ7E,MAAM,UAAU,aAAa,CAAC,MAAmB;IAC/C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACrC,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAEtC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,SAAS;QACX,CAAC;QAED,KAAK,IAAI,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpC,6CAA6C;YAC7C,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3D,OAAO,GAAG,MAAM,OAAO,EAAE,CAAC;YAC5B,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE;oBAC/B,GAAG,EAAE,YAAY;oBACjB,QAAQ,EAAE,IAAI;oBACd,MAAM,EAAE,MAAM,CAAC,OAAO;oBACtB,GAAG,EAAE,IAAI;oBACT,mBAAmB,EAAE,KAAK;oBAC1B,kBAAkB,EAAE,KAAK;iBAC1B,CAAC,CAAC;gBACH,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;YACzB,CAAC;YAAC,MAAM,CAAC;gBACP,2BAA2B;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IAED,cAAc;IACd,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,EAAqB,EACrB,MAAmB;IAEnB,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,OAAO,GAAiB,EAAE,CAAC;IAEjC,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,MAAM,GAAG,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;YACpD,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC;IAED,oFAAoF;IACpF,MAAM,UAAU,GAAG,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC,GAAG,EAAyC,CAAC;IACjH,KAAK,MAAM,OAAO,IAAI,UAAU,EAAE,CAAC;QACjC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACrE,EAAE,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACtE,EAAE,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import Database from "better-sqlite3";
2
+ export declare function getDb(dbPath?: string): Database.Database;
3
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/storage/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAKtC,wBAAgB,KAAK,CAAC,MAAM,GAAE,MAAgB,GAAG,QAAQ,CAAC,QAAQ,CAajE"}
@@ -0,0 +1,52 @@
1
+ import Database from "better-sqlite3";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { DB_PATH } from "../config/defaults.js";
5
+ export function getDb(dbPath = DB_PATH) {
6
+ const dir = path.dirname(dbPath);
7
+ if (!fs.existsSync(dir)) {
8
+ fs.mkdirSync(dir, { recursive: true });
9
+ }
10
+ const db = new Database(dbPath);
11
+ db.pragma("journal_mode = WAL");
12
+ db.pragma("foreign_keys = ON");
13
+ migrate(db);
14
+ return db;
15
+ }
16
+ function migrate(db) {
17
+ db.exec(`
18
+ CREATE TABLE IF NOT EXISTS tracked_files (
19
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
20
+ path TEXT UNIQUE NOT NULL,
21
+ category TEXT NOT NULL DEFAULT 'custom',
22
+ first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
23
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
24
+ );
25
+
26
+ CREATE TABLE IF NOT EXISTS snapshots (
27
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
28
+ file_id INTEGER NOT NULL REFERENCES tracked_files(id),
29
+ content_hash TEXT NOT NULL,
30
+ content TEXT NOT NULL,
31
+ diff TEXT,
32
+ git_commit TEXT,
33
+ git_author TEXT,
34
+ git_message TEXT,
35
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
36
+ );
37
+
38
+ CREATE TABLE IF NOT EXISTS sessions (
39
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
40
+ started_at TEXT NOT NULL DEFAULT (datetime('now'))
41
+ );
42
+
43
+ CREATE INDEX IF NOT EXISTS idx_snapshots_file_id ON snapshots(file_id);
44
+ CREATE INDEX IF NOT EXISTS idx_snapshots_created_at ON snapshots(created_at);
45
+ `);
46
+ // Migration: add last_viewed_at column
47
+ const columns = db.prepare("PRAGMA table_info(tracked_files)").all();
48
+ if (!columns.some((c) => c.name === "last_viewed_at")) {
49
+ db.exec("ALTER TABLE tracked_files ADD COLUMN last_viewed_at TEXT");
50
+ }
51
+ }
52
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/storage/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,OAAO,EAAa,MAAM,uBAAuB,CAAC;AAE3D,MAAM,UAAU,KAAK,CAAC,SAAiB,OAAO;IAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAE/B,OAAO,CAAC,EAAE,CAAC,CAAC;IAEZ,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,OAAO,CAAC,EAAqB;IACpC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BP,CAAC,CAAC;IAEH,uCAAuC;IACvC,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC,GAAG,EAA6B,CAAC;IAChG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,gBAAgB,CAAC,EAAE,CAAC;QACtD,EAAE,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;IACtE,CAAC;AACH,CAAC"}
@@ -0,0 +1,43 @@
1
+ import Database from "better-sqlite3";
2
+ export interface TrackedFile {
3
+ id: number;
4
+ path: string;
5
+ category: string;
6
+ first_seen_at: string;
7
+ last_seen_at: string;
8
+ }
9
+ export interface Snapshot {
10
+ id: number;
11
+ file_id: number;
12
+ content_hash: string;
13
+ content: string;
14
+ diff: string | null;
15
+ git_commit: string | null;
16
+ git_author: string | null;
17
+ git_message: string | null;
18
+ created_at: string;
19
+ }
20
+ export declare function hashContent(content: string): string;
21
+ export declare function categorizeFile(filePath: string): string;
22
+ export declare function upsertTrackedFile(db: Database.Database, filePath: string): TrackedFile;
23
+ export declare function getLatestSnapshot(db: Database.Database, fileId: number): Snapshot | undefined;
24
+ export interface StoreSnapshotResult {
25
+ changed: boolean;
26
+ isNew: boolean;
27
+ snapshot: Snapshot;
28
+ }
29
+ export declare function storeSnapshot(db: Database.Database, filePath: string, content: string, gitInfo?: {
30
+ commit: string;
31
+ author: string;
32
+ message: string;
33
+ }): StoreSnapshotResult;
34
+ export declare function getTrackedFiles(db: Database.Database): TrackedFile[];
35
+ export declare function getFileSnapshots(db: Database.Database, fileId: number, limit?: number, offset?: number): Snapshot[];
36
+ export declare function getRecentChanges(db: Database.Database, since?: string, limit?: number, offset?: number): (Snapshot & {
37
+ path: string;
38
+ category: string;
39
+ })[];
40
+ export declare function recordSession(db: Database.Database): number;
41
+ export declare function getLastSessionTime(db: Database.Database): string | null;
42
+ export declare function getLatestSessionTime(db: Database.Database): string | null;
43
+ //# sourceMappingURL=snapshots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshots.d.ts","sourceRoot":"","sources":["../../src/storage/snapshots.ts"],"names":[],"mappings":"AAEA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAGtC,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAevD;AAED,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,QAAQ,EAAE,MAAM,GACf,WAAW,CAYb;AAED,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE,MAAM,GACb,QAAQ,GAAG,SAAS,CAMtB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC5D,mBAAmB,CAkCrB;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,WAAW,EAAE,CAIpE;AAED,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,GACjB,QAAQ,EAAE,CAMZ;AAED,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,KAAK,CAAC,EAAE,MAAM,EACd,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,GACjB,CAAC,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,CAuBnD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,CAG3D;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,GAAG,IAAI,CAOvE;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,GAAG,IAAI,CAOzE"}
@@ -0,0 +1,102 @@
1
+ import crypto from "crypto";
2
+ import os from "os";
3
+ import { createPatch } from "diff";
4
+ export function hashContent(content) {
5
+ return crypto.createHash("sha256").update(content).digest("hex");
6
+ }
7
+ export function categorizeFile(filePath) {
8
+ if (filePath.includes("/.claude/projects/") && filePath.includes("/memory/")) {
9
+ return "memory";
10
+ }
11
+ if (filePath.includes("/.claude/")) {
12
+ return "global";
13
+ }
14
+ // Derive project name from path: ~/nanoclaw/anything -> "nanoclaw"
15
+ const homeDir = os.homedir();
16
+ const rel = filePath.startsWith(homeDir) ? filePath.slice(homeDir.length + 1) : filePath;
17
+ const firstDir = rel.split("/")[0];
18
+ if (firstDir) {
19
+ return "project:" + firstDir;
20
+ }
21
+ return "custom";
22
+ }
23
+ export function upsertTrackedFile(db, filePath) {
24
+ const category = categorizeFile(filePath);
25
+ db.prepare(`INSERT INTO tracked_files (path, category)
26
+ VALUES (?, ?)
27
+ ON CONFLICT(path) DO UPDATE SET category = ?, last_seen_at = datetime('now')`).run(filePath, category, category);
28
+ return db
29
+ .prepare("SELECT * FROM tracked_files WHERE path = ?")
30
+ .get(filePath);
31
+ }
32
+ export function getLatestSnapshot(db, fileId) {
33
+ return db
34
+ .prepare("SELECT * FROM snapshots WHERE file_id = ? ORDER BY created_at DESC, id DESC LIMIT 1")
35
+ .get(fileId);
36
+ }
37
+ export function storeSnapshot(db, filePath, content, gitInfo) {
38
+ const file = upsertTrackedFile(db, filePath);
39
+ const contentHash = hashContent(content);
40
+ const latest = getLatestSnapshot(db, file.id);
41
+ if (latest && latest.content_hash === contentHash) {
42
+ return { changed: false, isNew: false, snapshot: latest };
43
+ }
44
+ const isNew = !latest;
45
+ const diff = latest
46
+ ? createPatch(filePath, latest.content, content, "previous", "current")
47
+ : null;
48
+ const result = db
49
+ .prepare(`INSERT INTO snapshots (file_id, content_hash, content, diff, git_commit, git_author, git_message)
50
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
51
+ .run(file.id, contentHash, content, diff, gitInfo?.commit ?? null, gitInfo?.author ?? null, gitInfo?.message ?? null);
52
+ const snapshot = db
53
+ .prepare("SELECT * FROM snapshots WHERE id = ?")
54
+ .get(result.lastInsertRowid);
55
+ return { changed: true, isNew, snapshot };
56
+ }
57
+ export function getTrackedFiles(db) {
58
+ return db
59
+ .prepare("SELECT * FROM tracked_files ORDER BY category, path")
60
+ .all();
61
+ }
62
+ export function getFileSnapshots(db, fileId, limit = 50, offset = 0) {
63
+ return db
64
+ .prepare("SELECT * FROM snapshots WHERE file_id = ? ORDER BY created_at DESC, id DESC LIMIT ? OFFSET ?")
65
+ .all(fileId, limit, offset);
66
+ }
67
+ export function getRecentChanges(db, since, limit = 50, offset = 0) {
68
+ if (since) {
69
+ return db
70
+ .prepare(`SELECT s.*, f.path, f.category
71
+ FROM snapshots s
72
+ JOIN tracked_files f ON s.file_id = f.id
73
+ WHERE s.created_at > ?
74
+ ORDER BY s.created_at DESC, s.id DESC
75
+ LIMIT ? OFFSET ?`)
76
+ .all(since, limit, offset);
77
+ }
78
+ return db
79
+ .prepare(`SELECT s.*, f.path, f.category
80
+ FROM snapshots s
81
+ JOIN tracked_files f ON s.file_id = f.id
82
+ ORDER BY s.created_at DESC
83
+ LIMIT ? OFFSET ?`)
84
+ .all(limit, offset);
85
+ }
86
+ export function recordSession(db) {
87
+ const result = db.prepare("INSERT INTO sessions DEFAULT VALUES").run();
88
+ return Number(result.lastInsertRowid);
89
+ }
90
+ export function getLastSessionTime(db) {
91
+ const row = db
92
+ .prepare("SELECT started_at FROM sessions ORDER BY started_at DESC LIMIT 1 OFFSET 1")
93
+ .get();
94
+ return row?.started_at ?? null;
95
+ }
96
+ export function getLatestSessionTime(db) {
97
+ const row = db
98
+ .prepare("SELECT started_at FROM sessions ORDER BY started_at DESC LIMIT 1")
99
+ .get();
100
+ return row?.started_at ?? null;
101
+ }
102
+ //# sourceMappingURL=snapshots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../../src/storage/snapshots.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@mattli/dotmd",
3
+ "version": "0.1.0",
4
+ "description": "Track changes to AI instruction files (CLAUDE.md, AGENTS.md, memory files)",
5
+ "type": "module",
6
+ "bin": {
7
+ "dotmd": "./dist/cli/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "prepublishOnly": "npm run build",
16
+ "dev": "tsx src/cli/index.ts",
17
+ "dev:serve": "tsx --watch src/cli/index.ts serve",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest"
20
+ },
21
+ "keywords": [
22
+ "claude",
23
+ "claude-code",
24
+ "ai",
25
+ "instruction-files",
26
+ "dotmd",
27
+ "cursor",
28
+ "codex",
29
+ "agents-md"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/mattli/dotmd.git"
34
+ },
35
+ "homepage": "https://github.com/mattli/dotmd",
36
+ "author": "Matt Li",
37
+ "license": "MIT",
38
+ "engines": {
39
+ "node": ">=18"
40
+ },
41
+ "dependencies": {
42
+ "@hono/node-server": "^1.19.12",
43
+ "better-sqlite3": "^11.0.0",
44
+ "chalk": "^5.3.0",
45
+ "commander": "^12.0.0",
46
+ "diff": "^7.0.0",
47
+ "fast-glob": "^3.3.0",
48
+ "gray-matter": "^4.0.3",
49
+ "hono": "^4.12.9",
50
+ "yaml": "^2.4.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/better-sqlite3": "^7.6.0",
54
+ "@types/diff": "^6.0.0",
55
+ "@types/node": "^20.0.0",
56
+ "tsx": "^4.0.0",
57
+ "typescript": "^5.4.0",
58
+ "vitest": "^3.0.0"
59
+ }
60
+ }