@contextstream/mcp-server 0.4.60 → 0.4.61
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/hooks/auto-rules.js +1223 -71
- package/dist/hooks/post-write.js +7 -0
- package/dist/hooks/runner.js +5392 -1527
- package/dist/index.js +9954 -9700
- package/package.json +1 -1
package/dist/hooks/auto-rules.js
CHANGED
|
@@ -1,13 +1,1157 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
8
|
var __esm = (fn, res) => function __init() {
|
|
5
9
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
10
|
};
|
|
11
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
12
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
13
|
+
};
|
|
7
14
|
var __export = (target, all) => {
|
|
8
15
|
for (var name in all)
|
|
9
16
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
17
|
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
27
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
28
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
29
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
30
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
31
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
32
|
+
mod
|
|
33
|
+
));
|
|
34
|
+
|
|
35
|
+
// node_modules/ignore/index.js
|
|
36
|
+
var require_ignore = __commonJS({
|
|
37
|
+
"node_modules/ignore/index.js"(exports, module) {
|
|
38
|
+
function makeArray(subject) {
|
|
39
|
+
return Array.isArray(subject) ? subject : [subject];
|
|
40
|
+
}
|
|
41
|
+
var UNDEFINED = void 0;
|
|
42
|
+
var EMPTY = "";
|
|
43
|
+
var SPACE = " ";
|
|
44
|
+
var ESCAPE = "\\";
|
|
45
|
+
var REGEX_TEST_BLANK_LINE = /^\s+$/;
|
|
46
|
+
var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
|
|
47
|
+
var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
|
|
48
|
+
var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
|
|
49
|
+
var REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
50
|
+
var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
|
|
51
|
+
var REGEX_TEST_TRAILING_SLASH = /\/$/;
|
|
52
|
+
var SLASH = "/";
|
|
53
|
+
var TMP_KEY_IGNORE = "node-ignore";
|
|
54
|
+
if (typeof Symbol !== "undefined") {
|
|
55
|
+
TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
|
|
56
|
+
}
|
|
57
|
+
var KEY_IGNORE = TMP_KEY_IGNORE;
|
|
58
|
+
var define = (object, key, value) => {
|
|
59
|
+
Object.defineProperty(object, key, { value });
|
|
60
|
+
return value;
|
|
61
|
+
};
|
|
62
|
+
var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
|
|
63
|
+
var RETURN_FALSE = () => false;
|
|
64
|
+
var sanitizeRange = (range) => range.replace(
|
|
65
|
+
REGEX_REGEXP_RANGE,
|
|
66
|
+
(match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
|
|
67
|
+
);
|
|
68
|
+
var cleanRangeBackSlash = (slashes) => {
|
|
69
|
+
const { length } = slashes;
|
|
70
|
+
return slashes.slice(0, length - length % 2);
|
|
71
|
+
};
|
|
72
|
+
var REPLACERS = [
|
|
73
|
+
[
|
|
74
|
+
// Remove BOM
|
|
75
|
+
// TODO:
|
|
76
|
+
// Other similar zero-width characters?
|
|
77
|
+
/^\uFEFF/,
|
|
78
|
+
() => EMPTY
|
|
79
|
+
],
|
|
80
|
+
// > Trailing spaces are ignored unless they are quoted with backslash ("\")
|
|
81
|
+
[
|
|
82
|
+
// (a\ ) -> (a )
|
|
83
|
+
// (a ) -> (a)
|
|
84
|
+
// (a ) -> (a)
|
|
85
|
+
// (a \ ) -> (a )
|
|
86
|
+
/((?:\\\\)*?)(\\?\s+)$/,
|
|
87
|
+
(_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
|
|
88
|
+
],
|
|
89
|
+
// Replace (\ ) with ' '
|
|
90
|
+
// (\ ) -> ' '
|
|
91
|
+
// (\\ ) -> '\\ '
|
|
92
|
+
// (\\\ ) -> '\\ '
|
|
93
|
+
[
|
|
94
|
+
/(\\+?)\s/g,
|
|
95
|
+
(_, m1) => {
|
|
96
|
+
const { length } = m1;
|
|
97
|
+
return m1.slice(0, length - length % 2) + SPACE;
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
// Escape metacharacters
|
|
101
|
+
// which is written down by users but means special for regular expressions.
|
|
102
|
+
// > There are 12 characters with special meanings:
|
|
103
|
+
// > - the backslash \,
|
|
104
|
+
// > - the caret ^,
|
|
105
|
+
// > - the dollar sign $,
|
|
106
|
+
// > - the period or dot .,
|
|
107
|
+
// > - the vertical bar or pipe symbol |,
|
|
108
|
+
// > - the question mark ?,
|
|
109
|
+
// > - the asterisk or star *,
|
|
110
|
+
// > - the plus sign +,
|
|
111
|
+
// > - the opening parenthesis (,
|
|
112
|
+
// > - the closing parenthesis ),
|
|
113
|
+
// > - and the opening square bracket [,
|
|
114
|
+
// > - the opening curly brace {,
|
|
115
|
+
// > These special characters are often called "metacharacters".
|
|
116
|
+
[
|
|
117
|
+
/[\\$.|*+(){^]/g,
|
|
118
|
+
(match) => `\\${match}`
|
|
119
|
+
],
|
|
120
|
+
[
|
|
121
|
+
// > a question mark (?) matches a single character
|
|
122
|
+
/(?!\\)\?/g,
|
|
123
|
+
() => "[^/]"
|
|
124
|
+
],
|
|
125
|
+
// leading slash
|
|
126
|
+
[
|
|
127
|
+
// > A leading slash matches the beginning of the pathname.
|
|
128
|
+
// > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
|
|
129
|
+
// A leading slash matches the beginning of the pathname
|
|
130
|
+
/^\//,
|
|
131
|
+
() => "^"
|
|
132
|
+
],
|
|
133
|
+
// replace special metacharacter slash after the leading slash
|
|
134
|
+
[
|
|
135
|
+
/\//g,
|
|
136
|
+
() => "\\/"
|
|
137
|
+
],
|
|
138
|
+
[
|
|
139
|
+
// > A leading "**" followed by a slash means match in all directories.
|
|
140
|
+
// > For example, "**/foo" matches file or directory "foo" anywhere,
|
|
141
|
+
// > the same as pattern "foo".
|
|
142
|
+
// > "**/foo/bar" matches file or directory "bar" anywhere that is directly
|
|
143
|
+
// > under directory "foo".
|
|
144
|
+
// Notice that the '*'s have been replaced as '\\*'
|
|
145
|
+
/^\^*\\\*\\\*\\\//,
|
|
146
|
+
// '**/foo' <-> 'foo'
|
|
147
|
+
() => "^(?:.*\\/)?"
|
|
148
|
+
],
|
|
149
|
+
// starting
|
|
150
|
+
[
|
|
151
|
+
// there will be no leading '/'
|
|
152
|
+
// (which has been replaced by section "leading slash")
|
|
153
|
+
// If starts with '**', adding a '^' to the regular expression also works
|
|
154
|
+
/^(?=[^^])/,
|
|
155
|
+
function startingReplacer() {
|
|
156
|
+
return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
// two globstars
|
|
160
|
+
[
|
|
161
|
+
// Use lookahead assertions so that we could match more than one `'/**'`
|
|
162
|
+
/\\\/\\\*\\\*(?=\\\/|$)/g,
|
|
163
|
+
// Zero, one or several directories
|
|
164
|
+
// should not use '*', or it will be replaced by the next replacer
|
|
165
|
+
// Check if it is not the last `'/**'`
|
|
166
|
+
(_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
|
|
167
|
+
],
|
|
168
|
+
// normal intermediate wildcards
|
|
169
|
+
[
|
|
170
|
+
// Never replace escaped '*'
|
|
171
|
+
// ignore rule '\*' will match the path '*'
|
|
172
|
+
// 'abc.*/' -> go
|
|
173
|
+
// 'abc.*' -> skip this rule,
|
|
174
|
+
// coz trailing single wildcard will be handed by [trailing wildcard]
|
|
175
|
+
/(^|[^\\]+)(\\\*)+(?=.+)/g,
|
|
176
|
+
// '*.js' matches '.js'
|
|
177
|
+
// '*.js' doesn't match 'abc'
|
|
178
|
+
(_, p1, p2) => {
|
|
179
|
+
const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
|
|
180
|
+
return p1 + unescaped;
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
[
|
|
184
|
+
// unescape, revert step 3 except for back slash
|
|
185
|
+
// For example, if a user escape a '\\*',
|
|
186
|
+
// after step 3, the result will be '\\\\\\*'
|
|
187
|
+
/\\\\\\(?=[$.|*+(){^])/g,
|
|
188
|
+
() => ESCAPE
|
|
189
|
+
],
|
|
190
|
+
[
|
|
191
|
+
// '\\\\' -> '\\'
|
|
192
|
+
/\\\\/g,
|
|
193
|
+
() => ESCAPE
|
|
194
|
+
],
|
|
195
|
+
[
|
|
196
|
+
// > The range notation, e.g. [a-zA-Z],
|
|
197
|
+
// > can be used to match one of the characters in a range.
|
|
198
|
+
// `\` is escaped by step 3
|
|
199
|
+
/(\\)?\[([^\]/]*?)(\\*)($|\])/g,
|
|
200
|
+
(match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
|
|
201
|
+
],
|
|
202
|
+
// ending
|
|
203
|
+
[
|
|
204
|
+
// 'js' will not match 'js.'
|
|
205
|
+
// 'ab' will not match 'abc'
|
|
206
|
+
/(?:[^*])$/,
|
|
207
|
+
// WTF!
|
|
208
|
+
// https://git-scm.com/docs/gitignore
|
|
209
|
+
// changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
|
|
210
|
+
// which re-fixes #24, #38
|
|
211
|
+
// > If there is a separator at the end of the pattern then the pattern
|
|
212
|
+
// > will only match directories, otherwise the pattern can match both
|
|
213
|
+
// > files and directories.
|
|
214
|
+
// 'js*' will not match 'a.js'
|
|
215
|
+
// 'js/' will not match 'a.js'
|
|
216
|
+
// 'js' will match 'a.js' and 'a.js/'
|
|
217
|
+
(match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
|
|
218
|
+
]
|
|
219
|
+
];
|
|
220
|
+
var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
|
|
221
|
+
var MODE_IGNORE = "regex";
|
|
222
|
+
var MODE_CHECK_IGNORE = "checkRegex";
|
|
223
|
+
var UNDERSCORE = "_";
|
|
224
|
+
var TRAILING_WILD_CARD_REPLACERS = {
|
|
225
|
+
[MODE_IGNORE](_, p1) {
|
|
226
|
+
const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
|
|
227
|
+
return `${prefix}(?=$|\\/$)`;
|
|
228
|
+
},
|
|
229
|
+
[MODE_CHECK_IGNORE](_, p1) {
|
|
230
|
+
const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
|
|
231
|
+
return `${prefix}(?=$|\\/$)`;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
var makeRegexPrefix = (pattern) => REPLACERS.reduce(
|
|
235
|
+
(prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
|
|
236
|
+
pattern
|
|
237
|
+
);
|
|
238
|
+
var isString = (subject) => typeof subject === "string";
|
|
239
|
+
var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
|
|
240
|
+
var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
|
|
241
|
+
var IgnoreRule = class {
|
|
242
|
+
constructor(pattern, mark, body, ignoreCase, negative, prefix) {
|
|
243
|
+
this.pattern = pattern;
|
|
244
|
+
this.mark = mark;
|
|
245
|
+
this.negative = negative;
|
|
246
|
+
define(this, "body", body);
|
|
247
|
+
define(this, "ignoreCase", ignoreCase);
|
|
248
|
+
define(this, "regexPrefix", prefix);
|
|
249
|
+
}
|
|
250
|
+
get regex() {
|
|
251
|
+
const key = UNDERSCORE + MODE_IGNORE;
|
|
252
|
+
if (this[key]) {
|
|
253
|
+
return this[key];
|
|
254
|
+
}
|
|
255
|
+
return this._make(MODE_IGNORE, key);
|
|
256
|
+
}
|
|
257
|
+
get checkRegex() {
|
|
258
|
+
const key = UNDERSCORE + MODE_CHECK_IGNORE;
|
|
259
|
+
if (this[key]) {
|
|
260
|
+
return this[key];
|
|
261
|
+
}
|
|
262
|
+
return this._make(MODE_CHECK_IGNORE, key);
|
|
263
|
+
}
|
|
264
|
+
_make(mode, key) {
|
|
265
|
+
const str = this.regexPrefix.replace(
|
|
266
|
+
REGEX_REPLACE_TRAILING_WILDCARD,
|
|
267
|
+
// It does not need to bind pattern
|
|
268
|
+
TRAILING_WILD_CARD_REPLACERS[mode]
|
|
269
|
+
);
|
|
270
|
+
const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
|
|
271
|
+
return define(this, key, regex);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
var createRule = ({
|
|
275
|
+
pattern,
|
|
276
|
+
mark
|
|
277
|
+
}, ignoreCase) => {
|
|
278
|
+
let negative = false;
|
|
279
|
+
let body = pattern;
|
|
280
|
+
if (body.indexOf("!") === 0) {
|
|
281
|
+
negative = true;
|
|
282
|
+
body = body.substr(1);
|
|
283
|
+
}
|
|
284
|
+
body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
|
|
285
|
+
const regexPrefix = makeRegexPrefix(body);
|
|
286
|
+
return new IgnoreRule(
|
|
287
|
+
pattern,
|
|
288
|
+
mark,
|
|
289
|
+
body,
|
|
290
|
+
ignoreCase,
|
|
291
|
+
negative,
|
|
292
|
+
regexPrefix
|
|
293
|
+
);
|
|
294
|
+
};
|
|
295
|
+
var RuleManager = class {
|
|
296
|
+
constructor(ignoreCase) {
|
|
297
|
+
this._ignoreCase = ignoreCase;
|
|
298
|
+
this._rules = [];
|
|
299
|
+
}
|
|
300
|
+
_add(pattern) {
|
|
301
|
+
if (pattern && pattern[KEY_IGNORE]) {
|
|
302
|
+
this._rules = this._rules.concat(pattern._rules._rules);
|
|
303
|
+
this._added = true;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (isString(pattern)) {
|
|
307
|
+
pattern = {
|
|
308
|
+
pattern
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (checkPattern(pattern.pattern)) {
|
|
312
|
+
const rule = createRule(pattern, this._ignoreCase);
|
|
313
|
+
this._added = true;
|
|
314
|
+
this._rules.push(rule);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// @param {Array<string> | string | Ignore} pattern
|
|
318
|
+
add(pattern) {
|
|
319
|
+
this._added = false;
|
|
320
|
+
makeArray(
|
|
321
|
+
isString(pattern) ? splitPattern(pattern) : pattern
|
|
322
|
+
).forEach(this._add, this);
|
|
323
|
+
return this._added;
|
|
324
|
+
}
|
|
325
|
+
// Test one single path without recursively checking parent directories
|
|
326
|
+
//
|
|
327
|
+
// - checkUnignored `boolean` whether should check if the path is unignored,
|
|
328
|
+
// setting `checkUnignored` to `false` could reduce additional
|
|
329
|
+
// path matching.
|
|
330
|
+
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
331
|
+
// @returns {TestResult} true if a file is ignored
|
|
332
|
+
test(path5, checkUnignored, mode) {
|
|
333
|
+
let ignored = false;
|
|
334
|
+
let unignored = false;
|
|
335
|
+
let matchedRule;
|
|
336
|
+
this._rules.forEach((rule) => {
|
|
337
|
+
const { negative } = rule;
|
|
338
|
+
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const matched = rule[mode].test(path5);
|
|
342
|
+
if (!matched) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
ignored = !negative;
|
|
346
|
+
unignored = negative;
|
|
347
|
+
matchedRule = negative ? UNDEFINED : rule;
|
|
348
|
+
});
|
|
349
|
+
const ret = {
|
|
350
|
+
ignored,
|
|
351
|
+
unignored
|
|
352
|
+
};
|
|
353
|
+
if (matchedRule) {
|
|
354
|
+
ret.rule = matchedRule;
|
|
355
|
+
}
|
|
356
|
+
return ret;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
var throwError = (message, Ctor) => {
|
|
360
|
+
throw new Ctor(message);
|
|
361
|
+
};
|
|
362
|
+
var checkPath = (path5, originalPath, doThrow) => {
|
|
363
|
+
if (!isString(path5)) {
|
|
364
|
+
return doThrow(
|
|
365
|
+
`path must be a string, but got \`${originalPath}\``,
|
|
366
|
+
TypeError
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
if (!path5) {
|
|
370
|
+
return doThrow(`path must not be empty`, TypeError);
|
|
371
|
+
}
|
|
372
|
+
if (checkPath.isNotRelative(path5)) {
|
|
373
|
+
const r = "`path.relative()`d";
|
|
374
|
+
return doThrow(
|
|
375
|
+
`path should be a ${r} string, but got "${originalPath}"`,
|
|
376
|
+
RangeError
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
};
|
|
381
|
+
var isNotRelative = (path5) => REGEX_TEST_INVALID_PATH.test(path5);
|
|
382
|
+
checkPath.isNotRelative = isNotRelative;
|
|
383
|
+
checkPath.convert = (p) => p;
|
|
384
|
+
var Ignore2 = class {
|
|
385
|
+
constructor({
|
|
386
|
+
ignorecase = true,
|
|
387
|
+
ignoreCase = ignorecase,
|
|
388
|
+
allowRelativePaths = false
|
|
389
|
+
} = {}) {
|
|
390
|
+
define(this, KEY_IGNORE, true);
|
|
391
|
+
this._rules = new RuleManager(ignoreCase);
|
|
392
|
+
this._strictPathCheck = !allowRelativePaths;
|
|
393
|
+
this._initCache();
|
|
394
|
+
}
|
|
395
|
+
_initCache() {
|
|
396
|
+
this._ignoreCache = /* @__PURE__ */ Object.create(null);
|
|
397
|
+
this._testCache = /* @__PURE__ */ Object.create(null);
|
|
398
|
+
}
|
|
399
|
+
add(pattern) {
|
|
400
|
+
if (this._rules.add(pattern)) {
|
|
401
|
+
this._initCache();
|
|
402
|
+
}
|
|
403
|
+
return this;
|
|
404
|
+
}
|
|
405
|
+
// legacy
|
|
406
|
+
addPattern(pattern) {
|
|
407
|
+
return this.add(pattern);
|
|
408
|
+
}
|
|
409
|
+
// @returns {TestResult}
|
|
410
|
+
_test(originalPath, cache, checkUnignored, slices) {
|
|
411
|
+
const path5 = originalPath && checkPath.convert(originalPath);
|
|
412
|
+
checkPath(
|
|
413
|
+
path5,
|
|
414
|
+
originalPath,
|
|
415
|
+
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
416
|
+
);
|
|
417
|
+
return this._t(path5, cache, checkUnignored, slices);
|
|
418
|
+
}
|
|
419
|
+
checkIgnore(path5) {
|
|
420
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path5)) {
|
|
421
|
+
return this.test(path5);
|
|
422
|
+
}
|
|
423
|
+
const slices = path5.split(SLASH).filter(Boolean);
|
|
424
|
+
slices.pop();
|
|
425
|
+
if (slices.length) {
|
|
426
|
+
const parent = this._t(
|
|
427
|
+
slices.join(SLASH) + SLASH,
|
|
428
|
+
this._testCache,
|
|
429
|
+
true,
|
|
430
|
+
slices
|
|
431
|
+
);
|
|
432
|
+
if (parent.ignored) {
|
|
433
|
+
return parent;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return this._rules.test(path5, false, MODE_CHECK_IGNORE);
|
|
437
|
+
}
|
|
438
|
+
_t(path5, cache, checkUnignored, slices) {
|
|
439
|
+
if (path5 in cache) {
|
|
440
|
+
return cache[path5];
|
|
441
|
+
}
|
|
442
|
+
if (!slices) {
|
|
443
|
+
slices = path5.split(SLASH).filter(Boolean);
|
|
444
|
+
}
|
|
445
|
+
slices.pop();
|
|
446
|
+
if (!slices.length) {
|
|
447
|
+
return cache[path5] = this._rules.test(path5, checkUnignored, MODE_IGNORE);
|
|
448
|
+
}
|
|
449
|
+
const parent = this._t(
|
|
450
|
+
slices.join(SLASH) + SLASH,
|
|
451
|
+
cache,
|
|
452
|
+
checkUnignored,
|
|
453
|
+
slices
|
|
454
|
+
);
|
|
455
|
+
return cache[path5] = parent.ignored ? parent : this._rules.test(path5, checkUnignored, MODE_IGNORE);
|
|
456
|
+
}
|
|
457
|
+
ignores(path5) {
|
|
458
|
+
return this._test(path5, this._ignoreCache, false).ignored;
|
|
459
|
+
}
|
|
460
|
+
createFilter() {
|
|
461
|
+
return (path5) => !this.ignores(path5);
|
|
462
|
+
}
|
|
463
|
+
filter(paths) {
|
|
464
|
+
return makeArray(paths).filter(this.createFilter());
|
|
465
|
+
}
|
|
466
|
+
// @returns {TestResult}
|
|
467
|
+
test(path5) {
|
|
468
|
+
return this._test(path5, this._testCache, true);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
var factory = (options) => new Ignore2(options);
|
|
472
|
+
var isPathValid = (path5) => checkPath(path5 && checkPath.convert(path5), path5, RETURN_FALSE);
|
|
473
|
+
var setupWindows = () => {
|
|
474
|
+
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
475
|
+
checkPath.convert = makePosix;
|
|
476
|
+
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
477
|
+
checkPath.isNotRelative = (path5) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path5) || isNotRelative(path5);
|
|
478
|
+
};
|
|
479
|
+
if (
|
|
480
|
+
// Detect `process` so that it can run in browsers.
|
|
481
|
+
typeof process !== "undefined" && process.platform === "win32"
|
|
482
|
+
) {
|
|
483
|
+
setupWindows();
|
|
484
|
+
}
|
|
485
|
+
module.exports = factory;
|
|
486
|
+
factory.default = factory;
|
|
487
|
+
module.exports.isPathValid = isPathValid;
|
|
488
|
+
define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// src/ignore.ts
|
|
493
|
+
import * as fs from "fs";
|
|
494
|
+
import * as path from "path";
|
|
495
|
+
async function loadIgnorePatterns(projectRoot) {
|
|
496
|
+
const ig = (0, import_ignore.default)();
|
|
497
|
+
const patterns = [...DEFAULT_IGNORE_PATTERNS];
|
|
498
|
+
ig.add(DEFAULT_IGNORE_PATTERNS);
|
|
499
|
+
const ignoreFilePath = path.join(projectRoot, IGNORE_FILENAME);
|
|
500
|
+
let hasUserPatterns = false;
|
|
501
|
+
try {
|
|
502
|
+
const content = await fs.promises.readFile(ignoreFilePath, "utf-8");
|
|
503
|
+
const userPatterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
504
|
+
if (userPatterns.length > 0) {
|
|
505
|
+
ig.add(userPatterns);
|
|
506
|
+
patterns.push(...userPatterns);
|
|
507
|
+
hasUserPatterns = true;
|
|
508
|
+
}
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
ignores: (pathname) => ig.ignores(pathname),
|
|
513
|
+
patterns,
|
|
514
|
+
hasUserPatterns
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
var import_ignore, IGNORE_FILENAME, DEFAULT_IGNORE_PATTERNS;
|
|
518
|
+
var init_ignore = __esm({
|
|
519
|
+
"src/ignore.ts"() {
|
|
520
|
+
"use strict";
|
|
521
|
+
import_ignore = __toESM(require_ignore(), 1);
|
|
522
|
+
IGNORE_FILENAME = ".contextstream/ignore";
|
|
523
|
+
DEFAULT_IGNORE_PATTERNS = [
|
|
524
|
+
// Version control
|
|
525
|
+
".git/",
|
|
526
|
+
".svn/",
|
|
527
|
+
".hg/",
|
|
528
|
+
// Package managers / dependencies
|
|
529
|
+
"node_modules/",
|
|
530
|
+
"vendor/",
|
|
531
|
+
".pnpm/",
|
|
532
|
+
// Build outputs
|
|
533
|
+
"target/",
|
|
534
|
+
"dist/",
|
|
535
|
+
"build/",
|
|
536
|
+
"out/",
|
|
537
|
+
".next/",
|
|
538
|
+
".nuxt/",
|
|
539
|
+
// Python
|
|
540
|
+
"__pycache__/",
|
|
541
|
+
".pytest_cache/",
|
|
542
|
+
".mypy_cache/",
|
|
543
|
+
"venv/",
|
|
544
|
+
".venv/",
|
|
545
|
+
"env/",
|
|
546
|
+
".env/",
|
|
547
|
+
// IDE
|
|
548
|
+
".idea/",
|
|
549
|
+
".vscode/",
|
|
550
|
+
".vs/",
|
|
551
|
+
// Coverage
|
|
552
|
+
"coverage/",
|
|
553
|
+
".coverage/",
|
|
554
|
+
// Lock files
|
|
555
|
+
"package-lock.json",
|
|
556
|
+
"yarn.lock",
|
|
557
|
+
"pnpm-lock.yaml",
|
|
558
|
+
"Cargo.lock",
|
|
559
|
+
"poetry.lock",
|
|
560
|
+
"Gemfile.lock",
|
|
561
|
+
"composer.lock",
|
|
562
|
+
// OS files
|
|
563
|
+
".DS_Store",
|
|
564
|
+
"Thumbs.db"
|
|
565
|
+
];
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// src/files.ts
|
|
570
|
+
var files_exports = {};
|
|
571
|
+
__export(files_exports, {
|
|
572
|
+
clearGitContextCache: () => clearGitContextCache,
|
|
573
|
+
countIndexableFiles: () => countIndexableFiles,
|
|
574
|
+
deleteHashManifest: () => deleteHashManifest,
|
|
575
|
+
detectLanguage: () => detectLanguage,
|
|
576
|
+
readAllFilesInBatches: () => readAllFilesInBatches,
|
|
577
|
+
readChangedFilesInBatches: () => readChangedFilesInBatches,
|
|
578
|
+
readFilesFromDirectory: () => readFilesFromDirectory,
|
|
579
|
+
readHashManifest: () => readHashManifest,
|
|
580
|
+
sha256Hex: () => sha256Hex,
|
|
581
|
+
writeHashManifest: () => writeHashManifest
|
|
582
|
+
});
|
|
583
|
+
import * as fs2 from "fs";
|
|
584
|
+
import * as path2 from "path";
|
|
585
|
+
import { exec } from "child_process";
|
|
586
|
+
import { promisify } from "util";
|
|
587
|
+
import * as os from "os";
|
|
588
|
+
import * as crypto from "crypto";
|
|
589
|
+
function getMachineId() {
|
|
590
|
+
const hostname2 = os.hostname();
|
|
591
|
+
const hash = crypto.createHash("sha256").update(hostname2).digest("hex");
|
|
592
|
+
return hash.substring(0, 12);
|
|
593
|
+
}
|
|
594
|
+
async function isGitRepo(rootPath) {
|
|
595
|
+
try {
|
|
596
|
+
await execAsync("git rev-parse --is-inside-work-tree", {
|
|
597
|
+
cwd: rootPath,
|
|
598
|
+
timeout: 5e3
|
|
599
|
+
});
|
|
600
|
+
return true;
|
|
601
|
+
} catch {
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
async function getGitBranch(rootPath) {
|
|
606
|
+
try {
|
|
607
|
+
const { stdout } = await execAsync("git branch --show-current", {
|
|
608
|
+
cwd: rootPath,
|
|
609
|
+
timeout: 5e3
|
|
610
|
+
});
|
|
611
|
+
const branch = stdout.trim();
|
|
612
|
+
return branch || void 0;
|
|
613
|
+
} catch {
|
|
614
|
+
return void 0;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async function getGitDefaultBranch(rootPath) {
|
|
618
|
+
try {
|
|
619
|
+
const { stdout } = await execAsync(
|
|
620
|
+
"git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null",
|
|
621
|
+
{ cwd: rootPath, timeout: 5e3 }
|
|
622
|
+
);
|
|
623
|
+
const match = stdout.trim().match(/refs\/remotes\/origin\/(.+)/);
|
|
624
|
+
if (match) return match[1];
|
|
625
|
+
} catch {
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
const { stdout } = await execAsync(
|
|
629
|
+
"git config --get init.defaultBranch 2>/dev/null",
|
|
630
|
+
{ cwd: rootPath, timeout: 5e3 }
|
|
631
|
+
);
|
|
632
|
+
const branch = stdout.trim();
|
|
633
|
+
if (branch) return branch;
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
const { stdout } = await execAsync("git branch --list main master", {
|
|
638
|
+
cwd: rootPath,
|
|
639
|
+
timeout: 5e3
|
|
640
|
+
});
|
|
641
|
+
const branches = stdout.trim().split("\n").map((b) => b.replace(/^\*?\s*/, "").trim()).filter(Boolean);
|
|
642
|
+
if (branches.includes("main")) return "main";
|
|
643
|
+
if (branches.includes("master")) return "master";
|
|
644
|
+
} catch {
|
|
645
|
+
}
|
|
646
|
+
return void 0;
|
|
647
|
+
}
|
|
648
|
+
async function getFileGitInfo(rootPath, relativePath) {
|
|
649
|
+
try {
|
|
650
|
+
const { stdout } = await execAsync(
|
|
651
|
+
`git log -1 --format="%H %ct" -- "${relativePath}"`,
|
|
652
|
+
{ cwd: rootPath, timeout: 5e3 }
|
|
653
|
+
);
|
|
654
|
+
const parts = stdout.trim().split(" ");
|
|
655
|
+
if (parts.length >= 2) {
|
|
656
|
+
const sha = parts[0];
|
|
657
|
+
const unixTimestamp = parseInt(parts[1], 10);
|
|
658
|
+
if (sha && !isNaN(unixTimestamp)) {
|
|
659
|
+
const timestamp = new Date(unixTimestamp * 1e3).toISOString();
|
|
660
|
+
return { sha, timestamp };
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
} catch {
|
|
664
|
+
}
|
|
665
|
+
return void 0;
|
|
666
|
+
}
|
|
667
|
+
async function getGitContext(rootPath) {
|
|
668
|
+
const cached = gitContextCache.get(rootPath);
|
|
669
|
+
if (cached) return cached;
|
|
670
|
+
const machineId = getMachineId();
|
|
671
|
+
const isRepo = await isGitRepo(rootPath);
|
|
672
|
+
if (!isRepo) {
|
|
673
|
+
const context2 = { isGitRepo: false, machineId };
|
|
674
|
+
gitContextCache.set(rootPath, context2);
|
|
675
|
+
return context2;
|
|
676
|
+
}
|
|
677
|
+
const [branch, defaultBranch] = await Promise.all([
|
|
678
|
+
getGitBranch(rootPath),
|
|
679
|
+
getGitDefaultBranch(rootPath)
|
|
680
|
+
]);
|
|
681
|
+
const context = {
|
|
682
|
+
isGitRepo: true,
|
|
683
|
+
branch,
|
|
684
|
+
defaultBranch,
|
|
685
|
+
isDefaultBranch: branch !== void 0 && branch === defaultBranch,
|
|
686
|
+
machineId
|
|
687
|
+
};
|
|
688
|
+
gitContextCache.set(rootPath, context);
|
|
689
|
+
return context;
|
|
690
|
+
}
|
|
691
|
+
function clearGitContextCache() {
|
|
692
|
+
gitContextCache.clear();
|
|
693
|
+
}
|
|
694
|
+
async function readFilesFromDirectory(rootPath, options = {}) {
|
|
695
|
+
const maxFiles = options.maxFiles ?? MAX_FILES_PER_BATCH;
|
|
696
|
+
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
697
|
+
const files = [];
|
|
698
|
+
const ig = options.ignoreInstance ?? await loadIgnorePatterns(rootPath);
|
|
699
|
+
const gitCtx = await getGitContext(rootPath);
|
|
700
|
+
async function walkDir(dir, relativePath = "") {
|
|
701
|
+
if (files.length >= maxFiles) return;
|
|
702
|
+
let entries;
|
|
703
|
+
try {
|
|
704
|
+
entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
705
|
+
} catch {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
for (const entry of entries) {
|
|
709
|
+
if (files.length >= maxFiles) break;
|
|
710
|
+
const fullPath = path2.join(dir, entry.name);
|
|
711
|
+
const relPath = path2.join(relativePath, entry.name);
|
|
712
|
+
if (entry.isDirectory()) {
|
|
713
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
714
|
+
if (ig.ignores(relPath + "/")) continue;
|
|
715
|
+
await walkDir(fullPath, relPath);
|
|
716
|
+
} else if (entry.isFile()) {
|
|
717
|
+
if (IGNORE_FILES.has(entry.name)) continue;
|
|
718
|
+
if (ig.ignores(relPath)) continue;
|
|
719
|
+
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
720
|
+
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
721
|
+
try {
|
|
722
|
+
const stat = await fs2.promises.stat(fullPath);
|
|
723
|
+
if (stat.size > maxFileSize) continue;
|
|
724
|
+
const content = await fs2.promises.readFile(fullPath, "utf-8");
|
|
725
|
+
const file = {
|
|
726
|
+
path: relPath,
|
|
727
|
+
content,
|
|
728
|
+
// Always include machine_id and source_modified_at
|
|
729
|
+
machine_id: gitCtx.machineId,
|
|
730
|
+
source_modified_at: stat.mtime.toISOString()
|
|
731
|
+
};
|
|
732
|
+
if (gitCtx.isGitRepo) {
|
|
733
|
+
file.git_branch = gitCtx.branch;
|
|
734
|
+
file.git_default_branch = gitCtx.defaultBranch;
|
|
735
|
+
file.is_default_branch = gitCtx.isDefaultBranch;
|
|
736
|
+
const gitInfo = await getFileGitInfo(rootPath, relPath);
|
|
737
|
+
if (gitInfo) {
|
|
738
|
+
file.git_commit_sha = gitInfo.sha;
|
|
739
|
+
file.git_commit_timestamp = gitInfo.timestamp;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
files.push(file);
|
|
743
|
+
} catch {
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
await walkDir(rootPath);
|
|
749
|
+
return files;
|
|
750
|
+
}
|
|
751
|
+
async function* readAllFilesInBatches(rootPath, options = {}) {
|
|
752
|
+
const maxBatchBytes = options.maxBatchBytes ?? MAX_BATCH_BYTES;
|
|
753
|
+
const largeFileThreshold = options.largeFileThreshold ?? LARGE_FILE_THRESHOLD;
|
|
754
|
+
const maxFilesPerBatch = options.maxFilesPerBatch ?? options.batchSize ?? MAX_FILES_PER_BATCH;
|
|
755
|
+
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
756
|
+
const ig = options.ignoreInstance ?? await loadIgnorePatterns(rootPath);
|
|
757
|
+
const gitCtx = await getGitContext(rootPath);
|
|
758
|
+
let batch = [];
|
|
759
|
+
let currentBatchBytes = 0;
|
|
760
|
+
async function* walkDir(dir, relativePath = "") {
|
|
761
|
+
let entries;
|
|
762
|
+
try {
|
|
763
|
+
entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
764
|
+
} catch {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
for (const entry of entries) {
|
|
768
|
+
const fullPath = path2.join(dir, entry.name);
|
|
769
|
+
const relPath = path2.join(relativePath, entry.name);
|
|
770
|
+
if (entry.isDirectory()) {
|
|
771
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
772
|
+
if (ig.ignores(relPath + "/")) continue;
|
|
773
|
+
yield* walkDir(fullPath, relPath);
|
|
774
|
+
} else if (entry.isFile()) {
|
|
775
|
+
if (IGNORE_FILES.has(entry.name)) continue;
|
|
776
|
+
if (ig.ignores(relPath)) continue;
|
|
777
|
+
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
778
|
+
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
779
|
+
try {
|
|
780
|
+
const stat = await fs2.promises.stat(fullPath);
|
|
781
|
+
if (stat.size > maxFileSize) continue;
|
|
782
|
+
const content = await fs2.promises.readFile(fullPath, "utf-8");
|
|
783
|
+
const file = {
|
|
784
|
+
path: relPath,
|
|
785
|
+
content,
|
|
786
|
+
sizeBytes: stat.size,
|
|
787
|
+
// Always include machine_id and source_modified_at
|
|
788
|
+
machine_id: gitCtx.machineId,
|
|
789
|
+
source_modified_at: stat.mtime.toISOString()
|
|
790
|
+
};
|
|
791
|
+
if (gitCtx.isGitRepo) {
|
|
792
|
+
file.git_branch = gitCtx.branch;
|
|
793
|
+
file.git_default_branch = gitCtx.defaultBranch;
|
|
794
|
+
file.is_default_branch = gitCtx.isDefaultBranch;
|
|
795
|
+
const gitInfo = await getFileGitInfo(rootPath, relPath);
|
|
796
|
+
if (gitInfo) {
|
|
797
|
+
file.git_commit_sha = gitInfo.sha;
|
|
798
|
+
file.git_commit_timestamp = gitInfo.timestamp;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
yield file;
|
|
802
|
+
} catch {
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
for await (const file of walkDir(rootPath)) {
|
|
808
|
+
if (file.sizeBytes > largeFileThreshold) {
|
|
809
|
+
if (batch.length > 0) {
|
|
810
|
+
yield batch.map(({ sizeBytes: sizeBytes2, ...rest }) => rest);
|
|
811
|
+
batch = [];
|
|
812
|
+
currentBatchBytes = 0;
|
|
813
|
+
}
|
|
814
|
+
const { sizeBytes, ...fileData } = file;
|
|
815
|
+
yield [fileData];
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
const wouldExceedBytes = currentBatchBytes + file.sizeBytes > maxBatchBytes;
|
|
819
|
+
const wouldExceedFiles = batch.length >= maxFilesPerBatch;
|
|
820
|
+
if (wouldExceedBytes || wouldExceedFiles) {
|
|
821
|
+
if (batch.length > 0) {
|
|
822
|
+
yield batch.map(({ sizeBytes, ...rest }) => rest);
|
|
823
|
+
batch = [];
|
|
824
|
+
currentBatchBytes = 0;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
batch.push(file);
|
|
828
|
+
currentBatchBytes += file.sizeBytes;
|
|
829
|
+
}
|
|
830
|
+
if (batch.length > 0) {
|
|
831
|
+
yield batch.map(({ sizeBytes, ...rest }) => rest);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}) {
|
|
835
|
+
const maxBatchBytes = options.maxBatchBytes ?? MAX_BATCH_BYTES;
|
|
836
|
+
const largeFileThreshold = options.largeFileThreshold ?? LARGE_FILE_THRESHOLD;
|
|
837
|
+
const maxFilesPerBatch = options.maxFilesPerBatch ?? options.batchSize ?? MAX_FILES_PER_BATCH;
|
|
838
|
+
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
839
|
+
const sinceMs = sinceTimestamp.getTime();
|
|
840
|
+
const ig = options.ignoreInstance ?? await loadIgnorePatterns(rootPath);
|
|
841
|
+
const gitCtx = await getGitContext(rootPath);
|
|
842
|
+
let batch = [];
|
|
843
|
+
let currentBatchBytes = 0;
|
|
844
|
+
let filesScanned = 0;
|
|
845
|
+
let filesChanged = 0;
|
|
846
|
+
async function* walkDir(dir, relativePath = "") {
|
|
847
|
+
let entries;
|
|
848
|
+
try {
|
|
849
|
+
entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
850
|
+
} catch {
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
for (const entry of entries) {
|
|
854
|
+
const fullPath = path2.join(dir, entry.name);
|
|
855
|
+
const relPath = path2.join(relativePath, entry.name);
|
|
856
|
+
if (entry.isDirectory()) {
|
|
857
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
858
|
+
if (ig.ignores(relPath + "/")) continue;
|
|
859
|
+
yield* walkDir(fullPath, relPath);
|
|
860
|
+
} else if (entry.isFile()) {
|
|
861
|
+
if (IGNORE_FILES.has(entry.name)) continue;
|
|
862
|
+
if (ig.ignores(relPath)) continue;
|
|
863
|
+
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
864
|
+
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
865
|
+
try {
|
|
866
|
+
const stat = await fs2.promises.stat(fullPath);
|
|
867
|
+
filesScanned++;
|
|
868
|
+
if (stat.mtimeMs <= sinceMs) continue;
|
|
869
|
+
if (stat.size > maxFileSize) continue;
|
|
870
|
+
const content = await fs2.promises.readFile(fullPath, "utf-8");
|
|
871
|
+
filesChanged++;
|
|
872
|
+
const file = {
|
|
873
|
+
path: relPath,
|
|
874
|
+
content,
|
|
875
|
+
sizeBytes: stat.size,
|
|
876
|
+
// Always include machine_id and source_modified_at
|
|
877
|
+
machine_id: gitCtx.machineId,
|
|
878
|
+
source_modified_at: stat.mtime.toISOString()
|
|
879
|
+
};
|
|
880
|
+
if (gitCtx.isGitRepo) {
|
|
881
|
+
file.git_branch = gitCtx.branch;
|
|
882
|
+
file.git_default_branch = gitCtx.defaultBranch;
|
|
883
|
+
file.is_default_branch = gitCtx.isDefaultBranch;
|
|
884
|
+
const gitInfo = await getFileGitInfo(rootPath, relPath);
|
|
885
|
+
if (gitInfo) {
|
|
886
|
+
file.git_commit_sha = gitInfo.sha;
|
|
887
|
+
file.git_commit_timestamp = gitInfo.timestamp;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
yield file;
|
|
891
|
+
} catch {
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
for await (const file of walkDir(rootPath)) {
|
|
897
|
+
if (file.sizeBytes > largeFileThreshold) {
|
|
898
|
+
if (batch.length > 0) {
|
|
899
|
+
yield batch.map(({ sizeBytes: sizeBytes2, ...rest }) => rest);
|
|
900
|
+
batch = [];
|
|
901
|
+
currentBatchBytes = 0;
|
|
902
|
+
}
|
|
903
|
+
const { sizeBytes, ...fileData } = file;
|
|
904
|
+
yield [fileData];
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
const wouldExceedBytes = currentBatchBytes + file.sizeBytes > maxBatchBytes;
|
|
908
|
+
const wouldExceedFiles = batch.length >= maxFilesPerBatch;
|
|
909
|
+
if (wouldExceedBytes || wouldExceedFiles) {
|
|
910
|
+
if (batch.length > 0) {
|
|
911
|
+
yield batch.map(({ sizeBytes, ...rest }) => rest);
|
|
912
|
+
batch = [];
|
|
913
|
+
currentBatchBytes = 0;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
batch.push(file);
|
|
917
|
+
currentBatchBytes += file.sizeBytes;
|
|
918
|
+
}
|
|
919
|
+
if (batch.length > 0) {
|
|
920
|
+
yield batch.map(({ sizeBytes, ...rest }) => rest);
|
|
921
|
+
}
|
|
922
|
+
console.error(
|
|
923
|
+
`[ContextStream] Incremental scan: ${filesChanged} changed files out of ${filesScanned} scanned (since ${sinceTimestamp.toISOString()})`
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
async function countIndexableFiles(rootPath, options = {}) {
|
|
927
|
+
const maxFiles = options.maxFiles ?? 1;
|
|
928
|
+
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
929
|
+
const ig = options.ignoreInstance ?? await loadIgnorePatterns(rootPath);
|
|
930
|
+
let count = 0;
|
|
931
|
+
let stopped = false;
|
|
932
|
+
async function walkDir(dir, relativePath = "") {
|
|
933
|
+
if (count >= maxFiles) {
|
|
934
|
+
stopped = true;
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
let entries;
|
|
938
|
+
try {
|
|
939
|
+
entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
940
|
+
} catch {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
for (const entry of entries) {
|
|
944
|
+
if (count >= maxFiles) {
|
|
945
|
+
stopped = true;
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
const fullPath = path2.join(dir, entry.name);
|
|
949
|
+
const relPath = path2.join(relativePath, entry.name);
|
|
950
|
+
if (entry.isDirectory()) {
|
|
951
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
952
|
+
if (ig.ignores(relPath + "/")) continue;
|
|
953
|
+
await walkDir(fullPath, relPath);
|
|
954
|
+
} else if (entry.isFile()) {
|
|
955
|
+
if (IGNORE_FILES.has(entry.name)) continue;
|
|
956
|
+
if (ig.ignores(relPath)) continue;
|
|
957
|
+
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
958
|
+
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
959
|
+
try {
|
|
960
|
+
const stat = await fs2.promises.stat(fullPath);
|
|
961
|
+
if (stat.size > maxFileSize) continue;
|
|
962
|
+
count++;
|
|
963
|
+
if (count >= maxFiles) {
|
|
964
|
+
stopped = true;
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
} catch {
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
await walkDir(rootPath);
|
|
973
|
+
return { count, stopped };
|
|
974
|
+
}
|
|
975
|
+
function detectLanguage(filePath) {
|
|
976
|
+
const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
|
|
977
|
+
const langMap = {
|
|
978
|
+
rs: "rust",
|
|
979
|
+
ts: "typescript",
|
|
980
|
+
tsx: "typescript",
|
|
981
|
+
js: "javascript",
|
|
982
|
+
jsx: "javascript",
|
|
983
|
+
py: "python",
|
|
984
|
+
go: "go",
|
|
985
|
+
java: "java",
|
|
986
|
+
kt: "kotlin",
|
|
987
|
+
c: "c",
|
|
988
|
+
h: "c",
|
|
989
|
+
cpp: "cpp",
|
|
990
|
+
hpp: "cpp",
|
|
991
|
+
cs: "csharp",
|
|
992
|
+
rb: "ruby",
|
|
993
|
+
php: "php",
|
|
994
|
+
swift: "swift",
|
|
995
|
+
scala: "scala",
|
|
996
|
+
sql: "sql",
|
|
997
|
+
md: "markdown",
|
|
998
|
+
json: "json",
|
|
999
|
+
yaml: "yaml",
|
|
1000
|
+
yml: "yaml",
|
|
1001
|
+
toml: "toml",
|
|
1002
|
+
html: "html",
|
|
1003
|
+
css: "css",
|
|
1004
|
+
sh: "shell"
|
|
1005
|
+
};
|
|
1006
|
+
return langMap[ext] ?? "unknown";
|
|
1007
|
+
}
|
|
1008
|
+
function sha256Hex(content) {
|
|
1009
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
1010
|
+
}
|
|
1011
|
+
function hashManifestPath(projectId) {
|
|
1012
|
+
return path2.join(os.homedir(), ".contextstream", "file-hashes", `${projectId}.json`);
|
|
1013
|
+
}
|
|
1014
|
+
function readHashManifest(projectId) {
|
|
1015
|
+
try {
|
|
1016
|
+
const content = fs2.readFileSync(hashManifestPath(projectId), "utf-8");
|
|
1017
|
+
const parsed = JSON.parse(content);
|
|
1018
|
+
return new Map(Object.entries(parsed));
|
|
1019
|
+
} catch {
|
|
1020
|
+
return /* @__PURE__ */ new Map();
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
function writeHashManifest(projectId, hashes) {
|
|
1024
|
+
const filePath = hashManifestPath(projectId);
|
|
1025
|
+
try {
|
|
1026
|
+
const dir = path2.dirname(filePath);
|
|
1027
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1028
|
+
const obj = Object.fromEntries(hashes);
|
|
1029
|
+
fs2.writeFileSync(filePath, JSON.stringify(obj, null, 2));
|
|
1030
|
+
} catch {
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function deleteHashManifest(projectId) {
|
|
1034
|
+
try {
|
|
1035
|
+
fs2.unlinkSync(hashManifestPath(projectId));
|
|
1036
|
+
} catch {
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
var execAsync, CODE_EXTENSIONS, IGNORE_DIRS, IGNORE_FILES, MAX_FILE_SIZE, MAX_BATCH_BYTES, LARGE_FILE_THRESHOLD, MAX_FILES_PER_BATCH, gitContextCache;
|
|
1040
|
+
var init_files = __esm({
|
|
1041
|
+
"src/files.ts"() {
|
|
1042
|
+
"use strict";
|
|
1043
|
+
init_ignore();
|
|
1044
|
+
execAsync = promisify(exec);
|
|
1045
|
+
CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1046
|
+
// Rust
|
|
1047
|
+
"rs",
|
|
1048
|
+
// TypeScript/JavaScript
|
|
1049
|
+
"ts",
|
|
1050
|
+
"tsx",
|
|
1051
|
+
"js",
|
|
1052
|
+
"jsx",
|
|
1053
|
+
"mjs",
|
|
1054
|
+
"cjs",
|
|
1055
|
+
// Python
|
|
1056
|
+
"py",
|
|
1057
|
+
"pyi",
|
|
1058
|
+
// Go
|
|
1059
|
+
"go",
|
|
1060
|
+
// Java/Kotlin
|
|
1061
|
+
"java",
|
|
1062
|
+
"kt",
|
|
1063
|
+
"kts",
|
|
1064
|
+
// C/C++
|
|
1065
|
+
"c",
|
|
1066
|
+
"h",
|
|
1067
|
+
"cpp",
|
|
1068
|
+
"hpp",
|
|
1069
|
+
"cc",
|
|
1070
|
+
"cxx",
|
|
1071
|
+
// C#
|
|
1072
|
+
"cs",
|
|
1073
|
+
// Ruby
|
|
1074
|
+
"rb",
|
|
1075
|
+
// PHP
|
|
1076
|
+
"php",
|
|
1077
|
+
// Swift
|
|
1078
|
+
"swift",
|
|
1079
|
+
// Scala
|
|
1080
|
+
"scala",
|
|
1081
|
+
// Shell
|
|
1082
|
+
"sh",
|
|
1083
|
+
"bash",
|
|
1084
|
+
"zsh",
|
|
1085
|
+
// Config/Data
|
|
1086
|
+
"json",
|
|
1087
|
+
"yaml",
|
|
1088
|
+
"yml",
|
|
1089
|
+
"toml",
|
|
1090
|
+
"xml",
|
|
1091
|
+
// SQL
|
|
1092
|
+
"sql",
|
|
1093
|
+
// Markdown/Docs
|
|
1094
|
+
"md",
|
|
1095
|
+
"markdown",
|
|
1096
|
+
"rst",
|
|
1097
|
+
"txt",
|
|
1098
|
+
// HTML/CSS
|
|
1099
|
+
"html",
|
|
1100
|
+
"htm",
|
|
1101
|
+
"css",
|
|
1102
|
+
"scss",
|
|
1103
|
+
"sass",
|
|
1104
|
+
"less",
|
|
1105
|
+
// Other
|
|
1106
|
+
"graphql",
|
|
1107
|
+
"proto",
|
|
1108
|
+
"dockerfile"
|
|
1109
|
+
]);
|
|
1110
|
+
IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
1111
|
+
"node_modules",
|
|
1112
|
+
".git",
|
|
1113
|
+
".svn",
|
|
1114
|
+
".hg",
|
|
1115
|
+
"target",
|
|
1116
|
+
"dist",
|
|
1117
|
+
"build",
|
|
1118
|
+
"out",
|
|
1119
|
+
".next",
|
|
1120
|
+
".nuxt",
|
|
1121
|
+
"__pycache__",
|
|
1122
|
+
".pytest_cache",
|
|
1123
|
+
".mypy_cache",
|
|
1124
|
+
"venv",
|
|
1125
|
+
".venv",
|
|
1126
|
+
"env",
|
|
1127
|
+
".env",
|
|
1128
|
+
"vendor",
|
|
1129
|
+
"coverage",
|
|
1130
|
+
".coverage",
|
|
1131
|
+
".idea",
|
|
1132
|
+
".vscode",
|
|
1133
|
+
".vs"
|
|
1134
|
+
]);
|
|
1135
|
+
IGNORE_FILES = /* @__PURE__ */ new Set([
|
|
1136
|
+
".DS_Store",
|
|
1137
|
+
"Thumbs.db",
|
|
1138
|
+
".gitignore",
|
|
1139
|
+
".gitattributes",
|
|
1140
|
+
"package-lock.json",
|
|
1141
|
+
"yarn.lock",
|
|
1142
|
+
"pnpm-lock.yaml",
|
|
1143
|
+
"Cargo.lock",
|
|
1144
|
+
"poetry.lock",
|
|
1145
|
+
"Gemfile.lock",
|
|
1146
|
+
"composer.lock"
|
|
1147
|
+
]);
|
|
1148
|
+
MAX_FILE_SIZE = 1024 * 1024;
|
|
1149
|
+
MAX_BATCH_BYTES = 10 * 1024 * 1024;
|
|
1150
|
+
LARGE_FILE_THRESHOLD = 2 * 1024 * 1024;
|
|
1151
|
+
MAX_FILES_PER_BATCH = 200;
|
|
1152
|
+
gitContextCache = /* @__PURE__ */ new Map();
|
|
1153
|
+
}
|
|
1154
|
+
});
|
|
11
1155
|
|
|
12
1156
|
// src/hooks-config.ts
|
|
13
1157
|
var hooks_config_exports = {};
|
|
@@ -22,6 +1166,7 @@ __export(hooks_config_exports, {
|
|
|
22
1166
|
PRETOOLUSE_HOOK_SCRIPT: () => PRETOOLUSE_HOOK_SCRIPT,
|
|
23
1167
|
USER_PROMPT_HOOK_SCRIPT: () => USER_PROMPT_HOOK_SCRIPT,
|
|
24
1168
|
buildHooksConfig: () => buildHooksConfig,
|
|
1169
|
+
clearProjectIndex: () => clearProjectIndex,
|
|
25
1170
|
generateAllHooksDocumentation: () => generateAllHooksDocumentation,
|
|
26
1171
|
generateHooksDocumentation: () => generateHooksDocumentation,
|
|
27
1172
|
getClaudeSettingsPath: () => getClaudeSettingsPath,
|
|
@@ -51,17 +1196,17 @@ __export(hooks_config_exports, {
|
|
|
51
1196
|
writeCursorHooksConfig: () => writeCursorHooksConfig,
|
|
52
1197
|
writeIndexStatus: () => writeIndexStatus
|
|
53
1198
|
});
|
|
54
|
-
import * as
|
|
1199
|
+
import * as fs3 from "node:fs/promises";
|
|
55
1200
|
import * as fsSync from "node:fs";
|
|
56
|
-
import * as
|
|
57
|
-
import { homedir } from "node:os";
|
|
1201
|
+
import * as path3 from "node:path";
|
|
1202
|
+
import { homedir as homedir2 } from "node:os";
|
|
58
1203
|
import { fileURLToPath } from "node:url";
|
|
59
1204
|
function getHookCommand(hookName) {
|
|
60
1205
|
const isWindows = process.platform === "win32";
|
|
61
1206
|
if (isWindows) {
|
|
62
1207
|
const localAppData = process.env.LOCALAPPDATA;
|
|
63
1208
|
if (localAppData) {
|
|
64
|
-
const windowsBinaryPath =
|
|
1209
|
+
const windowsBinaryPath = path3.join(localAppData, "ContextStream", "contextstream-mcp.exe");
|
|
65
1210
|
if (fsSync.existsSync(windowsBinaryPath)) {
|
|
66
1211
|
return `"${windowsBinaryPath}" hook ${hookName}`;
|
|
67
1212
|
}
|
|
@@ -73,8 +1218,8 @@ function getHookCommand(hookName) {
|
|
|
73
1218
|
}
|
|
74
1219
|
}
|
|
75
1220
|
try {
|
|
76
|
-
const __dirname =
|
|
77
|
-
const indexPath =
|
|
1221
|
+
const __dirname = path3.dirname(fileURLToPath(import.meta.url));
|
|
1222
|
+
const indexPath = path3.join(__dirname, "index.js");
|
|
78
1223
|
if (fsSync.existsSync(indexPath)) {
|
|
79
1224
|
return `node "${indexPath}" hook ${hookName}`;
|
|
80
1225
|
}
|
|
@@ -84,15 +1229,15 @@ function getHookCommand(hookName) {
|
|
|
84
1229
|
}
|
|
85
1230
|
function getClaudeSettingsPath(scope, projectPath) {
|
|
86
1231
|
if (scope === "user") {
|
|
87
|
-
return
|
|
1232
|
+
return path3.join(homedir2(), ".claude", "settings.json");
|
|
88
1233
|
}
|
|
89
1234
|
if (!projectPath) {
|
|
90
1235
|
throw new Error("projectPath required for project scope");
|
|
91
1236
|
}
|
|
92
|
-
return
|
|
1237
|
+
return path3.join(projectPath, ".claude", "settings.json");
|
|
93
1238
|
}
|
|
94
1239
|
function getHooksDir() {
|
|
95
|
-
return
|
|
1240
|
+
return path3.join(homedir2(), ".claude", "hooks");
|
|
96
1241
|
}
|
|
97
1242
|
function buildHooksConfig(options) {
|
|
98
1243
|
const userPromptHooks = [
|
|
@@ -268,7 +1413,7 @@ function buildHooksConfig(options) {
|
|
|
268
1413
|
}
|
|
269
1414
|
async function installHookScripts(options) {
|
|
270
1415
|
const hooksDir = getHooksDir();
|
|
271
|
-
await
|
|
1416
|
+
await fs3.mkdir(hooksDir, { recursive: true });
|
|
272
1417
|
const result = {
|
|
273
1418
|
preToolUse: getHookCommand("pre-tool-use"),
|
|
274
1419
|
userPrompt: getHookCommand("user-prompt-submit")
|
|
@@ -287,7 +1432,7 @@ async function installHookScripts(options) {
|
|
|
287
1432
|
async function readClaudeSettings(scope, projectPath) {
|
|
288
1433
|
const settingsPath = getClaudeSettingsPath(scope, projectPath);
|
|
289
1434
|
try {
|
|
290
|
-
const content = await
|
|
1435
|
+
const content = await fs3.readFile(settingsPath, "utf-8");
|
|
291
1436
|
return JSON.parse(content);
|
|
292
1437
|
} catch {
|
|
293
1438
|
return {};
|
|
@@ -295,9 +1440,9 @@ async function readClaudeSettings(scope, projectPath) {
|
|
|
295
1440
|
}
|
|
296
1441
|
async function writeClaudeSettings(settings, scope, projectPath) {
|
|
297
1442
|
const settingsPath = getClaudeSettingsPath(scope, projectPath);
|
|
298
|
-
const dir =
|
|
299
|
-
await
|
|
300
|
-
await
|
|
1443
|
+
const dir = path3.dirname(settingsPath);
|
|
1444
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
1445
|
+
await fs3.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
301
1446
|
}
|
|
302
1447
|
function mergeHooksIntoSettings(existingSettings, newHooks) {
|
|
303
1448
|
const settings = { ...existingSettings };
|
|
@@ -447,12 +1592,12 @@ If you prefer to configure manually, add to \`~/.claude/settings.json\`:
|
|
|
447
1592
|
`.trim();
|
|
448
1593
|
}
|
|
449
1594
|
function getIndexStatusPath() {
|
|
450
|
-
return
|
|
1595
|
+
return path3.join(homedir2(), ".contextstream", "indexed-projects.json");
|
|
451
1596
|
}
|
|
452
1597
|
async function readIndexStatus() {
|
|
453
1598
|
const statusPath = getIndexStatusPath();
|
|
454
1599
|
try {
|
|
455
|
-
const content = await
|
|
1600
|
+
const content = await fs3.readFile(statusPath, "utf-8");
|
|
456
1601
|
return JSON.parse(content);
|
|
457
1602
|
} catch {
|
|
458
1603
|
return { version: 1, projects: {} };
|
|
@@ -460,13 +1605,13 @@ async function readIndexStatus() {
|
|
|
460
1605
|
}
|
|
461
1606
|
async function writeIndexStatus(status) {
|
|
462
1607
|
const statusPath = getIndexStatusPath();
|
|
463
|
-
const dir =
|
|
464
|
-
await
|
|
465
|
-
await
|
|
1608
|
+
const dir = path3.dirname(statusPath);
|
|
1609
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
1610
|
+
await fs3.writeFile(statusPath, JSON.stringify(status, null, 2));
|
|
466
1611
|
}
|
|
467
1612
|
async function markProjectIndexed(projectPath, options) {
|
|
468
1613
|
const status = await readIndexStatus();
|
|
469
|
-
const resolvedPath =
|
|
1614
|
+
const resolvedPath = path3.resolve(projectPath);
|
|
470
1615
|
status.projects[resolvedPath] = {
|
|
471
1616
|
indexed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
472
1617
|
project_id: options?.project_id,
|
|
@@ -476,18 +1621,25 @@ async function markProjectIndexed(projectPath, options) {
|
|
|
476
1621
|
}
|
|
477
1622
|
async function unmarkProjectIndexed(projectPath) {
|
|
478
1623
|
const status = await readIndexStatus();
|
|
479
|
-
const resolvedPath =
|
|
1624
|
+
const resolvedPath = path3.resolve(projectPath);
|
|
480
1625
|
delete status.projects[resolvedPath];
|
|
481
1626
|
await writeIndexStatus(status);
|
|
482
1627
|
}
|
|
1628
|
+
async function clearProjectIndex(projectPath, projectId) {
|
|
1629
|
+
await unmarkProjectIndexed(projectPath);
|
|
1630
|
+
if (projectId) {
|
|
1631
|
+
const { deleteHashManifest: deleteHashManifest2 } = await Promise.resolve().then(() => (init_files(), files_exports));
|
|
1632
|
+
deleteHashManifest2(projectId);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
483
1635
|
function getClineHooksDir(scope, projectPath) {
|
|
484
1636
|
if (scope === "global") {
|
|
485
|
-
return
|
|
1637
|
+
return path3.join(homedir2(), "Documents", "Cline", "Rules", "Hooks");
|
|
486
1638
|
}
|
|
487
1639
|
if (!projectPath) {
|
|
488
1640
|
throw new Error("projectPath required for project scope");
|
|
489
1641
|
}
|
|
490
|
-
return
|
|
1642
|
+
return path3.join(projectPath, ".clinerules", "hooks");
|
|
491
1643
|
}
|
|
492
1644
|
function getHookWrapperScript(hookName) {
|
|
493
1645
|
const isWindows = process.platform === "win32";
|
|
@@ -511,107 +1663,107 @@ exec ${command}
|
|
|
511
1663
|
}
|
|
512
1664
|
async function installClineHookScripts(options) {
|
|
513
1665
|
const hooksDir = getClineHooksDir(options.scope, options.projectPath);
|
|
514
|
-
await
|
|
1666
|
+
await fs3.mkdir(hooksDir, { recursive: true });
|
|
515
1667
|
const preToolUseWrapper = getHookWrapperScript("pre-tool-use");
|
|
516
1668
|
const userPromptWrapper = getHookWrapperScript("user-prompt-submit");
|
|
517
1669
|
const postWriteWrapper = getHookWrapperScript("post-write");
|
|
518
|
-
const preToolUsePath =
|
|
519
|
-
const userPromptPath =
|
|
520
|
-
const postToolUsePath =
|
|
521
|
-
await
|
|
522
|
-
await
|
|
1670
|
+
const preToolUsePath = path3.join(hooksDir, `PreToolUse${preToolUseWrapper.extension}`);
|
|
1671
|
+
const userPromptPath = path3.join(hooksDir, `UserPromptSubmit${userPromptWrapper.extension}`);
|
|
1672
|
+
const postToolUsePath = path3.join(hooksDir, `PostToolUse${postWriteWrapper.extension}`);
|
|
1673
|
+
await fs3.writeFile(preToolUsePath, preToolUseWrapper.content, { mode: 493 });
|
|
1674
|
+
await fs3.writeFile(userPromptPath, userPromptWrapper.content, { mode: 493 });
|
|
523
1675
|
const result = {
|
|
524
1676
|
preToolUse: preToolUsePath,
|
|
525
1677
|
userPromptSubmit: userPromptPath
|
|
526
1678
|
};
|
|
527
1679
|
if (options.includePostWrite !== false) {
|
|
528
|
-
await
|
|
1680
|
+
await fs3.writeFile(postToolUsePath, postWriteWrapper.content, { mode: 493 });
|
|
529
1681
|
result.postToolUse = postToolUsePath;
|
|
530
1682
|
}
|
|
531
1683
|
return result;
|
|
532
1684
|
}
|
|
533
1685
|
function getRooCodeHooksDir(scope, projectPath) {
|
|
534
1686
|
if (scope === "global") {
|
|
535
|
-
return
|
|
1687
|
+
return path3.join(homedir2(), ".roo", "hooks");
|
|
536
1688
|
}
|
|
537
1689
|
if (!projectPath) {
|
|
538
1690
|
throw new Error("projectPath required for project scope");
|
|
539
1691
|
}
|
|
540
|
-
return
|
|
1692
|
+
return path3.join(projectPath, ".roo", "hooks");
|
|
541
1693
|
}
|
|
542
1694
|
async function installRooCodeHookScripts(options) {
|
|
543
1695
|
const hooksDir = getRooCodeHooksDir(options.scope, options.projectPath);
|
|
544
|
-
await
|
|
1696
|
+
await fs3.mkdir(hooksDir, { recursive: true });
|
|
545
1697
|
const preToolUseWrapper = getHookWrapperScript("pre-tool-use");
|
|
546
1698
|
const userPromptWrapper = getHookWrapperScript("user-prompt-submit");
|
|
547
1699
|
const postWriteWrapper = getHookWrapperScript("post-write");
|
|
548
|
-
const preToolUsePath =
|
|
549
|
-
const userPromptPath =
|
|
550
|
-
const postToolUsePath =
|
|
551
|
-
await
|
|
552
|
-
await
|
|
1700
|
+
const preToolUsePath = path3.join(hooksDir, `PreToolUse${preToolUseWrapper.extension}`);
|
|
1701
|
+
const userPromptPath = path3.join(hooksDir, `UserPromptSubmit${userPromptWrapper.extension}`);
|
|
1702
|
+
const postToolUsePath = path3.join(hooksDir, `PostToolUse${postWriteWrapper.extension}`);
|
|
1703
|
+
await fs3.writeFile(preToolUsePath, preToolUseWrapper.content, { mode: 493 });
|
|
1704
|
+
await fs3.writeFile(userPromptPath, userPromptWrapper.content, { mode: 493 });
|
|
553
1705
|
const result = {
|
|
554
1706
|
preToolUse: preToolUsePath,
|
|
555
1707
|
userPromptSubmit: userPromptPath
|
|
556
1708
|
};
|
|
557
1709
|
if (options.includePostWrite !== false) {
|
|
558
|
-
await
|
|
1710
|
+
await fs3.writeFile(postToolUsePath, postWriteWrapper.content, { mode: 493 });
|
|
559
1711
|
result.postToolUse = postToolUsePath;
|
|
560
1712
|
}
|
|
561
1713
|
return result;
|
|
562
1714
|
}
|
|
563
1715
|
function getKiloCodeHooksDir(scope, projectPath) {
|
|
564
1716
|
if (scope === "global") {
|
|
565
|
-
return
|
|
1717
|
+
return path3.join(homedir2(), ".kilocode", "hooks");
|
|
566
1718
|
}
|
|
567
1719
|
if (!projectPath) {
|
|
568
1720
|
throw new Error("projectPath required for project scope");
|
|
569
1721
|
}
|
|
570
|
-
return
|
|
1722
|
+
return path3.join(projectPath, ".kilocode", "hooks");
|
|
571
1723
|
}
|
|
572
1724
|
async function installKiloCodeHookScripts(options) {
|
|
573
1725
|
const hooksDir = getKiloCodeHooksDir(options.scope, options.projectPath);
|
|
574
|
-
await
|
|
1726
|
+
await fs3.mkdir(hooksDir, { recursive: true });
|
|
575
1727
|
const preToolUseWrapper = getHookWrapperScript("pre-tool-use");
|
|
576
1728
|
const userPromptWrapper = getHookWrapperScript("user-prompt-submit");
|
|
577
1729
|
const postWriteWrapper = getHookWrapperScript("post-write");
|
|
578
|
-
const preToolUsePath =
|
|
579
|
-
const userPromptPath =
|
|
580
|
-
const postToolUsePath =
|
|
581
|
-
await
|
|
582
|
-
await
|
|
1730
|
+
const preToolUsePath = path3.join(hooksDir, `PreToolUse${preToolUseWrapper.extension}`);
|
|
1731
|
+
const userPromptPath = path3.join(hooksDir, `UserPromptSubmit${userPromptWrapper.extension}`);
|
|
1732
|
+
const postToolUsePath = path3.join(hooksDir, `PostToolUse${postWriteWrapper.extension}`);
|
|
1733
|
+
await fs3.writeFile(preToolUsePath, preToolUseWrapper.content, { mode: 493 });
|
|
1734
|
+
await fs3.writeFile(userPromptPath, userPromptWrapper.content, { mode: 493 });
|
|
583
1735
|
const result = {
|
|
584
1736
|
preToolUse: preToolUsePath,
|
|
585
1737
|
userPromptSubmit: userPromptPath
|
|
586
1738
|
};
|
|
587
1739
|
if (options.includePostWrite !== false) {
|
|
588
|
-
await
|
|
1740
|
+
await fs3.writeFile(postToolUsePath, postWriteWrapper.content, { mode: 493 });
|
|
589
1741
|
result.postToolUse = postToolUsePath;
|
|
590
1742
|
}
|
|
591
1743
|
return result;
|
|
592
1744
|
}
|
|
593
1745
|
function getCursorHooksConfigPath(scope, projectPath) {
|
|
594
1746
|
if (scope === "global") {
|
|
595
|
-
return
|
|
1747
|
+
return path3.join(homedir2(), ".cursor", "hooks.json");
|
|
596
1748
|
}
|
|
597
1749
|
if (!projectPath) {
|
|
598
1750
|
throw new Error("projectPath required for project scope");
|
|
599
1751
|
}
|
|
600
|
-
return
|
|
1752
|
+
return path3.join(projectPath, ".cursor", "hooks.json");
|
|
601
1753
|
}
|
|
602
1754
|
function getCursorHooksDir(scope, projectPath) {
|
|
603
1755
|
if (scope === "global") {
|
|
604
|
-
return
|
|
1756
|
+
return path3.join(homedir2(), ".cursor", "hooks");
|
|
605
1757
|
}
|
|
606
1758
|
if (!projectPath) {
|
|
607
1759
|
throw new Error("projectPath required for project scope");
|
|
608
1760
|
}
|
|
609
|
-
return
|
|
1761
|
+
return path3.join(projectPath, ".cursor", "hooks");
|
|
610
1762
|
}
|
|
611
1763
|
async function readCursorHooksConfig(scope, projectPath) {
|
|
612
1764
|
const configPath = getCursorHooksConfigPath(scope, projectPath);
|
|
613
1765
|
try {
|
|
614
|
-
const content = await
|
|
1766
|
+
const content = await fs3.readFile(configPath, "utf-8");
|
|
615
1767
|
return JSON.parse(content);
|
|
616
1768
|
} catch {
|
|
617
1769
|
return { version: 1, hooks: {} };
|
|
@@ -619,13 +1771,13 @@ async function readCursorHooksConfig(scope, projectPath) {
|
|
|
619
1771
|
}
|
|
620
1772
|
async function writeCursorHooksConfig(config, scope, projectPath) {
|
|
621
1773
|
const configPath = getCursorHooksConfigPath(scope, projectPath);
|
|
622
|
-
const dir =
|
|
623
|
-
await
|
|
624
|
-
await
|
|
1774
|
+
const dir = path3.dirname(configPath);
|
|
1775
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
1776
|
+
await fs3.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
625
1777
|
}
|
|
626
1778
|
async function installCursorHookScripts(options) {
|
|
627
1779
|
const hooksDir = getCursorHooksDir(options.scope, options.projectPath);
|
|
628
|
-
await
|
|
1780
|
+
await fs3.mkdir(hooksDir, { recursive: true });
|
|
629
1781
|
const existingConfig = await readCursorHooksConfig(options.scope, options.projectPath);
|
|
630
1782
|
const filterContextStreamHooks = (hooks) => {
|
|
631
1783
|
if (!hooks) return [];
|
|
@@ -1637,18 +2789,18 @@ if __name__ == "__main__":
|
|
|
1637
2789
|
});
|
|
1638
2790
|
|
|
1639
2791
|
// src/hooks/auto-rules.ts
|
|
1640
|
-
import * as
|
|
1641
|
-
import * as
|
|
1642
|
-
import { homedir as
|
|
2792
|
+
import * as fs4 from "node:fs";
|
|
2793
|
+
import * as path4 from "node:path";
|
|
2794
|
+
import { homedir as homedir3 } from "node:os";
|
|
1643
2795
|
var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
1644
2796
|
var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
1645
2797
|
var ENABLED = process.env.CONTEXTSTREAM_AUTO_RULES !== "false";
|
|
1646
|
-
var MARKER_FILE =
|
|
2798
|
+
var MARKER_FILE = path4.join(homedir3(), ".contextstream", ".auto-rules-ran");
|
|
1647
2799
|
var COOLDOWN_MS = 4 * 60 * 60 * 1e3;
|
|
1648
2800
|
function hasRunRecently() {
|
|
1649
2801
|
try {
|
|
1650
|
-
if (!
|
|
1651
|
-
const stat =
|
|
2802
|
+
if (!fs4.existsSync(MARKER_FILE)) return false;
|
|
2803
|
+
const stat = fs4.statSync(MARKER_FILE);
|
|
1652
2804
|
const age = Date.now() - stat.mtimeMs;
|
|
1653
2805
|
return age < COOLDOWN_MS;
|
|
1654
2806
|
} catch {
|
|
@@ -1657,11 +2809,11 @@ function hasRunRecently() {
|
|
|
1657
2809
|
}
|
|
1658
2810
|
function markAsRan() {
|
|
1659
2811
|
try {
|
|
1660
|
-
const dir =
|
|
1661
|
-
if (!
|
|
1662
|
-
|
|
2812
|
+
const dir = path4.dirname(MARKER_FILE);
|
|
2813
|
+
if (!fs4.existsSync(dir)) {
|
|
2814
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1663
2815
|
}
|
|
1664
|
-
|
|
2816
|
+
fs4.writeFileSync(MARKER_FILE, (/* @__PURE__ */ new Date()).toISOString());
|
|
1665
2817
|
} catch {
|
|
1666
2818
|
}
|
|
1667
2819
|
}
|
|
@@ -1690,8 +2842,8 @@ function extractCwd(input) {
|
|
|
1690
2842
|
}
|
|
1691
2843
|
function hasPythonHooks(settingsPath) {
|
|
1692
2844
|
try {
|
|
1693
|
-
if (!
|
|
1694
|
-
const content =
|
|
2845
|
+
if (!fs4.existsSync(settingsPath)) return false;
|
|
2846
|
+
const content = fs4.readFileSync(settingsPath, "utf-8");
|
|
1695
2847
|
const settings = JSON.parse(content);
|
|
1696
2848
|
const hooks = settings.hooks;
|
|
1697
2849
|
if (!hooks) return false;
|
|
@@ -1715,8 +2867,8 @@ function hasPythonHooks(settingsPath) {
|
|
|
1715
2867
|
}
|
|
1716
2868
|
}
|
|
1717
2869
|
function detectPythonHooks(cwd) {
|
|
1718
|
-
const globalSettingsPath =
|
|
1719
|
-
const projectSettingsPath =
|
|
2870
|
+
const globalSettingsPath = path4.join(homedir3(), ".claude", "settings.json");
|
|
2871
|
+
const projectSettingsPath = path4.join(cwd, ".claude", "settings.json");
|
|
1720
2872
|
return {
|
|
1721
2873
|
global: hasPythonHooks(globalSettingsPath),
|
|
1722
2874
|
project: hasPythonHooks(projectSettingsPath)
|