@sooink/ai-session-tidy 0.1.1 → 0.1.3

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.
@@ -1,6 +1,8 @@
1
1
  import { watch, FSWatcher } from 'chokidar';
2
2
  import { access } from 'fs/promises';
3
3
 
4
+ import { IGNORED_SYSTEM_PATTERNS } from './constants.js';
5
+
4
6
  export interface WatchEvent {
5
7
  path: string;
6
8
  timestamp: Date;
@@ -13,6 +15,8 @@ export interface WatcherOptions {
13
15
  /** Debounce time to batch multiple delete events (default: 10 seconds) */
14
16
  debounceMs?: number;
15
17
  depth?: number;
18
+ /** User-defined paths to ignore */
19
+ ignorePaths?: string[];
16
20
  /** Callback to handle batched delete events */
17
21
  onDelete: (events: WatchEvent[]) => void;
18
22
  }
@@ -62,6 +66,8 @@ export class Watcher {
62
66
  private batchedEvents: WatchEvent[] = [];
63
67
  /** Debounce timer */
64
68
  private debounceTimer: NodeJS.Timeout | null = null;
69
+ /** Paths that errored (to avoid duplicate logs) */
70
+ private erroredPaths: Set<string> = new Set();
65
71
 
66
72
  constructor(options: WatcherOptions) {
67
73
  this.options = options;
@@ -71,10 +77,17 @@ export class Watcher {
71
77
  start(): void {
72
78
  if (this.watcher) return;
73
79
 
80
+ // Combine hardcoded patterns with user-defined ignore paths
81
+ const ignored: (RegExp | string)[] = [...IGNORED_SYSTEM_PATTERNS];
82
+ if (this.options.ignorePaths) {
83
+ ignored.push(...this.options.ignorePaths);
84
+ }
85
+
74
86
  this.watcher = watch(this.options.watchPaths, {
75
87
  ignoreInitial: true,
76
88
  persistent: true,
77
89
  depth: this.options.depth ?? 3,
90
+ ignored,
78
91
  });
79
92
 
80
93
  this.watcher.on('unlinkDir', (path) => {
@@ -84,6 +97,15 @@ export class Watcher {
84
97
  this.watcher.on('addDir', (path) => {
85
98
  this.handleRecovery(path);
86
99
  });
100
+
101
+ // Handle errors gracefully - log and continue watching
102
+ this.watcher.on('error', (error) => {
103
+ const pathInfo = (error as NodeJS.ErrnoException & { path?: string }).path;
104
+ if (pathInfo && !this.erroredPaths.has(pathInfo)) {
105
+ this.erroredPaths.add(pathInfo);
106
+ console.error(`[watch] Cannot watch: ${pathInfo} (${(error as NodeJS.ErrnoException).code})`);
107
+ }
108
+ });
87
109
  }
88
110
 
89
111
  stop(): void {
@@ -28,11 +28,11 @@ export function getAppVersion(): string {
28
28
  }
29
29
 
30
30
  export interface Config {
31
- version?: string; // app version that last saved this config
32
- configVersion?: string; // config schema version
31
+ configVersion?: string; // config schema version for migration
33
32
  watchPaths?: string[];
34
33
  watchDelay?: number; // minutes
35
34
  watchDepth?: number; // folder depth (default: 3, max: 5)
35
+ ignorePaths?: string[]; // paths to ignore from watching
36
36
  }
37
37
 
38
38
  function getConfigPath(): string {
@@ -62,10 +62,12 @@ export function saveConfig(config: Config): void {
62
62
  mkdirSync(configDir, { recursive: true });
63
63
  }
64
64
 
65
+ // Remove deprecated 'version' field if present
66
+ const { version: _removed, ...cleanConfig } = config as Config & { version?: string };
67
+
65
68
  const configWithVersion: Config = {
66
- version: getAppVersion(),
67
69
  configVersion: CONFIG_VERSION,
68
- ...config,
70
+ ...cleanConfig,
69
71
  };
70
72
 
71
73
  writeFileSync(configPath, JSON.stringify(configWithVersion, null, 2), 'utf-8');
@@ -130,3 +132,31 @@ export function setWatchDepth(depth: number): void {
130
132
  export function resetConfig(): void {
131
133
  saveConfig({});
132
134
  }
135
+
136
+ export function getIgnorePaths(): string[] | undefined {
137
+ const config = loadConfig();
138
+ return config.ignorePaths;
139
+ }
140
+
141
+ export function addIgnorePath(path: string): void {
142
+ const config = loadConfig();
143
+ const resolved = resolve(path.replace(/^~/, homedir()));
144
+ const paths = config.ignorePaths ?? [];
145
+ if (!paths.includes(resolved)) {
146
+ paths.push(resolved);
147
+ config.ignorePaths = paths;
148
+ saveConfig(config);
149
+ }
150
+ }
151
+
152
+ export function removeIgnorePath(path: string): boolean {
153
+ const config = loadConfig();
154
+ const resolved = resolve(path.replace(/^~/, homedir()));
155
+ const paths = config.ignorePaths ?? [];
156
+ const index = paths.indexOf(resolved);
157
+ if (index === -1) return false;
158
+ paths.splice(index, 1);
159
+ config.ignorePaths = paths;
160
+ saveConfig(config);
161
+ return true;
162
+ }
@@ -1,3 +1,4 @@
1
+ import { existsSync } from 'fs';
1
2
  import { homedir } from 'os';
2
3
  import { join } from 'path';
3
4
 
@@ -14,13 +15,45 @@ export function encodePath(path: string): string {
14
15
 
15
16
  /**
16
17
  * Decode Claude Code encoded path to Unix path
17
- * -home-user-project /home/user/project
18
+ * Handles hyphenated folder names by checking filesystem
19
+ *
20
+ * -Users-kory-my-project → /Users/kory/my-project (if exists)
21
+ * -Users-kory-my-project → /Users/kory/my/project (fallback)
18
22
  */
19
23
  export function decodePath(encoded: string): string {
20
24
  if (encoded === '') return '';
21
- // Treat as Unix encoding if it starts with a dash
22
25
  if (!encoded.startsWith('-')) return encoded;
23
- return encoded.replace(/-/g, '/');
26
+
27
+ // Split by dash (remove leading dash first)
28
+ const parts = encoded.slice(1).split('-');
29
+
30
+ // Try to reconstruct path by checking filesystem
31
+ return reconstructPath(parts, '');
32
+ }
33
+
34
+ /**
35
+ * Recursively reconstruct path by trying different combinations
36
+ */
37
+ function reconstructPath(parts: string[], currentPath: string): string {
38
+ if (parts.length === 0) return currentPath;
39
+
40
+ // Try combining segments (longest first to prefer hyphenated names)
41
+ for (let len = parts.length; len >= 1; len--) {
42
+ const segment = parts.slice(0, len).join('-');
43
+ const testPath = currentPath + '/' + segment;
44
+
45
+ if (existsSync(testPath)) {
46
+ // Found existing path, continue with remaining parts
47
+ const result = reconstructPath(parts.slice(len), testPath);
48
+ // Verify the final path exists (or is the best we can do)
49
+ if (existsSync(result) || parts.slice(len).length === 0) {
50
+ return result;
51
+ }
52
+ }
53
+ }
54
+
55
+ // No existing path found, use simple decode for remaining parts
56
+ return currentPath + '/' + parts.join('/');
24
57
  }
25
58
 
26
59
  /**