marmot-logger 1.0.7 → 1.0.10

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/bin/marmot.js CHANGED
@@ -27,12 +27,12 @@ program
27
27
 
28
28
  program
29
29
  .command('enable <plugin>')
30
- .description('Enable a plugin (file-monitor, terminal, git-hooks, makefile, claude-hooks, process-monitor)')
30
+ .description('Enable a plugin (file-monitor, terminal, git-hooks, makefile, claude-hooks, process-monitor, all)')
31
31
  .action(enable);
32
32
 
33
33
  program
34
34
  .command('disable <plugin>')
35
- .description('Disable a plugin')
35
+ .description('Disable a plugin (or "all" to disable all)')
36
36
  .action(disable);
37
37
 
38
38
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "marmot-logger",
3
- "version": "1.0.7",
3
+ "version": "1.0.10",
4
4
  "description": "Activity monitoring tool for developer workflows - tracks file changes, terminal commands, git operations, and Claude Code hooks",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -4,10 +4,21 @@ const plugins = require('../plugins');
4
4
 
5
5
  const VALID_PLUGINS = ['file-monitor', 'terminal', 'git-hooks', 'makefile', 'claude-hooks', 'process-monitor'];
6
6
 
7
- module.exports = async function disable(pluginName) {
7
+ async function disable(pluginName) {
8
+ // Handle "all" - disable all plugins
9
+ if (pluginName === 'all') {
10
+ console.log(chalk.bold('Disabling all plugins...\n'));
11
+ for (const plugin of VALID_PLUGINS) {
12
+ await disable(plugin);
13
+ console.log('');
14
+ }
15
+ console.log(chalk.green('✓ All plugins disabled.'));
16
+ return;
17
+ }
18
+
8
19
  if (!VALID_PLUGINS.includes(pluginName)) {
9
20
  console.log(chalk.red(`Unknown plugin: ${pluginName}`));
10
- console.log('Available plugins:', VALID_PLUGINS.join(', '));
21
+ console.log('Available plugins:', VALID_PLUGINS.join(', '), ', all');
11
22
  process.exit(1);
12
23
  }
13
24
 
@@ -37,4 +48,6 @@ module.exports = async function disable(pluginName) {
37
48
  config.saveConfig(projectConfig);
38
49
 
39
50
  console.log(chalk.green(`✓ Plugin '${pluginName}' disabled.`));
40
- };
51
+ }
52
+
53
+ module.exports = disable;
package/src/cli/enable.js CHANGED
@@ -4,10 +4,21 @@ const plugins = require('../plugins');
4
4
 
5
5
  const VALID_PLUGINS = ['file-monitor', 'terminal', 'git-hooks', 'makefile', 'claude-hooks', 'process-monitor'];
6
6
 
7
- module.exports = async function enable(pluginName) {
7
+ async function enable(pluginName) {
8
+ // Handle "all" - enable all plugins
9
+ if (pluginName === 'all') {
10
+ console.log(chalk.bold('Enabling all plugins...\n'));
11
+ for (const plugin of VALID_PLUGINS) {
12
+ await enable(plugin);
13
+ console.log('');
14
+ }
15
+ console.log(chalk.green('✓ All plugins enabled.'));
16
+ return;
17
+ }
18
+
8
19
  if (!VALID_PLUGINS.includes(pluginName)) {
9
20
  console.log(chalk.red(`Unknown plugin: ${pluginName}`));
10
- console.log('Available plugins:', VALID_PLUGINS.join(', '));
21
+ console.log('Available plugins:', VALID_PLUGINS.join(', '), ', all');
11
22
  process.exit(1);
12
23
  }
13
24
 
@@ -37,4 +48,6 @@ module.exports = async function enable(pluginName) {
37
48
  console.log(chalk.red(`Setup error: ${err.message}`));
38
49
  }
39
50
  }
40
- };
51
+ }
52
+
53
+ module.exports = enable;
@@ -4,6 +4,8 @@ const path = require('path');
4
4
  /**
5
5
  * Parse .gitignore file and return patterns
6
6
  * Skips comments, empty lines, and negation patterns (!)
7
+ * Preserves trailing spaces (significant in gitignore)
8
+ * Handles escaped # and ! at line start
7
9
  */
