@yawlabs/ctxlint 0.9.19 → 0.10.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.
- package/README.md +99 -57
- package/dist/index.js +776 -353
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21934,11 +21934,11 @@ var init_protocol = __esm({
|
|
|
21934
21934
|
*
|
|
21935
21935
|
* The Protocol object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
|
|
21936
21936
|
*/
|
|
21937
|
-
async connect(
|
|
21937
|
+
async connect(transport) {
|
|
21938
21938
|
if (this._transport) {
|
|
21939
21939
|
throw new Error("Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection.");
|
|
21940
21940
|
}
|
|
21941
|
-
this._transport =
|
|
21941
|
+
this._transport = transport;
|
|
21942
21942
|
const _onclose = this.transport?.onclose;
|
|
21943
21943
|
this._transport.onclose = () => {
|
|
21944
21944
|
_onclose?.();
|
|
@@ -26254,49 +26254,49 @@ var require_fast_uri = __commonJS({
|
|
|
26254
26254
|
schemelessOptions.skipEscape = true;
|
|
26255
26255
|
return serialize(resolved, schemelessOptions);
|
|
26256
26256
|
}
|
|
26257
|
-
function resolveComponent(base,
|
|
26257
|
+
function resolveComponent(base, relative5, options, skipNormalization) {
|
|
26258
26258
|
const target = {};
|
|
26259
26259
|
if (!skipNormalization) {
|
|
26260
26260
|
base = parse4(serialize(base, options), options);
|
|
26261
|
-
|
|
26261
|
+
relative5 = parse4(serialize(relative5, options), options);
|
|
26262
26262
|
}
|
|
26263
26263
|
options = options || {};
|
|
26264
|
-
if (!options.tolerant &&
|
|
26265
|
-
target.scheme =
|
|
26266
|
-
target.userinfo =
|
|
26267
|
-
target.host =
|
|
26268
|
-
target.port =
|
|
26269
|
-
target.path = removeDotSegments(
|
|
26270
|
-
target.query =
|
|
26264
|
+
if (!options.tolerant && relative5.scheme) {
|
|
26265
|
+
target.scheme = relative5.scheme;
|
|
26266
|
+
target.userinfo = relative5.userinfo;
|
|
26267
|
+
target.host = relative5.host;
|
|
26268
|
+
target.port = relative5.port;
|
|
26269
|
+
target.path = removeDotSegments(relative5.path || "");
|
|
26270
|
+
target.query = relative5.query;
|
|
26271
26271
|
} else {
|
|
26272
|
-
if (
|
|
26273
|
-
target.userinfo =
|
|
26274
|
-
target.host =
|
|
26275
|
-
target.port =
|
|
26276
|
-
target.path = removeDotSegments(
|
|
26277
|
-
target.query =
|
|
26272
|
+
if (relative5.userinfo !== void 0 || relative5.host !== void 0 || relative5.port !== void 0) {
|
|
26273
|
+
target.userinfo = relative5.userinfo;
|
|
26274
|
+
target.host = relative5.host;
|
|
26275
|
+
target.port = relative5.port;
|
|
26276
|
+
target.path = removeDotSegments(relative5.path || "");
|
|
26277
|
+
target.query = relative5.query;
|
|
26278
26278
|
} else {
|
|
26279
|
-
if (!
|
|
26279
|
+
if (!relative5.path) {
|
|
26280
26280
|
target.path = base.path;
|
|
26281
|
-
if (
|
|
26282
|
-
target.query =
|
|
26281
|
+
if (relative5.query !== void 0) {
|
|
26282
|
+
target.query = relative5.query;
|
|
26283
26283
|
} else {
|
|
26284
26284
|
target.query = base.query;
|
|
26285
26285
|
}
|
|
26286
26286
|
} else {
|
|
26287
|
-
if (
|
|
26288
|
-
target.path = removeDotSegments(
|
|
26287
|
+
if (relative5.path[0] === "/") {
|
|
26288
|
+
target.path = removeDotSegments(relative5.path);
|
|
26289
26289
|
} else {
|
|
26290
26290
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
26291
|
-
target.path = "/" +
|
|
26291
|
+
target.path = "/" + relative5.path;
|
|
26292
26292
|
} else if (!base.path) {
|
|
26293
|
-
target.path =
|
|
26293
|
+
target.path = relative5.path;
|
|
26294
26294
|
} else {
|
|
26295
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
26295
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative5.path;
|
|
26296
26296
|
}
|
|
26297
26297
|
target.path = removeDotSegments(target.path);
|
|
26298
26298
|
}
|
|
26299
|
-
target.query =
|
|
26299
|
+
target.query = relative5.query;
|
|
26300
26300
|
}
|
|
26301
26301
|
target.userinfo = base.userinfo;
|
|
26302
26302
|
target.host = base.host;
|
|
@@ -26304,7 +26304,7 @@ var require_fast_uri = __commonJS({
|
|
|
26304
26304
|
}
|
|
26305
26305
|
target.scheme = base.scheme;
|
|
26306
26306
|
}
|
|
26307
|
-
target.fragment =
|
|
26307
|
+
target.fragment = relative5.fragment;
|
|
26308
26308
|
return target;
|
|
26309
26309
|
}
|
|
26310
26310
|
function equal(uriA, uriB, options) {
|
|
@@ -30411,8 +30411,8 @@ var init_mcp = __esm({
|
|
|
30411
30411
|
*
|
|
30412
30412
|
* The `server` object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
|
|
30413
30413
|
*/
|
|
30414
|
-
async connect(
|
|
30415
|
-
return await this.server.connect(
|
|
30414
|
+
async connect(transport) {
|
|
30415
|
+
return await this.server.connect(transport);
|
|
30416
30416
|
}
|
|
30417
30417
|
/**
|
|
30418
30418
|
* Closes the connection.
|
|
@@ -34201,10 +34201,13 @@ globstar while`, t2, d, e, f, m), this.matchOne(t2.slice(d), e.slice(f), s)) ret
|
|
|
34201
34201
|
// src/utils/fs.ts
|
|
34202
34202
|
import * as fs2 from "node:fs";
|
|
34203
34203
|
import * as path from "node:path";
|
|
34204
|
+
function stripBom(content) {
|
|
34205
|
+
return content.charCodeAt(0) === 65279 ? content.slice(1) : content;
|
|
34206
|
+
}
|
|
34204
34207
|
function loadPackageJson(projectRoot) {
|
|
34205
34208
|
if (pkgJsonCache?.root === projectRoot) return pkgJsonCache.data;
|
|
34206
34209
|
try {
|
|
34207
|
-
const content = fs2.readFileSync(path.join(projectRoot, "package.json"), "utf-8");
|
|
34210
|
+
const content = stripBom(fs2.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
|
|
34208
34211
|
const data = JSON.parse(content);
|
|
34209
34212
|
pkgJsonCache = { root: projectRoot, data };
|
|
34210
34213
|
return data;
|
|
@@ -34247,7 +34250,7 @@ function readSymlinkTarget(filePath) {
|
|
|
34247
34250
|
}
|
|
34248
34251
|
function readFileContent(filePath) {
|
|
34249
34252
|
try {
|
|
34250
|
-
return fs2.readFileSync(filePath, "utf-8");
|
|
34253
|
+
return stripBom(fs2.readFileSync(filePath, "utf-8"));
|
|
34251
34254
|
} catch (err) {
|
|
34252
34255
|
const code = err.code;
|
|
34253
34256
|
if (code === "ENOENT") {
|
|
@@ -34449,6 +34452,8 @@ async function scanGlobalMcpConfigs() {
|
|
|
34449
34452
|
} catch {
|
|
34450
34453
|
continue;
|
|
34451
34454
|
}
|
|
34455
|
+
const isGeneralClaudeFile = normalized.endsWith(`${path2.sep}.claude.json`) || normalized.endsWith(`${path2.sep}.claude${path2.sep}settings.json`);
|
|
34456
|
+
if (isGeneralClaudeFile && !mcpFileHasMcpKey(normalized)) continue;
|
|
34452
34457
|
const symlink = isSymlink(normalized);
|
|
34453
34458
|
const target = symlink ? readSymlinkTarget(normalized) : void 0;
|
|
34454
34459
|
found.push({
|
|
@@ -34461,6 +34466,14 @@ async function scanGlobalMcpConfigs() {
|
|
|
34461
34466
|
}
|
|
34462
34467
|
return found.sort((a, b2) => a.relativePath.localeCompare(b2.relativePath));
|
|
34463
34468
|
}
|
|
34469
|
+
function mcpFileHasMcpKey(filePath) {
|
|
34470
|
+
try {
|
|
34471
|
+
const content = fs3.readFileSync(filePath, "utf8");
|
|
34472
|
+
return /"(mcpServers|servers)"\s*:/.test(content);
|
|
34473
|
+
} catch {
|
|
34474
|
+
return false;
|
|
34475
|
+
}
|
|
34476
|
+
}
|
|
34464
34477
|
var CONTEXT_FILE_PATTERNS, IGNORED_DIRS2, MCP_CONFIG_PATTERNS, MCPH_CONFIG_PATTERNS;
|
|
34465
34478
|
var init_scanner = __esm({
|
|
34466
34479
|
"src/core/scanner.ts"() {
|
|
@@ -40656,10 +40669,9 @@ var init_git = __esm({
|
|
|
40656
40669
|
});
|
|
40657
40670
|
|
|
40658
40671
|
// src/core/mcp-parser.ts
|
|
40659
|
-
async function parseMcpConfig(file2, projectRoot,
|
|
40672
|
+
async function parseMcpConfig(file2, projectRoot, scope) {
|
|
40660
40673
|
const content = readFileContent(file2.absolutePath);
|
|
40661
40674
|
const client = detectClient(file2.relativePath);
|
|
40662
|
-
const scope = scopeOverride ?? detectScope(file2.relativePath);
|
|
40663
40675
|
const expectedRootKey = client === "vscode" ? "servers" : "mcpServers";
|
|
40664
40676
|
const isGitTracked = await checkGitTracked(file2.absolutePath, projectRoot);
|
|
40665
40677
|
const result = {
|
|
@@ -40697,16 +40709,17 @@ async function parseMcpConfig(file2, projectRoot, scopeOverride) {
|
|
|
40697
40709
|
return result;
|
|
40698
40710
|
}
|
|
40699
40711
|
const lines = content.split("\n");
|
|
40712
|
+
const rootKeyLine = findRootKeyLine(lines, rootKey);
|
|
40700
40713
|
for (const [name, value] of Object.entries(serversObj)) {
|
|
40701
40714
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
40702
40715
|
continue;
|
|
40703
40716
|
}
|
|
40704
40717
|
const raw = value;
|
|
40705
|
-
const line = findServerLine(lines, name);
|
|
40706
|
-
const
|
|
40718
|
+
const line = findServerLine(lines, name, rootKeyLine);
|
|
40719
|
+
const transport = inferTransport(raw);
|
|
40707
40720
|
const entry = {
|
|
40708
40721
|
name,
|
|
40709
|
-
transport
|
|
40722
|
+
transport,
|
|
40710
40723
|
line,
|
|
40711
40724
|
raw
|
|
40712
40725
|
};
|
|
@@ -40718,7 +40731,7 @@ async function parseMcpConfig(file2, projectRoot, scopeOverride) {
|
|
|
40718
40731
|
if (typeof raw.disabled === "boolean") entry.disabled = raw.disabled;
|
|
40719
40732
|
if (Array.isArray(raw.autoApprove)) entry.autoApprove = raw.autoApprove.map(String);
|
|
40720
40733
|
if (typeof raw.timeout === "number") entry.timeout = raw.timeout;
|
|
40721
|
-
if (typeof raw.oauth === "object" && raw.oauth !== null)
|
|
40734
|
+
if (typeof raw.oauth === "object" && raw.oauth !== null && !Array.isArray(raw.oauth))
|
|
40722
40735
|
entry.oauth = raw.oauth;
|
|
40723
40736
|
if (typeof raw.headersHelper === "string") entry.headersHelper = raw.headersHelper;
|
|
40724
40737
|
result.servers.push(entry);
|
|
@@ -40737,9 +40750,6 @@ function detectClient(relativePath) {
|
|
|
40737
40750
|
}
|
|
40738
40751
|
return "claude-code";
|
|
40739
40752
|
}
|
|
40740
|
-
function detectScope(_relativePath) {
|
|
40741
|
-
return "project";
|
|
40742
|
-
}
|
|
40743
40753
|
function findRootKey(parsed) {
|
|
40744
40754
|
if ("mcpServers" in parsed) return "mcpServers";
|
|
40745
40755
|
if ("servers" in parsed) return "servers";
|
|
@@ -40756,15 +40766,24 @@ function inferTransport(raw) {
|
|
|
40756
40766
|
if ("url" in raw) return "http";
|
|
40757
40767
|
return "unknown";
|
|
40758
40768
|
}
|
|
40759
|
-
function
|
|
40760
|
-
const escaped =
|
|
40769
|
+
function findRootKeyLine(lines, rootKey) {
|
|
40770
|
+
const escaped = rootKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
40761
40771
|
const pattern = new RegExp(`"${escaped}"\\s*:`);
|
|
40762
40772
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
40773
|
+
if (pattern.test(lines[i2])) return i2;
|
|
40774
|
+
}
|
|
40775
|
+
return -1;
|
|
40776
|
+
}
|
|
40777
|
+
function findServerLine(lines, serverName, rootKeyLine) {
|
|
40778
|
+
const escaped = serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
40779
|
+
const pattern = new RegExp(`"${escaped}"\\s*:`);
|
|
40780
|
+
const start = rootKeyLine >= 0 ? rootKeyLine + 1 : 0;
|
|
40781
|
+
for (let i2 = start; i2 < lines.length; i2++) {
|
|
40763
40782
|
if (pattern.test(lines[i2])) {
|
|
40764
40783
|
return i2 + 1;
|
|
40765
40784
|
}
|
|
40766
40785
|
}
|
|
40767
|
-
return 1;
|
|
40786
|
+
return rootKeyLine >= 0 ? rootKeyLine + 1 : 1;
|
|
40768
40787
|
}
|
|
40769
40788
|
function isStringRecord(value) {
|
|
40770
40789
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
@@ -41296,11 +41315,11 @@ function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
|
|
|
41296
41315
|
onValue({ type: getNodeType(value), offset, length, parent: currentParent, value });
|
|
41297
41316
|
ensurePropertyComplete(offset + length);
|
|
41298
41317
|
},
|
|
41299
|
-
onSeparator: (
|
|
41318
|
+
onSeparator: (sep2, offset, length) => {
|
|
41300
41319
|
if (currentParent.type === "property") {
|
|
41301
|
-
if (
|
|
41320
|
+
if (sep2 === ":") {
|
|
41302
41321
|
currentParent.colonOffset = offset;
|
|
41303
|
-
} else if (
|
|
41322
|
+
} else if (sep2 === ",") {
|
|
41304
41323
|
ensurePropertyComplete(offset);
|
|
41305
41324
|
}
|
|
41306
41325
|
}
|
|
@@ -41746,9 +41765,9 @@ var init_main = __esm({
|
|
|
41746
41765
|
});
|
|
41747
41766
|
|
|
41748
41767
|
// src/core/mcph-parser.ts
|
|
41749
|
-
async function
|
|
41768
|
+
async function parseMcphConfig(file2, projectRoot, scopeOverride) {
|
|
41750
41769
|
const content = readFileContent(file2.absolutePath);
|
|
41751
|
-
const scope = scopeOverride ??
|
|
41770
|
+
const scope = scopeOverride ?? detectScope(file2.relativePath);
|
|
41752
41771
|
const isGitTracked = await checkGitTracked2(file2.absolutePath, projectRoot);
|
|
41753
41772
|
const isGitignored = await checkGitignored(file2.absolutePath, projectRoot);
|
|
41754
41773
|
const result = {
|
|
@@ -41806,9 +41825,8 @@ async function parseMchpConfig(file2, projectRoot, scopeOverride) {
|
|
|
41806
41825
|
}
|
|
41807
41826
|
return result;
|
|
41808
41827
|
}
|
|
41809
|
-
function
|
|
41828
|
+
function detectScope(relativePath) {
|
|
41810
41829
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
41811
|
-
if (normalized.startsWith("~/")) return "global";
|
|
41812
41830
|
if (normalized.endsWith(".mcph.local.json")) return "project-local";
|
|
41813
41831
|
return "project";
|
|
41814
41832
|
}
|
|
@@ -42168,29 +42186,33 @@ async function checkPaths(file2, projectRoot) {
|
|
|
42168
42186
|
function findClosestMatch(target, files) {
|
|
42169
42187
|
const targetNorm = target.replace(/\\/g, "/");
|
|
42170
42188
|
const targetBase = path3.basename(targetNorm);
|
|
42171
|
-
let
|
|
42172
|
-
let
|
|
42189
|
+
let basenameMatch = null;
|
|
42190
|
+
let basenameDistance = Infinity;
|
|
42173
42191
|
for (const file2 of files) {
|
|
42174
42192
|
const fileNorm = file2.replace(/\\/g, "/");
|
|
42175
42193
|
if (path3.basename(fileNorm) === targetBase && fileNorm !== targetNorm) {
|
|
42176
42194
|
const dist = levenshtein(targetNorm, fileNorm);
|
|
42177
|
-
if (dist <
|
|
42178
|
-
|
|
42179
|
-
|
|
42195
|
+
if (dist < basenameDistance) {
|
|
42196
|
+
basenameDistance = dist;
|
|
42197
|
+
basenameMatch = fileNorm;
|
|
42180
42198
|
}
|
|
42181
42199
|
}
|
|
42182
42200
|
}
|
|
42183
|
-
if (
|
|
42184
|
-
|
|
42185
|
-
|
|
42186
|
-
|
|
42187
|
-
|
|
42188
|
-
|
|
42189
|
-
|
|
42190
|
-
|
|
42201
|
+
if (basenameMatch) return basenameMatch;
|
|
42202
|
+
const absoluteCap = Math.max(targetNorm.length * 0.4, 5);
|
|
42203
|
+
let fullPathMatch = null;
|
|
42204
|
+
let fullPathDistance = Infinity;
|
|
42205
|
+
for (const file2 of files) {
|
|
42206
|
+
const fileNorm = file2.replace(/\\/g, "/");
|
|
42207
|
+
const lenDelta = Math.abs(targetNorm.length - fileNorm.length);
|
|
42208
|
+
if (lenDelta >= fullPathDistance || lenDelta > absoluteCap) continue;
|
|
42209
|
+
const dist = levenshtein(targetNorm, fileNorm);
|
|
42210
|
+
if (dist < fullPathDistance && dist <= absoluteCap) {
|
|
42211
|
+
fullPathDistance = dist;
|
|
42212
|
+
fullPathMatch = fileNorm;
|
|
42191
42213
|
}
|
|
42192
42214
|
}
|
|
42193
|
-
return
|
|
42215
|
+
return fullPathMatch;
|
|
42194
42216
|
}
|
|
42195
42217
|
var import_fast_levenshtein, levenshtein, cachedProjectFiles;
|
|
42196
42218
|
var init_paths = __esm({
|
|
@@ -42208,6 +42230,24 @@ var init_paths = __esm({
|
|
|
42208
42230
|
// src/core/checks/commands.ts
|
|
42209
42231
|
import * as fs4 from "node:fs";
|
|
42210
42232
|
import * as path4 from "node:path";
|
|
42233
|
+
function extractNpxPackage(cmd) {
|
|
42234
|
+
if (!/^npx\b/.test(cmd)) return null;
|
|
42235
|
+
const tokens = cmd.split(/\s+/).slice(1);
|
|
42236
|
+
for (let i2 = 0; i2 < tokens.length; i2++) {
|
|
42237
|
+
const t2 = tokens[i2];
|
|
42238
|
+
if (t2 === "-p" || t2 === "--package") {
|
|
42239
|
+
const v2 = tokens[i2 + 1];
|
|
42240
|
+
if (v2 && !v2.startsWith("-")) return v2;
|
|
42241
|
+
continue;
|
|
42242
|
+
}
|
|
42243
|
+
if (t2.startsWith("-p=") || t2.startsWith("--package=")) {
|
|
42244
|
+
return t2.slice(t2.indexOf("=") + 1) || null;
|
|
42245
|
+
}
|
|
42246
|
+
if (t2.startsWith("-")) continue;
|
|
42247
|
+
return t2;
|
|
42248
|
+
}
|
|
42249
|
+
return null;
|
|
42250
|
+
}
|
|
42211
42251
|
async function checkCommands(file2, projectRoot) {
|
|
42212
42252
|
const issues = [];
|
|
42213
42253
|
const pkgJson = loadPackageJson(projectRoot);
|
|
@@ -42246,10 +42286,9 @@ async function checkCommands(file2, projectRoot) {
|
|
|
42246
42286
|
}
|
|
42247
42287
|
continue;
|
|
42248
42288
|
}
|
|
42249
|
-
|
|
42250
|
-
|
|
42251
|
-
|
|
42252
|
-
if (pkgName.startsWith("-")) continue;
|
|
42289
|
+
if (/^npx\b/.test(cmd) && pkgJson) {
|
|
42290
|
+
const pkgName = extractNpxPackage(cmd);
|
|
42291
|
+
if (!pkgName) continue;
|
|
42253
42292
|
const allDeps = {
|
|
42254
42293
|
...pkgJson.dependencies,
|
|
42255
42294
|
...pkgJson.devDependencies,
|
|
@@ -42324,7 +42363,7 @@ async function checkCommands(file2, projectRoot) {
|
|
|
42324
42363
|
}
|
|
42325
42364
|
function loadMakefile(projectRoot) {
|
|
42326
42365
|
try {
|
|
42327
|
-
return fs4.readFileSync(path4.join(projectRoot, "Makefile"), "utf-8");
|
|
42366
|
+
return stripBom(fs4.readFileSync(path4.join(projectRoot, "Makefile"), "utf-8"));
|
|
42328
42367
|
} catch {
|
|
42329
42368
|
return null;
|
|
42330
42369
|
}
|
|
@@ -42333,14 +42372,13 @@ function hasMakeTarget(makefile, target) {
|
|
|
42333
42372
|
const pattern = new RegExp(`^${target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*:`, "m");
|
|
42334
42373
|
return pattern.test(makefile);
|
|
42335
42374
|
}
|
|
42336
|
-
var NPM_SCRIPT_PATTERN, MAKE_PATTERN
|
|
42375
|
+
var NPM_SCRIPT_PATTERN, MAKE_PATTERN;
|
|
42337
42376
|
var init_commands = __esm({
|
|
42338
42377
|
"src/core/checks/commands.ts"() {
|
|
42339
42378
|
"use strict";
|
|
42340
42379
|
init_fs();
|
|
42341
42380
|
NPM_SCRIPT_PATTERN = /^(?:npm\s+run|pnpm(?:\s+run)?|yarn(?:\s+run)?|bun(?:\s+run)?)\s+(\S+)/;
|
|
42342
42381
|
MAKE_PATTERN = /^make\s+(\S+)/;
|
|
42343
|
-
NPX_PATTERN = /^npx\s+(\S+)/;
|
|
42344
42382
|
}
|
|
42345
42383
|
});
|
|
42346
42384
|
|
|
@@ -42405,26 +42443,21 @@ var init_staleness = __esm({
|
|
|
42405
42443
|
});
|
|
42406
42444
|
|
|
42407
42445
|
// src/core/checks/tokens.ts
|
|
42408
|
-
function
|
|
42409
|
-
|
|
42446
|
+
function resolveTokenThresholds(overrides) {
|
|
42447
|
+
if (!overrides) return DEFAULT_TOKEN_THRESHOLDS;
|
|
42448
|
+
const merged = { ...DEFAULT_TOKEN_THRESHOLDS, ...overrides };
|
|
42410
42449
|
if (merged.info >= merged.warning || merged.warning >= merged.error) {
|
|
42411
42450
|
console.error(
|
|
42412
42451
|
`Warning: token thresholds should satisfy info < warning < error (got ${merged.info}, ${merged.warning}, ${merged.error}) \u2014 using defaults`
|
|
42413
42452
|
);
|
|
42414
|
-
return;
|
|
42453
|
+
return DEFAULT_TOKEN_THRESHOLDS;
|
|
42415
42454
|
}
|
|
42416
|
-
|
|
42455
|
+
return merged;
|
|
42417
42456
|
}
|
|
42418
|
-
function
|
|
42419
|
-
currentThresholds = DEFAULT_THRESHOLDS;
|
|
42420
|
-
}
|
|
42421
|
-
function getTokenThresholds() {
|
|
42422
|
-
return currentThresholds;
|
|
42423
|
-
}
|
|
42424
|
-
async function checkTokens(file2, _projectRoot) {
|
|
42457
|
+
async function checkTokens(file2, _projectRoot, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
42425
42458
|
const issues = [];
|
|
42426
42459
|
const tokens = file2.totalTokens;
|
|
42427
|
-
if (tokens >=
|
|
42460
|
+
if (tokens >= thresholds.error) {
|
|
42428
42461
|
issues.push({
|
|
42429
42462
|
severity: "error",
|
|
42430
42463
|
check: "tokens",
|
|
@@ -42433,7 +42466,7 @@ async function checkTokens(file2, _projectRoot) {
|
|
|
42433
42466
|
message: `${tokens.toLocaleString()} tokens \u2014 consumes significant context window space`,
|
|
42434
42467
|
suggestion: "Consider splitting into focused sections or removing redundant content."
|
|
42435
42468
|
});
|
|
42436
|
-
} else if (tokens >=
|
|
42469
|
+
} else if (tokens >= thresholds.warning) {
|
|
42437
42470
|
issues.push({
|
|
42438
42471
|
severity: "warning",
|
|
42439
42472
|
check: "tokens",
|
|
@@ -42442,7 +42475,7 @@ async function checkTokens(file2, _projectRoot) {
|
|
|
42442
42475
|
message: `${tokens.toLocaleString()} tokens \u2014 large context file`,
|
|
42443
42476
|
suggestion: "Consider trimming \u2014 research shows diminishing returns past ~300 lines."
|
|
42444
42477
|
});
|
|
42445
|
-
} else if (tokens >=
|
|
42478
|
+
} else if (tokens >= thresholds.info) {
|
|
42446
42479
|
issues.push({
|
|
42447
42480
|
severity: "info",
|
|
42448
42481
|
check: "tokens",
|
|
@@ -42453,9 +42486,9 @@ async function checkTokens(file2, _projectRoot) {
|
|
|
42453
42486
|
}
|
|
42454
42487
|
return issues;
|
|
42455
42488
|
}
|
|
42456
|
-
function checkAggregateTokens(files) {
|
|
42489
|
+
function checkAggregateTokens(files, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
42457
42490
|
const total = files.reduce((sum, f) => sum + f.tokens, 0);
|
|
42458
|
-
if (total >
|
|
42491
|
+
if (total > thresholds.aggregate && files.length > 1) {
|
|
42459
42492
|
return {
|
|
42460
42493
|
severity: "warning",
|
|
42461
42494
|
check: "tokens",
|
|
@@ -42467,11 +42500,11 @@ function checkAggregateTokens(files) {
|
|
|
42467
42500
|
}
|
|
42468
42501
|
return null;
|
|
42469
42502
|
}
|
|
42470
|
-
var
|
|
42503
|
+
var DEFAULT_TOKEN_THRESHOLDS;
|
|
42471
42504
|
var init_tokens2 = __esm({
|
|
42472
42505
|
"src/core/checks/tokens.ts"() {
|
|
42473
42506
|
"use strict";
|
|
42474
|
-
|
|
42507
|
+
DEFAULT_TOKEN_THRESHOLDS = {
|
|
42475
42508
|
info: 1e3,
|
|
42476
42509
|
warning: 3e3,
|
|
42477
42510
|
error: 8e3,
|
|
@@ -42479,7 +42512,6 @@ var init_tokens2 = __esm({
|
|
|
42479
42512
|
tierBreakdown: 1e3,
|
|
42480
42513
|
tierAggregate: 4e3
|
|
42481
42514
|
};
|
|
42482
|
-
currentThresholds = DEFAULT_THRESHOLDS;
|
|
42483
42515
|
}
|
|
42484
42516
|
});
|
|
42485
42517
|
|
|
@@ -42515,7 +42547,14 @@ function isAlwaysLoaded(file2) {
|
|
|
42515
42547
|
return !hasPathsFrontmatter(file2.content);
|
|
42516
42548
|
}
|
|
42517
42549
|
const basename4 = rel.split("/").pop() ?? "";
|
|
42518
|
-
|
|
42550
|
+
for (const name of ALWAYS_LOADED_NAMES) {
|
|
42551
|
+
if (name.includes("/")) {
|
|
42552
|
+
if (rel === name || rel.endsWith("/" + name)) return true;
|
|
42553
|
+
} else if (basename4 === name) {
|
|
42554
|
+
return true;
|
|
42555
|
+
}
|
|
42556
|
+
}
|
|
42557
|
+
return false;
|
|
42519
42558
|
}
|
|
42520
42559
|
function computeSectionCosts(file2) {
|
|
42521
42560
|
if (file2.sections.length === 0) return [];
|
|
@@ -42537,7 +42576,7 @@ function loadSettingsSources(projectRoot) {
|
|
|
42537
42576
|
for (const p2 of candidates) {
|
|
42538
42577
|
let content;
|
|
42539
42578
|
try {
|
|
42540
|
-
content = fs5.readFileSync(p2, "utf-8");
|
|
42579
|
+
content = stripBom(fs5.readFileSync(p2, "utf-8"));
|
|
42541
42580
|
} catch {
|
|
42542
42581
|
continue;
|
|
42543
42582
|
}
|
|
@@ -42553,9 +42592,17 @@ function canonicalizeCommand(backticked) {
|
|
|
42553
42592
|
const beforeFlags = backticked.trim().split(/\s+--?/, 1)[0];
|
|
42554
42593
|
return beforeFlags.replace(/\s+/g, " ");
|
|
42555
42594
|
}
|
|
42595
|
+
function buildCommandPattern(cmd) {
|
|
42596
|
+
const tokens = cmd.split(/\s+/).filter(Boolean);
|
|
42597
|
+
const escaped = tokens.map((t2) => t2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
42598
|
+
if (tokens.length === 1) {
|
|
42599
|
+
return new RegExp(`(?<![A-Za-z0-9_\\-])${escaped[0]}(?![A-Za-z0-9_\\-])`, "i");
|
|
42600
|
+
}
|
|
42601
|
+
const body = escaped.join("[\\s\\-_]+");
|
|
42602
|
+
return new RegExp(`(?<![A-Za-z0-9])${body}(?![A-Za-z0-9])`, "i");
|
|
42603
|
+
}
|
|
42556
42604
|
function commandIsEnforced(cmd, settings) {
|
|
42557
|
-
const
|
|
42558
|
-
const pattern = new RegExp(`\\b${escaped}\\b`, "i");
|
|
42605
|
+
const pattern = buildCommandPattern(cmd);
|
|
42559
42606
|
for (const s of settings) {
|
|
42560
42607
|
for (const entry of s.permissions?.deny ?? []) {
|
|
42561
42608
|
if (pattern.test(entry)) return true;
|
|
@@ -42590,10 +42637,10 @@ function checkHardEnforcement(file2, settings) {
|
|
|
42590
42637
|
}
|
|
42591
42638
|
return issues;
|
|
42592
42639
|
}
|
|
42593
|
-
async function checkTierTokens(file2, projectRoot) {
|
|
42640
|
+
async function checkTierTokens(file2, projectRoot, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
42594
42641
|
if (!isAlwaysLoaded(file2)) return [];
|
|
42595
42642
|
const issues = [];
|
|
42596
|
-
const threshold =
|
|
42643
|
+
const threshold = thresholds.tierBreakdown;
|
|
42597
42644
|
if (file2.totalTokens >= threshold) {
|
|
42598
42645
|
const sectionCosts = computeSectionCosts(file2);
|
|
42599
42646
|
if (sectionCosts.length > 0) {
|
|
@@ -42616,11 +42663,11 @@ async function checkTierTokens(file2, projectRoot) {
|
|
|
42616
42663
|
issues.push(...checkHardEnforcement(file2, settings));
|
|
42617
42664
|
return issues;
|
|
42618
42665
|
}
|
|
42619
|
-
function checkAggregateTierTokens(files) {
|
|
42666
|
+
function checkAggregateTierTokens(files, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
42620
42667
|
const alwaysLoaded = files.filter(isAlwaysLoaded);
|
|
42621
42668
|
if (alwaysLoaded.length < 2) return null;
|
|
42622
42669
|
const total = alwaysLoaded.reduce((sum, f) => sum + f.totalTokens, 0);
|
|
42623
|
-
const threshold =
|
|
42670
|
+
const threshold = thresholds.tierAggregate;
|
|
42624
42671
|
if (total < threshold) return null;
|
|
42625
42672
|
const breakdown = alwaysLoaded.slice().sort((a, b2) => b2.totalTokens - a.totalTokens).slice(0, 5).map((f) => ` - ${f.relativePath}: ~${f.totalTokens.toLocaleString()} tokens`).join("\n");
|
|
42626
42673
|
return {
|
|
@@ -42633,13 +42680,14 @@ function checkAggregateTierTokens(files) {
|
|
|
42633
42680
|
suggestion: "Consider moving the largest files or their heaviest sections to on-demand tiers (skills, subagents, memory)."
|
|
42634
42681
|
};
|
|
42635
42682
|
}
|
|
42636
|
-
var
|
|
42683
|
+
var ALWAYS_LOADED_NAMES, TOP_SECTIONS_TO_REPORT, INVIOLABLE_WITH_COMMAND;
|
|
42637
42684
|
var init_tier_tokens = __esm({
|
|
42638
42685
|
"src/core/checks/tier-tokens.ts"() {
|
|
42639
42686
|
"use strict";
|
|
42640
42687
|
init_tokens();
|
|
42688
|
+
init_fs();
|
|
42641
42689
|
init_tokens2();
|
|
42642
|
-
|
|
42690
|
+
ALWAYS_LOADED_NAMES = [
|
|
42643
42691
|
"CLAUDE.md",
|
|
42644
42692
|
"CLAUDE.local.md",
|
|
42645
42693
|
"AGENTS.md",
|
|
@@ -42654,15 +42702,42 @@ var init_tier_tokens = __esm({
|
|
|
42654
42702
|
".rules",
|
|
42655
42703
|
".goosehints",
|
|
42656
42704
|
"replit.md",
|
|
42657
|
-
"copilot-instructions.md",
|
|
42658
|
-
"guidelines.md",
|
|
42659
|
-
"
|
|
42660
|
-
|
|
42705
|
+
".github/copilot-instructions.md",
|
|
42706
|
+
".junie/guidelines.md",
|
|
42707
|
+
".junie/AGENTS.md",
|
|
42708
|
+
".goose/instructions.md"
|
|
42709
|
+
];
|
|
42661
42710
|
TOP_SECTIONS_TO_REPORT = 3;
|
|
42662
42711
|
INVIOLABLE_WITH_COMMAND = /\b(NEVER|ALWAYS|DON'?T|DO NOT|MUST NOT)\b[^.!?`]{0,80}`([^`]+)`/i;
|
|
42663
42712
|
}
|
|
42664
42713
|
});
|
|
42665
42714
|
|
|
42715
|
+
// src/utils/similarity.ts
|
|
42716
|
+
function jaccardSimilarityFromSets(a, b2, opts = {}) {
|
|
42717
|
+
const bothEmptyIsIdentical = opts.bothEmptyIsIdentical ?? false;
|
|
42718
|
+
if (a.size === 0 && b2.size === 0) {
|
|
42719
|
+
return bothEmptyIsIdentical ? 1 : 0;
|
|
42720
|
+
}
|
|
42721
|
+
if (a.size === 0 || b2.size === 0) return 0;
|
|
42722
|
+
const [small, large] = a.size <= b2.size ? [a, b2] : [b2, a];
|
|
42723
|
+
let intersection2 = 0;
|
|
42724
|
+
for (const line of small) {
|
|
42725
|
+
if (large.has(line)) intersection2++;
|
|
42726
|
+
}
|
|
42727
|
+
const unionSize = a.size + b2.size - intersection2;
|
|
42728
|
+
return intersection2 / unionSize;
|
|
42729
|
+
}
|
|
42730
|
+
function toLineSet(text, minTokenLen) {
|
|
42731
|
+
return new Set(
|
|
42732
|
+
text.split("\n").map((l) => l.trim()).filter((l) => l.length > minTokenLen)
|
|
42733
|
+
);
|
|
42734
|
+
}
|
|
42735
|
+
var init_similarity = __esm({
|
|
42736
|
+
"src/utils/similarity.ts"() {
|
|
42737
|
+
"use strict";
|
|
42738
|
+
}
|
|
42739
|
+
});
|
|
42740
|
+
|
|
42666
42741
|
// src/core/checks/redundancy.ts
|
|
42667
42742
|
import * as path7 from "node:path";
|
|
42668
42743
|
function compilePatterns(allDeps) {
|
|
@@ -42685,6 +42760,20 @@ function compilePatterns(allDeps) {
|
|
|
42685
42760
|
}
|
|
42686
42761
|
return compiled;
|
|
42687
42762
|
}
|
|
42763
|
+
function getCompiledPatterns(projectRoot, allDeps) {
|
|
42764
|
+
const relevant = [];
|
|
42765
|
+
for (const pkg of Object.keys(PACKAGE_TECH_MAP)) {
|
|
42766
|
+
if (allDeps.has(pkg)) relevant.push(pkg);
|
|
42767
|
+
}
|
|
42768
|
+
relevant.sort();
|
|
42769
|
+
const key = `${projectRoot}\0${relevant.join(" ")}`;
|
|
42770
|
+
let compiled = compiledPatternsCache.get(key);
|
|
42771
|
+
if (!compiled) {
|
|
42772
|
+
compiled = compilePatterns(allDeps);
|
|
42773
|
+
compiledPatternsCache.set(key, compiled);
|
|
42774
|
+
}
|
|
42775
|
+
return compiled;
|
|
42776
|
+
}
|
|
42688
42777
|
async function checkRedundancy(file2, projectRoot) {
|
|
42689
42778
|
const issues = [];
|
|
42690
42779
|
const pkgJson = loadPackageJson(projectRoot);
|
|
@@ -42695,7 +42784,7 @@ async function checkRedundancy(file2, projectRoot) {
|
|
|
42695
42784
|
...Object.keys(pkgJson.peerDependencies || {}),
|
|
42696
42785
|
...Object.keys(pkgJson.optionalDependencies || {})
|
|
42697
42786
|
]);
|
|
42698
|
-
const compiledPatterns =
|
|
42787
|
+
const compiledPatterns = getCompiledPatterns(projectRoot, allDeps);
|
|
42699
42788
|
const lines2 = file2.content.split("\n");
|
|
42700
42789
|
for (let i2 = 0; i2 < lines2.length; i2++) {
|
|
42701
42790
|
const line = lines2[i2];
|
|
@@ -42746,10 +42835,14 @@ async function checkRedundancy(file2, projectRoot) {
|
|
|
42746
42835
|
}
|
|
42747
42836
|
function checkDuplicateContent(files) {
|
|
42748
42837
|
const issues = [];
|
|
42749
|
-
const
|
|
42838
|
+
const lineSets = files.map((f) => toLineSet(f.content, DUPLICATE_CONTENT_MIN_TOKEN_LEN));
|
|
42750
42839
|
for (let i2 = 0; i2 < files.length; i2++) {
|
|
42840
|
+
const a = lineSets[i2];
|
|
42841
|
+
if (a.size === 0) continue;
|
|
42751
42842
|
for (let j3 = i2 + 1; j3 < files.length; j3++) {
|
|
42752
|
-
const
|
|
42843
|
+
const b2 = lineSets[j3];
|
|
42844
|
+
if (b2.size === 0) continue;
|
|
42845
|
+
const overlap = jaccardSimilarityFromSets(a, b2);
|
|
42753
42846
|
if (overlap >= DUPLICATE_CONTENT_THRESHOLD) {
|
|
42754
42847
|
issues.push({
|
|
42755
42848
|
severity: "warning",
|
|
@@ -42764,29 +42857,15 @@ function checkDuplicateContent(files) {
|
|
|
42764
42857
|
}
|
|
42765
42858
|
return issues;
|
|
42766
42859
|
}
|
|
42767
|
-
function calculateLineOverlap(contentA, contentB) {
|
|
42768
|
-
const linesA = new Set(
|
|
42769
|
-
contentA.split("\n").map((l) => l.trim()).filter((l) => l.length > 10)
|
|
42770
|
-
);
|
|
42771
|
-
const linesB = new Set(
|
|
42772
|
-
contentB.split("\n").map((l) => l.trim()).filter((l) => l.length > 10)
|
|
42773
|
-
);
|
|
42774
|
-
if (linesA.size === 0 || linesB.size === 0) return 0;
|
|
42775
|
-
let intersection2 = 0;
|
|
42776
|
-
for (const line of linesA) {
|
|
42777
|
-
if (linesB.has(line)) intersection2++;
|
|
42778
|
-
}
|
|
42779
|
-
const unionSize = linesA.size + linesB.size - intersection2;
|
|
42780
|
-
return intersection2 / unionSize;
|
|
42781
|
-
}
|
|
42782
42860
|
function escapeRegex2(str) {
|
|
42783
42861
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
42784
42862
|
}
|
|
42785
|
-
var PACKAGE_TECH_MAP;
|
|
42863
|
+
var PACKAGE_TECH_MAP, compiledPatternsCache, DUPLICATE_CONTENT_THRESHOLD, DUPLICATE_CONTENT_MIN_TOKEN_LEN;
|
|
42786
42864
|
var init_redundancy = __esm({
|
|
42787
42865
|
"src/core/checks/redundancy.ts"() {
|
|
42788
42866
|
"use strict";
|
|
42789
42867
|
init_fs();
|
|
42868
|
+
init_similarity();
|
|
42790
42869
|
init_tokens();
|
|
42791
42870
|
PACKAGE_TECH_MAP = {
|
|
42792
42871
|
react: ["React", "react"],
|
|
@@ -42839,6 +42918,9 @@ var init_redundancy = __esm({
|
|
|
42839
42918
|
cypress: ["Cypress"],
|
|
42840
42919
|
puppeteer: ["Puppeteer"]
|
|
42841
42920
|
};
|
|
42921
|
+
compiledPatternsCache = /* @__PURE__ */ new Map();
|
|
42922
|
+
DUPLICATE_CONTENT_THRESHOLD = 0.6;
|
|
42923
|
+
DUPLICATE_CONTENT_MIN_TOKEN_LEN = 10;
|
|
42842
42924
|
}
|
|
42843
42925
|
});
|
|
42844
42926
|
|
|
@@ -43156,7 +43238,7 @@ var init_contradictions = __esm({
|
|
|
43156
43238
|
function parseFrontmatter(content) {
|
|
43157
43239
|
const lines = content.split("\n");
|
|
43158
43240
|
if (lines[0]?.trim() !== "---") {
|
|
43159
|
-
return { found: false, fields: {}, endLine: 0 };
|
|
43241
|
+
return { found: false, fields: {}, endLine: 0, unclosed: false };
|
|
43160
43242
|
}
|
|
43161
43243
|
const fields = {};
|
|
43162
43244
|
let endLine = 0;
|
|
@@ -43183,9 +43265,9 @@ function parseFrontmatter(content) {
|
|
|
43183
43265
|
}
|
|
43184
43266
|
}
|
|
43185
43267
|
if (endLine === 0) {
|
|
43186
|
-
return { found: true, fields, endLine: lines.length };
|
|
43268
|
+
return { found: true, fields, endLine: lines.length, unclosed: true };
|
|
43187
43269
|
}
|
|
43188
|
-
return { found: true, fields, endLine };
|
|
43270
|
+
return { found: true, fields, endLine, unclosed: false };
|
|
43189
43271
|
}
|
|
43190
43272
|
function isCursorMdc(file2) {
|
|
43191
43273
|
return file2.relativePath.endsWith(".mdc");
|
|
@@ -43196,6 +43278,23 @@ function isCopilotInstructions(file2) {
|
|
|
43196
43278
|
function isWindsurfRule(file2) {
|
|
43197
43279
|
return file2.relativePath.includes(".windsurf/rules/") && file2.relativePath.endsWith(".md");
|
|
43198
43280
|
}
|
|
43281
|
+
function hasUnbalancedBracketsOrQuotes(val) {
|
|
43282
|
+
let square = 0;
|
|
43283
|
+
let curly = 0;
|
|
43284
|
+
for (const ch of val) {
|
|
43285
|
+
if (ch === "[") square++;
|
|
43286
|
+
else if (ch === "]") square--;
|
|
43287
|
+
else if (ch === "{") curly++;
|
|
43288
|
+
else if (ch === "}") curly--;
|
|
43289
|
+
if (square < 0 || curly < 0) return true;
|
|
43290
|
+
}
|
|
43291
|
+
if (square !== 0 || curly !== 0) return true;
|
|
43292
|
+
const doubleQuotes = (val.match(/"/g) || []).length;
|
|
43293
|
+
const singleQuotes = (val.match(/'/g) || []).length;
|
|
43294
|
+
if (doubleQuotes % 2 !== 0) return true;
|
|
43295
|
+
if (singleQuotes % 2 !== 0) return true;
|
|
43296
|
+
return false;
|
|
43297
|
+
}
|
|
43199
43298
|
async function checkFrontmatter(file2, _projectRoot) {
|
|
43200
43299
|
const issues = [];
|
|
43201
43300
|
if (isCursorMdc(file2)) {
|
|
@@ -43207,9 +43306,23 @@ async function checkFrontmatter(file2, _projectRoot) {
|
|
|
43207
43306
|
}
|
|
43208
43307
|
return issues;
|
|
43209
43308
|
}
|
|
43309
|
+
function unclosedFrontmatterIssue() {
|
|
43310
|
+
return {
|
|
43311
|
+
severity: "error",
|
|
43312
|
+
check: "frontmatter",
|
|
43313
|
+
ruleId: "frontmatter/unclosed",
|
|
43314
|
+
line: 1,
|
|
43315
|
+
message: "Frontmatter opens with `---` but is never closed",
|
|
43316
|
+
suggestion: "Add a matching `---` line (with no leading whitespace) after the last frontmatter field"
|
|
43317
|
+
};
|
|
43318
|
+
}
|
|
43210
43319
|
function validateCursorMdc(file2) {
|
|
43211
43320
|
const issues = [];
|
|
43212
43321
|
const fm = parseFrontmatter(file2.content);
|
|
43322
|
+
if (fm.unclosed) {
|
|
43323
|
+
issues.push(unclosedFrontmatterIssue());
|
|
43324
|
+
return issues;
|
|
43325
|
+
}
|
|
43213
43326
|
if (!fm.found) {
|
|
43214
43327
|
issues.push({
|
|
43215
43328
|
severity: "warning",
|
|
@@ -43256,13 +43369,13 @@ function validateCursorMdc(file2) {
|
|
|
43256
43369
|
}
|
|
43257
43370
|
if ("globs" in fm.fields) {
|
|
43258
43371
|
const val = fm.fields["globs"];
|
|
43259
|
-
if (val &&
|
|
43372
|
+
if (val && hasUnbalancedBracketsOrQuotes(val)) {
|
|
43260
43373
|
issues.push({
|
|
43261
43374
|
severity: "warning",
|
|
43262
43375
|
check: "frontmatter",
|
|
43263
43376
|
ruleId: "frontmatter/invalid-value",
|
|
43264
43377
|
line: 1,
|
|
43265
|
-
message: `Possibly
|
|
43378
|
+
message: `Possibly malformed globs value: "${val}"`,
|
|
43266
43379
|
suggestion: 'globs should be a glob pattern like "src/**/*.ts" or an array like ["*.ts", "*.tsx"]'
|
|
43267
43380
|
});
|
|
43268
43381
|
}
|
|
@@ -43272,6 +43385,10 @@ function validateCursorMdc(file2) {
|
|
|
43272
43385
|
function validateCopilotInstructions(file2) {
|
|
43273
43386
|
const issues = [];
|
|
43274
43387
|
const fm = parseFrontmatter(file2.content);
|
|
43388
|
+
if (fm.unclosed) {
|
|
43389
|
+
issues.push(unclosedFrontmatterIssue());
|
|
43390
|
+
return issues;
|
|
43391
|
+
}
|
|
43275
43392
|
if (!fm.found) {
|
|
43276
43393
|
issues.push({
|
|
43277
43394
|
severity: "info",
|
|
@@ -43298,6 +43415,10 @@ function validateCopilotInstructions(file2) {
|
|
|
43298
43415
|
function validateWindsurfRule(file2) {
|
|
43299
43416
|
const issues = [];
|
|
43300
43417
|
const fm = parseFrontmatter(file2.content);
|
|
43418
|
+
if (fm.unclosed) {
|
|
43419
|
+
issues.push(unclosedFrontmatterIssue());
|
|
43420
|
+
return issues;
|
|
43421
|
+
}
|
|
43301
43422
|
if (!fm.found) {
|
|
43302
43423
|
issues.push({
|
|
43303
43424
|
severity: "info",
|
|
@@ -43349,7 +43470,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43349
43470
|
issues.push({
|
|
43350
43471
|
severity: "error",
|
|
43351
43472
|
check: "mcp-schema",
|
|
43352
|
-
ruleId: "invalid-json",
|
|
43473
|
+
ruleId: "mcp-schema/invalid-json",
|
|
43353
43474
|
line: 1,
|
|
43354
43475
|
message: `MCP config is not valid JSON: ${err}`
|
|
43355
43476
|
});
|
|
@@ -43360,7 +43481,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43360
43481
|
issues.push({
|
|
43361
43482
|
severity: "error",
|
|
43362
43483
|
check: "mcp-schema",
|
|
43363
|
-
ruleId: "missing-root-key",
|
|
43484
|
+
ruleId: "mcp-schema/missing-root-key",
|
|
43364
43485
|
line: 1,
|
|
43365
43486
|
message: `MCP config has no "${config2.expectedRootKey}" key`
|
|
43366
43487
|
});
|
|
@@ -43371,7 +43492,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43371
43492
|
issues.push({
|
|
43372
43493
|
severity: "error",
|
|
43373
43494
|
check: "mcp-schema",
|
|
43374
|
-
ruleId: "wrong-root-key",
|
|
43495
|
+
ruleId: "mcp-schema/wrong-root-key",
|
|
43375
43496
|
line,
|
|
43376
43497
|
message: `${config2.relativePath} must use "${config2.expectedRootKey}" as root key, not "${config2.actualRootKey}"`,
|
|
43377
43498
|
fix: {
|
|
@@ -43386,7 +43507,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43386
43507
|
issues.push({
|
|
43387
43508
|
severity: "info",
|
|
43388
43509
|
check: "mcp-schema",
|
|
43389
|
-
ruleId: "empty-servers",
|
|
43510
|
+
ruleId: "mcp-schema/empty-servers",
|
|
43390
43511
|
line: 1,
|
|
43391
43512
|
message: "MCP config has no server entries"
|
|
43392
43513
|
});
|
|
@@ -43397,7 +43518,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43397
43518
|
issues.push({
|
|
43398
43519
|
severity: "error",
|
|
43399
43520
|
check: "mcp-schema",
|
|
43400
|
-
ruleId: "no-name-field",
|
|
43521
|
+
ruleId: "mcp-schema/no-name-field",
|
|
43401
43522
|
line: server2.line,
|
|
43402
43523
|
message: "Server name cannot be empty"
|
|
43403
43524
|
});
|
|
@@ -43409,7 +43530,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43409
43530
|
issues.push({
|
|
43410
43531
|
severity: "warning",
|
|
43411
43532
|
check: "mcp-schema",
|
|
43412
|
-
ruleId: "unknown-transport",
|
|
43533
|
+
ruleId: "mcp-schema/unknown-transport",
|
|
43413
43534
|
line: server2.line,
|
|
43414
43535
|
message: `Server "${server2.name}" has unknown transport type "${typeVal}"`
|
|
43415
43536
|
});
|
|
@@ -43419,7 +43540,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43419
43540
|
issues.push({
|
|
43420
43541
|
severity: "warning",
|
|
43421
43542
|
check: "mcp-schema",
|
|
43422
|
-
ruleId: "ambiguous-transport",
|
|
43543
|
+
ruleId: "mcp-schema/ambiguous-transport",
|
|
43423
43544
|
line: server2.line,
|
|
43424
43545
|
message: `Server "${server2.name}" has both "command" and "url" \u2014 transport is ambiguous`
|
|
43425
43546
|
});
|
|
@@ -43428,7 +43549,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43428
43549
|
issues.push({
|
|
43429
43550
|
severity: "error",
|
|
43430
43551
|
check: "mcp-schema",
|
|
43431
|
-
ruleId: "missing-command",
|
|
43552
|
+
ruleId: "mcp-schema/missing-command",
|
|
43432
43553
|
line: server2.line,
|
|
43433
43554
|
message: `Server "${server2.name}" has no "command" field`
|
|
43434
43555
|
});
|
|
@@ -43437,7 +43558,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43437
43558
|
issues.push({
|
|
43438
43559
|
severity: "error",
|
|
43439
43560
|
check: "mcp-schema",
|
|
43440
|
-
ruleId: "missing-url",
|
|
43561
|
+
ruleId: "mcp-schema/missing-url",
|
|
43441
43562
|
line: server2.line,
|
|
43442
43563
|
message: `Server "${server2.name}" has no "url" field`
|
|
43443
43564
|
});
|
|
@@ -43469,6 +43590,10 @@ function isEnvVarRef(value) {
|
|
|
43469
43590
|
function isKnownApiKey(value) {
|
|
43470
43591
|
return API_KEY_PATTERNS.some((p2) => p2.test(value));
|
|
43471
43592
|
}
|
|
43593
|
+
function nameSuggestsSecret(name) {
|
|
43594
|
+
const upper = name.toUpperCase();
|
|
43595
|
+
return SECRET_NAME_KEYWORDS.some((kw) => upper.includes(kw));
|
|
43596
|
+
}
|
|
43472
43597
|
function isHighEntropySecret(value) {
|
|
43473
43598
|
if (isEnvVarRef(value)) return false;
|
|
43474
43599
|
return HIGH_ENTROPY_PATTERN.test(value);
|
|
@@ -43492,7 +43617,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43492
43617
|
issues.push({
|
|
43493
43618
|
severity: "error",
|
|
43494
43619
|
check: "mcp-security",
|
|
43495
|
-
ruleId: "hardcoded-bearer",
|
|
43620
|
+
ruleId: "mcp-security/hardcoded-bearer",
|
|
43496
43621
|
line: server2.line,
|
|
43497
43622
|
message: `Server "${server2.name}" has a hardcoded Bearer token in a git-tracked file`,
|
|
43498
43623
|
fix: {
|
|
@@ -43509,7 +43634,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43509
43634
|
issues.push({
|
|
43510
43635
|
severity: "error",
|
|
43511
43636
|
check: "mcp-security",
|
|
43512
|
-
ruleId: "hardcoded-api-key",
|
|
43637
|
+
ruleId: "mcp-security/hardcoded-api-key",
|
|
43513
43638
|
line: server2.line,
|
|
43514
43639
|
message: `Server "${server2.name}" has a hardcoded API key in a git-tracked file`
|
|
43515
43640
|
});
|
|
@@ -43517,13 +43642,14 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43517
43642
|
}
|
|
43518
43643
|
}
|
|
43519
43644
|
if (server2.env) {
|
|
43520
|
-
for (const envValue of Object.
|
|
43521
|
-
|
|
43645
|
+
for (const [envName, envValue] of Object.entries(server2.env)) {
|
|
43646
|
+
const isSecret = isKnownApiKey(envValue) || nameSuggestsSecret(envName) && isHighEntropySecret(envValue);
|
|
43647
|
+
if (!isEnvVarRef(envValue) && isSecret) {
|
|
43522
43648
|
const envVar = deriveEnvVarName(server2.name, "API_KEY");
|
|
43523
43649
|
issues.push({
|
|
43524
43650
|
severity: "error",
|
|
43525
43651
|
check: "mcp-security",
|
|
43526
|
-
ruleId: "hardcoded-api-key",
|
|
43652
|
+
ruleId: "mcp-security/hardcoded-api-key",
|
|
43527
43653
|
line: server2.line,
|
|
43528
43654
|
message: `Server "${server2.name}" has a hardcoded API key in a git-tracked file`,
|
|
43529
43655
|
fix: {
|
|
@@ -43540,7 +43666,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43540
43666
|
issues.push({
|
|
43541
43667
|
severity: "error",
|
|
43542
43668
|
check: "mcp-security",
|
|
43543
|
-
ruleId: "secret-in-url",
|
|
43669
|
+
ruleId: "mcp-security/secret-in-url",
|
|
43544
43670
|
line: server2.line,
|
|
43545
43671
|
message: `Server "${server2.name}" has a secret in the URL query string`
|
|
43546
43672
|
});
|
|
@@ -43553,7 +43679,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43553
43679
|
issues.push({
|
|
43554
43680
|
severity: "warning",
|
|
43555
43681
|
check: "mcp-security",
|
|
43556
|
-
ruleId: "http-no-tls",
|
|
43682
|
+
ruleId: "mcp-security/http-no-tls",
|
|
43557
43683
|
line: server2.line,
|
|
43558
43684
|
message: `Server "${server2.name}" uses HTTP without TLS`
|
|
43559
43685
|
});
|
|
@@ -43565,7 +43691,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43565
43691
|
}
|
|
43566
43692
|
return issues;
|
|
43567
43693
|
}
|
|
43568
|
-
var API_KEY_PATTERNS, ENV_VAR_REF, HIGH_ENTROPY_PATTERN, URL_SECRET_PARAMS;
|
|
43694
|
+
var API_KEY_PATTERNS, ENV_VAR_REF, HIGH_ENTROPY_PATTERN, URL_SECRET_PARAMS, SECRET_NAME_KEYWORDS;
|
|
43569
43695
|
var init_security = __esm({
|
|
43570
43696
|
"src/core/checks/mcp/security.ts"() {
|
|
43571
43697
|
"use strict";
|
|
@@ -43594,6 +43720,22 @@ var init_security = __esm({
|
|
|
43594
43720
|
ENV_VAR_REF = /\$\{[^}]+\}/;
|
|
43595
43721
|
HIGH_ENTROPY_PATTERN = /^[A-Za-z0-9+/=_-]{21,}$/;
|
|
43596
43722
|
URL_SECRET_PARAMS = /[?&](key|token|api_key|apikey|secret|password|access_token)=/i;
|
|
43723
|
+
SECRET_NAME_KEYWORDS = [
|
|
43724
|
+
"KEY",
|
|
43725
|
+
"TOKEN",
|
|
43726
|
+
"SECRET",
|
|
43727
|
+
"PASSWORD",
|
|
43728
|
+
"PASSWD",
|
|
43729
|
+
"PASS",
|
|
43730
|
+
"AUTH",
|
|
43731
|
+
"CREDENTIAL",
|
|
43732
|
+
"CREDENTIALS",
|
|
43733
|
+
"APIKEY",
|
|
43734
|
+
"PRIVATE",
|
|
43735
|
+
"SIGNING",
|
|
43736
|
+
"SESSION",
|
|
43737
|
+
"COOKIE"
|
|
43738
|
+
];
|
|
43597
43739
|
}
|
|
43598
43740
|
});
|
|
43599
43741
|
|
|
@@ -43609,7 +43751,7 @@ async function checkMcpCommands(config2, projectRoot) {
|
|
|
43609
43751
|
issues.push({
|
|
43610
43752
|
severity: "error",
|
|
43611
43753
|
check: "mcp-commands",
|
|
43612
|
-
ruleId: "windows-npx-no-wrapper",
|
|
43754
|
+
ruleId: "mcp-commands/windows-npx-no-wrapper",
|
|
43613
43755
|
line: server2.line,
|
|
43614
43756
|
message: `Server "${server2.name}": npx requires "cmd /c" wrapper on Windows`,
|
|
43615
43757
|
suggestion: 'Change "command" to "cmd" and prepend "/c", "npx" to args \u2014 e.g. "args": ["/c", "npx", ...]'
|
|
@@ -43621,7 +43763,7 @@ async function checkMcpCommands(config2, projectRoot) {
|
|
|
43621
43763
|
issues.push({
|
|
43622
43764
|
severity: "warning",
|
|
43623
43765
|
check: "mcp-commands",
|
|
43624
|
-
ruleId: "command-not-found",
|
|
43766
|
+
ruleId: "mcp-commands/command-not-found",
|
|
43625
43767
|
line: server2.line,
|
|
43626
43768
|
message: `Server "${server2.name}": command "${server2.command}" not found`
|
|
43627
43769
|
});
|
|
@@ -43636,7 +43778,7 @@ async function checkMcpCommands(config2, projectRoot) {
|
|
|
43636
43778
|
issues.push({
|
|
43637
43779
|
severity: "warning",
|
|
43638
43780
|
check: "mcp-commands",
|
|
43639
|
-
ruleId: "args-path-missing",
|
|
43781
|
+
ruleId: "mcp-commands/args-path-missing",
|
|
43640
43782
|
line: server2.line,
|
|
43641
43783
|
message: `Server "${server2.name}": arg "${arg}" looks like a file path but doesn't exist`
|
|
43642
43784
|
});
|
|
@@ -43675,7 +43817,7 @@ async function checkMcpDeprecated(config2, _projectRoot) {
|
|
|
43675
43817
|
issues.push({
|
|
43676
43818
|
severity: "warning",
|
|
43677
43819
|
check: "mcp-deprecated",
|
|
43678
|
-
ruleId: "sse-transport",
|
|
43820
|
+
ruleId: "mcp-deprecated/sse-transport",
|
|
43679
43821
|
line,
|
|
43680
43822
|
message: `Server "${server2.name}" uses deprecated SSE transport \u2014 use "http" (Streamable HTTP) instead`,
|
|
43681
43823
|
fix: {
|
|
@@ -43771,7 +43913,7 @@ async function checkMcpEnv(config2, _projectRoot) {
|
|
|
43771
43913
|
issues.push({
|
|
43772
43914
|
severity: "error",
|
|
43773
43915
|
check: "mcp-env",
|
|
43774
|
-
ruleId: "wrong-syntax",
|
|
43916
|
+
ruleId: "mcp-env/wrong-syntax",
|
|
43775
43917
|
line: server2.line,
|
|
43776
43918
|
message: `Server "${server2.name}": Claude Code uses \${VAR}, not \${env:VAR}`,
|
|
43777
43919
|
fix: buildSyntaxFix(config2, server2.line, value, "claude-code")
|
|
@@ -43783,7 +43925,7 @@ async function checkMcpEnv(config2, _projectRoot) {
|
|
|
43783
43925
|
issues.push({
|
|
43784
43926
|
severity: "error",
|
|
43785
43927
|
check: "mcp-env",
|
|
43786
|
-
ruleId: "wrong-syntax",
|
|
43928
|
+
ruleId: "mcp-env/wrong-syntax",
|
|
43787
43929
|
line: server2.line,
|
|
43788
43930
|
message: `Server "${server2.name}": Cursor uses \${env:VAR}, not \${VAR}`,
|
|
43789
43931
|
fix: buildSyntaxFix(config2, server2.line, value, "cursor")
|
|
@@ -43795,7 +43937,7 @@ async function checkMcpEnv(config2, _projectRoot) {
|
|
|
43795
43937
|
issues.push({
|
|
43796
43938
|
severity: "error",
|
|
43797
43939
|
check: "mcp-env",
|
|
43798
|
-
ruleId: "wrong-syntax",
|
|
43940
|
+
ruleId: "mcp-env/wrong-syntax",
|
|
43799
43941
|
line: server2.line,
|
|
43800
43942
|
message: `Server "${server2.name}": Continue uses \${{ secrets.VAR }}, not \${VAR}`,
|
|
43801
43943
|
fix: buildSyntaxFix(config2, server2.line, value, "continue")
|
|
@@ -43811,7 +43953,7 @@ async function checkMcpEnv(config2, _projectRoot) {
|
|
|
43811
43953
|
issues.push({
|
|
43812
43954
|
severity: "info",
|
|
43813
43955
|
check: "mcp-env",
|
|
43814
|
-
ruleId: "unset-variable",
|
|
43956
|
+
ruleId: "mcp-env/unset-variable",
|
|
43815
43957
|
line: server2.line,
|
|
43816
43958
|
message: `Server "${server2.name}": environment variable "${ref.varName}" is not set`
|
|
43817
43959
|
});
|
|
@@ -43823,7 +43965,7 @@ async function checkMcpEnv(config2, _projectRoot) {
|
|
|
43823
43965
|
issues.push({
|
|
43824
43966
|
severity: "info",
|
|
43825
43967
|
check: "mcp-env",
|
|
43826
|
-
ruleId: "empty-env-block",
|
|
43968
|
+
ruleId: "mcp-env/empty-env-block",
|
|
43827
43969
|
line: server2.line,
|
|
43828
43970
|
message: `Server "${server2.name}": empty "env" block can be removed`
|
|
43829
43971
|
});
|
|
@@ -43879,7 +44021,7 @@ async function checkMcpUrls(config2, _projectRoot) {
|
|
|
43879
44021
|
issues.push({
|
|
43880
44022
|
severity: "error",
|
|
43881
44023
|
check: "mcp-urls",
|
|
43882
|
-
ruleId: "malformed-url",
|
|
44024
|
+
ruleId: "mcp-urls/malformed-url",
|
|
43883
44025
|
line: server2.line,
|
|
43884
44026
|
message: `Server "${server2.name}": invalid URL "${server2.url}"`
|
|
43885
44027
|
});
|
|
@@ -43889,7 +44031,7 @@ async function checkMcpUrls(config2, _projectRoot) {
|
|
|
43889
44031
|
issues.push({
|
|
43890
44032
|
severity: "warning",
|
|
43891
44033
|
check: "mcp-urls",
|
|
43892
|
-
ruleId: "localhost-in-project-config",
|
|
44034
|
+
ruleId: "mcp-urls/localhost-in-project-config",
|
|
43893
44035
|
line: server2.line,
|
|
43894
44036
|
message: `Server "${server2.name}": localhost URL in project config won't work for teammates`
|
|
43895
44037
|
});
|
|
@@ -43898,7 +44040,7 @@ async function checkMcpUrls(config2, _projectRoot) {
|
|
|
43898
44040
|
issues.push({
|
|
43899
44041
|
severity: "info",
|
|
43900
44042
|
check: "mcp-urls",
|
|
43901
|
-
ruleId: "missing-path",
|
|
44043
|
+
ruleId: "mcp-urls/missing-path",
|
|
43902
44044
|
line: server2.line,
|
|
43903
44045
|
message: `Server "${server2.name}": URL has no path \u2014 most MCP servers expect /mcp`
|
|
43904
44046
|
});
|
|
@@ -43946,7 +44088,7 @@ async function checkMcpConsistency(configs) {
|
|
|
43946
44088
|
issues.push({
|
|
43947
44089
|
severity: "warning",
|
|
43948
44090
|
check: "mcp-consistency",
|
|
43949
|
-
ruleId: "same-server-different-config",
|
|
44091
|
+
ruleId: "mcp-consistency/same-server-different-config",
|
|
43950
44092
|
line: a.line,
|
|
43951
44093
|
message: `Server "${name}" is configured differently in ${a.config.relativePath} and ${b2.config.relativePath}`
|
|
43952
44094
|
});
|
|
@@ -43972,7 +44114,7 @@ function checkMissingFromClient(configs) {
|
|
|
43972
44114
|
issues.push({
|
|
43973
44115
|
severity: "info",
|
|
43974
44116
|
check: "mcp-consistency",
|
|
43975
|
-
ruleId: "missing-from-client",
|
|
44117
|
+
ruleId: "mcp-consistency/missing-from-client",
|
|
43976
44118
|
line: primaryServer.line,
|
|
43977
44119
|
message: `Server "${primaryServer.name}" is in .mcp.json but missing from ${other.relativePath}`
|
|
43978
44120
|
});
|
|
@@ -44049,7 +44191,7 @@ function checkSingleFileIssues(configs) {
|
|
|
44049
44191
|
issues.push({
|
|
44050
44192
|
severity: "warning",
|
|
44051
44193
|
check: "mcp-consistency",
|
|
44052
|
-
ruleId: "duplicate-server-name",
|
|
44194
|
+
ruleId: "mcp-consistency/duplicate-server-name",
|
|
44053
44195
|
line: 1,
|
|
44054
44196
|
message: `Duplicate server name "${name}" in ${config2.relativePath} \u2014 only the last definition is used`
|
|
44055
44197
|
});
|
|
@@ -44073,7 +44215,7 @@ async function checkMcpRedundancy(configs) {
|
|
|
44073
44215
|
issues.push({
|
|
44074
44216
|
severity: "info",
|
|
44075
44217
|
check: "mcp-redundancy",
|
|
44076
|
-
ruleId: "disabled-server",
|
|
44218
|
+
ruleId: "mcp-redundancy/disabled-server",
|
|
44077
44219
|
line: server2.line,
|
|
44078
44220
|
message: `Server "${server2.name}" is disabled \u2014 consider removing it if no longer needed`
|
|
44079
44221
|
});
|
|
@@ -44103,7 +44245,7 @@ async function checkMcpRedundancy(configs) {
|
|
|
44103
44245
|
issues.push({
|
|
44104
44246
|
severity: "info",
|
|
44105
44247
|
check: "mcp-redundancy",
|
|
44106
|
-
ruleId: "identical-across-scopes",
|
|
44248
|
+
ruleId: "mcp-redundancy/identical-across-scopes",
|
|
44107
44249
|
line: projectServer.line,
|
|
44108
44250
|
message: `Server "${projectServer.name}" is identically configured in both ${projectConfig.relativePath} and ${globalConfig2.relativePath}`
|
|
44109
44251
|
});
|
|
@@ -44129,7 +44271,7 @@ async function checkMcphTokenSecurity(config2, _projectRoot, options = {}) {
|
|
|
44129
44271
|
issues.push({
|
|
44130
44272
|
severity: "error",
|
|
44131
44273
|
check: "mcph-token-security",
|
|
44132
|
-
ruleId: "mcph-
|
|
44274
|
+
ruleId: "mcph-token-security/invalid-token-format",
|
|
44133
44275
|
line: tokenPos.line,
|
|
44134
44276
|
message: `"token" does not match expected format ^mcp_pat_[A-Za-z0-9_-]+$`,
|
|
44135
44277
|
suggestion: `Check that the token was copied in full. A valid mcp.hosting PAT looks like: mcp_pat_aBcDeFg123...
|
|
@@ -44140,7 +44282,7 @@ If the token was truncated or wrapped in quotes elsewhere, re-issue it from http
|
|
|
44140
44282
|
issues.push({
|
|
44141
44283
|
severity: "error",
|
|
44142
44284
|
check: "mcph-token-security",
|
|
44143
|
-
ruleId: "mcph-
|
|
44285
|
+
ruleId: "mcph-token-security/token-in-project-scope",
|
|
44144
44286
|
line: tokenPos.line,
|
|
44145
44287
|
message: `"token" in a git-tracked project-scope .mcph.json \u2014 PAT will leak via git history`,
|
|
44146
44288
|
suggestion: `Delete line ${tokenPos.line} (the "token" field) from ${config2.relativePath}.
|
|
@@ -44158,7 +44300,7 @@ If this token was already committed, ROTATE it now: https://mcp.hosting/settings
|
|
|
44158
44300
|
issues.push({
|
|
44159
44301
|
severity,
|
|
44160
44302
|
check: "mcph-token-security",
|
|
44161
|
-
ruleId: "mcph-
|
|
44303
|
+
ruleId: "mcph-token-security/prefer-env-token",
|
|
44162
44304
|
line: tokenPos.line,
|
|
44163
44305
|
message: `prefer MCPH_TOKEN env var over a file-stored token in ${config2.relativePath}`,
|
|
44164
44306
|
suggestion: `Delete line ${tokenPos.line} (the "token" field) and export instead:
|
|
@@ -44199,7 +44341,7 @@ async function checkMcphApibase(config2, _projectRoot) {
|
|
|
44199
44341
|
issues.push({
|
|
44200
44342
|
severity: "error",
|
|
44201
44343
|
check: "mcph-apibase",
|
|
44202
|
-
ruleId: "mcph-
|
|
44344
|
+
ruleId: "mcph-apibase/invalid-apibase",
|
|
44203
44345
|
line: pos.line,
|
|
44204
44346
|
message: `"apiBase" is not a valid URL: ${value}`,
|
|
44205
44347
|
suggestion: `Use an absolute http(s) URL, e.g. "https://mcp.hosting".`
|
|
@@ -44210,7 +44352,7 @@ async function checkMcphApibase(config2, _projectRoot) {
|
|
|
44210
44352
|
issues.push({
|
|
44211
44353
|
severity: "warning",
|
|
44212
44354
|
check: "mcph-apibase",
|
|
44213
|
-
ruleId: "mcph-
|
|
44355
|
+
ruleId: "mcph-apibase/insecure-apibase",
|
|
44214
44356
|
line: pos.line,
|
|
44215
44357
|
message: `"apiBase" uses plaintext HTTP to a public host (${parsed.hostname})`,
|
|
44216
44358
|
suggestion: `Use https:// instead. Plaintext HTTP exposes your MCPH_TOKEN on the wire.
|
|
@@ -44246,7 +44388,7 @@ async function checkMcphSchemaConformance(config2, _projectRoot) {
|
|
|
44246
44388
|
issues.push({
|
|
44247
44389
|
severity: "warning",
|
|
44248
44390
|
check: "mcph-schema-conformance",
|
|
44249
|
-
ruleId: "mcph-
|
|
44391
|
+
ruleId: "mcph-schema-conformance/unknown-field",
|
|
44250
44392
|
line: field.position.line,
|
|
44251
44393
|
message: `unknown field "${field.name}" \u2014 not in the mcph config schema`,
|
|
44252
44394
|
suggestion: `Known fields: $schema, version, token, apiBase, servers, blocked. Check for typos (e.g. "tokens" vs "token", "blockList" vs "blocked").`
|
|
@@ -44258,7 +44400,7 @@ async function checkMcphSchemaConformance(config2, _projectRoot) {
|
|
|
44258
44400
|
issues.push({
|
|
44259
44401
|
severity: "info",
|
|
44260
44402
|
check: "mcph-schema-conformance",
|
|
44261
|
-
ruleId: "mcph-
|
|
44403
|
+
ruleId: "mcph-schema-conformance/stale-version",
|
|
44262
44404
|
line: versionPos.line,
|
|
44263
44405
|
message: `"version": ${version2} is older than the current schema version (${CURRENT_SCHEMA_VERSION})`,
|
|
44264
44406
|
suggestion: `Update to "version": ${CURRENT_SCHEMA_VERSION}. Older versions continue to load but may miss newer fields.`
|
|
@@ -44286,7 +44428,7 @@ async function checkMcphLists(config2, _projectRoot) {
|
|
|
44286
44428
|
issues.push({
|
|
44287
44429
|
severity: "warning",
|
|
44288
44430
|
check: "mcph-lists",
|
|
44289
|
-
ruleId: "mcph-
|
|
44431
|
+
ruleId: "mcph-lists/allowlist-denylist-conflict",
|
|
44290
44432
|
line: entry.position.line,
|
|
44291
44433
|
message: `server "${entry.value}" is in both "servers" (allow-list) and "blocked" (deny-list)`,
|
|
44292
44434
|
suggestion: `Remove "${entry.value}" from one of the two lists. "blocked" wins in practice (deny > allow), so the allow-list entry is dead weight.`
|
|
@@ -44302,7 +44444,7 @@ async function checkMcphLists(config2, _projectRoot) {
|
|
|
44302
44444
|
issues.push({
|
|
44303
44445
|
severity: "info",
|
|
44304
44446
|
check: "mcph-lists",
|
|
44305
|
-
ruleId: "mcph-
|
|
44447
|
+
ruleId: "mcph-lists/duplicate-entries",
|
|
44306
44448
|
line: entry.position.line,
|
|
44307
44449
|
message: `"${entry.value}" appears multiple times in "${listName}" (first at line ${prevLine})`,
|
|
44308
44450
|
suggestion: `Remove the duplicate entry at line ${entry.position.line}.`
|
|
@@ -44329,7 +44471,7 @@ async function checkMcphGitignore(config2, _projectRoot) {
|
|
|
44329
44471
|
issues.push({
|
|
44330
44472
|
severity: "error",
|
|
44331
44473
|
check: "mcph-gitignore",
|
|
44332
|
-
ruleId: "mcph-
|
|
44474
|
+
ruleId: "mcph-gitignore/local-file-not-gitignored",
|
|
44333
44475
|
line: 1,
|
|
44334
44476
|
message: `${basename4} is not covered by .gitignore \u2014 machine-local overrides can leak via git`,
|
|
44335
44477
|
suggestion: `Add "${basename4}" to .gitignore in your project root.`
|
|
@@ -44360,6 +44502,12 @@ function extractPaths(content) {
|
|
|
44360
44502
|
paths.push(p2);
|
|
44361
44503
|
}
|
|
44362
44504
|
}
|
|
44505
|
+
for (const match of content.matchAll(BARE_FILE_PATH)) {
|
|
44506
|
+
const p2 = match[1].replace(/[)}\]]+$/, "");
|
|
44507
|
+
if (p2.length > 2 && !p2.startsWith("http")) {
|
|
44508
|
+
paths.push(p2);
|
|
44509
|
+
}
|
|
44510
|
+
}
|
|
44363
44511
|
return [...new Set(paths)];
|
|
44364
44512
|
}
|
|
44365
44513
|
function parseFrontmatter2(content) {
|
|
@@ -44389,7 +44537,7 @@ function parseFrontmatter2(content) {
|
|
|
44389
44537
|
};
|
|
44390
44538
|
}
|
|
44391
44539
|
async function parseMemoryFile(filePath, projectDir) {
|
|
44392
|
-
const content = await readFile(filePath, "utf-8");
|
|
44540
|
+
const content = stripBom(await readFile(filePath, "utf-8"));
|
|
44393
44541
|
const { name, description, type, body } = parseFrontmatter2(content);
|
|
44394
44542
|
const referencedPaths = extractPaths(body);
|
|
44395
44543
|
return {
|
|
@@ -44402,11 +44550,13 @@ async function parseMemoryFile(filePath, projectDir) {
|
|
|
44402
44550
|
referencedPaths
|
|
44403
44551
|
};
|
|
44404
44552
|
}
|
|
44405
|
-
var PATH_PATTERN2;
|
|
44553
|
+
var PATH_PATTERN2, BARE_FILE_PATH;
|
|
44406
44554
|
var init_session_parser = __esm({
|
|
44407
44555
|
"src/core/session-parser.ts"() {
|
|
44408
44556
|
"use strict";
|
|
44557
|
+
init_fs();
|
|
44409
44558
|
PATH_PATTERN2 = /(?:^|\s|['"`(])([.~/][^\s'"`),;:!?]+)/g;
|
|
44559
|
+
BARE_FILE_PATH = /(?:^|[\s`"'(])([\w][\w-]*(?:\/[\w.-]+)+\.[a-zA-Z0-9]{1,8})\b/g;
|
|
44410
44560
|
}
|
|
44411
44561
|
});
|
|
44412
44562
|
|
|
@@ -44438,30 +44588,46 @@ async function parseJsonlFiltered(filePath, filter) {
|
|
|
44438
44588
|
}
|
|
44439
44589
|
return results;
|
|
44440
44590
|
}
|
|
44441
|
-
|
|
44442
|
-
const
|
|
44443
|
-
|
|
44444
|
-
if (
|
|
44591
|
+
function pickField(entry, fields) {
|
|
44592
|
+
for (const f of fields) {
|
|
44593
|
+
const v2 = entry[f];
|
|
44594
|
+
if (typeof v2 === "string" && v2.length > 0) return v2;
|
|
44595
|
+
}
|
|
44596
|
+
return "";
|
|
44597
|
+
}
|
|
44598
|
+
async function readJsonlHistory(opts) {
|
|
44599
|
+
return parseJsonlFiltered(opts.historyPath, (parsed) => {
|
|
44600
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
44601
|
+
const entry = parsed;
|
|
44602
|
+
const display = pickField(entry, opts.displayFields);
|
|
44603
|
+
if (!display) return null;
|
|
44604
|
+
const project = pickField(entry, opts.projectFields);
|
|
44605
|
+
if (opts.requireProject && !project) return null;
|
|
44445
44606
|
return {
|
|
44446
|
-
display
|
|
44447
|
-
timestamp: entry.timestamp
|
|
44448
|
-
project:
|
|
44449
|
-
sessionId: entry.sessionId
|
|
44450
|
-
provider:
|
|
44607
|
+
display,
|
|
44608
|
+
timestamp: typeof entry.timestamp === "number" ? entry.timestamp : 0,
|
|
44609
|
+
project: project.replace(/\\/g, "/"),
|
|
44610
|
+
sessionId: typeof entry.sessionId === "string" ? entry.sessionId : "",
|
|
44611
|
+
provider: opts.provider
|
|
44451
44612
|
};
|
|
44452
44613
|
});
|
|
44453
44614
|
}
|
|
44615
|
+
async function readClaudeHistory() {
|
|
44616
|
+
return readJsonlHistory({
|
|
44617
|
+
historyPath: join5(home, ".claude", "history.jsonl"),
|
|
44618
|
+
provider: "claude-code",
|
|
44619
|
+
displayFields: ["display"],
|
|
44620
|
+
projectFields: ["project"],
|
|
44621
|
+
requireProject: true
|
|
44622
|
+
});
|
|
44623
|
+
}
|
|
44454
44624
|
async function readCodexHistory() {
|
|
44455
|
-
|
|
44456
|
-
|
|
44457
|
-
|
|
44458
|
-
|
|
44459
|
-
|
|
44460
|
-
|
|
44461
|
-
project: (entry.project || entry.cwd || "").replace(/\\/g, "/"),
|
|
44462
|
-
sessionId: entry.sessionId || "",
|
|
44463
|
-
provider: "codex-cli"
|
|
44464
|
-
};
|
|
44625
|
+
return readJsonlHistory({
|
|
44626
|
+
historyPath: join5(home, ".codex", "history.jsonl"),
|
|
44627
|
+
provider: "codex-cli",
|
|
44628
|
+
displayFields: ["display", "command"],
|
|
44629
|
+
projectFields: ["project", "cwd"],
|
|
44630
|
+
requireProject: false
|
|
44465
44631
|
});
|
|
44466
44632
|
}
|
|
44467
44633
|
async function readClaudeMemories() {
|
|
@@ -44526,47 +44692,29 @@ async function detectSiblings(projectRoot) {
|
|
|
44526
44692
|
if (orgMatch) currentOrg = orgMatch[1];
|
|
44527
44693
|
} catch {
|
|
44528
44694
|
}
|
|
44529
|
-
const
|
|
44530
|
-
const results = await Promise.all(
|
|
44531
|
-
gitCandidates.map(async (c3) => {
|
|
44532
|
-
const sibling = { path: c3.entryPath.replace(/\\/g, "/"), name: c3.name };
|
|
44533
|
-
try {
|
|
44534
|
-
const git = simpleGit(c3.fullPath);
|
|
44535
|
-
const remotes = await git.getRemotes(true);
|
|
44536
|
-
const origin = remotes.find((r2) => r2.name === "origin");
|
|
44537
|
-
if (origin?.refs?.fetch) {
|
|
44538
|
-
sibling.gitRemoteUrl = origin.refs.fetch;
|
|
44539
|
-
const orgMatch = origin.refs.fetch.match(/github\.com[:/]([^/]+)\//);
|
|
44540
|
-
if (orgMatch) sibling.gitOrg = orgMatch[1];
|
|
44541
|
-
}
|
|
44542
|
-
} catch {
|
|
44543
|
-
}
|
|
44544
|
-
return sibling;
|
|
44545
|
-
})
|
|
44546
|
-
);
|
|
44695
|
+
const results = await Promise.all(candidates.map((c3) => resolveSibling(c3)));
|
|
44547
44696
|
if (currentOrg) {
|
|
44548
|
-
return results.filter((s) => s.gitOrg === currentOrg);
|
|
44697
|
+
return results.filter((s) => !s.gitOrg || s.gitOrg === currentOrg);
|
|
44549
44698
|
}
|
|
44550
44699
|
return results;
|
|
44551
44700
|
}
|
|
44552
|
-
|
|
44553
|
-
|
|
44554
|
-
|
|
44555
|
-
|
|
44556
|
-
|
|
44557
|
-
|
|
44558
|
-
|
|
44559
|
-
|
|
44560
|
-
|
|
44561
|
-
|
|
44562
|
-
|
|
44563
|
-
|
|
44564
|
-
|
|
44565
|
-
|
|
44566
|
-
|
|
44567
|
-
|
|
44568
|
-
|
|
44569
|
-
return siblings;
|
|
44701
|
+
return Promise.all(candidates.map((c3) => resolveSibling(c3)));
|
|
44702
|
+
}
|
|
44703
|
+
async function resolveSibling(c3) {
|
|
44704
|
+
const sibling = { path: c3.entryPath.replace(/\\/g, "/"), name: c3.name };
|
|
44705
|
+
if (!existsSync(join5(c3.fullPath, ".git"))) return sibling;
|
|
44706
|
+
try {
|
|
44707
|
+
const git = simpleGit(c3.fullPath);
|
|
44708
|
+
const remotes = await git.getRemotes(true);
|
|
44709
|
+
const origin = remotes.find((r2) => r2.name === "origin");
|
|
44710
|
+
if (origin?.refs?.fetch) {
|
|
44711
|
+
sibling.gitRemoteUrl = origin.refs.fetch;
|
|
44712
|
+
const orgMatch = origin.refs.fetch.match(/github\.com[:/]([^/]+)\//);
|
|
44713
|
+
if (orgMatch) sibling.gitOrg = orgMatch[1];
|
|
44714
|
+
}
|
|
44715
|
+
} catch {
|
|
44716
|
+
}
|
|
44717
|
+
return sibling;
|
|
44570
44718
|
}
|
|
44571
44719
|
async function scanSessionData(projectRoot) {
|
|
44572
44720
|
const providers = detectProviders();
|
|
@@ -44666,7 +44814,7 @@ async function checkMissingSecret(ctx) {
|
|
|
44666
44814
|
issues.push({
|
|
44667
44815
|
severity: "error",
|
|
44668
44816
|
check: "session-missing-secret",
|
|
44669
|
-
ruleId: "session/missing-secret",
|
|
44817
|
+
ruleId: "session-missing-secret/missing-secret",
|
|
44670
44818
|
line: 0,
|
|
44671
44819
|
message: `GitHub secret "${secretName}" is set on ${siblingMatches.length} sibling repos (${sibNames}) but not on this project`,
|
|
44672
44820
|
suggestion: `Run: gh secret set ${secretName} --repo <owner>/<repo>`,
|
|
@@ -44686,48 +44834,57 @@ var init_missing_secret = __esm({
|
|
|
44686
44834
|
});
|
|
44687
44835
|
|
|
44688
44836
|
// src/core/checks/session/diverged-file.ts
|
|
44689
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
44837
|
+
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
44690
44838
|
import { join as join6 } from "node:path";
|
|
44691
44839
|
import { existsSync as existsSync2 } from "node:fs";
|
|
44692
|
-
function
|
|
44693
|
-
|
|
44694
|
-
|
|
44695
|
-
|
|
44696
|
-
|
|
44697
|
-
|
|
44698
|
-
|
|
44699
|
-
|
|
44700
|
-
|
|
44701
|
-
let intersection2 = 0;
|
|
44702
|
-
for (const line of linesA) {
|
|
44703
|
-
if (linesB.has(line)) intersection2++;
|
|
44840
|
+
async function loadLineSet(absPath) {
|
|
44841
|
+
let mtimeMs;
|
|
44842
|
+
let size;
|
|
44843
|
+
try {
|
|
44844
|
+
const stats = await stat2(absPath);
|
|
44845
|
+
mtimeMs = stats.mtimeMs;
|
|
44846
|
+
size = stats.size;
|
|
44847
|
+
} catch {
|
|
44848
|
+
return null;
|
|
44704
44849
|
}
|
|
44705
|
-
const
|
|
44706
|
-
|
|
44850
|
+
const cached2 = lineSetCache.get(absPath);
|
|
44851
|
+
if (cached2 && cached2.mtimeMs === mtimeMs && cached2.size === size) {
|
|
44852
|
+
lineSetCache.delete(absPath);
|
|
44853
|
+
lineSetCache.set(absPath, cached2);
|
|
44854
|
+
return cached2.lineSet;
|
|
44855
|
+
}
|
|
44856
|
+
let content;
|
|
44857
|
+
try {
|
|
44858
|
+
content = stripBom(await readFile2(absPath, "utf-8"));
|
|
44859
|
+
} catch {
|
|
44860
|
+
return null;
|
|
44861
|
+
}
|
|
44862
|
+
const lineSet = toLineSet(content, MIN_TOKEN_LEN);
|
|
44863
|
+
lineSetCache.set(absPath, { mtimeMs, size, lineSet });
|
|
44864
|
+
if (lineSetCache.size > CACHE_MAX_ENTRIES) {
|
|
44865
|
+
const oldest = lineSetCache.keys().next().value;
|
|
44866
|
+
if (oldest !== void 0) lineSetCache.delete(oldest);
|
|
44867
|
+
}
|
|
44868
|
+
return lineSet;
|
|
44707
44869
|
}
|
|
44708
44870
|
async function checkDivergedFile(ctx) {
|
|
44709
44871
|
const issues = [];
|
|
44710
44872
|
for (const fileName of CANONICAL_FILES) {
|
|
44711
44873
|
const currentPath = join6(ctx.currentProject, fileName);
|
|
44712
44874
|
if (!existsSync2(currentPath)) continue;
|
|
44713
|
-
|
|
44714
|
-
|
|
44715
|
-
currentContent = await readFile2(currentPath, "utf-8");
|
|
44716
|
-
} catch {
|
|
44717
|
-
continue;
|
|
44718
|
-
}
|
|
44875
|
+
const currentLineSet = await loadLineSet(currentPath);
|
|
44876
|
+
if (!currentLineSet) continue;
|
|
44719
44877
|
const diverged = [];
|
|
44720
44878
|
for (const sib of ctx.siblings) {
|
|
44721
44879
|
const sibPath = join6(sib.path, fileName);
|
|
44722
44880
|
if (!existsSync2(sibPath)) continue;
|
|
44723
|
-
|
|
44724
|
-
|
|
44725
|
-
|
|
44726
|
-
|
|
44727
|
-
|
|
44728
|
-
|
|
44729
|
-
|
|
44730
|
-
continue;
|
|
44881
|
+
const sibLineSet = await loadLineSet(sibPath);
|
|
44882
|
+
if (!sibLineSet) continue;
|
|
44883
|
+
const overlap = jaccardSimilarityFromSets(currentLineSet, sibLineSet, {
|
|
44884
|
+
bothEmptyIsIdentical: true
|
|
44885
|
+
});
|
|
44886
|
+
if (overlap >= 0.2 && overlap < 0.9) {
|
|
44887
|
+
diverged.push({ sibling: sib.name, overlap: Math.round(overlap * 100) });
|
|
44731
44888
|
}
|
|
44732
44889
|
}
|
|
44733
44890
|
if (diverged.length > 0) {
|
|
@@ -44735,7 +44892,7 @@ async function checkDivergedFile(ctx) {
|
|
|
44735
44892
|
issues.push({
|
|
44736
44893
|
severity: "warning",
|
|
44737
44894
|
check: "session-diverged-file",
|
|
44738
|
-
ruleId: "session/diverged-file",
|
|
44895
|
+
ruleId: "session-diverged-file/diverged-file",
|
|
44739
44896
|
line: 0,
|
|
44740
44897
|
message: `${fileName} has diverged from sibling repos: ${details}`,
|
|
44741
44898
|
suggestion: `Compare with sibling versions to identify unintentional drift`,
|
|
@@ -44745,10 +44902,12 @@ async function checkDivergedFile(ctx) {
|
|
|
44745
44902
|
}
|
|
44746
44903
|
return issues;
|
|
44747
44904
|
}
|
|
44748
|
-
var CANONICAL_FILES;
|
|
44905
|
+
var CANONICAL_FILES, MIN_TOKEN_LEN, CACHE_MAX_ENTRIES, lineSetCache;
|
|
44749
44906
|
var init_diverged_file = __esm({
|
|
44750
44907
|
"src/core/checks/session/diverged-file.ts"() {
|
|
44751
44908
|
"use strict";
|
|
44909
|
+
init_similarity();
|
|
44910
|
+
init_fs();
|
|
44752
44911
|
CANONICAL_FILES = [
|
|
44753
44912
|
"release.sh",
|
|
44754
44913
|
".github/workflows/ci.yml",
|
|
@@ -44759,6 +44918,9 @@ var init_diverged_file = __esm({
|
|
|
44759
44918
|
"tsconfig.json",
|
|
44760
44919
|
".gitignore"
|
|
44761
44920
|
];
|
|
44921
|
+
MIN_TOKEN_LEN = 3;
|
|
44922
|
+
CACHE_MAX_ENTRIES = 256;
|
|
44923
|
+
lineSetCache = /* @__PURE__ */ new Map();
|
|
44762
44924
|
}
|
|
44763
44925
|
});
|
|
44764
44926
|
|
|
@@ -44802,7 +44964,7 @@ async function checkMissingWorkflow(ctx) {
|
|
|
44802
44964
|
issues.push({
|
|
44803
44965
|
severity: "warning",
|
|
44804
44966
|
check: "session-missing-workflow",
|
|
44805
|
-
ruleId: "session/missing-workflow",
|
|
44967
|
+
ruleId: "session-missing-workflow/missing-workflow",
|
|
44806
44968
|
line: 0,
|
|
44807
44969
|
message: `GitHub Actions workflow "${workflow}" exists in ${siblings.length} sibling repos (${sibNames}) but not in this project`,
|
|
44808
44970
|
suggestion: `Consider adding .github/workflows/${workflow} for consistency`,
|
|
@@ -44848,7 +45010,7 @@ async function checkStaleMemory(ctx) {
|
|
|
44848
45010
|
issues.push({
|
|
44849
45011
|
severity: "info",
|
|
44850
45012
|
check: "session-stale-memory",
|
|
44851
|
-
ruleId: "session/stale-memory",
|
|
45013
|
+
ruleId: "session-stale-memory/stale-memory",
|
|
44852
45014
|
line: 0,
|
|
44853
45015
|
message: `Memory "${name}" references ${brokenPaths.length} path(s) that no longer exist: ${brokenPaths.join(", ")}`,
|
|
44854
45016
|
suggestion: `Update or remove the memory file: ${mem.filePath}`,
|
|
@@ -44866,24 +45028,10 @@ var init_stale_memory = __esm({
|
|
|
44866
45028
|
});
|
|
44867
45029
|
|
|
44868
45030
|
// src/core/checks/session/duplicate-memory.ts
|
|
44869
|
-
function calculateLineOverlap2(a, b2) {
|
|
44870
|
-
const linesA = new Set(
|
|
44871
|
-
a.split("\n").map((l) => l.trim()).filter((l) => l.length > 5)
|
|
44872
|
-
);
|
|
44873
|
-
const linesB = new Set(
|
|
44874
|
-
b2.split("\n").map((l) => l.trim()).filter((l) => l.length > 5)
|
|
44875
|
-
);
|
|
44876
|
-
if (linesA.size === 0 || linesB.size === 0) return 0;
|
|
44877
|
-
let intersection2 = 0;
|
|
44878
|
-
for (const line of linesA) {
|
|
44879
|
-
if (linesB.has(line)) intersection2++;
|
|
44880
|
-
}
|
|
44881
|
-
const unionSize = linesA.size + linesB.size - intersection2;
|
|
44882
|
-
return intersection2 / unionSize;
|
|
44883
|
-
}
|
|
44884
45031
|
async function checkDuplicateMemory(ctx) {
|
|
44885
45032
|
const issues = [];
|
|
44886
45033
|
const reported = /* @__PURE__ */ new Set();
|
|
45034
|
+
const lineSets = ctx.memories.map((m) => toLineSet(m.content, MIN_TOKEN_LEN2));
|
|
44887
45035
|
for (let i2 = 0; i2 < ctx.memories.length; i2++) {
|
|
44888
45036
|
for (let j3 = i2 + 1; j3 < ctx.memories.length; j3++) {
|
|
44889
45037
|
const a = ctx.memories[i2];
|
|
@@ -44893,7 +45041,7 @@ async function checkDuplicateMemory(ctx) {
|
|
|
44893
45041
|
const bIsCurrent = projectDirMatchesPath(b2.projectDir, ctx.currentProject);
|
|
44894
45042
|
if (!aIsCurrent && !bIsCurrent) continue;
|
|
44895
45043
|
if (a.content.length < 50 || b2.content.length < 50) continue;
|
|
44896
|
-
const overlap =
|
|
45044
|
+
const overlap = jaccardSimilarityFromSets(lineSets[i2], lineSets[j3]);
|
|
44897
45045
|
if (overlap < 0.6) continue;
|
|
44898
45046
|
const pairKey = [a.filePath, b2.filePath].sort().join("::");
|
|
44899
45047
|
if (reported.has(pairKey)) continue;
|
|
@@ -44905,7 +45053,7 @@ async function checkDuplicateMemory(ctx) {
|
|
|
44905
45053
|
issues.push({
|
|
44906
45054
|
severity: "info",
|
|
44907
45055
|
check: "session-duplicate-memory",
|
|
44908
|
-
ruleId: "session/duplicate-memory",
|
|
45056
|
+
ruleId: "session-duplicate-memory/duplicate-memory",
|
|
44909
45057
|
line: 0,
|
|
44910
45058
|
message: `Memory "${nameA}" (${projA}) and "${nameB}" (${projB}) have ${Math.round(overlap * 100)}% overlap`,
|
|
44911
45059
|
suggestion: `Consider consolidating into a shared memory or removing the duplicate`,
|
|
@@ -44915,10 +45063,13 @@ async function checkDuplicateMemory(ctx) {
|
|
|
44915
45063
|
}
|
|
44916
45064
|
return issues;
|
|
44917
45065
|
}
|
|
45066
|
+
var MIN_TOKEN_LEN2;
|
|
44918
45067
|
var init_duplicate_memory = __esm({
|
|
44919
45068
|
"src/core/checks/session/duplicate-memory.ts"() {
|
|
44920
45069
|
"use strict";
|
|
44921
45070
|
init_session_parser();
|
|
45071
|
+
init_similarity();
|
|
45072
|
+
MIN_TOKEN_LEN2 = 5;
|
|
44922
45073
|
}
|
|
44923
45074
|
});
|
|
44924
45075
|
|
|
@@ -44973,7 +45124,8 @@ function findCyclicPatterns(displays) {
|
|
|
44973
45124
|
async function checkLoopDetection(ctx) {
|
|
44974
45125
|
const issues = [];
|
|
44975
45126
|
const currentNorm = normalizeProject(ctx.currentProject);
|
|
44976
|
-
const
|
|
45127
|
+
const filtered = ctx.history.filter((e) => normalizeProject(e.project) === currentNorm).sort((a, b2) => a.timestamp - b2.timestamp);
|
|
45128
|
+
const entries = filtered.length > MAX_HISTORY_ENTRIES ? filtered.slice(-MAX_HISTORY_ENTRIES) : filtered;
|
|
44977
45129
|
if (entries.length < CONSECUTIVE_THRESHOLD) return issues;
|
|
44978
45130
|
const displays = entries.map((e) => e.display);
|
|
44979
45131
|
const repeats = findConsecutiveRepeats(displays);
|
|
@@ -44982,7 +45134,7 @@ async function checkLoopDetection(ctx) {
|
|
|
44982
45134
|
issues.push({
|
|
44983
45135
|
severity: "warning",
|
|
44984
45136
|
check: "session-loop-detection",
|
|
44985
|
-
ruleId: "session/consecutive-repeat",
|
|
45137
|
+
ruleId: "session-loop-detection/consecutive-repeat",
|
|
44986
45138
|
line: 0,
|
|
44987
45139
|
message: `Command run ${count} times consecutively: "${truncated}"`,
|
|
44988
45140
|
suggestion: "An agent may be looping on this command. Check history.jsonl for context on what went wrong"
|
|
@@ -44994,7 +45146,7 @@ async function checkLoopDetection(ctx) {
|
|
|
44994
45146
|
issues.push({
|
|
44995
45147
|
severity: "warning",
|
|
44996
45148
|
check: "session-loop-detection",
|
|
44997
|
-
ruleId: "session/cyclic-pattern",
|
|
45149
|
+
ruleId: "session-loop-detection/cyclic-pattern",
|
|
44998
45150
|
line: 0,
|
|
44999
45151
|
message: `Cyclic pattern repeated ${reps} times: ${cycleStr}`,
|
|
45000
45152
|
suggestion: "An agent may be stuck in a loop. Check if a context file is missing instructions for this workflow"
|
|
@@ -45002,42 +45154,42 @@ async function checkLoopDetection(ctx) {
|
|
|
45002
45154
|
}
|
|
45003
45155
|
return issues;
|
|
45004
45156
|
}
|
|
45005
|
-
var CONSECUTIVE_THRESHOLD, CYCLE_REPEAT_THRESHOLD, MAX_CYCLE_LENGTH;
|
|
45157
|
+
var CONSECUTIVE_THRESHOLD, CYCLE_REPEAT_THRESHOLD, MAX_CYCLE_LENGTH, MAX_HISTORY_ENTRIES;
|
|
45006
45158
|
var init_loop_detection = __esm({
|
|
45007
45159
|
"src/core/checks/session/loop-detection.ts"() {
|
|
45008
45160
|
"use strict";
|
|
45009
45161
|
CONSECUTIVE_THRESHOLD = 3;
|
|
45010
45162
|
CYCLE_REPEAT_THRESHOLD = 2;
|
|
45011
45163
|
MAX_CYCLE_LENGTH = 3;
|
|
45164
|
+
MAX_HISTORY_ENTRIES = 5e3;
|
|
45012
45165
|
}
|
|
45013
45166
|
});
|
|
45014
45167
|
|
|
45015
45168
|
// src/core/checks/session/memory-index-overflow.ts
|
|
45016
|
-
import { readFile as readFile3
|
|
45169
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
45017
45170
|
import { join as join8 } from "node:path";
|
|
45018
45171
|
async function checkMemoryIndexOverflow(ctx) {
|
|
45019
45172
|
const home2 = process.env.HOME || process.env.USERPROFILE || "";
|
|
45020
45173
|
if (!home2) return [];
|
|
45021
45174
|
const encoded = encodeProjectDir(ctx.currentProject);
|
|
45022
45175
|
const memoryFile = join8(home2, ".claude", "projects", encoded, "memory", "MEMORY.md");
|
|
45023
|
-
let
|
|
45176
|
+
let content;
|
|
45024
45177
|
try {
|
|
45025
|
-
|
|
45178
|
+
content = stripBom(await readFile3(memoryFile, "utf-8"));
|
|
45026
45179
|
} catch {
|
|
45027
45180
|
return [];
|
|
45028
45181
|
}
|
|
45029
|
-
const content = await readFile3(memoryFile, "utf-8").catch(() => "");
|
|
45030
45182
|
if (!content) return [];
|
|
45031
45183
|
const lines = content.split("\n");
|
|
45032
45184
|
const lineCount = lines.length;
|
|
45033
|
-
const byteSize =
|
|
45185
|
+
const byteSize = Buffer.byteLength(content, "utf8");
|
|
45034
45186
|
const issues = [];
|
|
45035
45187
|
if (lineCount > MAX_LINES) {
|
|
45036
45188
|
const excess = lineCount - MAX_LINES;
|
|
45037
45189
|
issues.push({
|
|
45038
45190
|
severity: "warning",
|
|
45039
45191
|
check: "session-memory-index-overflow",
|
|
45040
|
-
ruleId: "session/memory-index-overflow",
|
|
45192
|
+
ruleId: "session-memory-index-overflow/memory-index-overflow",
|
|
45041
45193
|
line: MAX_LINES + 1,
|
|
45042
45194
|
message: `MEMORY.md has ${lineCount.toLocaleString()} lines \u2014 only the first ${MAX_LINES} are loaded. ${excess.toLocaleString()} line(s) are effectively invisible to the agent.`,
|
|
45043
45195
|
detail: `File: ${memoryFile}`,
|
|
@@ -45049,7 +45201,7 @@ async function checkMemoryIndexOverflow(ctx) {
|
|
|
45049
45201
|
issues.push({
|
|
45050
45202
|
severity: "warning",
|
|
45051
45203
|
check: "session-memory-index-overflow",
|
|
45052
|
-
ruleId: "session/memory-index-overflow",
|
|
45204
|
+
ruleId: "session-memory-index-overflow/memory-index-overflow",
|
|
45053
45205
|
line: 0,
|
|
45054
45206
|
message: `MEMORY.md is ${byteSize.toLocaleString()} bytes \u2014 only the first ${MAX_BYTES.toLocaleString()} bytes are loaded. ~${excess.toLocaleString()} bytes are effectively invisible.`,
|
|
45055
45207
|
detail: `File: ${memoryFile}`,
|
|
@@ -45063,6 +45215,7 @@ var init_memory_index_overflow = __esm({
|
|
|
45063
45215
|
"src/core/checks/session/memory-index-overflow.ts"() {
|
|
45064
45216
|
"use strict";
|
|
45065
45217
|
init_session_parser();
|
|
45218
|
+
init_fs();
|
|
45066
45219
|
MAX_LINES = 200;
|
|
45067
45220
|
MAX_BYTES = 25 * 1024;
|
|
45068
45221
|
}
|
|
@@ -45089,7 +45242,7 @@ async function findReleaseWorkflows(projectRoot) {
|
|
|
45089
45242
|
continue;
|
|
45090
45243
|
}
|
|
45091
45244
|
try {
|
|
45092
|
-
const content = await readFile4(join9(workflowDir, f), "utf-8");
|
|
45245
|
+
const content = stripBom(await readFile4(join9(workflowDir, f), "utf-8"));
|
|
45093
45246
|
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
45094
45247
|
if (nameMatch && RELEASE_FILENAME_PATTERNS.some((p2) => p2.test(nameMatch[1]))) {
|
|
45095
45248
|
releaseWorkflows.push(f);
|
|
@@ -45115,7 +45268,7 @@ async function checkCiCoverage(files, projectRoot) {
|
|
|
45115
45268
|
{
|
|
45116
45269
|
severity: "info",
|
|
45117
45270
|
check: "ci-coverage",
|
|
45118
|
-
ruleId: "ci/no-release-docs",
|
|
45271
|
+
ruleId: "ci-coverage/no-release-docs",
|
|
45119
45272
|
line: 0,
|
|
45120
45273
|
message: `Release workflow${releaseWorkflows.length > 1 ? "s" : ""} found (${releaseWorkflows.join(", ")}) but no context file documents the release process`,
|
|
45121
45274
|
suggestion: `Document how releases work (e.g. "push a v* tag to trigger CI") in a context file so agents don't guess`
|
|
@@ -45126,6 +45279,7 @@ var RELEASE_FILENAME_PATTERNS, RELEASE_DOC_PATTERNS;
|
|
|
45126
45279
|
var init_ci_coverage = __esm({
|
|
45127
45280
|
"src/core/checks/ci-coverage.ts"() {
|
|
45128
45281
|
"use strict";
|
|
45282
|
+
init_fs();
|
|
45129
45283
|
RELEASE_FILENAME_PATTERNS = [/release/i, /deploy/i, /publish/i, /\bcd\b/i];
|
|
45130
45284
|
RELEASE_DOC_PATTERNS = [
|
|
45131
45285
|
/release\s+(process|workflow|steps|via|by|using)/i,
|
|
@@ -45160,7 +45314,7 @@ async function findSecretUsages(projectRoot) {
|
|
|
45160
45314
|
if (!(f.endsWith(".yml") || f.endsWith(".yaml"))) continue;
|
|
45161
45315
|
let content;
|
|
45162
45316
|
try {
|
|
45163
|
-
content = await readFile5(join10(workflowDir, f), "utf-8");
|
|
45317
|
+
content = stripBom(await readFile5(join10(workflowDir, f), "utf-8"));
|
|
45164
45318
|
} catch {
|
|
45165
45319
|
continue;
|
|
45166
45320
|
}
|
|
@@ -45177,7 +45331,7 @@ async function findSecretUsages(projectRoot) {
|
|
|
45177
45331
|
}
|
|
45178
45332
|
function contextMentionsSecret(files, secretName) {
|
|
45179
45333
|
const escaped = secretName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45180
|
-
const pattern = new RegExp(`\\b${escaped.replace(/_/g, "[_
|
|
45334
|
+
const pattern = new RegExp(`\\b${escaped.replace(/_/g, "[ _-]")}\\b`, "i");
|
|
45181
45335
|
return files.some((f) => pattern.test(f.content));
|
|
45182
45336
|
}
|
|
45183
45337
|
async function checkCiSecrets(files, projectRoot) {
|
|
@@ -45189,7 +45343,7 @@ async function checkCiSecrets(files, projectRoot) {
|
|
|
45189
45343
|
issues.push({
|
|
45190
45344
|
severity: "info",
|
|
45191
45345
|
check: "ci-secrets",
|
|
45192
|
-
ruleId: "ci/undocumented-secret",
|
|
45346
|
+
ruleId: "ci-secrets/undocumented-secret",
|
|
45193
45347
|
line: 0,
|
|
45194
45348
|
message: `CI secret "${name}" is used in ${workflows.join(", ")} but not mentioned in any context file`,
|
|
45195
45349
|
suggestion: `Document what ${name} is and how to set it (e.g. "gh secret set ${name}") so agents don't create new tokens or guess`
|
|
@@ -45201,6 +45355,7 @@ var BUILTIN_SECRETS, SECRETS_REGEX;
|
|
|
45201
45355
|
var init_ci_secrets = __esm({
|
|
45202
45356
|
"src/core/checks/ci-secrets.ts"() {
|
|
45203
45357
|
"use strict";
|
|
45358
|
+
init_fs();
|
|
45204
45359
|
BUILTIN_SECRETS = /* @__PURE__ */ new Set([
|
|
45205
45360
|
"GITHUB_TOKEN",
|
|
45206
45361
|
"ACTIONS_RUNTIME_TOKEN",
|
|
@@ -45216,15 +45371,239 @@ var init_ci_secrets = __esm({
|
|
|
45216
45371
|
}
|
|
45217
45372
|
});
|
|
45218
45373
|
|
|
45374
|
+
// src/core/checks/content-secrets.ts
|
|
45375
|
+
function lineLooksLikePlaceholder(line) {
|
|
45376
|
+
const lower = line.toLowerCase();
|
|
45377
|
+
for (const tok of PLACEHOLDER_TOKENS) {
|
|
45378
|
+
if (lower.includes(tok)) return true;
|
|
45379
|
+
}
|
|
45380
|
+
return false;
|
|
45381
|
+
}
|
|
45382
|
+
function isCommentedExample(line) {
|
|
45383
|
+
if (!COMMENT_PREFIX.test(line)) return false;
|
|
45384
|
+
const lower = line.toLowerCase();
|
|
45385
|
+
return lower.includes("fake") || lower.includes("example");
|
|
45386
|
+
}
|
|
45387
|
+
function isPlaceholderWrapped(line, start, end) {
|
|
45388
|
+
const before2 = line.slice(Math.max(0, start - 2), start);
|
|
45389
|
+
const after2 = line.slice(end, end + 2);
|
|
45390
|
+
if (before2.endsWith("<") && after2.startsWith(">")) return true;
|
|
45391
|
+
if (before2.endsWith("${") && after2.startsWith("}")) return true;
|
|
45392
|
+
if (before2.endsWith("{") && after2.startsWith("}")) return true;
|
|
45393
|
+
const dollarOpen = line.lastIndexOf("${", start);
|
|
45394
|
+
if (dollarOpen !== -1) {
|
|
45395
|
+
const close = line.indexOf("}", dollarOpen);
|
|
45396
|
+
if (close !== -1 && close >= end) return true;
|
|
45397
|
+
}
|
|
45398
|
+
const angleOpen = line.lastIndexOf("<", start);
|
|
45399
|
+
if (angleOpen !== -1) {
|
|
45400
|
+
const close = line.indexOf(">", angleOpen);
|
|
45401
|
+
if (close !== -1 && close >= end) return true;
|
|
45402
|
+
}
|
|
45403
|
+
return false;
|
|
45404
|
+
}
|
|
45405
|
+
function computeFenceLanguages(lines) {
|
|
45406
|
+
const out = new Array(lines.length).fill(null);
|
|
45407
|
+
let activeMarker = null;
|
|
45408
|
+
let activeLang = null;
|
|
45409
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
45410
|
+
const line = lines[i2];
|
|
45411
|
+
const trimmed2 = line.trim();
|
|
45412
|
+
if (activeMarker === null) {
|
|
45413
|
+
const open = trimmed2.match(/^(```+|~~~+)(.*)$/);
|
|
45414
|
+
if (open) {
|
|
45415
|
+
activeMarker = open[1][0];
|
|
45416
|
+
activeLang = open[2].trim().toLowerCase();
|
|
45417
|
+
continue;
|
|
45418
|
+
}
|
|
45419
|
+
} else {
|
|
45420
|
+
out[i2] = activeLang;
|
|
45421
|
+
if (trimmed2.startsWith(activeMarker.repeat(3)) && trimmed2.replace(new RegExp(`^${activeMarker === "`" ? "`" : "~"}+`), "").trim() === "") {
|
|
45422
|
+
activeMarker = null;
|
|
45423
|
+
activeLang = null;
|
|
45424
|
+
}
|
|
45425
|
+
}
|
|
45426
|
+
}
|
|
45427
|
+
return out;
|
|
45428
|
+
}
|
|
45429
|
+
function redactedPrefix(value) {
|
|
45430
|
+
const head = value.slice(0, 6);
|
|
45431
|
+
return `${head}...`;
|
|
45432
|
+
}
|
|
45433
|
+
async function checkContentSecrets(file2, _projectRoot) {
|
|
45434
|
+
const issues = [];
|
|
45435
|
+
const lines = file2.content.split(/\r?\n/);
|
|
45436
|
+
const fenceLangs = computeFenceLanguages(lines);
|
|
45437
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
45438
|
+
const line = lines[i2];
|
|
45439
|
+
const lineNo = i2 + 1;
|
|
45440
|
+
if (isCommentedExample(line)) continue;
|
|
45441
|
+
const fenceLang = fenceLangs[i2];
|
|
45442
|
+
if (fenceLang !== null && ILLUSTRATIVE_FENCES.has(fenceLang)) continue;
|
|
45443
|
+
if (lineLooksLikePlaceholder(line)) continue;
|
|
45444
|
+
if (PRIVATE_KEY_HEADER.test(line)) {
|
|
45445
|
+
issues.push({
|
|
45446
|
+
severity: "error",
|
|
45447
|
+
check: "content-secrets",
|
|
45448
|
+
ruleId: "content-secrets/private-key-header",
|
|
45449
|
+
line: lineNo,
|
|
45450
|
+
message: `Private key header detected in ${file2.relativePath}`,
|
|
45451
|
+
suggestion: "Move the secret to a `.env` or secret manager and reference it by name. If this token is real, rotate it immediately."
|
|
45452
|
+
});
|
|
45453
|
+
continue;
|
|
45454
|
+
}
|
|
45455
|
+
const seen = /* @__PURE__ */ new Set();
|
|
45456
|
+
for (const pattern of PATTERNS) {
|
|
45457
|
+
const re2 = new RegExp(pattern.regex.source, pattern.regex.flags);
|
|
45458
|
+
let m;
|
|
45459
|
+
while ((m = re2.exec(line)) !== null) {
|
|
45460
|
+
const matched = m[0];
|
|
45461
|
+
const start = m.index;
|
|
45462
|
+
const end = start + matched.length;
|
|
45463
|
+
const key = `${start}:${pattern.ruleSlug}`;
|
|
45464
|
+
if (seen.has(key)) continue;
|
|
45465
|
+
let overlap = false;
|
|
45466
|
+
for (const k3 of seen) {
|
|
45467
|
+
const [s] = k3.split(":");
|
|
45468
|
+
if (parseInt(s, 10) === start) {
|
|
45469
|
+
overlap = true;
|
|
45470
|
+
break;
|
|
45471
|
+
}
|
|
45472
|
+
}
|
|
45473
|
+
if (overlap) continue;
|
|
45474
|
+
if (isPlaceholderWrapped(line, start, end)) continue;
|
|
45475
|
+
seen.add(key);
|
|
45476
|
+
issues.push({
|
|
45477
|
+
severity: "error",
|
|
45478
|
+
check: "content-secrets",
|
|
45479
|
+
ruleId: `content-secrets/${pattern.ruleSlug}`,
|
|
45480
|
+
line: lineNo,
|
|
45481
|
+
message: `${pattern.label} detected in ${file2.relativePath} (${redactedPrefix(matched)})`,
|
|
45482
|
+
suggestion: "Move the secret to a `.env` or secret manager and reference it by name. If this token is real, rotate it immediately."
|
|
45483
|
+
});
|
|
45484
|
+
}
|
|
45485
|
+
}
|
|
45486
|
+
}
|
|
45487
|
+
return issues;
|
|
45488
|
+
}
|
|
45489
|
+
var PATTERNS, PRIVATE_KEY_HEADER, PLACEHOLDER_TOKENS, COMMENT_PREFIX, ILLUSTRATIVE_FENCES;
|
|
45490
|
+
var init_content_secrets = __esm({
|
|
45491
|
+
"src/core/checks/content-secrets.ts"() {
|
|
45492
|
+
"use strict";
|
|
45493
|
+
PATTERNS = [
|
|
45494
|
+
// AWS access key (long-lived) -- AKIA prefix + 16 uppercase alphanum chars.
|
|
45495
|
+
{
|
|
45496
|
+
ruleSlug: "aws-access-key",
|
|
45497
|
+
label: "AWS access key",
|
|
45498
|
+
regex: /\bAKIA[0-9A-Z]{16}\b/g
|
|
45499
|
+
},
|
|
45500
|
+
// AWS STS temporary access key -- ASIA prefix.
|
|
45501
|
+
{
|
|
45502
|
+
ruleSlug: "aws-access-key",
|
|
45503
|
+
label: "AWS access key",
|
|
45504
|
+
regex: /\bASIA[0-9A-Z]{16}\b/g
|
|
45505
|
+
},
|
|
45506
|
+
// GitHub classic PAT.
|
|
45507
|
+
{
|
|
45508
|
+
ruleSlug: "github-pat",
|
|
45509
|
+
label: "GitHub personal access token",
|
|
45510
|
+
regex: /\bghp_[A-Za-z0-9]{36,}\b/g
|
|
45511
|
+
},
|
|
45512
|
+
// GitHub fine-grained PAT.
|
|
45513
|
+
{
|
|
45514
|
+
ruleSlug: "github-pat",
|
|
45515
|
+
label: "GitHub personal access token",
|
|
45516
|
+
regex: /\bgithub_pat_[A-Za-z0-9_]{82,}\b/g
|
|
45517
|
+
},
|
|
45518
|
+
// GitHub server-to-server / OAuth / user / refresh tokens.
|
|
45519
|
+
{
|
|
45520
|
+
ruleSlug: "github-pat",
|
|
45521
|
+
label: "GitHub token",
|
|
45522
|
+
regex: /\bghs_[A-Za-z0-9]{36,}\b/g
|
|
45523
|
+
},
|
|
45524
|
+
{
|
|
45525
|
+
ruleSlug: "github-pat",
|
|
45526
|
+
label: "GitHub token",
|
|
45527
|
+
regex: /\bgho_[A-Za-z0-9]{36,}\b/g
|
|
45528
|
+
},
|
|
45529
|
+
{
|
|
45530
|
+
ruleSlug: "github-pat",
|
|
45531
|
+
label: "GitHub token",
|
|
45532
|
+
regex: /\bghu_[A-Za-z0-9]{36,}\b/g
|
|
45533
|
+
},
|
|
45534
|
+
{
|
|
45535
|
+
ruleSlug: "github-pat",
|
|
45536
|
+
label: "GitHub token",
|
|
45537
|
+
regex: /\bghr_[A-Za-z0-9]{36,}\b/g
|
|
45538
|
+
},
|
|
45539
|
+
// Anthropic API keys.
|
|
45540
|
+
{
|
|
45541
|
+
ruleSlug: "anthropic-key",
|
|
45542
|
+
label: "Anthropic API key",
|
|
45543
|
+
regex: /\bsk-ant-[A-Za-z0-9\-_]{20,}\b/g
|
|
45544
|
+
},
|
|
45545
|
+
// OpenAI API keys (project-scoped or classic). Match sk- or sk-proj- with at
|
|
45546
|
+
// least 20 random chars to avoid catching `sk-...` ellipses in docs.
|
|
45547
|
+
{
|
|
45548
|
+
ruleSlug: "openai-key",
|
|
45549
|
+
label: "OpenAI API key",
|
|
45550
|
+
regex: /\bsk-(?:proj-)?[A-Za-z0-9_\-]{20,}\b/g
|
|
45551
|
+
},
|
|
45552
|
+
// npm automation tokens.
|
|
45553
|
+
{
|
|
45554
|
+
ruleSlug: "npm-token",
|
|
45555
|
+
label: "npm token",
|
|
45556
|
+
regex: /\bnpm_[A-Za-z0-9]{36,}\b/g
|
|
45557
|
+
},
|
|
45558
|
+
// Slack tokens (bot, user, app, admin, refresh).
|
|
45559
|
+
{
|
|
45560
|
+
ruleSlug: "slack-token",
|
|
45561
|
+
label: "Slack token",
|
|
45562
|
+
regex: /\bxox[bpoasr]-[A-Za-z0-9\-]{10,}\b/g
|
|
45563
|
+
},
|
|
45564
|
+
// mcp.hosting PAT -- consistent with mcph/token-security.ts.
|
|
45565
|
+
{
|
|
45566
|
+
ruleSlug: "mcph-pat",
|
|
45567
|
+
label: "mcp.hosting PAT",
|
|
45568
|
+
regex: /\bmcp_pat_[A-Za-z0-9]{32,}\b/g
|
|
45569
|
+
},
|
|
45570
|
+
// Google API keys.
|
|
45571
|
+
{
|
|
45572
|
+
ruleSlug: "google-api-key",
|
|
45573
|
+
label: "Google API key",
|
|
45574
|
+
regex: /\bAIza[0-9A-Za-z\-_]{35}\b/g
|
|
45575
|
+
},
|
|
45576
|
+
// Stripe live secret keys.
|
|
45577
|
+
{
|
|
45578
|
+
ruleSlug: "stripe-secret",
|
|
45579
|
+
label: "Stripe live secret key",
|
|
45580
|
+
regex: /\bsk_live_[0-9a-zA-Z]{24,}\b/g
|
|
45581
|
+
}
|
|
45582
|
+
];
|
|
45583
|
+
PRIVATE_KEY_HEADER = /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/;
|
|
45584
|
+
PLACEHOLDER_TOKENS = [
|
|
45585
|
+
"example",
|
|
45586
|
+
"placeholder",
|
|
45587
|
+
"your-key",
|
|
45588
|
+
"<replace",
|
|
45589
|
+
"redacted",
|
|
45590
|
+
"xxxx",
|
|
45591
|
+
"****"
|
|
45592
|
+
];
|
|
45593
|
+
COMMENT_PREFIX = /^\s*(?:#|\/\/|--|<!--)/;
|
|
45594
|
+
ILLUSTRATIVE_FENCES = /* @__PURE__ */ new Set(["text", "txt", "example", "pseudocode", "none", ""]);
|
|
45595
|
+
}
|
|
45596
|
+
});
|
|
45597
|
+
|
|
45219
45598
|
// src/version.ts
|
|
45220
|
-
import { readFileSync as
|
|
45599
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
45221
45600
|
import { resolve as resolve8, dirname as dirname3 } from "node:path";
|
|
45222
45601
|
import { fileURLToPath } from "node:url";
|
|
45223
45602
|
function loadVersion() {
|
|
45224
|
-
if (true) return "0.
|
|
45603
|
+
if (true) return "0.10.0";
|
|
45225
45604
|
const __dir = dirname3(fileURLToPath(import.meta.url));
|
|
45226
45605
|
const pkgPath = resolve8(__dir, "../package.json");
|
|
45227
|
-
const pkg = JSON.parse(
|
|
45606
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
45228
45607
|
return pkg.version;
|
|
45229
45608
|
}
|
|
45230
45609
|
var VERSION;
|
|
@@ -45245,8 +45624,14 @@ function hasMcphChecks(checks) {
|
|
|
45245
45624
|
function hasSessionChecks(checks) {
|
|
45246
45625
|
return checks.some((c3) => c3.startsWith("session-"));
|
|
45247
45626
|
}
|
|
45627
|
+
function deriveChecksToRun(activeChecks, prefix, enabled, allChecks) {
|
|
45628
|
+
const filtered = activeChecks.filter((c3) => c3.startsWith(prefix));
|
|
45629
|
+
if (filtered.length > 0) return filtered;
|
|
45630
|
+
return enabled ? [...allChecks] : [];
|
|
45631
|
+
}
|
|
45248
45632
|
async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
45249
45633
|
const fileResults = [];
|
|
45634
|
+
const thresholds = resolveTokenThresholds(options.tokenThresholds);
|
|
45250
45635
|
const shouldRunContextChecks = !options.mcpOnly && !options.mcphOnly && !options.sessionOnly;
|
|
45251
45636
|
const shouldRunMcpChecks = options.mcp || options.mcpGlobal || options.mcpOnly || hasMcpChecks(activeChecks);
|
|
45252
45637
|
const shouldRunMcphChecks = options.mcph || options.mcphGlobal || options.mcphOnly || hasMcphChecks(activeChecks);
|
|
@@ -45262,13 +45647,16 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45262
45647
|
if (activeChecks.includes("paths")) checkPromises.push(checkPaths(file2, projectRoot));
|
|
45263
45648
|
if (activeChecks.includes("commands")) checkPromises.push(checkCommands(file2, projectRoot));
|
|
45264
45649
|
if (activeChecks.includes("staleness")) checkPromises.push(checkStaleness(file2, projectRoot));
|
|
45265
|
-
if (activeChecks.includes("tokens"))
|
|
45650
|
+
if (activeChecks.includes("tokens"))
|
|
45651
|
+
checkPromises.push(checkTokens(file2, projectRoot, thresholds));
|
|
45266
45652
|
if (activeChecks.includes("tier-tokens"))
|
|
45267
|
-
checkPromises.push(checkTierTokens(file2, projectRoot));
|
|
45653
|
+
checkPromises.push(checkTierTokens(file2, projectRoot, thresholds));
|
|
45268
45654
|
if (activeChecks.includes("redundancy"))
|
|
45269
45655
|
checkPromises.push(checkRedundancy(file2, projectRoot));
|
|
45270
45656
|
if (activeChecks.includes("frontmatter"))
|
|
45271
45657
|
checkPromises.push(checkFrontmatter(file2, projectRoot));
|
|
45658
|
+
if (activeChecks.includes("content-secrets"))
|
|
45659
|
+
checkPromises.push(checkContentSecrets(file2, projectRoot));
|
|
45272
45660
|
const results = await Promise.all(checkPromises);
|
|
45273
45661
|
const issues = results.flat();
|
|
45274
45662
|
fileResults.push({
|
|
@@ -45283,12 +45671,13 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45283
45671
|
const crossFileIssues = [];
|
|
45284
45672
|
if (activeChecks.includes("tokens")) {
|
|
45285
45673
|
const aggIssue = checkAggregateTokens(
|
|
45286
|
-
fileResults.map((f) => ({ path: f.path, tokens: f.tokens }))
|
|
45674
|
+
fileResults.map((f) => ({ path: f.path, tokens: f.tokens })),
|
|
45675
|
+
thresholds
|
|
45287
45676
|
);
|
|
45288
45677
|
if (aggIssue) crossFileIssues.push(aggIssue);
|
|
45289
45678
|
}
|
|
45290
45679
|
if (activeChecks.includes("tier-tokens")) {
|
|
45291
|
-
const tierAgg = checkAggregateTierTokens(parsed);
|
|
45680
|
+
const tierAgg = checkAggregateTierTokens(parsed, thresholds);
|
|
45292
45681
|
if (tierAgg) crossFileIssues.push(tierAgg);
|
|
45293
45682
|
}
|
|
45294
45683
|
if (activeChecks.includes("redundancy")) {
|
|
@@ -45325,8 +45714,12 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45325
45714
|
);
|
|
45326
45715
|
mcpConfigs.push(...globalConfigs);
|
|
45327
45716
|
}
|
|
45328
|
-
const
|
|
45329
|
-
|
|
45717
|
+
const mcpChecksToRun = deriveChecksToRun(
|
|
45718
|
+
activeChecks,
|
|
45719
|
+
"mcp-",
|
|
45720
|
+
Boolean(options.mcp || options.mcpGlobal || options.mcpOnly),
|
|
45721
|
+
ALL_MCP_CHECKS
|
|
45722
|
+
);
|
|
45330
45723
|
for (const config2 of mcpConfigs) {
|
|
45331
45724
|
const checkPromises = [];
|
|
45332
45725
|
if (mcpChecksToRun.includes("mcp-schema"))
|
|
@@ -45372,7 +45765,7 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45372
45765
|
const projectMcphFiles = await scanForMcphConfigs(projectRoot);
|
|
45373
45766
|
const mcphConfigs = await Promise.all(
|
|
45374
45767
|
projectMcphFiles.map(
|
|
45375
|
-
(f) =>
|
|
45768
|
+
(f) => parseMcphConfig(
|
|
45376
45769
|
f,
|
|
45377
45770
|
projectRoot,
|
|
45378
45771
|
f.relativePath.endsWith(".mcph.local.json") ? "project-local" : "project"
|
|
@@ -45382,12 +45775,16 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45382
45775
|
if (options.mcphGlobal) {
|
|
45383
45776
|
const globalMcphFiles = await scanGlobalMcphConfigs();
|
|
45384
45777
|
const globalMcphConfigs = await Promise.all(
|
|
45385
|
-
globalMcphFiles.map((f) =>
|
|
45778
|
+
globalMcphFiles.map((f) => parseMcphConfig(f, projectRoot, "global"))
|
|
45386
45779
|
);
|
|
45387
45780
|
mcphConfigs.push(...globalMcphConfigs);
|
|
45388
45781
|
}
|
|
45389
|
-
const
|
|
45390
|
-
|
|
45782
|
+
const mcphChecksToRun = deriveChecksToRun(
|
|
45783
|
+
activeChecks,
|
|
45784
|
+
"mcph-",
|
|
45785
|
+
Boolean(options.mcph || options.mcphGlobal || options.mcphOnly),
|
|
45786
|
+
ALL_MCPH_CHECKS
|
|
45787
|
+
);
|
|
45391
45788
|
for (const config2 of mcphConfigs) {
|
|
45392
45789
|
const checkPromises = [];
|
|
45393
45790
|
if (mcphChecksToRun.includes("mcph-token-security")) {
|
|
@@ -45411,14 +45808,16 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45411
45808
|
}
|
|
45412
45809
|
const results = await Promise.all(checkPromises);
|
|
45413
45810
|
const issues = results.flat();
|
|
45414
|
-
|
|
45415
|
-
|
|
45416
|
-
|
|
45417
|
-
|
|
45418
|
-
|
|
45419
|
-
|
|
45420
|
-
|
|
45421
|
-
|
|
45811
|
+
if (mcphChecksToRun.includes("mcph-schema-conformance")) {
|
|
45812
|
+
for (const err of config2.parseErrors) {
|
|
45813
|
+
issues.push({
|
|
45814
|
+
severity: "error",
|
|
45815
|
+
check: "mcph-schema-conformance",
|
|
45816
|
+
ruleId: "mcph-schema-conformance/parse-error",
|
|
45817
|
+
line: 1,
|
|
45818
|
+
message: err
|
|
45819
|
+
});
|
|
45820
|
+
}
|
|
45422
45821
|
}
|
|
45423
45822
|
const lines = config2.content.split("\n").length;
|
|
45424
45823
|
fileResults.push({
|
|
@@ -45454,15 +45853,13 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45454
45853
|
sessionPromises.push(checkMemoryIndexOverflow(sessionCtx));
|
|
45455
45854
|
const sessionResults = await Promise.all(sessionPromises);
|
|
45456
45855
|
const sessionIssues = sessionResults.flat();
|
|
45457
|
-
|
|
45458
|
-
|
|
45459
|
-
|
|
45460
|
-
|
|
45461
|
-
|
|
45462
|
-
|
|
45463
|
-
|
|
45464
|
-
});
|
|
45465
|
-
}
|
|
45856
|
+
fileResults.push({
|
|
45857
|
+
path: "~/.claude/ (session audit)",
|
|
45858
|
+
isSymlink: false,
|
|
45859
|
+
tokens: 0,
|
|
45860
|
+
lines: 0,
|
|
45861
|
+
issues: sessionIssues
|
|
45862
|
+
});
|
|
45466
45863
|
}
|
|
45467
45864
|
}
|
|
45468
45865
|
let estimatedWaste = 0;
|
|
@@ -45537,6 +45934,7 @@ var init_audit = __esm({
|
|
|
45537
45934
|
init_memory_index_overflow();
|
|
45538
45935
|
init_ci_coverage();
|
|
45539
45936
|
init_ci_secrets();
|
|
45937
|
+
init_content_secrets();
|
|
45540
45938
|
init_version2();
|
|
45541
45939
|
ALL_CHECKS = [
|
|
45542
45940
|
"paths",
|
|
@@ -45548,7 +45946,8 @@ var init_audit = __esm({
|
|
|
45548
45946
|
"contradictions",
|
|
45549
45947
|
"frontmatter",
|
|
45550
45948
|
"ci-coverage",
|
|
45551
|
-
"ci-secrets"
|
|
45949
|
+
"ci-secrets",
|
|
45950
|
+
"content-secrets"
|
|
45552
45951
|
];
|
|
45553
45952
|
ALL_MCP_CHECKS = [
|
|
45554
45953
|
"mcp-schema",
|
|
@@ -46219,11 +46618,22 @@ var init_fixer = __esm({
|
|
|
46219
46618
|
|
|
46220
46619
|
// src/mcp/server.ts
|
|
46221
46620
|
var server_exports = {};
|
|
46621
|
+
__export(server_exports, {
|
|
46622
|
+
server: () => server,
|
|
46623
|
+
startServer: () => startServer
|
|
46624
|
+
});
|
|
46222
46625
|
import * as path10 from "node:path";
|
|
46626
|
+
function describeDisallowed(rawPath) {
|
|
46627
|
+
const m = rawPath.match(PATH_DISALLOWED);
|
|
46628
|
+
if (!m) return "unknown";
|
|
46629
|
+
return JSON.stringify(m[0]);
|
|
46630
|
+
}
|
|
46223
46631
|
function validateProjectPath(rawPath) {
|
|
46224
46632
|
if (!rawPath) return process.cwd();
|
|
46225
46633
|
if (PATH_DISALLOWED.test(rawPath)) {
|
|
46226
|
-
throw new Error(
|
|
46634
|
+
throw new Error(
|
|
46635
|
+
`projectPath contains disallowed character ${describeDisallowed(rawPath)} (control chars and shell metacharacters are rejected)`
|
|
46636
|
+
);
|
|
46227
46637
|
}
|
|
46228
46638
|
const resolved = path10.resolve(rawPath);
|
|
46229
46639
|
if (!isDirectory(resolved)) {
|
|
@@ -46233,12 +46643,28 @@ function validateProjectPath(rawPath) {
|
|
|
46233
46643
|
}
|
|
46234
46644
|
function validateFilePathInput(rawPath) {
|
|
46235
46645
|
if (PATH_DISALLOWED.test(rawPath)) {
|
|
46236
|
-
throw new Error(
|
|
46646
|
+
throw new Error(
|
|
46647
|
+
`path contains disallowed character ${describeDisallowed(rawPath)} (control chars and shell metacharacters are rejected)`
|
|
46648
|
+
);
|
|
46649
|
+
}
|
|
46650
|
+
}
|
|
46651
|
+
function resolveWithinRoot(filePath, root) {
|
|
46652
|
+
const resolvedRoot = path10.resolve(root);
|
|
46653
|
+
const resolved = path10.resolve(resolvedRoot, filePath);
|
|
46654
|
+
const rel = path10.relative(resolvedRoot, resolved);
|
|
46655
|
+
if (rel.startsWith("..") || path10.isAbsolute(rel)) {
|
|
46656
|
+
throw new Error("path escapes the project root");
|
|
46237
46657
|
}
|
|
46658
|
+
return resolved;
|
|
46238
46659
|
}
|
|
46239
|
-
|
|
46660
|
+
async function startServer() {
|
|
46661
|
+
keepEncoderAlive(true);
|
|
46662
|
+
const transport = new StdioServerTransport();
|
|
46663
|
+
await server.connect(transport);
|
|
46664
|
+
}
|
|
46665
|
+
var contextCheckEnum, mcpCheckEnum, mcphCheckEnum, sessionCheckEnum, PATH_DISALLOWED, server;
|
|
46240
46666
|
var init_server3 = __esm({
|
|
46241
|
-
|
|
46667
|
+
"src/mcp/server.ts"() {
|
|
46242
46668
|
"use strict";
|
|
46243
46669
|
init_mcp();
|
|
46244
46670
|
init_stdio2();
|
|
@@ -46312,7 +46738,7 @@ var init_server3 = __esm({
|
|
|
46312
46738
|
try {
|
|
46313
46739
|
validateFilePathInput(filePath);
|
|
46314
46740
|
const root = validateProjectPath(projectPath);
|
|
46315
|
-
const resolved =
|
|
46741
|
+
const resolved = resolveWithinRoot(filePath, root);
|
|
46316
46742
|
const result = {
|
|
46317
46743
|
path: filePath,
|
|
46318
46744
|
exists: fileExists(resolved) || isDirectory(resolved)
|
|
@@ -46555,9 +46981,6 @@ var init_server3 = __esm({
|
|
|
46555
46981
|
}
|
|
46556
46982
|
}
|
|
46557
46983
|
);
|
|
46558
|
-
keepEncoderAlive(true);
|
|
46559
|
-
transport = new StdioServerTransport();
|
|
46560
|
-
await server.connect(transport);
|
|
46561
46984
|
}
|
|
46562
46985
|
});
|
|
46563
46986
|
|
|
@@ -53035,6 +53458,7 @@ var init_ora = __esm({
|
|
|
53035
53458
|
|
|
53036
53459
|
// src/core/reporter.ts
|
|
53037
53460
|
function classifyFile(f) {
|
|
53461
|
+
if (f.path === "(project)") return "context";
|
|
53038
53462
|
if (f.path === "(mcp)") return "mcp";
|
|
53039
53463
|
if (f.path.includes("session audit")) return "session";
|
|
53040
53464
|
for (const issue2 of f.issues) {
|
|
@@ -53298,6 +53722,11 @@ function buildRuleDescriptors() {
|
|
|
53298
53722
|
shortDescription: { text: "CI secret not documented in context files" },
|
|
53299
53723
|
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
53300
53724
|
},
|
|
53725
|
+
{
|
|
53726
|
+
id: "ctxlint/content-secrets",
|
|
53727
|
+
shortDescription: { text: "Inline-pasted secret detected in a context file" },
|
|
53728
|
+
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
53729
|
+
},
|
|
53301
53730
|
{
|
|
53302
53731
|
id: "ctxlint/mcp-schema",
|
|
53303
53732
|
shortDescription: { text: "MCP config structural validation error" },
|
|
@@ -53494,7 +53923,8 @@ function suggestKey(unknown2) {
|
|
|
53494
53923
|
best = known;
|
|
53495
53924
|
}
|
|
53496
53925
|
}
|
|
53497
|
-
|
|
53926
|
+
const threshold = Math.min(4, Math.max(2, Math.floor(unknown2.length / 3)));
|
|
53927
|
+
if (best && bestDist <= threshold) {
|
|
53498
53928
|
return best;
|
|
53499
53929
|
}
|
|
53500
53930
|
return null;
|
|
@@ -53532,7 +53962,7 @@ function loadConfig(projectRoot) {
|
|
|
53532
53962
|
const filePath = path11.join(projectRoot, filename);
|
|
53533
53963
|
let content;
|
|
53534
53964
|
try {
|
|
53535
|
-
content = fs8.readFileSync(filePath, "utf-8");
|
|
53965
|
+
content = stripBom(fs8.readFileSync(filePath, "utf-8"));
|
|
53536
53966
|
} catch {
|
|
53537
53967
|
continue;
|
|
53538
53968
|
}
|
|
@@ -53543,7 +53973,7 @@ function loadConfig(projectRoot) {
|
|
|
53543
53973
|
function loadConfigFromExplicitPath(configPath) {
|
|
53544
53974
|
let content;
|
|
53545
53975
|
try {
|
|
53546
|
-
content = fs8.readFileSync(configPath, "utf-8");
|
|
53976
|
+
content = stripBom(fs8.readFileSync(configPath, "utf-8"));
|
|
53547
53977
|
} catch (err) {
|
|
53548
53978
|
const detail = err instanceof Error ? err.message : String(err);
|
|
53549
53979
|
throw new Error(`could not load config from ${configPath}: ${detail}`, { cause: err });
|
|
@@ -53555,6 +53985,7 @@ var init_config2 = __esm({
|
|
|
53555
53985
|
"src/core/config.ts"() {
|
|
53556
53986
|
"use strict";
|
|
53557
53987
|
import_fast_levenshtein2 = __toESM(require_levenshtein(), 1);
|
|
53988
|
+
init_fs();
|
|
53558
53989
|
levenshtein2 = import_fast_levenshtein2.default.get;
|
|
53559
53990
|
KNOWN_CONFIG_KEYS = [
|
|
53560
53991
|
"checks",
|
|
@@ -53621,7 +54052,8 @@ async function runCli() {
|
|
|
53621
54052
|
mcphOnly: options.mcphOnly,
|
|
53622
54053
|
mcphStrictEnvToken: options.mcphStrictEnvToken,
|
|
53623
54054
|
session: options.session,
|
|
53624
|
-
sessionOnly: options.sessionOnly
|
|
54055
|
+
sessionOnly: options.sessionOnly,
|
|
54056
|
+
tokenThresholds: config2?.tokenThresholds
|
|
53625
54057
|
});
|
|
53626
54058
|
spinner?.stop();
|
|
53627
54059
|
if (result.files.length === 0) {
|
|
@@ -53714,7 +54146,6 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
53714
54146
|
resetGit();
|
|
53715
54147
|
resetPathsCache();
|
|
53716
54148
|
resetPackageJsonCache();
|
|
53717
|
-
resetTokenThresholds();
|
|
53718
54149
|
}
|
|
53719
54150
|
if (opts.watch) {
|
|
53720
54151
|
const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
|
|
@@ -53776,11 +54207,6 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
53776
54207
|
console.error("Error reloading config:", err instanceof Error ? err.message : err);
|
|
53777
54208
|
}
|
|
53778
54209
|
try {
|
|
53779
|
-
if (liveConfig?.tokenThresholds) {
|
|
53780
|
-
setTokenThresholds(liveConfig.tokenThresholds);
|
|
53781
|
-
} else {
|
|
53782
|
-
resetTokenThresholds();
|
|
53783
|
-
}
|
|
53784
54210
|
const result = await runAudit(resolvedPath, liveActiveChecks, {
|
|
53785
54211
|
depth: liveOptions.depth,
|
|
53786
54212
|
extraPatterns: liveConfig?.contextFiles,
|
|
@@ -53792,7 +54218,8 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
53792
54218
|
mcphOnly: liveOptions.mcphOnly,
|
|
53793
54219
|
mcphStrictEnvToken: liveOptions.mcphStrictEnvToken,
|
|
53794
54220
|
session: liveOptions.session,
|
|
53795
|
-
sessionOnly: liveOptions.sessionOnly
|
|
54221
|
+
sessionOnly: liveOptions.sessionOnly,
|
|
54222
|
+
tokenThresholds: liveConfig?.tokenThresholds
|
|
53796
54223
|
});
|
|
53797
54224
|
if (result.files.length === 0) {
|
|
53798
54225
|
console.log("\nNo context files found.\n");
|
|
@@ -53812,7 +54239,6 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
53812
54239
|
resetGit();
|
|
53813
54240
|
resetPathsCache();
|
|
53814
54241
|
resetPackageJsonCache();
|
|
53815
|
-
resetTokenThresholds();
|
|
53816
54242
|
}
|
|
53817
54243
|
console.log(chalk2.dim("\nWatching for changes... (Ctrl+C to stop)\n"));
|
|
53818
54244
|
}, 300);
|
|
@@ -53956,9 +54382,6 @@ function resolveSession(resolvedPath, opts) {
|
|
|
53956
54382
|
session: effectiveSession,
|
|
53957
54383
|
sessionOnly
|
|
53958
54384
|
};
|
|
53959
|
-
if (config2?.tokenThresholds) {
|
|
53960
|
-
setTokenThresholds(config2.tokenThresholds);
|
|
53961
|
-
}
|
|
53962
54385
|
const activeChecks = options.checks.filter((c3) => !options.ignore.includes(c3));
|
|
53963
54386
|
return { config: config2, options, activeChecks };
|
|
53964
54387
|
}
|
|
@@ -53978,7 +54401,6 @@ var init_cli = __esm({
|
|
|
53978
54401
|
init_esm3();
|
|
53979
54402
|
init_ora();
|
|
53980
54403
|
init_paths();
|
|
53981
|
-
init_tokens2();
|
|
53982
54404
|
init_reporter();
|
|
53983
54405
|
init_fixer();
|
|
53984
54406
|
init_tokens();
|
|
@@ -53999,7 +54421,8 @@ var init_cli = __esm({
|
|
|
53999
54421
|
// src/index.ts
|
|
54000
54422
|
var args = process.argv.slice(2);
|
|
54001
54423
|
if (args[0] === "serve" || args.includes("--mcp-server")) {
|
|
54002
|
-
await
|
|
54424
|
+
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server3(), server_exports));
|
|
54425
|
+
await startServer2();
|
|
54003
54426
|
} else {
|
|
54004
54427
|
const { runCli: runCli2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
54005
54428
|
await runCli2();
|