@yawlabs/ctxlint 0.9.19 → 0.9.20
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 +428 -247
- 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") {
|
|
@@ -40656,10 +40659,9 @@ var init_git = __esm({
|
|
|
40656
40659
|
});
|
|
40657
40660
|
|
|
40658
40661
|
// src/core/mcp-parser.ts
|
|
40659
|
-
async function parseMcpConfig(file2, projectRoot,
|
|
40662
|
+
async function parseMcpConfig(file2, projectRoot, scope) {
|
|
40660
40663
|
const content = readFileContent(file2.absolutePath);
|
|
40661
40664
|
const client = detectClient(file2.relativePath);
|
|
40662
|
-
const scope = scopeOverride ?? detectScope(file2.relativePath);
|
|
40663
40665
|
const expectedRootKey = client === "vscode" ? "servers" : "mcpServers";
|
|
40664
40666
|
const isGitTracked = await checkGitTracked(file2.absolutePath, projectRoot);
|
|
40665
40667
|
const result = {
|
|
@@ -40703,10 +40705,10 @@ async function parseMcpConfig(file2, projectRoot, scopeOverride) {
|
|
|
40703
40705
|
}
|
|
40704
40706
|
const raw = value;
|
|
40705
40707
|
const line = findServerLine(lines, name);
|
|
40706
|
-
const
|
|
40708
|
+
const transport = inferTransport(raw);
|
|
40707
40709
|
const entry = {
|
|
40708
40710
|
name,
|
|
40709
|
-
transport
|
|
40711
|
+
transport,
|
|
40710
40712
|
line,
|
|
40711
40713
|
raw
|
|
40712
40714
|
};
|
|
@@ -40718,7 +40720,7 @@ async function parseMcpConfig(file2, projectRoot, scopeOverride) {
|
|
|
40718
40720
|
if (typeof raw.disabled === "boolean") entry.disabled = raw.disabled;
|
|
40719
40721
|
if (Array.isArray(raw.autoApprove)) entry.autoApprove = raw.autoApprove.map(String);
|
|
40720
40722
|
if (typeof raw.timeout === "number") entry.timeout = raw.timeout;
|
|
40721
|
-
if (typeof raw.oauth === "object" && raw.oauth !== null)
|
|
40723
|
+
if (typeof raw.oauth === "object" && raw.oauth !== null && !Array.isArray(raw.oauth))
|
|
40722
40724
|
entry.oauth = raw.oauth;
|
|
40723
40725
|
if (typeof raw.headersHelper === "string") entry.headersHelper = raw.headersHelper;
|
|
40724
40726
|
result.servers.push(entry);
|
|
@@ -40737,9 +40739,6 @@ function detectClient(relativePath) {
|
|
|
40737
40739
|
}
|
|
40738
40740
|
return "claude-code";
|
|
40739
40741
|
}
|
|
40740
|
-
function detectScope(_relativePath) {
|
|
40741
|
-
return "project";
|
|
40742
|
-
}
|
|
40743
40742
|
function findRootKey(parsed) {
|
|
40744
40743
|
if ("mcpServers" in parsed) return "mcpServers";
|
|
40745
40744
|
if ("servers" in parsed) return "servers";
|
|
@@ -41746,9 +41745,9 @@ var init_main = __esm({
|
|
|
41746
41745
|
});
|
|
41747
41746
|
|
|
41748
41747
|
// src/core/mcph-parser.ts
|
|
41749
|
-
async function
|
|
41748
|
+
async function parseMcphConfig(file2, projectRoot, scopeOverride) {
|
|
41750
41749
|
const content = readFileContent(file2.absolutePath);
|
|
41751
|
-
const scope = scopeOverride ??
|
|
41750
|
+
const scope = scopeOverride ?? detectScope(file2.relativePath);
|
|
41752
41751
|
const isGitTracked = await checkGitTracked2(file2.absolutePath, projectRoot);
|
|
41753
41752
|
const isGitignored = await checkGitignored(file2.absolutePath, projectRoot);
|
|
41754
41753
|
const result = {
|
|
@@ -41806,7 +41805,7 @@ async function parseMchpConfig(file2, projectRoot, scopeOverride) {
|
|
|
41806
41805
|
}
|
|
41807
41806
|
return result;
|
|
41808
41807
|
}
|
|
41809
|
-
function
|
|
41808
|
+
function detectScope(relativePath) {
|
|
41810
41809
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
41811
41810
|
if (normalized.startsWith("~/")) return "global";
|
|
41812
41811
|
if (normalized.endsWith(".mcph.local.json")) return "project-local";
|
|
@@ -42168,29 +42167,33 @@ async function checkPaths(file2, projectRoot) {
|
|
|
42168
42167
|
function findClosestMatch(target, files) {
|
|
42169
42168
|
const targetNorm = target.replace(/\\/g, "/");
|
|
42170
42169
|
const targetBase = path3.basename(targetNorm);
|
|
42171
|
-
let
|
|
42172
|
-
let
|
|
42170
|
+
let basenameMatch = null;
|
|
42171
|
+
let basenameDistance = Infinity;
|
|
42173
42172
|
for (const file2 of files) {
|
|
42174
42173
|
const fileNorm = file2.replace(/\\/g, "/");
|
|
42175
42174
|
if (path3.basename(fileNorm) === targetBase && fileNorm !== targetNorm) {
|
|
42176
42175
|
const dist = levenshtein(targetNorm, fileNorm);
|
|
42177
|
-
if (dist <
|
|
42178
|
-
|
|
42179
|
-
|
|
42176
|
+
if (dist < basenameDistance) {
|
|
42177
|
+
basenameDistance = dist;
|
|
42178
|
+
basenameMatch = fileNorm;
|
|
42180
42179
|
}
|
|
42181
42180
|
}
|
|
42182
42181
|
}
|
|
42183
|
-
if (
|
|
42184
|
-
|
|
42185
|
-
|
|
42186
|
-
|
|
42187
|
-
|
|
42188
|
-
|
|
42189
|
-
|
|
42190
|
-
|
|
42182
|
+
if (basenameMatch) return basenameMatch;
|
|
42183
|
+
const absoluteCap = Math.max(targetNorm.length * 0.4, 5);
|
|
42184
|
+
let fullPathMatch = null;
|
|
42185
|
+
let fullPathDistance = Infinity;
|
|
42186
|
+
for (const file2 of files) {
|
|
42187
|
+
const fileNorm = file2.replace(/\\/g, "/");
|
|
42188
|
+
const lenDelta = Math.abs(targetNorm.length - fileNorm.length);
|
|
42189
|
+
if (lenDelta >= fullPathDistance || lenDelta > absoluteCap) continue;
|
|
42190
|
+
const dist = levenshtein(targetNorm, fileNorm);
|
|
42191
|
+
if (dist < fullPathDistance && dist <= absoluteCap) {
|
|
42192
|
+
fullPathDistance = dist;
|
|
42193
|
+
fullPathMatch = fileNorm;
|
|
42191
42194
|
}
|
|
42192
42195
|
}
|
|
42193
|
-
return
|
|
42196
|
+
return fullPathMatch;
|
|
42194
42197
|
}
|
|
42195
42198
|
var import_fast_levenshtein, levenshtein, cachedProjectFiles;
|
|
42196
42199
|
var init_paths = __esm({
|
|
@@ -42208,6 +42211,24 @@ var init_paths = __esm({
|
|
|
42208
42211
|
// src/core/checks/commands.ts
|
|
42209
42212
|
import * as fs4 from "node:fs";
|
|
42210
42213
|
import * as path4 from "node:path";
|
|
42214
|
+
function extractNpxPackage(cmd) {
|
|
42215
|
+
if (!/^npx\b/.test(cmd)) return null;
|
|
42216
|
+
const tokens = cmd.split(/\s+/).slice(1);
|
|
42217
|
+
for (let i2 = 0; i2 < tokens.length; i2++) {
|
|
42218
|
+
const t2 = tokens[i2];
|
|
42219
|
+
if (t2 === "-p" || t2 === "--package") {
|
|
42220
|
+
const v2 = tokens[i2 + 1];
|
|
42221
|
+
if (v2 && !v2.startsWith("-")) return v2;
|
|
42222
|
+
continue;
|
|
42223
|
+
}
|
|
42224
|
+
if (t2.startsWith("-p=") || t2.startsWith("--package=")) {
|
|
42225
|
+
return t2.slice(t2.indexOf("=") + 1) || null;
|
|
42226
|
+
}
|
|
42227
|
+
if (t2.startsWith("-")) continue;
|
|
42228
|
+
return t2;
|
|
42229
|
+
}
|
|
42230
|
+
return null;
|
|
42231
|
+
}
|
|
42211
42232
|
async function checkCommands(file2, projectRoot) {
|
|
42212
42233
|
const issues = [];
|
|
42213
42234
|
const pkgJson = loadPackageJson(projectRoot);
|
|
@@ -42246,10 +42267,9 @@ async function checkCommands(file2, projectRoot) {
|
|
|
42246
42267
|
}
|
|
42247
42268
|
continue;
|
|
42248
42269
|
}
|
|
42249
|
-
|
|
42250
|
-
|
|
42251
|
-
|
|
42252
|
-
if (pkgName.startsWith("-")) continue;
|
|
42270
|
+
if (/^npx\b/.test(cmd) && pkgJson) {
|
|
42271
|
+
const pkgName = extractNpxPackage(cmd);
|
|
42272
|
+
if (!pkgName) continue;
|
|
42253
42273
|
const allDeps = {
|
|
42254
42274
|
...pkgJson.dependencies,
|
|
42255
42275
|
...pkgJson.devDependencies,
|
|
@@ -42324,7 +42344,7 @@ async function checkCommands(file2, projectRoot) {
|
|
|
42324
42344
|
}
|
|
42325
42345
|
function loadMakefile(projectRoot) {
|
|
42326
42346
|
try {
|
|
42327
|
-
return fs4.readFileSync(path4.join(projectRoot, "Makefile"), "utf-8");
|
|
42347
|
+
return stripBom(fs4.readFileSync(path4.join(projectRoot, "Makefile"), "utf-8"));
|
|
42328
42348
|
} catch {
|
|
42329
42349
|
return null;
|
|
42330
42350
|
}
|
|
@@ -42333,14 +42353,13 @@ function hasMakeTarget(makefile, target) {
|
|
|
42333
42353
|
const pattern = new RegExp(`^${target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*:`, "m");
|
|
42334
42354
|
return pattern.test(makefile);
|
|
42335
42355
|
}
|
|
42336
|
-
var NPM_SCRIPT_PATTERN, MAKE_PATTERN
|
|
42356
|
+
var NPM_SCRIPT_PATTERN, MAKE_PATTERN;
|
|
42337
42357
|
var init_commands = __esm({
|
|
42338
42358
|
"src/core/checks/commands.ts"() {
|
|
42339
42359
|
"use strict";
|
|
42340
42360
|
init_fs();
|
|
42341
42361
|
NPM_SCRIPT_PATTERN = /^(?:npm\s+run|pnpm(?:\s+run)?|yarn(?:\s+run)?|bun(?:\s+run)?)\s+(\S+)/;
|
|
42342
42362
|
MAKE_PATTERN = /^make\s+(\S+)/;
|
|
42343
|
-
NPX_PATTERN = /^npx\s+(\S+)/;
|
|
42344
42363
|
}
|
|
42345
42364
|
});
|
|
42346
42365
|
|
|
@@ -42515,7 +42534,14 @@ function isAlwaysLoaded(file2) {
|
|
|
42515
42534
|
return !hasPathsFrontmatter(file2.content);
|
|
42516
42535
|
}
|
|
42517
42536
|
const basename4 = rel.split("/").pop() ?? "";
|
|
42518
|
-
|
|
42537
|
+
for (const name of ALWAYS_LOADED_NAMES) {
|
|
42538
|
+
if (name.includes("/")) {
|
|
42539
|
+
if (rel === name || rel.endsWith("/" + name)) return true;
|
|
42540
|
+
} else if (basename4 === name) {
|
|
42541
|
+
return true;
|
|
42542
|
+
}
|
|
42543
|
+
}
|
|
42544
|
+
return false;
|
|
42519
42545
|
}
|
|
42520
42546
|
function computeSectionCosts(file2) {
|
|
42521
42547
|
if (file2.sections.length === 0) return [];
|
|
@@ -42537,7 +42563,7 @@ function loadSettingsSources(projectRoot) {
|
|
|
42537
42563
|
for (const p2 of candidates) {
|
|
42538
42564
|
let content;
|
|
42539
42565
|
try {
|
|
42540
|
-
content = fs5.readFileSync(p2, "utf-8");
|
|
42566
|
+
content = stripBom(fs5.readFileSync(p2, "utf-8"));
|
|
42541
42567
|
} catch {
|
|
42542
42568
|
continue;
|
|
42543
42569
|
}
|
|
@@ -42553,9 +42579,17 @@ function canonicalizeCommand(backticked) {
|
|
|
42553
42579
|
const beforeFlags = backticked.trim().split(/\s+--?/, 1)[0];
|
|
42554
42580
|
return beforeFlags.replace(/\s+/g, " ");
|
|
42555
42581
|
}
|
|
42582
|
+
function buildCommandPattern(cmd) {
|
|
42583
|
+
const tokens = cmd.split(/\s+/).filter(Boolean);
|
|
42584
|
+
const escaped = tokens.map((t2) => t2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
42585
|
+
if (tokens.length === 1) {
|
|
42586
|
+
return new RegExp(`(?<![A-Za-z0-9_\\-])${escaped[0]}(?![A-Za-z0-9_\\-])`, "i");
|
|
42587
|
+
}
|
|
42588
|
+
const body = escaped.join("[\\s\\-_]+");
|
|
42589
|
+
return new RegExp(`(?<![A-Za-z0-9])${body}(?![A-Za-z0-9])`, "i");
|
|
42590
|
+
}
|
|
42556
42591
|
function commandIsEnforced(cmd, settings) {
|
|
42557
|
-
const
|
|
42558
|
-
const pattern = new RegExp(`\\b${escaped}\\b`, "i");
|
|
42592
|
+
const pattern = buildCommandPattern(cmd);
|
|
42559
42593
|
for (const s of settings) {
|
|
42560
42594
|
for (const entry of s.permissions?.deny ?? []) {
|
|
42561
42595
|
if (pattern.test(entry)) return true;
|
|
@@ -42633,13 +42667,14 @@ function checkAggregateTierTokens(files) {
|
|
|
42633
42667
|
suggestion: "Consider moving the largest files or their heaviest sections to on-demand tiers (skills, subagents, memory)."
|
|
42634
42668
|
};
|
|
42635
42669
|
}
|
|
42636
|
-
var
|
|
42670
|
+
var ALWAYS_LOADED_NAMES, TOP_SECTIONS_TO_REPORT, INVIOLABLE_WITH_COMMAND;
|
|
42637
42671
|
var init_tier_tokens = __esm({
|
|
42638
42672
|
"src/core/checks/tier-tokens.ts"() {
|
|
42639
42673
|
"use strict";
|
|
42640
42674
|
init_tokens();
|
|
42675
|
+
init_fs();
|
|
42641
42676
|
init_tokens2();
|
|
42642
|
-
|
|
42677
|
+
ALWAYS_LOADED_NAMES = [
|
|
42643
42678
|
"CLAUDE.md",
|
|
42644
42679
|
"CLAUDE.local.md",
|
|
42645
42680
|
"AGENTS.md",
|
|
@@ -42654,15 +42689,42 @@ var init_tier_tokens = __esm({
|
|
|
42654
42689
|
".rules",
|
|
42655
42690
|
".goosehints",
|
|
42656
42691
|
"replit.md",
|
|
42657
|
-
"copilot-instructions.md",
|
|
42658
|
-
"guidelines.md",
|
|
42659
|
-
"
|
|
42660
|
-
|
|
42692
|
+
".github/copilot-instructions.md",
|
|
42693
|
+
".junie/guidelines.md",
|
|
42694
|
+
".junie/AGENTS.md",
|
|
42695
|
+
".goose/instructions.md"
|
|
42696
|
+
];
|
|
42661
42697
|
TOP_SECTIONS_TO_REPORT = 3;
|
|
42662
42698
|
INVIOLABLE_WITH_COMMAND = /\b(NEVER|ALWAYS|DON'?T|DO NOT|MUST NOT)\b[^.!?`]{0,80}`([^`]+)`/i;
|
|
42663
42699
|
}
|
|
42664
42700
|
});
|
|
42665
42701
|
|
|
42702
|
+
// src/utils/similarity.ts
|
|
42703
|
+
function jaccardSimilarityFromSets(a, b2, opts = {}) {
|
|
42704
|
+
const bothEmptyIsIdentical = opts.bothEmptyIsIdentical ?? false;
|
|
42705
|
+
if (a.size === 0 && b2.size === 0) {
|
|
42706
|
+
return bothEmptyIsIdentical ? 1 : 0;
|
|
42707
|
+
}
|
|
42708
|
+
if (a.size === 0 || b2.size === 0) return 0;
|
|
42709
|
+
const [small, large] = a.size <= b2.size ? [a, b2] : [b2, a];
|
|
42710
|
+
let intersection2 = 0;
|
|
42711
|
+
for (const line of small) {
|
|
42712
|
+
if (large.has(line)) intersection2++;
|
|
42713
|
+
}
|
|
42714
|
+
const unionSize = a.size + b2.size - intersection2;
|
|
42715
|
+
return intersection2 / unionSize;
|
|
42716
|
+
}
|
|
42717
|
+
function toLineSet(text, minTokenLen) {
|
|
42718
|
+
return new Set(
|
|
42719
|
+
text.split("\n").map((l) => l.trim()).filter((l) => l.length > minTokenLen)
|
|
42720
|
+
);
|
|
42721
|
+
}
|
|
42722
|
+
var init_similarity = __esm({
|
|
42723
|
+
"src/utils/similarity.ts"() {
|
|
42724
|
+
"use strict";
|
|
42725
|
+
}
|
|
42726
|
+
});
|
|
42727
|
+
|
|
42666
42728
|
// src/core/checks/redundancy.ts
|
|
42667
42729
|
import * as path7 from "node:path";
|
|
42668
42730
|
function compilePatterns(allDeps) {
|
|
@@ -42685,6 +42747,20 @@ function compilePatterns(allDeps) {
|
|
|
42685
42747
|
}
|
|
42686
42748
|
return compiled;
|
|
42687
42749
|
}
|
|
42750
|
+
function getCompiledPatterns(projectRoot, allDeps) {
|
|
42751
|
+
const relevant = [];
|
|
42752
|
+
for (const pkg of Object.keys(PACKAGE_TECH_MAP)) {
|
|
42753
|
+
if (allDeps.has(pkg)) relevant.push(pkg);
|
|
42754
|
+
}
|
|
42755
|
+
relevant.sort();
|
|
42756
|
+
const key = `${projectRoot}\0${relevant.join(" ")}`;
|
|
42757
|
+
let compiled = compiledPatternsCache.get(key);
|
|
42758
|
+
if (!compiled) {
|
|
42759
|
+
compiled = compilePatterns(allDeps);
|
|
42760
|
+
compiledPatternsCache.set(key, compiled);
|
|
42761
|
+
}
|
|
42762
|
+
return compiled;
|
|
42763
|
+
}
|
|
42688
42764
|
async function checkRedundancy(file2, projectRoot) {
|
|
42689
42765
|
const issues = [];
|
|
42690
42766
|
const pkgJson = loadPackageJson(projectRoot);
|
|
@@ -42695,7 +42771,7 @@ async function checkRedundancy(file2, projectRoot) {
|
|
|
42695
42771
|
...Object.keys(pkgJson.peerDependencies || {}),
|
|
42696
42772
|
...Object.keys(pkgJson.optionalDependencies || {})
|
|
42697
42773
|
]);
|
|
42698
|
-
const compiledPatterns =
|
|
42774
|
+
const compiledPatterns = getCompiledPatterns(projectRoot, allDeps);
|
|
42699
42775
|
const lines2 = file2.content.split("\n");
|
|
42700
42776
|
for (let i2 = 0; i2 < lines2.length; i2++) {
|
|
42701
42777
|
const line = lines2[i2];
|
|
@@ -42746,10 +42822,14 @@ async function checkRedundancy(file2, projectRoot) {
|
|
|
42746
42822
|
}
|
|
42747
42823
|
function checkDuplicateContent(files) {
|
|
42748
42824
|
const issues = [];
|
|
42749
|
-
const
|
|
42825
|
+
const lineSets = files.map((f) => toLineSet(f.content, DUPLICATE_CONTENT_MIN_TOKEN_LEN));
|
|
42750
42826
|
for (let i2 = 0; i2 < files.length; i2++) {
|
|
42827
|
+
const a = lineSets[i2];
|
|
42828
|
+
if (a.size === 0) continue;
|
|
42751
42829
|
for (let j3 = i2 + 1; j3 < files.length; j3++) {
|
|
42752
|
-
const
|
|
42830
|
+
const b2 = lineSets[j3];
|
|
42831
|
+
if (b2.size === 0) continue;
|
|
42832
|
+
const overlap = jaccardSimilarityFromSets(a, b2);
|
|
42753
42833
|
if (overlap >= DUPLICATE_CONTENT_THRESHOLD) {
|
|
42754
42834
|
issues.push({
|
|
42755
42835
|
severity: "warning",
|
|
@@ -42764,29 +42844,15 @@ function checkDuplicateContent(files) {
|
|
|
42764
42844
|
}
|
|
42765
42845
|
return issues;
|
|
42766
42846
|
}
|
|
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
42847
|
function escapeRegex2(str) {
|
|
42783
42848
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
42784
42849
|
}
|
|
42785
|
-
var PACKAGE_TECH_MAP;
|
|
42850
|
+
var PACKAGE_TECH_MAP, compiledPatternsCache, DUPLICATE_CONTENT_THRESHOLD, DUPLICATE_CONTENT_MIN_TOKEN_LEN;
|
|
42786
42851
|
var init_redundancy = __esm({
|
|
42787
42852
|
"src/core/checks/redundancy.ts"() {
|
|
42788
42853
|
"use strict";
|
|
42789
42854
|
init_fs();
|
|
42855
|
+
init_similarity();
|
|
42790
42856
|
init_tokens();
|
|
42791
42857
|
PACKAGE_TECH_MAP = {
|
|
42792
42858
|
react: ["React", "react"],
|
|
@@ -42839,6 +42905,9 @@ var init_redundancy = __esm({
|
|
|
42839
42905
|
cypress: ["Cypress"],
|
|
42840
42906
|
puppeteer: ["Puppeteer"]
|
|
42841
42907
|
};
|
|
42908
|
+
compiledPatternsCache = /* @__PURE__ */ new Map();
|
|
42909
|
+
DUPLICATE_CONTENT_THRESHOLD = 0.6;
|
|
42910
|
+
DUPLICATE_CONTENT_MIN_TOKEN_LEN = 10;
|
|
42842
42911
|
}
|
|
42843
42912
|
});
|
|
42844
42913
|
|
|
@@ -43156,7 +43225,7 @@ var init_contradictions = __esm({
|
|
|
43156
43225
|
function parseFrontmatter(content) {
|
|
43157
43226
|
const lines = content.split("\n");
|
|
43158
43227
|
if (lines[0]?.trim() !== "---") {
|
|
43159
|
-
return { found: false, fields: {}, endLine: 0 };
|
|
43228
|
+
return { found: false, fields: {}, endLine: 0, unclosed: false };
|
|
43160
43229
|
}
|
|
43161
43230
|
const fields = {};
|
|
43162
43231
|
let endLine = 0;
|
|
@@ -43183,9 +43252,9 @@ function parseFrontmatter(content) {
|
|
|
43183
43252
|
}
|
|
43184
43253
|
}
|
|
43185
43254
|
if (endLine === 0) {
|
|
43186
|
-
return { found: true, fields, endLine: lines.length };
|
|
43255
|
+
return { found: true, fields, endLine: lines.length, unclosed: true };
|
|
43187
43256
|
}
|
|
43188
|
-
return { found: true, fields, endLine };
|
|
43257
|
+
return { found: true, fields, endLine, unclosed: false };
|
|
43189
43258
|
}
|
|
43190
43259
|
function isCursorMdc(file2) {
|
|
43191
43260
|
return file2.relativePath.endsWith(".mdc");
|
|
@@ -43196,6 +43265,23 @@ function isCopilotInstructions(file2) {
|
|
|
43196
43265
|
function isWindsurfRule(file2) {
|
|
43197
43266
|
return file2.relativePath.includes(".windsurf/rules/") && file2.relativePath.endsWith(".md");
|
|
43198
43267
|
}
|
|
43268
|
+
function hasUnbalancedBracketsOrQuotes(val) {
|
|
43269
|
+
let square = 0;
|
|
43270
|
+
let curly = 0;
|
|
43271
|
+
for (const ch of val) {
|
|
43272
|
+
if (ch === "[") square++;
|
|
43273
|
+
else if (ch === "]") square--;
|
|
43274
|
+
else if (ch === "{") curly++;
|
|
43275
|
+
else if (ch === "}") curly--;
|
|
43276
|
+
if (square < 0 || curly < 0) return true;
|
|
43277
|
+
}
|
|
43278
|
+
if (square !== 0 || curly !== 0) return true;
|
|
43279
|
+
const doubleQuotes = (val.match(/"/g) || []).length;
|
|
43280
|
+
const singleQuotes = (val.match(/'/g) || []).length;
|
|
43281
|
+
if (doubleQuotes % 2 !== 0) return true;
|
|
43282
|
+
if (singleQuotes % 2 !== 0) return true;
|
|
43283
|
+
return false;
|
|
43284
|
+
}
|
|
43199
43285
|
async function checkFrontmatter(file2, _projectRoot) {
|
|
43200
43286
|
const issues = [];
|
|
43201
43287
|
if (isCursorMdc(file2)) {
|
|
@@ -43207,9 +43293,23 @@ async function checkFrontmatter(file2, _projectRoot) {
|
|
|
43207
43293
|
}
|
|
43208
43294
|
return issues;
|
|
43209
43295
|
}
|
|
43296
|
+
function unclosedFrontmatterIssue() {
|
|
43297
|
+
return {
|
|
43298
|
+
severity: "error",
|
|
43299
|
+
check: "frontmatter",
|
|
43300
|
+
ruleId: "frontmatter/unclosed",
|
|
43301
|
+
line: 1,
|
|
43302
|
+
message: "Frontmatter opens with `---` but is never closed",
|
|
43303
|
+
suggestion: "Add a matching `---` line (with no leading whitespace) after the last frontmatter field"
|
|
43304
|
+
};
|
|
43305
|
+
}
|
|
43210
43306
|
function validateCursorMdc(file2) {
|
|
43211
43307
|
const issues = [];
|
|
43212
43308
|
const fm = parseFrontmatter(file2.content);
|
|
43309
|
+
if (fm.unclosed) {
|
|
43310
|
+
issues.push(unclosedFrontmatterIssue());
|
|
43311
|
+
return issues;
|
|
43312
|
+
}
|
|
43213
43313
|
if (!fm.found) {
|
|
43214
43314
|
issues.push({
|
|
43215
43315
|
severity: "warning",
|
|
@@ -43256,13 +43356,13 @@ function validateCursorMdc(file2) {
|
|
|
43256
43356
|
}
|
|
43257
43357
|
if ("globs" in fm.fields) {
|
|
43258
43358
|
const val = fm.fields["globs"];
|
|
43259
|
-
if (val &&
|
|
43359
|
+
if (val && hasUnbalancedBracketsOrQuotes(val)) {
|
|
43260
43360
|
issues.push({
|
|
43261
43361
|
severity: "warning",
|
|
43262
43362
|
check: "frontmatter",
|
|
43263
43363
|
ruleId: "frontmatter/invalid-value",
|
|
43264
43364
|
line: 1,
|
|
43265
|
-
message: `Possibly
|
|
43365
|
+
message: `Possibly malformed globs value: "${val}"`,
|
|
43266
43366
|
suggestion: 'globs should be a glob pattern like "src/**/*.ts" or an array like ["*.ts", "*.tsx"]'
|
|
43267
43367
|
});
|
|
43268
43368
|
}
|
|
@@ -43272,6 +43372,10 @@ function validateCursorMdc(file2) {
|
|
|
43272
43372
|
function validateCopilotInstructions(file2) {
|
|
43273
43373
|
const issues = [];
|
|
43274
43374
|
const fm = parseFrontmatter(file2.content);
|
|
43375
|
+
if (fm.unclosed) {
|
|
43376
|
+
issues.push(unclosedFrontmatterIssue());
|
|
43377
|
+
return issues;
|
|
43378
|
+
}
|
|
43275
43379
|
if (!fm.found) {
|
|
43276
43380
|
issues.push({
|
|
43277
43381
|
severity: "info",
|
|
@@ -43298,6 +43402,10 @@ function validateCopilotInstructions(file2) {
|
|
|
43298
43402
|
function validateWindsurfRule(file2) {
|
|
43299
43403
|
const issues = [];
|
|
43300
43404
|
const fm = parseFrontmatter(file2.content);
|
|
43405
|
+
if (fm.unclosed) {
|
|
43406
|
+
issues.push(unclosedFrontmatterIssue());
|
|
43407
|
+
return issues;
|
|
43408
|
+
}
|
|
43301
43409
|
if (!fm.found) {
|
|
43302
43410
|
issues.push({
|
|
43303
43411
|
severity: "info",
|
|
@@ -43469,6 +43577,10 @@ function isEnvVarRef(value) {
|
|
|
43469
43577
|
function isKnownApiKey(value) {
|
|
43470
43578
|
return API_KEY_PATTERNS.some((p2) => p2.test(value));
|
|
43471
43579
|
}
|
|
43580
|
+
function nameSuggestsSecret(name) {
|
|
43581
|
+
const upper = name.toUpperCase();
|
|
43582
|
+
return SECRET_NAME_KEYWORDS.some((kw) => upper.includes(kw));
|
|
43583
|
+
}
|
|
43472
43584
|
function isHighEntropySecret(value) {
|
|
43473
43585
|
if (isEnvVarRef(value)) return false;
|
|
43474
43586
|
return HIGH_ENTROPY_PATTERN.test(value);
|
|
@@ -43517,8 +43629,9 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43517
43629
|
}
|
|
43518
43630
|
}
|
|
43519
43631
|
if (server2.env) {
|
|
43520
|
-
for (const envValue of Object.
|
|
43521
|
-
|
|
43632
|
+
for (const [envName, envValue] of Object.entries(server2.env)) {
|
|
43633
|
+
const isSecret = isKnownApiKey(envValue) || nameSuggestsSecret(envName) && isHighEntropySecret(envValue);
|
|
43634
|
+
if (!isEnvVarRef(envValue) && isSecret) {
|
|
43522
43635
|
const envVar = deriveEnvVarName(server2.name, "API_KEY");
|
|
43523
43636
|
issues.push({
|
|
43524
43637
|
severity: "error",
|
|
@@ -43565,7 +43678,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43565
43678
|
}
|
|
43566
43679
|
return issues;
|
|
43567
43680
|
}
|
|
43568
|
-
var API_KEY_PATTERNS, ENV_VAR_REF, HIGH_ENTROPY_PATTERN, URL_SECRET_PARAMS;
|
|
43681
|
+
var API_KEY_PATTERNS, ENV_VAR_REF, HIGH_ENTROPY_PATTERN, URL_SECRET_PARAMS, SECRET_NAME_KEYWORDS;
|
|
43569
43682
|
var init_security = __esm({
|
|
43570
43683
|
"src/core/checks/mcp/security.ts"() {
|
|
43571
43684
|
"use strict";
|
|
@@ -43594,6 +43707,22 @@ var init_security = __esm({
|
|
|
43594
43707
|
ENV_VAR_REF = /\$\{[^}]+\}/;
|
|
43595
43708
|
HIGH_ENTROPY_PATTERN = /^[A-Za-z0-9+/=_-]{21,}$/;
|
|
43596
43709
|
URL_SECRET_PARAMS = /[?&](key|token|api_key|apikey|secret|password|access_token)=/i;
|
|
43710
|
+
SECRET_NAME_KEYWORDS = [
|
|
43711
|
+
"KEY",
|
|
43712
|
+
"TOKEN",
|
|
43713
|
+
"SECRET",
|
|
43714
|
+
"PASSWORD",
|
|
43715
|
+
"PASSWD",
|
|
43716
|
+
"PASS",
|
|
43717
|
+
"AUTH",
|
|
43718
|
+
"CREDENTIAL",
|
|
43719
|
+
"CREDENTIALS",
|
|
43720
|
+
"APIKEY",
|
|
43721
|
+
"PRIVATE",
|
|
43722
|
+
"SIGNING",
|
|
43723
|
+
"SESSION",
|
|
43724
|
+
"COOKIE"
|
|
43725
|
+
];
|
|
43597
43726
|
}
|
|
43598
43727
|
});
|
|
43599
43728
|
|
|
@@ -44360,6 +44489,12 @@ function extractPaths(content) {
|
|
|
44360
44489
|
paths.push(p2);
|
|
44361
44490
|
}
|
|
44362
44491
|
}
|
|
44492
|
+
for (const match of content.matchAll(BARE_FILE_PATH)) {
|
|
44493
|
+
const p2 = match[1].replace(/[)}\]]+$/, "");
|
|
44494
|
+
if (p2.length > 2 && !p2.startsWith("http")) {
|
|
44495
|
+
paths.push(p2);
|
|
44496
|
+
}
|
|
44497
|
+
}
|
|
44363
44498
|
return [...new Set(paths)];
|
|
44364
44499
|
}
|
|
44365
44500
|
function parseFrontmatter2(content) {
|
|
@@ -44389,7 +44524,7 @@ function parseFrontmatter2(content) {
|
|
|
44389
44524
|
};
|
|
44390
44525
|
}
|
|
44391
44526
|
async function parseMemoryFile(filePath, projectDir) {
|
|
44392
|
-
const content = await readFile(filePath, "utf-8");
|
|
44527
|
+
const content = stripBom(await readFile(filePath, "utf-8"));
|
|
44393
44528
|
const { name, description, type, body } = parseFrontmatter2(content);
|
|
44394
44529
|
const referencedPaths = extractPaths(body);
|
|
44395
44530
|
return {
|
|
@@ -44402,11 +44537,13 @@ async function parseMemoryFile(filePath, projectDir) {
|
|
|
44402
44537
|
referencedPaths
|
|
44403
44538
|
};
|
|
44404
44539
|
}
|
|
44405
|
-
var PATH_PATTERN2;
|
|
44540
|
+
var PATH_PATTERN2, BARE_FILE_PATH;
|
|
44406
44541
|
var init_session_parser = __esm({
|
|
44407
44542
|
"src/core/session-parser.ts"() {
|
|
44408
44543
|
"use strict";
|
|
44544
|
+
init_fs();
|
|
44409
44545
|
PATH_PATTERN2 = /(?:^|\s|['"`(])([.~/][^\s'"`),;:!?]+)/g;
|
|
44546
|
+
BARE_FILE_PATH = /(?:^|[\s`"'(])([\w][\w-]*(?:\/[\w.-]+)+\.[a-zA-Z0-9]{1,8})\b/g;
|
|
44410
44547
|
}
|
|
44411
44548
|
});
|
|
44412
44549
|
|
|
@@ -44438,30 +44575,46 @@ async function parseJsonlFiltered(filePath, filter) {
|
|
|
44438
44575
|
}
|
|
44439
44576
|
return results;
|
|
44440
44577
|
}
|
|
44441
|
-
|
|
44442
|
-
const
|
|
44443
|
-
|
|
44444
|
-
if (
|
|
44578
|
+
function pickField(entry, fields) {
|
|
44579
|
+
for (const f of fields) {
|
|
44580
|
+
const v2 = entry[f];
|
|
44581
|
+
if (typeof v2 === "string" && v2.length > 0) return v2;
|
|
44582
|
+
}
|
|
44583
|
+
return "";
|
|
44584
|
+
}
|
|
44585
|
+
async function readJsonlHistory(opts) {
|
|
44586
|
+
return parseJsonlFiltered(opts.historyPath, (parsed) => {
|
|
44587
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
44588
|
+
const entry = parsed;
|
|
44589
|
+
const display = pickField(entry, opts.displayFields);
|
|
44590
|
+
if (!display) return null;
|
|
44591
|
+
const project = pickField(entry, opts.projectFields);
|
|
44592
|
+
if (opts.requireProject && !project) return null;
|
|
44445
44593
|
return {
|
|
44446
|
-
display
|
|
44447
|
-
timestamp: entry.timestamp
|
|
44448
|
-
project:
|
|
44449
|
-
sessionId: entry.sessionId
|
|
44450
|
-
provider:
|
|
44594
|
+
display,
|
|
44595
|
+
timestamp: typeof entry.timestamp === "number" ? entry.timestamp : 0,
|
|
44596
|
+
project: project.replace(/\\/g, "/"),
|
|
44597
|
+
sessionId: typeof entry.sessionId === "string" ? entry.sessionId : "",
|
|
44598
|
+
provider: opts.provider
|
|
44451
44599
|
};
|
|
44452
44600
|
});
|
|
44453
44601
|
}
|
|
44602
|
+
async function readClaudeHistory() {
|
|
44603
|
+
return readJsonlHistory({
|
|
44604
|
+
historyPath: join5(home, ".claude", "history.jsonl"),
|
|
44605
|
+
provider: "claude-code",
|
|
44606
|
+
displayFields: ["display"],
|
|
44607
|
+
projectFields: ["project"],
|
|
44608
|
+
requireProject: true
|
|
44609
|
+
});
|
|
44610
|
+
}
|
|
44454
44611
|
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
|
-
};
|
|
44612
|
+
return readJsonlHistory({
|
|
44613
|
+
historyPath: join5(home, ".codex", "history.jsonl"),
|
|
44614
|
+
provider: "codex-cli",
|
|
44615
|
+
displayFields: ["display", "command"],
|
|
44616
|
+
projectFields: ["project", "cwd"],
|
|
44617
|
+
requireProject: false
|
|
44465
44618
|
});
|
|
44466
44619
|
}
|
|
44467
44620
|
async function readClaudeMemories() {
|
|
@@ -44526,47 +44679,29 @@ async function detectSiblings(projectRoot) {
|
|
|
44526
44679
|
if (orgMatch) currentOrg = orgMatch[1];
|
|
44527
44680
|
} catch {
|
|
44528
44681
|
}
|
|
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
|
-
);
|
|
44682
|
+
const results = await Promise.all(candidates.map((c3) => resolveSibling(c3)));
|
|
44547
44683
|
if (currentOrg) {
|
|
44548
|
-
return results.filter((s) => s.gitOrg === currentOrg);
|
|
44684
|
+
return results.filter((s) => !s.gitOrg || s.gitOrg === currentOrg);
|
|
44549
44685
|
}
|
|
44550
44686
|
return results;
|
|
44551
44687
|
}
|
|
44552
|
-
|
|
44553
|
-
|
|
44554
|
-
|
|
44555
|
-
|
|
44556
|
-
|
|
44557
|
-
|
|
44558
|
-
|
|
44559
|
-
|
|
44560
|
-
|
|
44561
|
-
|
|
44562
|
-
|
|
44563
|
-
|
|
44564
|
-
|
|
44565
|
-
|
|
44566
|
-
|
|
44567
|
-
|
|
44568
|
-
|
|
44569
|
-
return siblings;
|
|
44688
|
+
return Promise.all(candidates.map((c3) => resolveSibling(c3)));
|
|
44689
|
+
}
|
|
44690
|
+
async function resolveSibling(c3) {
|
|
44691
|
+
const sibling = { path: c3.entryPath.replace(/\\/g, "/"), name: c3.name };
|
|
44692
|
+
if (!existsSync(join5(c3.fullPath, ".git"))) return sibling;
|
|
44693
|
+
try {
|
|
44694
|
+
const git = simpleGit(c3.fullPath);
|
|
44695
|
+
const remotes = await git.getRemotes(true);
|
|
44696
|
+
const origin = remotes.find((r2) => r2.name === "origin");
|
|
44697
|
+
if (origin?.refs?.fetch) {
|
|
44698
|
+
sibling.gitRemoteUrl = origin.refs.fetch;
|
|
44699
|
+
const orgMatch = origin.refs.fetch.match(/github\.com[:/]([^/]+)\//);
|
|
44700
|
+
if (orgMatch) sibling.gitOrg = orgMatch[1];
|
|
44701
|
+
}
|
|
44702
|
+
} catch {
|
|
44703
|
+
}
|
|
44704
|
+
return sibling;
|
|
44570
44705
|
}
|
|
44571
44706
|
async function scanSessionData(projectRoot) {
|
|
44572
44707
|
const providers = detectProviders();
|
|
@@ -44686,48 +44821,57 @@ var init_missing_secret = __esm({
|
|
|
44686
44821
|
});
|
|
44687
44822
|
|
|
44688
44823
|
// src/core/checks/session/diverged-file.ts
|
|
44689
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
44824
|
+
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
44690
44825
|
import { join as join6 } from "node:path";
|
|
44691
44826
|
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++;
|
|
44827
|
+
async function loadLineSet(absPath) {
|
|
44828
|
+
let mtimeMs;
|
|
44829
|
+
let size;
|
|
44830
|
+
try {
|
|
44831
|
+
const stats = await stat2(absPath);
|
|
44832
|
+
mtimeMs = stats.mtimeMs;
|
|
44833
|
+
size = stats.size;
|
|
44834
|
+
} catch {
|
|
44835
|
+
return null;
|
|
44704
44836
|
}
|
|
44705
|
-
const
|
|
44706
|
-
|
|
44837
|
+
const cached2 = lineSetCache.get(absPath);
|
|
44838
|
+
if (cached2 && cached2.mtimeMs === mtimeMs && cached2.size === size) {
|
|
44839
|
+
lineSetCache.delete(absPath);
|
|
44840
|
+
lineSetCache.set(absPath, cached2);
|
|
44841
|
+
return cached2.lineSet;
|
|
44842
|
+
}
|
|
44843
|
+
let content;
|
|
44844
|
+
try {
|
|
44845
|
+
content = stripBom(await readFile2(absPath, "utf-8"));
|
|
44846
|
+
} catch {
|
|
44847
|
+
return null;
|
|
44848
|
+
}
|
|
44849
|
+
const lineSet = toLineSet(content, MIN_TOKEN_LEN);
|
|
44850
|
+
lineSetCache.set(absPath, { mtimeMs, size, lineSet });
|
|
44851
|
+
if (lineSetCache.size > CACHE_MAX_ENTRIES) {
|
|
44852
|
+
const oldest = lineSetCache.keys().next().value;
|
|
44853
|
+
if (oldest !== void 0) lineSetCache.delete(oldest);
|
|
44854
|
+
}
|
|
44855
|
+
return lineSet;
|
|
44707
44856
|
}
|
|
44708
44857
|
async function checkDivergedFile(ctx) {
|
|
44709
44858
|
const issues = [];
|
|
44710
44859
|
for (const fileName of CANONICAL_FILES) {
|
|
44711
44860
|
const currentPath = join6(ctx.currentProject, fileName);
|
|
44712
44861
|
if (!existsSync2(currentPath)) continue;
|
|
44713
|
-
|
|
44714
|
-
|
|
44715
|
-
currentContent = await readFile2(currentPath, "utf-8");
|
|
44716
|
-
} catch {
|
|
44717
|
-
continue;
|
|
44718
|
-
}
|
|
44862
|
+
const currentLineSet = await loadLineSet(currentPath);
|
|
44863
|
+
if (!currentLineSet) continue;
|
|
44719
44864
|
const diverged = [];
|
|
44720
44865
|
for (const sib of ctx.siblings) {
|
|
44721
44866
|
const sibPath = join6(sib.path, fileName);
|
|
44722
44867
|
if (!existsSync2(sibPath)) continue;
|
|
44723
|
-
|
|
44724
|
-
|
|
44725
|
-
|
|
44726
|
-
|
|
44727
|
-
|
|
44728
|
-
|
|
44729
|
-
|
|
44730
|
-
continue;
|
|
44868
|
+
const sibLineSet = await loadLineSet(sibPath);
|
|
44869
|
+
if (!sibLineSet) continue;
|
|
44870
|
+
const overlap = jaccardSimilarityFromSets(currentLineSet, sibLineSet, {
|
|
44871
|
+
bothEmptyIsIdentical: true
|
|
44872
|
+
});
|
|
44873
|
+
if (overlap >= 0.2 && overlap < 0.9) {
|
|
44874
|
+
diverged.push({ sibling: sib.name, overlap: Math.round(overlap * 100) });
|
|
44731
44875
|
}
|
|
44732
44876
|
}
|
|
44733
44877
|
if (diverged.length > 0) {
|
|
@@ -44745,10 +44889,12 @@ async function checkDivergedFile(ctx) {
|
|
|
44745
44889
|
}
|
|
44746
44890
|
return issues;
|
|
44747
44891
|
}
|
|
44748
|
-
var CANONICAL_FILES;
|
|
44892
|
+
var CANONICAL_FILES, MIN_TOKEN_LEN, CACHE_MAX_ENTRIES, lineSetCache;
|
|
44749
44893
|
var init_diverged_file = __esm({
|
|
44750
44894
|
"src/core/checks/session/diverged-file.ts"() {
|
|
44751
44895
|
"use strict";
|
|
44896
|
+
init_similarity();
|
|
44897
|
+
init_fs();
|
|
44752
44898
|
CANONICAL_FILES = [
|
|
44753
44899
|
"release.sh",
|
|
44754
44900
|
".github/workflows/ci.yml",
|
|
@@ -44759,6 +44905,9 @@ var init_diverged_file = __esm({
|
|
|
44759
44905
|
"tsconfig.json",
|
|
44760
44906
|
".gitignore"
|
|
44761
44907
|
];
|
|
44908
|
+
MIN_TOKEN_LEN = 3;
|
|
44909
|
+
CACHE_MAX_ENTRIES = 256;
|
|
44910
|
+
lineSetCache = /* @__PURE__ */ new Map();
|
|
44762
44911
|
}
|
|
44763
44912
|
});
|
|
44764
44913
|
|
|
@@ -44866,24 +45015,10 @@ var init_stale_memory = __esm({
|
|
|
44866
45015
|
});
|
|
44867
45016
|
|
|
44868
45017
|
// 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
45018
|
async function checkDuplicateMemory(ctx) {
|
|
44885
45019
|
const issues = [];
|
|
44886
45020
|
const reported = /* @__PURE__ */ new Set();
|
|
45021
|
+
const lineSets = ctx.memories.map((m) => toLineSet(m.content, MIN_TOKEN_LEN2));
|
|
44887
45022
|
for (let i2 = 0; i2 < ctx.memories.length; i2++) {
|
|
44888
45023
|
for (let j3 = i2 + 1; j3 < ctx.memories.length; j3++) {
|
|
44889
45024
|
const a = ctx.memories[i2];
|
|
@@ -44893,7 +45028,7 @@ async function checkDuplicateMemory(ctx) {
|
|
|
44893
45028
|
const bIsCurrent = projectDirMatchesPath(b2.projectDir, ctx.currentProject);
|
|
44894
45029
|
if (!aIsCurrent && !bIsCurrent) continue;
|
|
44895
45030
|
if (a.content.length < 50 || b2.content.length < 50) continue;
|
|
44896
|
-
const overlap =
|
|
45031
|
+
const overlap = jaccardSimilarityFromSets(lineSets[i2], lineSets[j3]);
|
|
44897
45032
|
if (overlap < 0.6) continue;
|
|
44898
45033
|
const pairKey = [a.filePath, b2.filePath].sort().join("::");
|
|
44899
45034
|
if (reported.has(pairKey)) continue;
|
|
@@ -44915,10 +45050,13 @@ async function checkDuplicateMemory(ctx) {
|
|
|
44915
45050
|
}
|
|
44916
45051
|
return issues;
|
|
44917
45052
|
}
|
|
45053
|
+
var MIN_TOKEN_LEN2;
|
|
44918
45054
|
var init_duplicate_memory = __esm({
|
|
44919
45055
|
"src/core/checks/session/duplicate-memory.ts"() {
|
|
44920
45056
|
"use strict";
|
|
44921
45057
|
init_session_parser();
|
|
45058
|
+
init_similarity();
|
|
45059
|
+
MIN_TOKEN_LEN2 = 5;
|
|
44922
45060
|
}
|
|
44923
45061
|
});
|
|
44924
45062
|
|
|
@@ -45013,24 +45151,23 @@ var init_loop_detection = __esm({
|
|
|
45013
45151
|
});
|
|
45014
45152
|
|
|
45015
45153
|
// src/core/checks/session/memory-index-overflow.ts
|
|
45016
|
-
import { readFile as readFile3
|
|
45154
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
45017
45155
|
import { join as join8 } from "node:path";
|
|
45018
45156
|
async function checkMemoryIndexOverflow(ctx) {
|
|
45019
45157
|
const home2 = process.env.HOME || process.env.USERPROFILE || "";
|
|
45020
45158
|
if (!home2) return [];
|
|
45021
45159
|
const encoded = encodeProjectDir(ctx.currentProject);
|
|
45022
45160
|
const memoryFile = join8(home2, ".claude", "projects", encoded, "memory", "MEMORY.md");
|
|
45023
|
-
let
|
|
45161
|
+
let content;
|
|
45024
45162
|
try {
|
|
45025
|
-
|
|
45163
|
+
content = stripBom(await readFile3(memoryFile, "utf-8"));
|
|
45026
45164
|
} catch {
|
|
45027
45165
|
return [];
|
|
45028
45166
|
}
|
|
45029
|
-
const content = await readFile3(memoryFile, "utf-8").catch(() => "");
|
|
45030
45167
|
if (!content) return [];
|
|
45031
45168
|
const lines = content.split("\n");
|
|
45032
45169
|
const lineCount = lines.length;
|
|
45033
|
-
const byteSize =
|
|
45170
|
+
const byteSize = Buffer.byteLength(content, "utf8");
|
|
45034
45171
|
const issues = [];
|
|
45035
45172
|
if (lineCount > MAX_LINES) {
|
|
45036
45173
|
const excess = lineCount - MAX_LINES;
|
|
@@ -45063,6 +45200,7 @@ var init_memory_index_overflow = __esm({
|
|
|
45063
45200
|
"src/core/checks/session/memory-index-overflow.ts"() {
|
|
45064
45201
|
"use strict";
|
|
45065
45202
|
init_session_parser();
|
|
45203
|
+
init_fs();
|
|
45066
45204
|
MAX_LINES = 200;
|
|
45067
45205
|
MAX_BYTES = 25 * 1024;
|
|
45068
45206
|
}
|
|
@@ -45089,7 +45227,7 @@ async function findReleaseWorkflows(projectRoot) {
|
|
|
45089
45227
|
continue;
|
|
45090
45228
|
}
|
|
45091
45229
|
try {
|
|
45092
|
-
const content = await readFile4(join9(workflowDir, f), "utf-8");
|
|
45230
|
+
const content = stripBom(await readFile4(join9(workflowDir, f), "utf-8"));
|
|
45093
45231
|
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
45094
45232
|
if (nameMatch && RELEASE_FILENAME_PATTERNS.some((p2) => p2.test(nameMatch[1]))) {
|
|
45095
45233
|
releaseWorkflows.push(f);
|
|
@@ -45126,6 +45264,7 @@ var RELEASE_FILENAME_PATTERNS, RELEASE_DOC_PATTERNS;
|
|
|
45126
45264
|
var init_ci_coverage = __esm({
|
|
45127
45265
|
"src/core/checks/ci-coverage.ts"() {
|
|
45128
45266
|
"use strict";
|
|
45267
|
+
init_fs();
|
|
45129
45268
|
RELEASE_FILENAME_PATTERNS = [/release/i, /deploy/i, /publish/i, /\bcd\b/i];
|
|
45130
45269
|
RELEASE_DOC_PATTERNS = [
|
|
45131
45270
|
/release\s+(process|workflow|steps|via|by|using)/i,
|
|
@@ -45160,7 +45299,7 @@ async function findSecretUsages(projectRoot) {
|
|
|
45160
45299
|
if (!(f.endsWith(".yml") || f.endsWith(".yaml"))) continue;
|
|
45161
45300
|
let content;
|
|
45162
45301
|
try {
|
|
45163
|
-
content = await readFile5(join10(workflowDir, f), "utf-8");
|
|
45302
|
+
content = stripBom(await readFile5(join10(workflowDir, f), "utf-8"));
|
|
45164
45303
|
} catch {
|
|
45165
45304
|
continue;
|
|
45166
45305
|
}
|
|
@@ -45177,7 +45316,7 @@ async function findSecretUsages(projectRoot) {
|
|
|
45177
45316
|
}
|
|
45178
45317
|
function contextMentionsSecret(files, secretName) {
|
|
45179
45318
|
const escaped = secretName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45180
|
-
const pattern = new RegExp(`\\b${escaped.replace(/_/g, "[_
|
|
45319
|
+
const pattern = new RegExp(`\\b${escaped.replace(/_/g, "[ _-]")}\\b`, "i");
|
|
45181
45320
|
return files.some((f) => pattern.test(f.content));
|
|
45182
45321
|
}
|
|
45183
45322
|
async function checkCiSecrets(files, projectRoot) {
|
|
@@ -45201,6 +45340,7 @@ var BUILTIN_SECRETS, SECRETS_REGEX;
|
|
|
45201
45340
|
var init_ci_secrets = __esm({
|
|
45202
45341
|
"src/core/checks/ci-secrets.ts"() {
|
|
45203
45342
|
"use strict";
|
|
45343
|
+
init_fs();
|
|
45204
45344
|
BUILTIN_SECRETS = /* @__PURE__ */ new Set([
|
|
45205
45345
|
"GITHUB_TOKEN",
|
|
45206
45346
|
"ACTIONS_RUNTIME_TOKEN",
|
|
@@ -45221,7 +45361,7 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
45221
45361
|
import { resolve as resolve8, dirname as dirname3 } from "node:path";
|
|
45222
45362
|
import { fileURLToPath } from "node:url";
|
|
45223
45363
|
function loadVersion() {
|
|
45224
|
-
if (true) return "0.9.
|
|
45364
|
+
if (true) return "0.9.20";
|
|
45225
45365
|
const __dir = dirname3(fileURLToPath(import.meta.url));
|
|
45226
45366
|
const pkgPath = resolve8(__dir, "../package.json");
|
|
45227
45367
|
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
@@ -45245,6 +45385,11 @@ function hasMcphChecks(checks) {
|
|
|
45245
45385
|
function hasSessionChecks(checks) {
|
|
45246
45386
|
return checks.some((c3) => c3.startsWith("session-"));
|
|
45247
45387
|
}
|
|
45388
|
+
function deriveChecksToRun(activeChecks, prefix, enabled, allChecks) {
|
|
45389
|
+
const filtered = activeChecks.filter((c3) => c3.startsWith(prefix));
|
|
45390
|
+
if (filtered.length > 0) return filtered;
|
|
45391
|
+
return enabled ? [...allChecks] : [];
|
|
45392
|
+
}
|
|
45248
45393
|
async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
45249
45394
|
const fileResults = [];
|
|
45250
45395
|
const shouldRunContextChecks = !options.mcpOnly && !options.mcphOnly && !options.sessionOnly;
|
|
@@ -45325,8 +45470,12 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45325
45470
|
);
|
|
45326
45471
|
mcpConfigs.push(...globalConfigs);
|
|
45327
45472
|
}
|
|
45328
|
-
const
|
|
45329
|
-
|
|
45473
|
+
const mcpChecksToRun = deriveChecksToRun(
|
|
45474
|
+
activeChecks,
|
|
45475
|
+
"mcp-",
|
|
45476
|
+
Boolean(options.mcp || options.mcpGlobal || options.mcpOnly),
|
|
45477
|
+
ALL_MCP_CHECKS
|
|
45478
|
+
);
|
|
45330
45479
|
for (const config2 of mcpConfigs) {
|
|
45331
45480
|
const checkPromises = [];
|
|
45332
45481
|
if (mcpChecksToRun.includes("mcp-schema"))
|
|
@@ -45372,7 +45521,7 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45372
45521
|
const projectMcphFiles = await scanForMcphConfigs(projectRoot);
|
|
45373
45522
|
const mcphConfigs = await Promise.all(
|
|
45374
45523
|
projectMcphFiles.map(
|
|
45375
|
-
(f) =>
|
|
45524
|
+
(f) => parseMcphConfig(
|
|
45376
45525
|
f,
|
|
45377
45526
|
projectRoot,
|
|
45378
45527
|
f.relativePath.endsWith(".mcph.local.json") ? "project-local" : "project"
|
|
@@ -45382,12 +45531,16 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45382
45531
|
if (options.mcphGlobal) {
|
|
45383
45532
|
const globalMcphFiles = await scanGlobalMcphConfigs();
|
|
45384
45533
|
const globalMcphConfigs = await Promise.all(
|
|
45385
|
-
globalMcphFiles.map((f) =>
|
|
45534
|
+
globalMcphFiles.map((f) => parseMcphConfig(f, projectRoot, "global"))
|
|
45386
45535
|
);
|
|
45387
45536
|
mcphConfigs.push(...globalMcphConfigs);
|
|
45388
45537
|
}
|
|
45389
|
-
const
|
|
45390
|
-
|
|
45538
|
+
const mcphChecksToRun = deriveChecksToRun(
|
|
45539
|
+
activeChecks,
|
|
45540
|
+
"mcph-",
|
|
45541
|
+
Boolean(options.mcph || options.mcphGlobal || options.mcphOnly),
|
|
45542
|
+
ALL_MCPH_CHECKS
|
|
45543
|
+
);
|
|
45391
45544
|
for (const config2 of mcphConfigs) {
|
|
45392
45545
|
const checkPromises = [];
|
|
45393
45546
|
if (mcphChecksToRun.includes("mcph-token-security")) {
|
|
@@ -45411,14 +45564,16 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45411
45564
|
}
|
|
45412
45565
|
const results = await Promise.all(checkPromises);
|
|
45413
45566
|
const issues = results.flat();
|
|
45414
|
-
|
|
45415
|
-
|
|
45416
|
-
|
|
45417
|
-
|
|
45418
|
-
|
|
45419
|
-
|
|
45420
|
-
|
|
45421
|
-
|
|
45567
|
+
if (mcphChecksToRun.includes("mcph-schema-conformance")) {
|
|
45568
|
+
for (const err of config2.parseErrors) {
|
|
45569
|
+
issues.push({
|
|
45570
|
+
severity: "error",
|
|
45571
|
+
check: "mcph-schema-conformance",
|
|
45572
|
+
ruleId: "mcph-config/parse-error",
|
|
45573
|
+
line: 1,
|
|
45574
|
+
message: err
|
|
45575
|
+
});
|
|
45576
|
+
}
|
|
45422
45577
|
}
|
|
45423
45578
|
const lines = config2.content.split("\n").length;
|
|
45424
45579
|
fileResults.push({
|
|
@@ -45454,15 +45609,13 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45454
45609
|
sessionPromises.push(checkMemoryIndexOverflow(sessionCtx));
|
|
45455
45610
|
const sessionResults = await Promise.all(sessionPromises);
|
|
45456
45611
|
const sessionIssues = sessionResults.flat();
|
|
45457
|
-
|
|
45458
|
-
|
|
45459
|
-
|
|
45460
|
-
|
|
45461
|
-
|
|
45462
|
-
|
|
45463
|
-
|
|
45464
|
-
});
|
|
45465
|
-
}
|
|
45612
|
+
fileResults.push({
|
|
45613
|
+
path: "~/.claude/ (session audit)",
|
|
45614
|
+
isSymlink: false,
|
|
45615
|
+
tokens: 0,
|
|
45616
|
+
lines: 0,
|
|
45617
|
+
issues: sessionIssues
|
|
45618
|
+
});
|
|
45466
45619
|
}
|
|
45467
45620
|
}
|
|
45468
45621
|
let estimatedWaste = 0;
|
|
@@ -46219,11 +46372,22 @@ var init_fixer = __esm({
|
|
|
46219
46372
|
|
|
46220
46373
|
// src/mcp/server.ts
|
|
46221
46374
|
var server_exports = {};
|
|
46375
|
+
__export(server_exports, {
|
|
46376
|
+
server: () => server,
|
|
46377
|
+
startServer: () => startServer
|
|
46378
|
+
});
|
|
46222
46379
|
import * as path10 from "node:path";
|
|
46380
|
+
function describeDisallowed(rawPath) {
|
|
46381
|
+
const m = rawPath.match(PATH_DISALLOWED);
|
|
46382
|
+
if (!m) return "unknown";
|
|
46383
|
+
return JSON.stringify(m[0]);
|
|
46384
|
+
}
|
|
46223
46385
|
function validateProjectPath(rawPath) {
|
|
46224
46386
|
if (!rawPath) return process.cwd();
|
|
46225
46387
|
if (PATH_DISALLOWED.test(rawPath)) {
|
|
46226
|
-
throw new Error(
|
|
46388
|
+
throw new Error(
|
|
46389
|
+
`projectPath contains disallowed character ${describeDisallowed(rawPath)} (control chars and shell metacharacters are rejected)`
|
|
46390
|
+
);
|
|
46227
46391
|
}
|
|
46228
46392
|
const resolved = path10.resolve(rawPath);
|
|
46229
46393
|
if (!isDirectory(resolved)) {
|
|
@@ -46233,12 +46397,28 @@ function validateProjectPath(rawPath) {
|
|
|
46233
46397
|
}
|
|
46234
46398
|
function validateFilePathInput(rawPath) {
|
|
46235
46399
|
if (PATH_DISALLOWED.test(rawPath)) {
|
|
46236
|
-
throw new Error(
|
|
46400
|
+
throw new Error(
|
|
46401
|
+
`path contains disallowed character ${describeDisallowed(rawPath)} (control chars and shell metacharacters are rejected)`
|
|
46402
|
+
);
|
|
46237
46403
|
}
|
|
46238
46404
|
}
|
|
46239
|
-
|
|
46405
|
+
function resolveWithinRoot(filePath, root) {
|
|
46406
|
+
const resolvedRoot = path10.resolve(root);
|
|
46407
|
+
const resolved = path10.resolve(resolvedRoot, filePath);
|
|
46408
|
+
const rel = path10.relative(resolvedRoot, resolved);
|
|
46409
|
+
if (rel.startsWith("..") || path10.isAbsolute(rel)) {
|
|
46410
|
+
throw new Error("path escapes the project root");
|
|
46411
|
+
}
|
|
46412
|
+
return resolved;
|
|
46413
|
+
}
|
|
46414
|
+
async function startServer() {
|
|
46415
|
+
keepEncoderAlive(true);
|
|
46416
|
+
const transport = new StdioServerTransport();
|
|
46417
|
+
await server.connect(transport);
|
|
46418
|
+
}
|
|
46419
|
+
var contextCheckEnum, mcpCheckEnum, mcphCheckEnum, sessionCheckEnum, PATH_DISALLOWED, server;
|
|
46240
46420
|
var init_server3 = __esm({
|
|
46241
|
-
|
|
46421
|
+
"src/mcp/server.ts"() {
|
|
46242
46422
|
"use strict";
|
|
46243
46423
|
init_mcp();
|
|
46244
46424
|
init_stdio2();
|
|
@@ -46312,7 +46492,7 @@ var init_server3 = __esm({
|
|
|
46312
46492
|
try {
|
|
46313
46493
|
validateFilePathInput(filePath);
|
|
46314
46494
|
const root = validateProjectPath(projectPath);
|
|
46315
|
-
const resolved =
|
|
46495
|
+
const resolved = resolveWithinRoot(filePath, root);
|
|
46316
46496
|
const result = {
|
|
46317
46497
|
path: filePath,
|
|
46318
46498
|
exists: fileExists(resolved) || isDirectory(resolved)
|
|
@@ -46555,9 +46735,6 @@ var init_server3 = __esm({
|
|
|
46555
46735
|
}
|
|
46556
46736
|
}
|
|
46557
46737
|
);
|
|
46558
|
-
keepEncoderAlive(true);
|
|
46559
|
-
transport = new StdioServerTransport();
|
|
46560
|
-
await server.connect(transport);
|
|
46561
46738
|
}
|
|
46562
46739
|
});
|
|
46563
46740
|
|
|
@@ -53035,7 +53212,9 @@ var init_ora = __esm({
|
|
|
53035
53212
|
|
|
53036
53213
|
// src/core/reporter.ts
|
|
53037
53214
|
function classifyFile(f) {
|
|
53215
|
+
if (f.path === "(project)") return "context";
|
|
53038
53216
|
if (f.path === "(mcp)") return "mcp";
|
|
53217
|
+
if (f.path === "(mcph)") return "mcph";
|
|
53039
53218
|
if (f.path.includes("session audit")) return "session";
|
|
53040
53219
|
for (const issue2 of f.issues) {
|
|
53041
53220
|
if (issue2.check.startsWith("session-")) return "session";
|
|
@@ -53532,7 +53711,7 @@ function loadConfig(projectRoot) {
|
|
|
53532
53711
|
const filePath = path11.join(projectRoot, filename);
|
|
53533
53712
|
let content;
|
|
53534
53713
|
try {
|
|
53535
|
-
content = fs8.readFileSync(filePath, "utf-8");
|
|
53714
|
+
content = stripBom(fs8.readFileSync(filePath, "utf-8"));
|
|
53536
53715
|
} catch {
|
|
53537
53716
|
continue;
|
|
53538
53717
|
}
|
|
@@ -53543,7 +53722,7 @@ function loadConfig(projectRoot) {
|
|
|
53543
53722
|
function loadConfigFromExplicitPath(configPath) {
|
|
53544
53723
|
let content;
|
|
53545
53724
|
try {
|
|
53546
|
-
content = fs8.readFileSync(configPath, "utf-8");
|
|
53725
|
+
content = stripBom(fs8.readFileSync(configPath, "utf-8"));
|
|
53547
53726
|
} catch (err) {
|
|
53548
53727
|
const detail = err instanceof Error ? err.message : String(err);
|
|
53549
53728
|
throw new Error(`could not load config from ${configPath}: ${detail}`, { cause: err });
|
|
@@ -53555,6 +53734,7 @@ var init_config2 = __esm({
|
|
|
53555
53734
|
"src/core/config.ts"() {
|
|
53556
53735
|
"use strict";
|
|
53557
53736
|
import_fast_levenshtein2 = __toESM(require_levenshtein(), 1);
|
|
53737
|
+
init_fs();
|
|
53558
53738
|
levenshtein2 = import_fast_levenshtein2.default.get;
|
|
53559
53739
|
KNOWN_CONFIG_KEYS = [
|
|
53560
53740
|
"checks",
|
|
@@ -53999,7 +54179,8 @@ var init_cli = __esm({
|
|
|
53999
54179
|
// src/index.ts
|
|
54000
54180
|
var args = process.argv.slice(2);
|
|
54001
54181
|
if (args[0] === "serve" || args.includes("--mcp-server")) {
|
|
54002
|
-
await
|
|
54182
|
+
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server3(), server_exports));
|
|
54183
|
+
await startServer2();
|
|
54003
54184
|
} else {
|
|
54004
54185
|
const { runCli: runCli2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
54005
54186
|
await runCli2();
|