8
10
  function parseGitignore(projectDir) {
9
11
  const gitignorePath = path.join(projectDir, '.gitignore');
@@ -16,8 +18,15 @@ function parseGitignore(projectDir) {
16
18
  const content = fs.readFileSync(gitignorePath, 'utf8');
17
19
  return content
18
20
  .split('\n')
19
- .map(line => line.trim())
20
- .filter(line => line && !line.startsWith('#') && !line.startsWith('!'));
21
+ .map(line => line.replace(/^\s+/, '')) // Only trim leading whitespace
22
+ .filter(line => {
23
+ if (!line) return false;
24
+ // Skip comments (but not escaped \#)
25
+ if (line.startsWith('#') && !line.startsWith('\\#')) return false;
26
+ // Skip negation patterns (but not escaped \!)
27
+ if (line.startsWith('!') && !line.startsWith('\\!')) return false;
28
+ return true;
29
+ });
21
30
  } catch (err) {
22
31
  return [];
23
32
  }
@@ -67,30 +76,107 @@ function shouldExclude(filePath, projectDir) {
67
76
  }
68
77
 
69
78
  /**
70
- * Simple pattern matching for gitignore-style patterns
71
- * Supports: wildcards (*), directory markers (/), character classes ([])
79
+ * Comprehensive gitignore-style pattern matching
80
+ * Supports:
81
+ * - Wildcards: * (single component), ** (any depth), ? (single char)
82
+ * - Directory markers: trailing / (directory only)
83
+ * - Root anchoring: leading / (match from root only)
84
+ * - Character classes: [abc], [0-9], [!abc] (negated)
85
+ * - Escape sequences: \#, \!, \ , \\
86
+ * - Globstar patterns: **\/foo, foo/**, a/**\/b
72
87
  */
73
88
  function matchPattern(filePath, pattern) {
74
- // Handle trailing slash (directory pattern)
75
- const isDir = pattern.endsWith('/');
76
- let cleanPattern = isDir ? pattern.slice(0, -1) : pattern;
89
+ // Skip empty patterns
90
+ if (!pattern) return false;
77
91
 
78
- // Check if pattern has path separators (anchored pattern)
79
- const isAnchored = cleanPattern.includes('/');
92
+ // Handle escape sequences (\ followed by special char)
93
+ // \# -> #, \! -> !, \ -> space, \\ -> \
94
+ let cleanPattern = pattern.replace(/\\([#! \\])/g, '\x00ESC:$1\x00');
80
95
 
81
- // Convert gitignore pattern to regex
96
+ // Handle trailing slash (directory pattern - matches dir and contents)
97
+ const isDir = cleanPattern.endsWith('/');
98
+ if (isDir) {
99
+ cleanPattern = cleanPattern.slice(0, -1);
100
+ }
101
+
102
+ // Handle leading slash (root-anchored)
103
+ const hasLeadingSlash = cleanPattern.startsWith('/');
104
+ if (hasLeadingSlash) {
105
+ cleanPattern = cleanPattern.slice(1);
106
+ }
107
+
108
+ // Handle **/ prefix (matches at any depth, including root)
109
+ const hasGlobstarPrefix = cleanPattern.startsWith('**/');
110
+ if (hasGlobstarPrefix) {
111
+ cleanPattern = cleanPattern.slice(3);
112
+ }
113
+
114
+ // Handle /** suffix (matches everything inside)
115
+ const hasGlobstarSuffix = cleanPattern.endsWith('/**');
116
+ if (hasGlobstarSuffix) {
117
+ cleanPattern = cleanPattern.slice(0, -3);
118
+ }
119
+
120
+ // Handle */ prefix (matches basename at any depth) - rsync style
121
+ const hasWildcardPrefix = cleanPattern.startsWith('*/');
122
+ if (hasWildcardPrefix) {
123
+ cleanPattern = cleanPattern.slice(2);
124
+ }
125
+
126
+ // Determine if pattern is anchored to root
127
+ // Anchored if: had leading slash, OR contains / in the middle (after processing prefixes)
128
+ const isAnchored = hasLeadingSlash || (cleanPattern.includes('/') && !hasGlobstarPrefix && !hasWildcardPrefix);
129
+
130
+ // Convert pattern to regex
82
131
  let regex = cleanPattern
83
- .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape regex special chars except * and ?
84
- .replace(/\*\*/g, '<<<GLOBSTAR>>>') // Preserve **
85
- .replace(/\*/g, '[^/]*') // * matches anything except /
86
- .replace(/<<<GLOBSTAR>>>/g, '.*') // ** matches anything including /
87
- .replace(/\?/g, '[^/]'); // ? matches single char except /
88
-
89
- // Patterns without / match anywhere in path
90
- if (!isAnchored) {
91
- regex = `(^|/)${regex}($|/)`;
92
- } else {
132
+ // First, protect character classes from escaping
133
+ .replace(/\[(!?)([^\]]+)\]/g, '\x00CHARCLASS:$1:$2\x00')
134
+ // Escape regex special chars (except * and ?)
135
+ .replace(/[.+^${}()|\\]/g, '\\$&')
136
+ // Restore character classes with proper regex syntax
137
+ .replace(/\x00CHARCLASS:(!?):([^\x00]+)\x00/g, (_, neg, chars) => {
138
+ return '[' + (neg === '!' ? '^' : '') + chars + ']';
139
+ })
140
+ // Handle **/ in middle of pattern (matches zero or more path components)
141
+ // a/**/b should match a/b, a/x/b, a/x/y/b
142
+ .replace(/\/\*\*\//g, '\x00MIDDLEGLOB\x00')
143
+ // Handle ** (globstar - matches anything including /)
144
+ .replace(/\*\*/g, '\x00GLOBSTAR\x00')
145
+ // Handle * (matches anything except /)
146
+ .replace(/\*/g, '[^/]*')
147
+ // Handle ? (matches single char except /)
148
+ .replace(/\?/g, '[^/]')
149
+ // Restore globstars with proper regex
150
+ .replace(/\x00MIDDLEGLOB\x00/g, '/(.*/)?')
151
+ .replace(/\x00GLOBSTAR\x00/g, '.*')
152
+ // Restore escaped characters
153
+ .replace(/\x00ESC:#\x00/g, '#')
154
+ .replace(/\x00ESC:!\x00/g, '!')
155
+ .replace(/\x00ESC: \x00/g, ' ')
156
+ .replace(/\x00ESC:\\\x00/g, '\\\\');
157
+
158
+ // Build final regex based on pattern type
159
+ if (hasGlobstarPrefix || hasWildcardPrefix) {
160
+ // Match at any depth (including root)
161
+ regex = `(^|/)${regex}`;
162
+ } else if (isAnchored) {
163
+ // Match from root only
93
164
  regex = `^${regex}`;
165
+ } else {
166
+ // Unanchored pattern: match as basename anywhere in path
167
+ regex = `(^|/)${regex}`;
168
+ }
169
+
170
+ // Handle suffix matching - what comes after the pattern
171
+ if (hasGlobstarSuffix) {
172
+ // foo/** matches foo and everything inside
173
+ regex = regex + '($|/)';
174
+ } else if (isDir) {
175
+ // Directory pattern (trailing /): match dir and its contents
176
+ regex = regex + '($|/)';
177
+ } else {
178
+ // Regular pattern: match as complete path component or with trailing content
179
+ regex = regex + '($|/)';
94
180
  }
95
181
 
96
182
  try {