claudex-setup 1.16.0 → 1.16.2
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/CHANGELOG.md +21 -0
- package/README.md +52 -23
- package/bin/cli.js +92 -5
- package/content/launch-posts.md +159 -92
- package/package.json +2 -2
- package/src/activity.js +195 -1
- package/src/analyze.js +11 -6
- package/src/audit.js +49 -14
- package/src/context.js +106 -0
- package/src/deep-review.js +95 -68
- package/src/domain-packs.js +13 -4
- package/src/index.js +4 -0
- package/src/mcp-packs.js +16 -0
- package/src/secret-patterns.js +30 -0
- package/src/techniques.js +4 -2
- package/src/watch.js +170 -42
package/src/watch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Watch mode - monitors project for Claude Code config changes and re-audits.
|
|
3
|
-
* Uses Node.js fs.watch (zero dependencies)
|
|
3
|
+
* Uses Node.js fs.watch (zero dependencies) with a recursive-directory fallback
|
|
4
|
+
* on platforms where native recursive watch is not reliable.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
const fs = require('fs');
|
|
@@ -13,20 +14,129 @@ const COLORS = {
|
|
|
13
14
|
};
|
|
14
15
|
const c = (text, color) => `${COLORS[color] || ''}${text}${COLORS.reset}`;
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const FILE_WATCH_PATHS = [
|
|
17
18
|
'CLAUDE.md',
|
|
18
|
-
'.claude',
|
|
19
19
|
'.gitignore',
|
|
20
20
|
'package.json',
|
|
21
21
|
'tsconfig.json',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const DIRECTORY_WATCH_PATHS = [
|
|
25
|
+
'.claude',
|
|
22
26
|
'.github',
|
|
23
27
|
];
|
|
24
28
|
|
|
29
|
+
function supportsNativeRecursiveWatch(platform = process.platform) {
|
|
30
|
+
return platform === 'win32' || platform === 'darwin';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function statIfExists(fullPath) {
|
|
34
|
+
try {
|
|
35
|
+
return fs.statSync(fullPath);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function listRecursiveDirectories(dir) {
|
|
42
|
+
const directories = [dir];
|
|
43
|
+
let entries = [];
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return directories;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
directories.push(...listRecursiveDirectories(path.join(dir, entry.name)));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return directories;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildWatchPlan(rootDir, platform = process.platform) {
|
|
61
|
+
const plan = [];
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
const recursiveSupported = supportsNativeRecursiveWatch(platform);
|
|
64
|
+
|
|
65
|
+
const addTarget = (fullPath, recursive, source) => {
|
|
66
|
+
const resolved = path.resolve(fullPath);
|
|
67
|
+
const key = `${resolved}|${recursive}`;
|
|
68
|
+
if (seen.has(key)) return;
|
|
69
|
+
seen.add(key);
|
|
70
|
+
plan.push({ path: resolved, recursive, source });
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
addTarget(rootDir, false, 'repo-root');
|
|
74
|
+
|
|
75
|
+
for (const watchPath of FILE_WATCH_PATHS) {
|
|
76
|
+
const fullPath = path.join(rootDir, watchPath);
|
|
77
|
+
const stat = statIfExists(fullPath);
|
|
78
|
+
if (stat && stat.isFile()) {
|
|
79
|
+
addTarget(fullPath, false, watchPath);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const watchPath of DIRECTORY_WATCH_PATHS) {
|
|
84
|
+
const fullPath = path.join(rootDir, watchPath);
|
|
85
|
+
const stat = statIfExists(fullPath);
|
|
86
|
+
if (!stat || !stat.isDirectory()) continue;
|
|
87
|
+
|
|
88
|
+
if (recursiveSupported) {
|
|
89
|
+
addTarget(fullPath, true, watchPath);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const dir of listRecursiveDirectories(fullPath)) {
|
|
94
|
+
addTarget(dir, false, watchPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return plan;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function registerWatchers(rootDir, watchers, onChange, platform = process.platform) {
|
|
102
|
+
const plan = buildWatchPlan(rootDir, platform);
|
|
103
|
+
|
|
104
|
+
for (const item of plan) {
|
|
105
|
+
const key = `${item.path}|${item.recursive}`;
|
|
106
|
+
if (watchers.has(key)) continue;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const watcher = fs.watch(item.path, { recursive: item.recursive }, (eventType, filename) => {
|
|
110
|
+
onChange(item, eventType, filename);
|
|
111
|
+
});
|
|
112
|
+
watchers.set(key, watcher);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
// Ignore unsupported or transient watch registration failures.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return watchers.size;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function closeWatchers(watchers) {
|
|
122
|
+
for (const watcher of watchers.values()) {
|
|
123
|
+
try {
|
|
124
|
+
watcher.close();
|
|
125
|
+
} catch (e) {
|
|
126
|
+
// Ignore close errors during shutdown.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
watchers.clear();
|
|
130
|
+
}
|
|
131
|
+
|
|
25
132
|
async function watch(options) {
|
|
133
|
+
const recursiveSupported = supportsNativeRecursiveWatch();
|
|
134
|
+
|
|
26
135
|
console.log('');
|
|
27
136
|
console.log(c(' claudex-setup watch mode', 'bold'));
|
|
28
137
|
console.log(c(' ═══════════════════════════════════════', 'dim'));
|
|
29
138
|
console.log(c(` Watching: ${options.dir}`, 'dim'));
|
|
139
|
+
console.log(c(` Mode: ${recursiveSupported ? 'native recursive directories' : 'expanded directory fallback (cross-platform safe)'}`, 'dim'));
|
|
30
140
|
console.log(c(' Press Ctrl+C to stop', 'dim'));
|
|
31
141
|
console.log('');
|
|
32
142
|
|
|
@@ -43,50 +153,64 @@ async function watch(options) {
|
|
|
43
153
|
}
|
|
44
154
|
|
|
45
155
|
// Watch relevant paths
|
|
46
|
-
const watchers =
|
|
156
|
+
const watchers = new Map();
|
|
47
157
|
let debounceTimer = null;
|
|
158
|
+
let shuttingDown = false;
|
|
48
159
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (result.score > lastScore) {
|
|
67
|
-
console.log(c(' Nice improvement!', 'green'));
|
|
68
|
-
} else if (result.score < lastScore) {
|
|
69
|
-
console.log(c(' Score dropped - check what changed.', 'yellow'));
|
|
70
|
-
}
|
|
71
|
-
lastScore = result.score;
|
|
72
|
-
console.log('');
|
|
73
|
-
} catch (e) {
|
|
74
|
-
// Ignore transient errors during file saves
|
|
75
|
-
}
|
|
76
|
-
}, 500);
|
|
77
|
-
});
|
|
78
|
-
watchers.push(watcher);
|
|
79
|
-
} catch (e) {
|
|
80
|
-
// Path doesn't exist yet - that's fine
|
|
81
|
-
}
|
|
82
|
-
}
|
|
160
|
+
const cleanupAndExit = () => {
|
|
161
|
+
if (shuttingDown) return;
|
|
162
|
+
shuttingDown = true;
|
|
163
|
+
clearTimeout(debounceTimer);
|
|
164
|
+
closeWatchers(watchers);
|
|
165
|
+
console.log('');
|
|
166
|
+
console.log(c(' Watch mode stopped.', 'dim'));
|
|
167
|
+
process.exit(0);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const handleChange = (item, eventType, filename) => {
|
|
171
|
+
clearTimeout(debounceTimer);
|
|
172
|
+
debounceTimer = setTimeout(async () => {
|
|
173
|
+
const changedLabel = filename
|
|
174
|
+
? String(filename)
|
|
175
|
+
: path.relative(options.dir, item.path) || path.basename(item.path);
|
|
176
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
83
177
|
|
|
84
|
-
|
|
85
|
-
|
|
178
|
+
// Pick up newly created directories or newly materialized watch paths.
|
|
179
|
+
registerWatchers(options.dir, watchers, handleChange);
|
|
180
|
+
|
|
181
|
+
console.log(c(` [${timestamp}] Change detected: ${changedLabel}`, 'dim'));
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const result = await audit({ ...options, silent: true });
|
|
185
|
+
const delta = lastScore !== null ? result.score - lastScore : 0;
|
|
186
|
+
const arrow = delta > 0 ? c(`+${delta}`, 'green') : delta < 0 ? c(String(delta), 'yellow') : '';
|
|
187
|
+
|
|
188
|
+
console.log(` Score: ${scoreColor(result.score)} ${arrow} (${result.passed}/${result.passed + result.failed} passing)`);
|
|
189
|
+
|
|
190
|
+
if (lastScore !== null && result.score > lastScore) {
|
|
191
|
+
console.log(c(' Nice improvement!', 'green'));
|
|
192
|
+
} else if (lastScore !== null && result.score < lastScore) {
|
|
193
|
+
console.log(c(' Score dropped - check what changed.', 'yellow'));
|
|
194
|
+
}
|
|
195
|
+
lastScore = result.score;
|
|
196
|
+
console.log('');
|
|
197
|
+
} catch (e) {
|
|
198
|
+
// Ignore transient errors during file saves.
|
|
199
|
+
}
|
|
200
|
+
}, 500);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
registerWatchers(options.dir, watchers, handleChange);
|
|
204
|
+
|
|
205
|
+
if (watchers.size === 0) {
|
|
206
|
+
console.log(c(' Could not register any filesystem watchers in this environment.', 'yellow'));
|
|
86
207
|
return;
|
|
87
208
|
}
|
|
88
209
|
|
|
89
|
-
|
|
210
|
+
process.once('SIGINT', cleanupAndExit);
|
|
211
|
+
process.once('SIGTERM', cleanupAndExit);
|
|
212
|
+
|
|
213
|
+
console.log(c(` Watching ${watchers.size} targets for changes...`, 'dim'));
|
|
90
214
|
console.log('');
|
|
91
215
|
|
|
92
216
|
// Keep alive
|
|
@@ -98,4 +222,8 @@ function scoreColor(score) {
|
|
|
98
222
|
return c(`${score}/100`, color);
|
|
99
223
|
}
|
|
100
224
|
|
|
101
|
-
module.exports = {
|
|
225
|
+
module.exports = {
|
|
226
|
+
watch,
|
|
227
|
+
buildWatchPlan,
|
|
228
|
+
supportsNativeRecursiveWatch,
|
|
229
|
+
};
|