@yawlabs/ctxlint 0.9.18 → 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/.pre-commit-hooks.yaml +1 -1
- package/README.md +101 -59
- package/dist/index.js +708 -404
- 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";
|
|
@@ -41316,6 +41315,28 @@ function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
|
|
|
41316
41315
|
}
|
|
41317
41316
|
return result;
|
|
41318
41317
|
}
|
|
41318
|
+
function getNodeValue(node) {
|
|
41319
|
+
switch (node.type) {
|
|
41320
|
+
case "array":
|
|
41321
|
+
return node.children.map(getNodeValue);
|
|
41322
|
+
case "object":
|
|
41323
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
41324
|
+
for (let prop of node.children) {
|
|
41325
|
+
const valueNode = prop.children[1];
|
|
41326
|
+
if (valueNode) {
|
|
41327
|
+
obj[prop.children[0].value] = getNodeValue(valueNode);
|
|
41328
|
+
}
|
|
41329
|
+
}
|
|
41330
|
+
return obj;
|
|
41331
|
+
case "null":
|
|
41332
|
+
case "string":
|
|
41333
|
+
case "number":
|
|
41334
|
+
case "boolean":
|
|
41335
|
+
return node.value;
|
|
41336
|
+
default:
|
|
41337
|
+
return void 0;
|
|
41338
|
+
}
|
|
41339
|
+
}
|
|
41319
41340
|
function visit(text, visitor, options = ParseOptions.DEFAULT) {
|
|
41320
41341
|
const _scanner = createScanner(text, false);
|
|
41321
41342
|
const _jsonPath = [];
|
|
@@ -41664,7 +41685,7 @@ var init_edit = __esm({
|
|
|
41664
41685
|
});
|
|
41665
41686
|
|
|
41666
41687
|
// node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/main.js
|
|
41667
|
-
var ScanError, SyntaxKind, parseTree2, ParseErrorCode;
|
|
41688
|
+
var ScanError, SyntaxKind, parseTree2, getNodeValue2, ParseErrorCode;
|
|
41668
41689
|
var init_main = __esm({
|
|
41669
41690
|
"node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/main.js"() {
|
|
41670
41691
|
"use strict";
|
|
@@ -41701,6 +41722,7 @@ var init_main = __esm({
|
|
|
41701
41722
|
SyntaxKind2[SyntaxKind2["EOF"] = 17] = "EOF";
|
|
41702
41723
|
})(SyntaxKind || (SyntaxKind = {}));
|
|
41703
41724
|
parseTree2 = parseTree;
|
|
41725
|
+
getNodeValue2 = getNodeValue;
|
|
41704
41726
|
(function(ParseErrorCode2) {
|
|
41705
41727
|
ParseErrorCode2[ParseErrorCode2["InvalidSymbol"] = 1] = "InvalidSymbol";
|
|
41706
41728
|
ParseErrorCode2[ParseErrorCode2["InvalidNumberFormat"] = 2] = "InvalidNumberFormat";
|
|
@@ -41723,9 +41745,9 @@ var init_main = __esm({
|
|
|
41723
41745
|
});
|
|
41724
41746
|
|
|
41725
41747
|
// src/core/mcph-parser.ts
|
|
41726
|
-
async function
|
|
41748
|
+
async function parseMcphConfig(file2, projectRoot, scopeOverride) {
|
|
41727
41749
|
const content = readFileContent(file2.absolutePath);
|
|
41728
|
-
const scope = scopeOverride ??
|
|
41750
|
+
const scope = scopeOverride ?? detectScope(file2.relativePath);
|
|
41729
41751
|
const isGitTracked = await checkGitTracked2(file2.absolutePath, projectRoot);
|
|
41730
41752
|
const isGitignored = await checkGitignored(file2.absolutePath, projectRoot);
|
|
41731
41753
|
const result = {
|
|
@@ -41754,11 +41776,7 @@ async function parseMchpConfig(file2, projectRoot, scopeOverride) {
|
|
|
41754
41776
|
result.parseErrors.push(".mcph.json must be a JSON object at the root");
|
|
41755
41777
|
return result;
|
|
41756
41778
|
}
|
|
41757
|
-
|
|
41758
|
-
result.raw = JSON.parse(stripComments2(content));
|
|
41759
|
-
} catch {
|
|
41760
|
-
return result;
|
|
41761
|
-
}
|
|
41779
|
+
result.raw = getNodeValue2(tree);
|
|
41762
41780
|
const rootProps = tree.children ?? [];
|
|
41763
41781
|
for (const prop of rootProps) {
|
|
41764
41782
|
if (prop.type !== "property" || !prop.children || prop.children.length < 2) continue;
|
|
@@ -41787,7 +41805,7 @@ async function parseMchpConfig(file2, projectRoot, scopeOverride) {
|
|
|
41787
41805
|
}
|
|
41788
41806
|
return result;
|
|
41789
41807
|
}
|
|
41790
|
-
function
|
|
41808
|
+
function detectScope(relativePath) {
|
|
41791
41809
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
41792
41810
|
if (normalized.startsWith("~/")) return "global";
|
|
41793
41811
|
if (normalized.endsWith(".mcph.local.json")) return "project-local";
|
|
@@ -41816,46 +41834,6 @@ function offsetToPosition(content, offset) {
|
|
|
41816
41834
|
}
|
|
41817
41835
|
return { line, column };
|
|
41818
41836
|
}
|
|
41819
|
-
function stripComments2(src) {
|
|
41820
|
-
let out = "";
|
|
41821
|
-
let i2 = 0;
|
|
41822
|
-
let inString = false;
|
|
41823
|
-
let escape2 = false;
|
|
41824
|
-
while (i2 < src.length) {
|
|
41825
|
-
const ch = src[i2];
|
|
41826
|
-
if (inString) {
|
|
41827
|
-
out += ch;
|
|
41828
|
-
if (escape2) {
|
|
41829
|
-
escape2 = false;
|
|
41830
|
-
} else if (ch === "\\") {
|
|
41831
|
-
escape2 = true;
|
|
41832
|
-
} else if (ch === '"') {
|
|
41833
|
-
inString = false;
|
|
41834
|
-
}
|
|
41835
|
-
i2++;
|
|
41836
|
-
continue;
|
|
41837
|
-
}
|
|
41838
|
-
if (ch === '"') {
|
|
41839
|
-
inString = true;
|
|
41840
|
-
out += ch;
|
|
41841
|
-
i2++;
|
|
41842
|
-
continue;
|
|
41843
|
-
}
|
|
41844
|
-
if (ch === "/" && src[i2 + 1] === "/") {
|
|
41845
|
-
while (i2 < src.length && src[i2] !== "\n") i2++;
|
|
41846
|
-
continue;
|
|
41847
|
-
}
|
|
41848
|
-
if (ch === "/" && src[i2 + 1] === "*") {
|
|
41849
|
-
i2 += 2;
|
|
41850
|
-
while (i2 < src.length && !(src[i2] === "*" && src[i2 + 1] === "/")) i2++;
|
|
41851
|
-
i2 += 2;
|
|
41852
|
-
continue;
|
|
41853
|
-
}
|
|
41854
|
-
out += ch;
|
|
41855
|
-
i2++;
|
|
41856
|
-
}
|
|
41857
|
-
return out;
|
|
41858
|
-
}
|
|
41859
41837
|
async function checkGitTracked2(filePath, projectRoot) {
|
|
41860
41838
|
try {
|
|
41861
41839
|
const git = getGit(projectRoot);
|
|
@@ -42189,29 +42167,33 @@ async function checkPaths(file2, projectRoot) {
|
|
|
42189
42167
|
function findClosestMatch(target, files) {
|
|
42190
42168
|
const targetNorm = target.replace(/\\/g, "/");
|
|
42191
42169
|
const targetBase = path3.basename(targetNorm);
|
|
42192
|
-
let
|
|
42193
|
-
let
|
|
42170
|
+
let basenameMatch = null;
|
|
42171
|
+
let basenameDistance = Infinity;
|
|
42194
42172
|
for (const file2 of files) {
|
|
42195
42173
|
const fileNorm = file2.replace(/\\/g, "/");
|
|
42196
42174
|
if (path3.basename(fileNorm) === targetBase && fileNorm !== targetNorm) {
|
|
42197
42175
|
const dist = levenshtein(targetNorm, fileNorm);
|
|
42198
|
-
if (dist <
|
|
42199
|
-
|
|
42200
|
-
|
|
42176
|
+
if (dist < basenameDistance) {
|
|
42177
|
+
basenameDistance = dist;
|
|
42178
|
+
basenameMatch = fileNorm;
|
|
42201
42179
|
}
|
|
42202
42180
|
}
|
|
42203
42181
|
}
|
|
42204
|
-
if (
|
|
42205
|
-
|
|
42206
|
-
|
|
42207
|
-
|
|
42208
|
-
|
|
42209
|
-
|
|
42210
|
-
|
|
42211
|
-
|
|
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;
|
|
42212
42194
|
}
|
|
42213
42195
|
}
|
|
42214
|
-
return
|
|
42196
|
+
return fullPathMatch;
|
|
42215
42197
|
}
|
|
42216
42198
|
var import_fast_levenshtein, levenshtein, cachedProjectFiles;
|
|
42217
42199
|
var init_paths = __esm({
|
|
@@ -42229,6 +42211,24 @@ var init_paths = __esm({
|
|
|
42229
42211
|
// src/core/checks/commands.ts
|
|
42230
42212
|
import * as fs4 from "node:fs";
|
|
42231
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
|
+
}
|
|
42232
42232
|
async function checkCommands(file2, projectRoot) {
|
|
42233
42233
|
const issues = [];
|
|
42234
42234
|
const pkgJson = loadPackageJson(projectRoot);
|
|
@@ -42267,10 +42267,9 @@ async function checkCommands(file2, projectRoot) {
|
|
|
42267
42267
|
}
|
|
42268
42268
|
continue;
|
|
42269
42269
|
}
|
|
42270
|
-
|
|
42271
|
-
|
|
42272
|
-
|
|
42273
|
-
if (pkgName.startsWith("-")) continue;
|
|
42270
|
+
if (/^npx\b/.test(cmd) && pkgJson) {
|
|
42271
|
+
const pkgName = extractNpxPackage(cmd);
|
|
42272
|
+
if (!pkgName) continue;
|
|
42274
42273
|
const allDeps = {
|
|
42275
42274
|
...pkgJson.dependencies,
|
|
42276
42275
|
...pkgJson.devDependencies,
|
|
@@ -42345,7 +42344,7 @@ async function checkCommands(file2, projectRoot) {
|
|
|
42345
42344
|
}
|
|
42346
42345
|
function loadMakefile(projectRoot) {
|
|
42347
42346
|
try {
|
|
42348
|
-
return fs4.readFileSync(path4.join(projectRoot, "Makefile"), "utf-8");
|
|
42347
|
+
return stripBom(fs4.readFileSync(path4.join(projectRoot, "Makefile"), "utf-8"));
|
|
42349
42348
|
} catch {
|
|
42350
42349
|
return null;
|
|
42351
42350
|
}
|
|
@@ -42354,14 +42353,13 @@ function hasMakeTarget(makefile, target) {
|
|
|
42354
42353
|
const pattern = new RegExp(`^${target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*:`, "m");
|
|
42355
42354
|
return pattern.test(makefile);
|
|
42356
42355
|
}
|
|
42357
|
-
var NPM_SCRIPT_PATTERN, MAKE_PATTERN
|
|
42356
|
+
var NPM_SCRIPT_PATTERN, MAKE_PATTERN;
|
|
42358
42357
|
var init_commands = __esm({
|
|
42359
42358
|
"src/core/checks/commands.ts"() {
|
|
42360
42359
|
"use strict";
|
|
42361
42360
|
init_fs();
|
|
42362
42361
|
NPM_SCRIPT_PATTERN = /^(?:npm\s+run|pnpm(?:\s+run)?|yarn(?:\s+run)?|bun(?:\s+run)?)\s+(\S+)/;
|
|
42363
42362
|
MAKE_PATTERN = /^make\s+(\S+)/;
|
|
42364
|
-
NPX_PATTERN = /^npx\s+(\S+)/;
|
|
42365
42363
|
}
|
|
42366
42364
|
});
|
|
42367
42365
|
|
|
@@ -42536,7 +42534,14 @@ function isAlwaysLoaded(file2) {
|
|
|
42536
42534
|
return !hasPathsFrontmatter(file2.content);
|
|
42537
42535
|
}
|
|
42538
42536
|
const basename4 = rel.split("/").pop() ?? "";
|
|
42539
|
-
|
|
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;
|
|
42540
42545
|
}
|
|
42541
42546
|
function computeSectionCosts(file2) {
|
|
42542
42547
|
if (file2.sections.length === 0) return [];
|
|
@@ -42558,7 +42563,7 @@ function loadSettingsSources(projectRoot) {
|
|
|
42558
42563
|
for (const p2 of candidates) {
|
|
42559
42564
|
let content;
|
|
42560
42565
|
try {
|
|
42561
|
-
content = fs5.readFileSync(p2, "utf-8");
|
|
42566
|
+
content = stripBom(fs5.readFileSync(p2, "utf-8"));
|
|
42562
42567
|
} catch {
|
|
42563
42568
|
continue;
|
|
42564
42569
|
}
|
|
@@ -42574,9 +42579,17 @@ function canonicalizeCommand(backticked) {
|
|
|
42574
42579
|
const beforeFlags = backticked.trim().split(/\s+--?/, 1)[0];
|
|
42575
42580
|
return beforeFlags.replace(/\s+/g, " ");
|
|
42576
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
|
+
}
|
|
42577
42591
|
function commandIsEnforced(cmd, settings) {
|
|
42578
|
-
const
|
|
42579
|
-
const pattern = new RegExp(`\\b${escaped}\\b`, "i");
|
|
42592
|
+
const pattern = buildCommandPattern(cmd);
|
|
42580
42593
|
for (const s of settings) {
|
|
42581
42594
|
for (const entry of s.permissions?.deny ?? []) {
|
|
42582
42595
|
if (pattern.test(entry)) return true;
|
|
@@ -42654,13 +42667,14 @@ function checkAggregateTierTokens(files) {
|
|
|
42654
42667
|
suggestion: "Consider moving the largest files or their heaviest sections to on-demand tiers (skills, subagents, memory)."
|
|
42655
42668
|
};
|
|
42656
42669
|
}
|
|
42657
|
-
var
|
|
42670
|
+
var ALWAYS_LOADED_NAMES, TOP_SECTIONS_TO_REPORT, INVIOLABLE_WITH_COMMAND;
|
|
42658
42671
|
var init_tier_tokens = __esm({
|
|
42659
42672
|
"src/core/checks/tier-tokens.ts"() {
|
|
42660
42673
|
"use strict";
|
|
42661
42674
|
init_tokens();
|
|
42675
|
+
init_fs();
|
|
42662
42676
|
init_tokens2();
|
|
42663
|
-
|
|
42677
|
+
ALWAYS_LOADED_NAMES = [
|
|
42664
42678
|
"CLAUDE.md",
|
|
42665
42679
|
"CLAUDE.local.md",
|
|
42666
42680
|
"AGENTS.md",
|
|
@@ -42675,15 +42689,42 @@ var init_tier_tokens = __esm({
|
|
|
42675
42689
|
".rules",
|
|
42676
42690
|
".goosehints",
|
|
42677
42691
|
"replit.md",
|
|
42678
|
-
"copilot-instructions.md",
|
|
42679
|
-
"guidelines.md",
|
|
42680
|
-
"
|
|
42681
|
-
|
|
42692
|
+
".github/copilot-instructions.md",
|
|
42693
|
+
".junie/guidelines.md",
|
|
42694
|
+
".junie/AGENTS.md",
|
|
42695
|
+
".goose/instructions.md"
|
|
42696
|
+
];
|
|
42682
42697
|
TOP_SECTIONS_TO_REPORT = 3;
|
|
42683
42698
|
INVIOLABLE_WITH_COMMAND = /\b(NEVER|ALWAYS|DON'?T|DO NOT|MUST NOT)\b[^.!?`]{0,80}`([^`]+)`/i;
|
|
42684
42699
|
}
|
|
42685
42700
|
});
|
|
42686
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
|
+
|
|
42687
42728
|
// src/core/checks/redundancy.ts
|
|
42688
42729
|
import * as path7 from "node:path";
|
|
42689
42730
|
function compilePatterns(allDeps) {
|
|
@@ -42706,6 +42747,20 @@ function compilePatterns(allDeps) {
|
|
|
42706
42747
|
}
|
|
42707
42748
|
return compiled;
|
|
42708
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
|
+
}
|
|
42709
42764
|
async function checkRedundancy(file2, projectRoot) {
|
|
42710
42765
|
const issues = [];
|
|
42711
42766
|
const pkgJson = loadPackageJson(projectRoot);
|
|
@@ -42716,7 +42771,7 @@ async function checkRedundancy(file2, projectRoot) {
|
|
|
42716
42771
|
...Object.keys(pkgJson.peerDependencies || {}),
|
|
42717
42772
|
...Object.keys(pkgJson.optionalDependencies || {})
|
|
42718
42773
|
]);
|
|
42719
|
-
const compiledPatterns =
|
|
42774
|
+
const compiledPatterns = getCompiledPatterns(projectRoot, allDeps);
|
|
42720
42775
|
const lines2 = file2.content.split("\n");
|
|
42721
42776
|
for (let i2 = 0; i2 < lines2.length; i2++) {
|
|
42722
42777
|
const line = lines2[i2];
|
|
@@ -42767,10 +42822,14 @@ async function checkRedundancy(file2, projectRoot) {
|
|
|
42767
42822
|
}
|
|
42768
42823
|
function checkDuplicateContent(files) {
|
|
42769
42824
|
const issues = [];
|
|
42770
|
-
const
|
|
42825
|
+
const lineSets = files.map((f) => toLineSet(f.content, DUPLICATE_CONTENT_MIN_TOKEN_LEN));
|
|
42771
42826
|
for (let i2 = 0; i2 < files.length; i2++) {
|
|
42827
|
+
const a = lineSets[i2];
|
|
42828
|
+
if (a.size === 0) continue;
|
|
42772
42829
|
for (let j3 = i2 + 1; j3 < files.length; j3++) {
|
|
42773
|
-
const
|
|
42830
|
+
const b2 = lineSets[j3];
|
|
42831
|
+
if (b2.size === 0) continue;
|
|
42832
|
+
const overlap = jaccardSimilarityFromSets(a, b2);
|
|
42774
42833
|
if (overlap >= DUPLICATE_CONTENT_THRESHOLD) {
|
|
42775
42834
|
issues.push({
|
|
42776
42835
|
severity: "warning",
|
|
@@ -42785,29 +42844,15 @@ function checkDuplicateContent(files) {
|
|
|
42785
42844
|
}
|
|
42786
42845
|
return issues;
|
|
42787
42846
|
}
|
|
42788
|
-
function calculateLineOverlap(contentA, contentB) {
|
|
42789
|
-
const linesA = new Set(
|
|
42790
|
-
contentA.split("\n").map((l) => l.trim()).filter((l) => l.length > 10)
|
|
42791
|
-
);
|
|
42792
|
-
const linesB = new Set(
|
|
42793
|
-
contentB.split("\n").map((l) => l.trim()).filter((l) => l.length > 10)
|
|
42794
|
-
);
|
|
42795
|
-
if (linesA.size === 0 || linesB.size === 0) return 0;
|
|
42796
|
-
let intersection2 = 0;
|
|
42797
|
-
for (const line of linesA) {
|
|
42798
|
-
if (linesB.has(line)) intersection2++;
|
|
42799
|
-
}
|
|
42800
|
-
const unionSize = linesA.size + linesB.size - intersection2;
|
|
42801
|
-
return intersection2 / unionSize;
|
|
42802
|
-
}
|
|
42803
42847
|
function escapeRegex2(str) {
|
|
42804
42848
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
42805
42849
|
}
|
|
42806
|
-
var PACKAGE_TECH_MAP;
|
|
42850
|
+
var PACKAGE_TECH_MAP, compiledPatternsCache, DUPLICATE_CONTENT_THRESHOLD, DUPLICATE_CONTENT_MIN_TOKEN_LEN;
|
|
42807
42851
|
var init_redundancy = __esm({
|
|
42808
42852
|
"src/core/checks/redundancy.ts"() {
|
|
42809
42853
|
"use strict";
|
|
42810
42854
|
init_fs();
|
|
42855
|
+
init_similarity();
|
|
42811
42856
|
init_tokens();
|
|
42812
42857
|
PACKAGE_TECH_MAP = {
|
|
42813
42858
|
react: ["React", "react"],
|
|
@@ -42860,6 +42905,9 @@ var init_redundancy = __esm({
|
|
|
42860
42905
|
cypress: ["Cypress"],
|
|
42861
42906
|
puppeteer: ["Puppeteer"]
|
|
42862
42907
|
};
|
|
42908
|
+
compiledPatternsCache = /* @__PURE__ */ new Map();
|
|
42909
|
+
DUPLICATE_CONTENT_THRESHOLD = 0.6;
|
|
42910
|
+
DUPLICATE_CONTENT_MIN_TOKEN_LEN = 10;
|
|
42863
42911
|
}
|
|
42864
42912
|
});
|
|
42865
42913
|
|
|
@@ -43177,7 +43225,7 @@ var init_contradictions = __esm({
|
|
|
43177
43225
|
function parseFrontmatter(content) {
|
|
43178
43226
|
const lines = content.split("\n");
|
|
43179
43227
|
if (lines[0]?.trim() !== "---") {
|
|
43180
|
-
return { found: false, fields: {}, endLine: 0 };
|
|
43228
|
+
return { found: false, fields: {}, endLine: 0, unclosed: false };
|
|
43181
43229
|
}
|
|
43182
43230
|
const fields = {};
|
|
43183
43231
|
let endLine = 0;
|
|
@@ -43204,9 +43252,9 @@ function parseFrontmatter(content) {
|
|
|
43204
43252
|
}
|
|
43205
43253
|
}
|
|
43206
43254
|
if (endLine === 0) {
|
|
43207
|
-
return { found: true, fields, endLine: lines.length };
|
|
43255
|
+
return { found: true, fields, endLine: lines.length, unclosed: true };
|
|
43208
43256
|
}
|
|
43209
|
-
return { found: true, fields, endLine };
|
|
43257
|
+
return { found: true, fields, endLine, unclosed: false };
|
|
43210
43258
|
}
|
|
43211
43259
|
function isCursorMdc(file2) {
|
|
43212
43260
|
return file2.relativePath.endsWith(".mdc");
|
|
@@ -43217,6 +43265,23 @@ function isCopilotInstructions(file2) {
|
|
|
43217
43265
|
function isWindsurfRule(file2) {
|
|
43218
43266
|
return file2.relativePath.includes(".windsurf/rules/") && file2.relativePath.endsWith(".md");
|
|
43219
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
|
+
}
|
|
43220
43285
|
async function checkFrontmatter(file2, _projectRoot) {
|
|
43221
43286
|
const issues = [];
|
|
43222
43287
|
if (isCursorMdc(file2)) {
|
|
@@ -43228,9 +43293,23 @@ async function checkFrontmatter(file2, _projectRoot) {
|
|
|
43228
43293
|
}
|
|
43229
43294
|
return issues;
|
|
43230
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
|
+
}
|
|
43231
43306
|
function validateCursorMdc(file2) {
|
|
43232
43307
|
const issues = [];
|
|
43233
43308
|
const fm = parseFrontmatter(file2.content);
|
|
43309
|
+
if (fm.unclosed) {
|
|
43310
|
+
issues.push(unclosedFrontmatterIssue());
|
|
43311
|
+
return issues;
|
|
43312
|
+
}
|
|
43234
43313
|
if (!fm.found) {
|
|
43235
43314
|
issues.push({
|
|
43236
43315
|
severity: "warning",
|
|
@@ -43277,13 +43356,13 @@ function validateCursorMdc(file2) {
|
|
|
43277
43356
|
}
|
|
43278
43357
|
if ("globs" in fm.fields) {
|
|
43279
43358
|
const val = fm.fields["globs"];
|
|
43280
|
-
if (val &&
|
|
43359
|
+
if (val && hasUnbalancedBracketsOrQuotes(val)) {
|
|
43281
43360
|
issues.push({
|
|
43282
43361
|
severity: "warning",
|
|
43283
43362
|
check: "frontmatter",
|
|
43284
43363
|
ruleId: "frontmatter/invalid-value",
|
|
43285
43364
|
line: 1,
|
|
43286
|
-
message: `Possibly
|
|
43365
|
+
message: `Possibly malformed globs value: "${val}"`,
|
|
43287
43366
|
suggestion: 'globs should be a glob pattern like "src/**/*.ts" or an array like ["*.ts", "*.tsx"]'
|
|
43288
43367
|
});
|
|
43289
43368
|
}
|
|
@@ -43293,6 +43372,10 @@ function validateCursorMdc(file2) {
|
|
|
43293
43372
|
function validateCopilotInstructions(file2) {
|
|
43294
43373
|
const issues = [];
|
|
43295
43374
|
const fm = parseFrontmatter(file2.content);
|
|
43375
|
+
if (fm.unclosed) {
|
|
43376
|
+
issues.push(unclosedFrontmatterIssue());
|
|
43377
|
+
return issues;
|
|
43378
|
+
}
|
|
43296
43379
|
if (!fm.found) {
|
|
43297
43380
|
issues.push({
|
|
43298
43381
|
severity: "info",
|
|
@@ -43319,6 +43402,10 @@ function validateCopilotInstructions(file2) {
|
|
|
43319
43402
|
function validateWindsurfRule(file2) {
|
|
43320
43403
|
const issues = [];
|
|
43321
43404
|
const fm = parseFrontmatter(file2.content);
|
|
43405
|
+
if (fm.unclosed) {
|
|
43406
|
+
issues.push(unclosedFrontmatterIssue());
|
|
43407
|
+
return issues;
|
|
43408
|
+
}
|
|
43322
43409
|
if (!fm.found) {
|
|
43323
43410
|
issues.push({
|
|
43324
43411
|
severity: "info",
|
|
@@ -43490,6 +43577,10 @@ function isEnvVarRef(value) {
|
|
|
43490
43577
|
function isKnownApiKey(value) {
|
|
43491
43578
|
return API_KEY_PATTERNS.some((p2) => p2.test(value));
|
|
43492
43579
|
}
|
|
43580
|
+
function nameSuggestsSecret(name) {
|
|
43581
|
+
const upper = name.toUpperCase();
|
|
43582
|
+
return SECRET_NAME_KEYWORDS.some((kw) => upper.includes(kw));
|
|
43583
|
+
}
|
|
43493
43584
|
function isHighEntropySecret(value) {
|
|
43494
43585
|
if (isEnvVarRef(value)) return false;
|
|
43495
43586
|
return HIGH_ENTROPY_PATTERN.test(value);
|
|
@@ -43538,8 +43629,9 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43538
43629
|
}
|
|
43539
43630
|
}
|
|
43540
43631
|
if (server2.env) {
|
|
43541
|
-
for (const envValue of Object.
|
|
43542
|
-
|
|
43632
|
+
for (const [envName, envValue] of Object.entries(server2.env)) {
|
|
43633
|
+
const isSecret = isKnownApiKey(envValue) || nameSuggestsSecret(envName) && isHighEntropySecret(envValue);
|
|
43634
|
+
if (!isEnvVarRef(envValue) && isSecret) {
|
|
43543
43635
|
const envVar = deriveEnvVarName(server2.name, "API_KEY");
|
|
43544
43636
|
issues.push({
|
|
43545
43637
|
severity: "error",
|
|
@@ -43586,7 +43678,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43586
43678
|
}
|
|
43587
43679
|
return issues;
|
|
43588
43680
|
}
|
|
43589
|
-
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;
|
|
43590
43682
|
var init_security = __esm({
|
|
43591
43683
|
"src/core/checks/mcp/security.ts"() {
|
|
43592
43684
|
"use strict";
|
|
@@ -43615,6 +43707,22 @@ var init_security = __esm({
|
|
|
43615
43707
|
ENV_VAR_REF = /\$\{[^}]+\}/;
|
|
43616
43708
|
HIGH_ENTROPY_PATTERN = /^[A-Za-z0-9+/=_-]{21,}$/;
|
|
43617
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
|
+
];
|
|
43618
43726
|
}
|
|
43619
43727
|
});
|
|
43620
43728
|
|
|
@@ -44381,6 +44489,12 @@ function extractPaths(content) {
|
|
|
44381
44489
|
paths.push(p2);
|
|
44382
44490
|
}
|
|
44383
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
|
+
}
|
|
44384
44498
|
return [...new Set(paths)];
|
|
44385
44499
|
}
|
|
44386
44500
|
function parseFrontmatter2(content) {
|
|
@@ -44410,7 +44524,7 @@ function parseFrontmatter2(content) {
|
|
|
44410
44524
|
};
|
|
44411
44525
|
}
|
|
44412
44526
|
async function parseMemoryFile(filePath, projectDir) {
|
|
44413
|
-
const content = await readFile(filePath, "utf-8");
|
|
44527
|
+
const content = stripBom(await readFile(filePath, "utf-8"));
|
|
44414
44528
|
const { name, description, type, body } = parseFrontmatter2(content);
|
|
44415
44529
|
const referencedPaths = extractPaths(body);
|
|
44416
44530
|
return {
|
|
@@ -44423,11 +44537,13 @@ async function parseMemoryFile(filePath, projectDir) {
|
|
|
44423
44537
|
referencedPaths
|
|
44424
44538
|
};
|
|
44425
44539
|
}
|
|
44426
|
-
var PATH_PATTERN2;
|
|
44540
|
+
var PATH_PATTERN2, BARE_FILE_PATH;
|
|
44427
44541
|
var init_session_parser = __esm({
|
|
44428
44542
|
"src/core/session-parser.ts"() {
|
|
44429
44543
|
"use strict";
|
|
44544
|
+
init_fs();
|
|
44430
44545
|
PATH_PATTERN2 = /(?:^|\s|['"`(])([.~/][^\s'"`),;:!?]+)/g;
|
|
44546
|
+
BARE_FILE_PATH = /(?:^|[\s`"'(])([\w][\w-]*(?:\/[\w.-]+)+\.[a-zA-Z0-9]{1,8})\b/g;
|
|
44431
44547
|
}
|
|
44432
44548
|
});
|
|
44433
44549
|
|
|
@@ -44459,30 +44575,46 @@ async function parseJsonlFiltered(filePath, filter) {
|
|
|
44459
44575
|
}
|
|
44460
44576
|
return results;
|
|
44461
44577
|
}
|
|
44462
|
-
|
|
44463
|
-
const
|
|
44464
|
-
|
|
44465
|
-
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;
|
|
44466
44593
|
return {
|
|
44467
|
-
display
|
|
44468
|
-
timestamp: entry.timestamp
|
|
44469
|
-
project:
|
|
44470
|
-
sessionId: entry.sessionId
|
|
44471
|
-
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
|
|
44472
44599
|
};
|
|
44473
44600
|
});
|
|
44474
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
|
+
}
|
|
44475
44611
|
async function readCodexHistory() {
|
|
44476
|
-
|
|
44477
|
-
|
|
44478
|
-
|
|
44479
|
-
|
|
44480
|
-
|
|
44481
|
-
|
|
44482
|
-
project: (entry.project || entry.cwd || "").replace(/\\/g, "/"),
|
|
44483
|
-
sessionId: entry.sessionId || "",
|
|
44484
|
-
provider: "codex-cli"
|
|
44485
|
-
};
|
|
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
|
|
44486
44618
|
});
|
|
44487
44619
|
}
|
|
44488
44620
|
async function readClaudeMemories() {
|
|
@@ -44547,47 +44679,29 @@ async function detectSiblings(projectRoot) {
|
|
|
44547
44679
|
if (orgMatch) currentOrg = orgMatch[1];
|
|
44548
44680
|
} catch {
|
|
44549
44681
|
}
|
|
44550
|
-
const
|
|
44551
|
-
const results = await Promise.all(
|
|
44552
|
-
gitCandidates.map(async (c3) => {
|
|
44553
|
-
const sibling = { path: c3.entryPath.replace(/\\/g, "/"), name: c3.name };
|
|
44554
|
-
try {
|
|
44555
|
-
const git = simpleGit(c3.fullPath);
|
|
44556
|
-
const remotes = await git.getRemotes(true);
|
|
44557
|
-
const origin = remotes.find((r2) => r2.name === "origin");
|
|
44558
|
-
if (origin?.refs?.fetch) {
|
|
44559
|
-
sibling.gitRemoteUrl = origin.refs.fetch;
|
|
44560
|
-
const orgMatch = origin.refs.fetch.match(/github\.com[:/]([^/]+)\//);
|
|
44561
|
-
if (orgMatch) sibling.gitOrg = orgMatch[1];
|
|
44562
|
-
}
|
|
44563
|
-
} catch {
|
|
44564
|
-
}
|
|
44565
|
-
return sibling;
|
|
44566
|
-
})
|
|
44567
|
-
);
|
|
44682
|
+
const results = await Promise.all(candidates.map((c3) => resolveSibling(c3)));
|
|
44568
44683
|
if (currentOrg) {
|
|
44569
|
-
return results.filter((s) => s.gitOrg === currentOrg);
|
|
44684
|
+
return results.filter((s) => !s.gitOrg || s.gitOrg === currentOrg);
|
|
44570
44685
|
}
|
|
44571
44686
|
return results;
|
|
44572
44687
|
}
|
|
44573
|
-
|
|
44574
|
-
|
|
44575
|
-
|
|
44576
|
-
|
|
44577
|
-
|
|
44578
|
-
|
|
44579
|
-
|
|
44580
|
-
|
|
44581
|
-
|
|
44582
|
-
|
|
44583
|
-
|
|
44584
|
-
|
|
44585
|
-
|
|
44586
|
-
|
|
44587
|
-
|
|
44588
|
-
|
|
44589
|
-
|
|
44590
|
-
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;
|
|
44591
44705
|
}
|
|
44592
44706
|
async function scanSessionData(projectRoot) {
|
|
44593
44707
|
const providers = detectProviders();
|
|
@@ -44707,48 +44821,57 @@ var init_missing_secret = __esm({
|
|
|
44707
44821
|
});
|
|
44708
44822
|
|
|
44709
44823
|
// src/core/checks/session/diverged-file.ts
|
|
44710
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
44824
|
+
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
44711
44825
|
import { join as join6 } from "node:path";
|
|
44712
44826
|
import { existsSync as existsSync2 } from "node:fs";
|
|
44713
|
-
function
|
|
44714
|
-
|
|
44715
|
-
|
|
44716
|
-
|
|
44717
|
-
|
|
44718
|
-
|
|
44719
|
-
|
|
44720
|
-
|
|
44721
|
-
|
|
44722
|
-
let intersection2 = 0;
|
|
44723
|
-
for (const line of linesA) {
|
|
44724
|
-
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;
|
|
44725
44836
|
}
|
|
44726
|
-
const
|
|
44727
|
-
|
|
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;
|
|
44728
44856
|
}
|
|
44729
44857
|
async function checkDivergedFile(ctx) {
|
|
44730
44858
|
const issues = [];
|
|
44731
44859
|
for (const fileName of CANONICAL_FILES) {
|
|
44732
44860
|
const currentPath = join6(ctx.currentProject, fileName);
|
|
44733
44861
|
if (!existsSync2(currentPath)) continue;
|
|
44734
|
-
|
|
44735
|
-
|
|
44736
|
-
currentContent = await readFile2(currentPath, "utf-8");
|
|
44737
|
-
} catch {
|
|
44738
|
-
continue;
|
|
44739
|
-
}
|
|
44862
|
+
const currentLineSet = await loadLineSet(currentPath);
|
|
44863
|
+
if (!currentLineSet) continue;
|
|
44740
44864
|
const diverged = [];
|
|
44741
44865
|
for (const sib of ctx.siblings) {
|
|
44742
44866
|
const sibPath = join6(sib.path, fileName);
|
|
44743
44867
|
if (!existsSync2(sibPath)) continue;
|
|
44744
|
-
|
|
44745
|
-
|
|
44746
|
-
|
|
44747
|
-
|
|
44748
|
-
|
|
44749
|
-
|
|
44750
|
-
|
|
44751
|
-
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) });
|
|
44752
44875
|
}
|
|
44753
44876
|
}
|
|
44754
44877
|
if (diverged.length > 0) {
|
|
@@ -44766,10 +44889,12 @@ async function checkDivergedFile(ctx) {
|
|
|
44766
44889
|
}
|
|
44767
44890
|
return issues;
|
|
44768
44891
|
}
|
|
44769
|
-
var CANONICAL_FILES;
|
|
44892
|
+
var CANONICAL_FILES, MIN_TOKEN_LEN, CACHE_MAX_ENTRIES, lineSetCache;
|
|
44770
44893
|
var init_diverged_file = __esm({
|
|
44771
44894
|
"src/core/checks/session/diverged-file.ts"() {
|
|
44772
44895
|
"use strict";
|
|
44896
|
+
init_similarity();
|
|
44897
|
+
init_fs();
|
|
44773
44898
|
CANONICAL_FILES = [
|
|
44774
44899
|
"release.sh",
|
|
44775
44900
|
".github/workflows/ci.yml",
|
|
@@ -44780,6 +44905,9 @@ var init_diverged_file = __esm({
|
|
|
44780
44905
|
"tsconfig.json",
|
|
44781
44906
|
".gitignore"
|
|
44782
44907
|
];
|
|
44908
|
+
MIN_TOKEN_LEN = 3;
|
|
44909
|
+
CACHE_MAX_ENTRIES = 256;
|
|
44910
|
+
lineSetCache = /* @__PURE__ */ new Map();
|
|
44783
44911
|
}
|
|
44784
44912
|
});
|
|
44785
44913
|
|
|
@@ -44887,24 +45015,10 @@ var init_stale_memory = __esm({
|
|
|
44887
45015
|
});
|
|
44888
45016
|
|
|
44889
45017
|
// src/core/checks/session/duplicate-memory.ts
|
|
44890
|
-
function calculateLineOverlap2(a, b2) {
|
|
44891
|
-
const linesA = new Set(
|
|
44892
|
-
a.split("\n").map((l) => l.trim()).filter((l) => l.length > 5)
|
|
44893
|
-
);
|
|
44894
|
-
const linesB = new Set(
|
|
44895
|
-
b2.split("\n").map((l) => l.trim()).filter((l) => l.length > 5)
|
|
44896
|
-
);
|
|
44897
|
-
if (linesA.size === 0 || linesB.size === 0) return 0;
|
|
44898
|
-
let intersection2 = 0;
|
|
44899
|
-
for (const line of linesA) {
|
|
44900
|
-
if (linesB.has(line)) intersection2++;
|
|
44901
|
-
}
|
|
44902
|
-
const unionSize = linesA.size + linesB.size - intersection2;
|
|
44903
|
-
return intersection2 / unionSize;
|
|
44904
|
-
}
|
|
44905
45018
|
async function checkDuplicateMemory(ctx) {
|
|
44906
45019
|
const issues = [];
|
|
44907
45020
|
const reported = /* @__PURE__ */ new Set();
|
|
45021
|
+
const lineSets = ctx.memories.map((m) => toLineSet(m.content, MIN_TOKEN_LEN2));
|
|
44908
45022
|
for (let i2 = 0; i2 < ctx.memories.length; i2++) {
|
|
44909
45023
|
for (let j3 = i2 + 1; j3 < ctx.memories.length; j3++) {
|
|
44910
45024
|
const a = ctx.memories[i2];
|
|
@@ -44914,7 +45028,7 @@ async function checkDuplicateMemory(ctx) {
|
|
|
44914
45028
|
const bIsCurrent = projectDirMatchesPath(b2.projectDir, ctx.currentProject);
|
|
44915
45029
|
if (!aIsCurrent && !bIsCurrent) continue;
|
|
44916
45030
|
if (a.content.length < 50 || b2.content.length < 50) continue;
|
|
44917
|
-
const overlap =
|
|
45031
|
+
const overlap = jaccardSimilarityFromSets(lineSets[i2], lineSets[j3]);
|
|
44918
45032
|
if (overlap < 0.6) continue;
|
|
44919
45033
|
const pairKey = [a.filePath, b2.filePath].sort().join("::");
|
|
44920
45034
|
if (reported.has(pairKey)) continue;
|
|
@@ -44936,10 +45050,13 @@ async function checkDuplicateMemory(ctx) {
|
|
|
44936
45050
|
}
|
|
44937
45051
|
return issues;
|
|
44938
45052
|
}
|
|
45053
|
+
var MIN_TOKEN_LEN2;
|
|
44939
45054
|
var init_duplicate_memory = __esm({
|
|
44940
45055
|
"src/core/checks/session/duplicate-memory.ts"() {
|
|
44941
45056
|
"use strict";
|
|
44942
45057
|
init_session_parser();
|
|
45058
|
+
init_similarity();
|
|
45059
|
+
MIN_TOKEN_LEN2 = 5;
|
|
44943
45060
|
}
|
|
44944
45061
|
});
|
|
44945
45062
|
|
|
@@ -45034,24 +45151,23 @@ var init_loop_detection = __esm({
|
|
|
45034
45151
|
});
|
|
45035
45152
|
|
|
45036
45153
|
// src/core/checks/session/memory-index-overflow.ts
|
|
45037
|
-
import { readFile as readFile3
|
|
45154
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
45038
45155
|
import { join as join8 } from "node:path";
|
|
45039
45156
|
async function checkMemoryIndexOverflow(ctx) {
|
|
45040
45157
|
const home2 = process.env.HOME || process.env.USERPROFILE || "";
|
|
45041
45158
|
if (!home2) return [];
|
|
45042
45159
|
const encoded = encodeProjectDir(ctx.currentProject);
|
|
45043
45160
|
const memoryFile = join8(home2, ".claude", "projects", encoded, "memory", "MEMORY.md");
|
|
45044
|
-
let
|
|
45161
|
+
let content;
|
|
45045
45162
|
try {
|
|
45046
|
-
|
|
45163
|
+
content = stripBom(await readFile3(memoryFile, "utf-8"));
|
|
45047
45164
|
} catch {
|
|
45048
45165
|
return [];
|
|
45049
45166
|
}
|
|
45050
|
-
const content = await readFile3(memoryFile, "utf-8").catch(() => "");
|
|
45051
45167
|
if (!content) return [];
|
|
45052
45168
|
const lines = content.split("\n");
|
|
45053
45169
|
const lineCount = lines.length;
|
|
45054
|
-
const byteSize =
|
|
45170
|
+
const byteSize = Buffer.byteLength(content, "utf8");
|
|
45055
45171
|
const issues = [];
|
|
45056
45172
|
if (lineCount > MAX_LINES) {
|
|
45057
45173
|
const excess = lineCount - MAX_LINES;
|
|
@@ -45084,6 +45200,7 @@ var init_memory_index_overflow = __esm({
|
|
|
45084
45200
|
"src/core/checks/session/memory-index-overflow.ts"() {
|
|
45085
45201
|
"use strict";
|
|
45086
45202
|
init_session_parser();
|
|
45203
|
+
init_fs();
|
|
45087
45204
|
MAX_LINES = 200;
|
|
45088
45205
|
MAX_BYTES = 25 * 1024;
|
|
45089
45206
|
}
|
|
@@ -45110,7 +45227,7 @@ async function findReleaseWorkflows(projectRoot) {
|
|
|
45110
45227
|
continue;
|
|
45111
45228
|
}
|
|
45112
45229
|
try {
|
|
45113
|
-
const content = await readFile4(join9(workflowDir, f), "utf-8");
|
|
45230
|
+
const content = stripBom(await readFile4(join9(workflowDir, f), "utf-8"));
|
|
45114
45231
|
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
45115
45232
|
if (nameMatch && RELEASE_FILENAME_PATTERNS.some((p2) => p2.test(nameMatch[1]))) {
|
|
45116
45233
|
releaseWorkflows.push(f);
|
|
@@ -45147,6 +45264,7 @@ var RELEASE_FILENAME_PATTERNS, RELEASE_DOC_PATTERNS;
|
|
|
45147
45264
|
var init_ci_coverage = __esm({
|
|
45148
45265
|
"src/core/checks/ci-coverage.ts"() {
|
|
45149
45266
|
"use strict";
|
|
45267
|
+
init_fs();
|
|
45150
45268
|
RELEASE_FILENAME_PATTERNS = [/release/i, /deploy/i, /publish/i, /\bcd\b/i];
|
|
45151
45269
|
RELEASE_DOC_PATTERNS = [
|
|
45152
45270
|
/release\s+(process|workflow|steps|via|by|using)/i,
|
|
@@ -45181,7 +45299,7 @@ async function findSecretUsages(projectRoot) {
|
|
|
45181
45299
|
if (!(f.endsWith(".yml") || f.endsWith(".yaml"))) continue;
|
|
45182
45300
|
let content;
|
|
45183
45301
|
try {
|
|
45184
|
-
content = await readFile5(join10(workflowDir, f), "utf-8");
|
|
45302
|
+
content = stripBom(await readFile5(join10(workflowDir, f), "utf-8"));
|
|
45185
45303
|
} catch {
|
|
45186
45304
|
continue;
|
|
45187
45305
|
}
|
|
@@ -45198,7 +45316,7 @@ async function findSecretUsages(projectRoot) {
|
|
|
45198
45316
|
}
|
|
45199
45317
|
function contextMentionsSecret(files, secretName) {
|
|
45200
45318
|
const escaped = secretName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45201
|
-
const pattern = new RegExp(`\\b${escaped.replace(/_/g, "[_
|
|
45319
|
+
const pattern = new RegExp(`\\b${escaped.replace(/_/g, "[ _-]")}\\b`, "i");
|
|
45202
45320
|
return files.some((f) => pattern.test(f.content));
|
|
45203
45321
|
}
|
|
45204
45322
|
async function checkCiSecrets(files, projectRoot) {
|
|
@@ -45222,6 +45340,7 @@ var BUILTIN_SECRETS, SECRETS_REGEX;
|
|
|
45222
45340
|
var init_ci_secrets = __esm({
|
|
45223
45341
|
"src/core/checks/ci-secrets.ts"() {
|
|
45224
45342
|
"use strict";
|
|
45343
|
+
init_fs();
|
|
45225
45344
|
BUILTIN_SECRETS = /* @__PURE__ */ new Set([
|
|
45226
45345
|
"GITHUB_TOKEN",
|
|
45227
45346
|
"ACTIONS_RUNTIME_TOKEN",
|
|
@@ -45242,7 +45361,7 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
45242
45361
|
import { resolve as resolve8, dirname as dirname3 } from "node:path";
|
|
45243
45362
|
import { fileURLToPath } from "node:url";
|
|
45244
45363
|
function loadVersion() {
|
|
45245
|
-
if (true) return "0.9.
|
|
45364
|
+
if (true) return "0.9.20";
|
|
45246
45365
|
const __dir = dirname3(fileURLToPath(import.meta.url));
|
|
45247
45366
|
const pkgPath = resolve8(__dir, "../package.json");
|
|
45248
45367
|
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
@@ -45266,6 +45385,11 @@ function hasMcphChecks(checks) {
|
|
|
45266
45385
|
function hasSessionChecks(checks) {
|
|
45267
45386
|
return checks.some((c3) => c3.startsWith("session-"));
|
|
45268
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
|
+
}
|
|
45269
45393
|
async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
45270
45394
|
const fileResults = [];
|
|
45271
45395
|
const shouldRunContextChecks = !options.mcpOnly && !options.mcphOnly && !options.sessionOnly;
|
|
@@ -45346,8 +45470,12 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45346
45470
|
);
|
|
45347
45471
|
mcpConfigs.push(...globalConfigs);
|
|
45348
45472
|
}
|
|
45349
|
-
const
|
|
45350
|
-
|
|
45473
|
+
const mcpChecksToRun = deriveChecksToRun(
|
|
45474
|
+
activeChecks,
|
|
45475
|
+
"mcp-",
|
|
45476
|
+
Boolean(options.mcp || options.mcpGlobal || options.mcpOnly),
|
|
45477
|
+
ALL_MCP_CHECKS
|
|
45478
|
+
);
|
|
45351
45479
|
for (const config2 of mcpConfigs) {
|
|
45352
45480
|
const checkPromises = [];
|
|
45353
45481
|
if (mcpChecksToRun.includes("mcp-schema"))
|
|
@@ -45393,7 +45521,7 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45393
45521
|
const projectMcphFiles = await scanForMcphConfigs(projectRoot);
|
|
45394
45522
|
const mcphConfigs = await Promise.all(
|
|
45395
45523
|
projectMcphFiles.map(
|
|
45396
|
-
(f) =>
|
|
45524
|
+
(f) => parseMcphConfig(
|
|
45397
45525
|
f,
|
|
45398
45526
|
projectRoot,
|
|
45399
45527
|
f.relativePath.endsWith(".mcph.local.json") ? "project-local" : "project"
|
|
@@ -45403,12 +45531,16 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45403
45531
|
if (options.mcphGlobal) {
|
|
45404
45532
|
const globalMcphFiles = await scanGlobalMcphConfigs();
|
|
45405
45533
|
const globalMcphConfigs = await Promise.all(
|
|
45406
|
-
globalMcphFiles.map((f) =>
|
|
45534
|
+
globalMcphFiles.map((f) => parseMcphConfig(f, projectRoot, "global"))
|
|
45407
45535
|
);
|
|
45408
45536
|
mcphConfigs.push(...globalMcphConfigs);
|
|
45409
45537
|
}
|
|
45410
|
-
const
|
|
45411
|
-
|
|
45538
|
+
const mcphChecksToRun = deriveChecksToRun(
|
|
45539
|
+
activeChecks,
|
|
45540
|
+
"mcph-",
|
|
45541
|
+
Boolean(options.mcph || options.mcphGlobal || options.mcphOnly),
|
|
45542
|
+
ALL_MCPH_CHECKS
|
|
45543
|
+
);
|
|
45412
45544
|
for (const config2 of mcphConfigs) {
|
|
45413
45545
|
const checkPromises = [];
|
|
45414
45546
|
if (mcphChecksToRun.includes("mcph-token-security")) {
|
|
@@ -45432,14 +45564,16 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45432
45564
|
}
|
|
45433
45565
|
const results = await Promise.all(checkPromises);
|
|
45434
45566
|
const issues = results.flat();
|
|
45435
|
-
|
|
45436
|
-
|
|
45437
|
-
|
|
45438
|
-
|
|
45439
|
-
|
|
45440
|
-
|
|
45441
|
-
|
|
45442
|
-
|
|
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
|
+
}
|
|
45443
45577
|
}
|
|
45444
45578
|
const lines = config2.content.split("\n").length;
|
|
45445
45579
|
fileResults.push({
|
|
@@ -45475,15 +45609,13 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45475
45609
|
sessionPromises.push(checkMemoryIndexOverflow(sessionCtx));
|
|
45476
45610
|
const sessionResults = await Promise.all(sessionPromises);
|
|
45477
45611
|
const sessionIssues = sessionResults.flat();
|
|
45478
|
-
|
|
45479
|
-
|
|
45480
|
-
|
|
45481
|
-
|
|
45482
|
-
|
|
45483
|
-
|
|
45484
|
-
|
|
45485
|
-
});
|
|
45486
|
-
}
|
|
45612
|
+
fileResults.push({
|
|
45613
|
+
path: "~/.claude/ (session audit)",
|
|
45614
|
+
isSymlink: false,
|
|
45615
|
+
tokens: 0,
|
|
45616
|
+
lines: 0,
|
|
45617
|
+
issues: sessionIssues
|
|
45618
|
+
});
|
|
45487
45619
|
}
|
|
45488
45620
|
}
|
|
45489
45621
|
let estimatedWaste = 0;
|
|
@@ -46190,6 +46322,8 @@ function applyFixes(result, options = {}) {
|
|
|
46190
46322
|
existing.push(fix);
|
|
46191
46323
|
fixesByLine.set(fix.line, existing);
|
|
46192
46324
|
}
|
|
46325
|
+
let perFileFixCount = 0;
|
|
46326
|
+
const perFileLogs = [];
|
|
46193
46327
|
for (const [lineNum, lineFixes] of fixesByLine) {
|
|
46194
46328
|
const lineIdx = lineNum - 1;
|
|
46195
46329
|
if (lineIdx < 0 || lineIdx >= lines.length) continue;
|
|
@@ -46197,9 +46331,9 @@ function applyFixes(result, options = {}) {
|
|
|
46197
46331
|
for (const fix of lineFixes) {
|
|
46198
46332
|
if (line.includes(fix.oldText)) {
|
|
46199
46333
|
line = line.replaceAll(fix.oldText, fix.newText);
|
|
46200
|
-
|
|
46334
|
+
perFileFixCount++;
|
|
46201
46335
|
const prefix = dryRun ? source_default.cyan(" Would fix") : source_default.green(" Fixed");
|
|
46202
|
-
|
|
46336
|
+
perFileLogs.push(
|
|
46203
46337
|
prefix + ` Line ${fix.line}: ${source_default.dim(fix.oldText)} ${source_default.dim("->")} ${fix.newText}`
|
|
46204
46338
|
);
|
|
46205
46339
|
}
|
|
@@ -46223,6 +46357,8 @@ function applyFixes(result, options = {}) {
|
|
|
46223
46357
|
fs7.writeFileSync(filePath, newContent, "utf-8");
|
|
46224
46358
|
}
|
|
46225
46359
|
filesModified.push(filePath);
|
|
46360
|
+
totalFixes += perFileFixCount;
|
|
46361
|
+
for (const m of perFileLogs) log(m);
|
|
46226
46362
|
}
|
|
46227
46363
|
}
|
|
46228
46364
|
return { totalFixes, filesModified };
|
|
@@ -46236,11 +46372,22 @@ var init_fixer = __esm({
|
|
|
46236
46372
|
|
|
46237
46373
|
// src/mcp/server.ts
|
|
46238
46374
|
var server_exports = {};
|
|
46375
|
+
__export(server_exports, {
|
|
46376
|
+
server: () => server,
|
|
46377
|
+
startServer: () => startServer
|
|
46378
|
+
});
|
|
46239
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
|
+
}
|
|
46240
46385
|
function validateProjectPath(rawPath) {
|
|
46241
46386
|
if (!rawPath) return process.cwd();
|
|
46242
46387
|
if (PATH_DISALLOWED.test(rawPath)) {
|
|
46243
|
-
throw new Error(
|
|
46388
|
+
throw new Error(
|
|
46389
|
+
`projectPath contains disallowed character ${describeDisallowed(rawPath)} (control chars and shell metacharacters are rejected)`
|
|
46390
|
+
);
|
|
46244
46391
|
}
|
|
46245
46392
|
const resolved = path10.resolve(rawPath);
|
|
46246
46393
|
if (!isDirectory(resolved)) {
|
|
@@ -46250,12 +46397,28 @@ function validateProjectPath(rawPath) {
|
|
|
46250
46397
|
}
|
|
46251
46398
|
function validateFilePathInput(rawPath) {
|
|
46252
46399
|
if (PATH_DISALLOWED.test(rawPath)) {
|
|
46253
|
-
throw new Error(
|
|
46400
|
+
throw new Error(
|
|
46401
|
+
`path contains disallowed character ${describeDisallowed(rawPath)} (control chars and shell metacharacters are rejected)`
|
|
46402
|
+
);
|
|
46254
46403
|
}
|
|
46255
46404
|
}
|
|
46256
|
-
|
|
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;
|
|
46257
46420
|
var init_server3 = __esm({
|
|
46258
|
-
|
|
46421
|
+
"src/mcp/server.ts"() {
|
|
46259
46422
|
"use strict";
|
|
46260
46423
|
init_mcp();
|
|
46261
46424
|
init_stdio2();
|
|
@@ -46272,6 +46435,7 @@ var init_server3 = __esm({
|
|
|
46272
46435
|
init_version2();
|
|
46273
46436
|
contextCheckEnum = external_exports3.enum(ALL_CHECKS);
|
|
46274
46437
|
mcpCheckEnum = external_exports3.enum(ALL_MCP_CHECKS);
|
|
46438
|
+
mcphCheckEnum = external_exports3.enum(ALL_MCPH_CHECKS);
|
|
46275
46439
|
sessionCheckEnum = external_exports3.enum(ALL_SESSION_CHECKS);
|
|
46276
46440
|
PATH_DISALLOWED = /[\n\r\t;`|]|\$\(|\$\{/;
|
|
46277
46441
|
server = new McpServer({
|
|
@@ -46328,7 +46492,7 @@ var init_server3 = __esm({
|
|
|
46328
46492
|
try {
|
|
46329
46493
|
validateFilePathInput(filePath);
|
|
46330
46494
|
const root = validateProjectPath(projectPath);
|
|
46331
|
-
const resolved =
|
|
46495
|
+
const resolved = resolveWithinRoot(filePath, root);
|
|
46332
46496
|
const result = {
|
|
46333
46497
|
path: filePath,
|
|
46334
46498
|
exists: fileExists(resolved) || isDirectory(resolved)
|
|
@@ -46493,6 +46657,48 @@ var init_server3 = __esm({
|
|
|
46493
46657
|
}
|
|
46494
46658
|
}
|
|
46495
46659
|
);
|
|
46660
|
+
server.tool(
|
|
46661
|
+
"ctxlint_mcph_audit",
|
|
46662
|
+
"Lint .mcph.json (the @yawlabs/mcph CLI config) files. Checks for PAT (mcp_pat_*) leakage in git-tracked project-scope files, environment-variable posture, plaintext HTTP apiBase to public hosts, schema drift, allow/deny list conflicts and duplicates, and machine-local override files not covered by .gitignore. Distinct from ctxlint_mcp_audit, which lints client-side .mcp.json server lists.",
|
|
46663
|
+
{
|
|
46664
|
+
projectPath: external_exports3.string().optional().describe("Path to the project root. Defaults to current working directory."),
|
|
46665
|
+
checks: external_exports3.array(mcphCheckEnum).optional().describe("Specific mcph checks to run (default: all mcph-* checks)."),
|
|
46666
|
+
includeGlobal: external_exports3.boolean().optional().describe("Also scan ~/.mcph.json (user-global config)."),
|
|
46667
|
+
strictEnvToken: external_exports3.boolean().optional().describe(
|
|
46668
|
+
"Upgrade mcph-config/prefer-env-token from warning to error (env-var-only posture)."
|
|
46669
|
+
)
|
|
46670
|
+
},
|
|
46671
|
+
{
|
|
46672
|
+
readOnlyHint: true,
|
|
46673
|
+
destructiveHint: false,
|
|
46674
|
+
idempotentHint: true,
|
|
46675
|
+
openWorldHint: false
|
|
46676
|
+
},
|
|
46677
|
+
async ({ projectPath, checks, includeGlobal, strictEnvToken }) => {
|
|
46678
|
+
try {
|
|
46679
|
+
const root = validateProjectPath(projectPath);
|
|
46680
|
+
const activeChecks = checks?.length ? checks : ALL_MCPH_CHECKS;
|
|
46681
|
+
const result = await runAudit(root, activeChecks, {
|
|
46682
|
+
mcph: true,
|
|
46683
|
+
mcphOnly: true,
|
|
46684
|
+
mcphGlobal: includeGlobal || false,
|
|
46685
|
+
mcphStrictEnvToken: strictEnvToken || false
|
|
46686
|
+
});
|
|
46687
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
46688
|
+
} catch (err) {
|
|
46689
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
46690
|
+
return {
|
|
46691
|
+
content: [{ type: "text", text: JSON.stringify({ error: msg }) }],
|
|
46692
|
+
isError: true
|
|
46693
|
+
};
|
|
46694
|
+
} finally {
|
|
46695
|
+
freeEncoder();
|
|
46696
|
+
resetGit();
|
|
46697
|
+
resetPathsCache();
|
|
46698
|
+
resetPackageJsonCache();
|
|
46699
|
+
}
|
|
46700
|
+
}
|
|
46701
|
+
);
|
|
46496
46702
|
server.tool(
|
|
46497
46703
|
"ctxlint_session_audit",
|
|
46498
46704
|
"Audit AI agent session data for cross-project consistency. Checks for missing GitHub secrets, diverged config files, missing workflows, stale memory entries, and duplicate memories across sibling repositories.",
|
|
@@ -46529,9 +46735,6 @@ var init_server3 = __esm({
|
|
|
46529
46735
|
}
|
|
46530
46736
|
}
|
|
46531
46737
|
);
|
|
46532
|
-
keepEncoderAlive(true);
|
|
46533
|
-
transport = new StdioServerTransport();
|
|
46534
|
-
await server.connect(transport);
|
|
46535
46738
|
}
|
|
46536
46739
|
});
|
|
46537
46740
|
|
|
@@ -53008,6 +53211,26 @@ var init_ora = __esm({
|
|
|
53008
53211
|
});
|
|
53009
53212
|
|
|
53010
53213
|
// src/core/reporter.ts
|
|
53214
|
+
function classifyFile(f) {
|
|
53215
|
+
if (f.path === "(project)") return "context";
|
|
53216
|
+
if (f.path === "(mcp)") return "mcp";
|
|
53217
|
+
if (f.path === "(mcph)") return "mcph";
|
|
53218
|
+
if (f.path.includes("session audit")) return "session";
|
|
53219
|
+
for (const issue2 of f.issues) {
|
|
53220
|
+
if (issue2.check.startsWith("session-")) return "session";
|
|
53221
|
+
if (issue2.check.startsWith("mcph-")) return "mcph";
|
|
53222
|
+
if (issue2.check.startsWith("mcp-")) return "mcp";
|
|
53223
|
+
}
|
|
53224
|
+
const norm = f.path.replace(/\\/g, "/");
|
|
53225
|
+
if (norm.endsWith(".mcph.json") || norm.endsWith(".mcph.local.json")) return "mcph";
|
|
53226
|
+
if (norm === ".mcp.json" || norm.endsWith("/.mcp.json") || norm.endsWith("/mcp.json") || norm.includes("/mcpServers/") || norm.endsWith("/.claude.json") || norm.endsWith(".claude/settings.json") || norm.endsWith("claude_desktop_config.json")) {
|
|
53227
|
+
return "mcp";
|
|
53228
|
+
}
|
|
53229
|
+
return "context";
|
|
53230
|
+
}
|
|
53231
|
+
function isSyntheticBucket(p2) {
|
|
53232
|
+
return p2.startsWith("(") || p2.includes("session audit");
|
|
53233
|
+
}
|
|
53011
53234
|
function formatText(result, verbose = false) {
|
|
53012
53235
|
const lines = [];
|
|
53013
53236
|
lines.push("");
|
|
@@ -53015,30 +53238,45 @@ function formatText(result, verbose = false) {
|
|
|
53015
53238
|
lines.push("");
|
|
53016
53239
|
lines.push(`Scanning ${result.projectRoot}...`);
|
|
53017
53240
|
lines.push("");
|
|
53018
|
-
const
|
|
53019
|
-
|
|
53020
|
-
|
|
53241
|
+
const groups = {
|
|
53242
|
+
context: [],
|
|
53243
|
+
mcp: [],
|
|
53244
|
+
mcph: [],
|
|
53245
|
+
session: []
|
|
53246
|
+
};
|
|
53247
|
+
for (const f of result.files) {
|
|
53248
|
+
groups[classifyFile(f)].push(f);
|
|
53249
|
+
}
|
|
53021
53250
|
const totalTokens = result.summary.totalTokens;
|
|
53022
|
-
|
|
53251
|
+
let renderedAnySummary = false;
|
|
53252
|
+
const contextReal = groups.context.filter((f) => !isSyntheticBucket(f.path));
|
|
53253
|
+
if (contextReal.length > 0) {
|
|
53023
53254
|
lines.push(
|
|
53024
|
-
`Found ${
|
|
53255
|
+
`Found ${contextReal.length} context file${contextReal.length !== 1 ? "s" : ""} (${totalTokens.toLocaleString()} tokens total)`
|
|
53025
53256
|
);
|
|
53026
|
-
for (const file2 of
|
|
53257
|
+
for (const file2 of contextReal) {
|
|
53027
53258
|
let desc = ` ${file2.path} (${file2.tokens.toLocaleString()} tokens, ${file2.lines} lines)`;
|
|
53028
53259
|
if (file2.isSymlink && file2.symlinkTarget) {
|
|
53029
53260
|
desc = ` ${file2.path} ${source_default.dim(`-> ${file2.symlinkTarget} (symlink)`)}`;
|
|
53030
53261
|
}
|
|
53031
53262
|
lines.push(desc);
|
|
53032
53263
|
}
|
|
53264
|
+
renderedAnySummary = true;
|
|
53033
53265
|
}
|
|
53034
|
-
|
|
53035
|
-
|
|
53036
|
-
|
|
53037
|
-
|
|
53038
|
-
|
|
53039
|
-
}
|
|
53266
|
+
for (const g of ["mcp", "mcph"]) {
|
|
53267
|
+
const real = groups[g].filter((f) => !isSyntheticBucket(f.path));
|
|
53268
|
+
if (real.length === 0) continue;
|
|
53269
|
+
if (renderedAnySummary) lines.push("");
|
|
53270
|
+
lines.push(`Found ${real.length} ${GROUP_SUMMARY_NOUNS[g]}${real.length !== 1 ? "s" : ""}`);
|
|
53271
|
+
for (const file2 of real) lines.push(` ${file2.path}`);
|
|
53272
|
+
renderedAnySummary = true;
|
|
53040
53273
|
}
|
|
53041
|
-
if (
|
|
53274
|
+
if (groups.session.length > 0) {
|
|
53275
|
+
if (renderedAnySummary) lines.push("");
|
|
53276
|
+
lines.push("Session audit scanned");
|
|
53277
|
+
renderedAnySummary = true;
|
|
53278
|
+
}
|
|
53279
|
+
if (!renderedAnySummary) {
|
|
53042
53280
|
lines.push(`Found ${result.files.length} file${result.files.length !== 1 ? "s" : ""}`);
|
|
53043
53281
|
}
|
|
53044
53282
|
lines.push("");
|
|
@@ -53057,16 +53295,13 @@ function formatText(result, verbose = false) {
|
|
|
53057
53295
|
lines.push("");
|
|
53058
53296
|
}
|
|
53059
53297
|
};
|
|
53060
|
-
|
|
53061
|
-
|
|
53062
|
-
|
|
53063
|
-
|
|
53064
|
-
|
|
53065
|
-
|
|
53066
|
-
|
|
53067
|
-
if (mcpWithIssues.length > 0) {
|
|
53068
|
-
lines.push(source_default.bold("MCP Configs"));
|
|
53069
|
-
renderFileGroup(mcpFiles);
|
|
53298
|
+
const groupsToRender = GROUP_ORDER.filter(
|
|
53299
|
+
(g) => groups[g].some((f) => f.issues.length > 0 || verbose)
|
|
53300
|
+
);
|
|
53301
|
+
if (groupsToRender.length > 1) {
|
|
53302
|
+
for (const g of groupsToRender) {
|
|
53303
|
+
lines.push(source_default.bold(GROUP_LABELS[g]));
|
|
53304
|
+
renderFileGroup(groups[g]);
|
|
53070
53305
|
}
|
|
53071
53306
|
} else {
|
|
53072
53307
|
renderFileGroup(result.files);
|
|
@@ -53282,6 +53517,31 @@ function buildRuleDescriptors() {
|
|
|
53282
53517
|
shortDescription: { text: "Redundant MCP config entry" },
|
|
53283
53518
|
helpUri: "https://github.com/yawlabs/ctxlint#mcp-config-linting"
|
|
53284
53519
|
},
|
|
53520
|
+
{
|
|
53521
|
+
id: "ctxlint/mcph-token-security",
|
|
53522
|
+
shortDescription: { text: "mcp.hosting PAT leakage or env-var posture" },
|
|
53523
|
+
helpUri: "https://github.com/yawlabs/ctxlint#mcph-config-linting"
|
|
53524
|
+
},
|
|
53525
|
+
{
|
|
53526
|
+
id: "ctxlint/mcph-apibase",
|
|
53527
|
+
shortDescription: { text: "mcph apiBase URL validation" },
|
|
53528
|
+
helpUri: "https://github.com/yawlabs/ctxlint#mcph-config-linting"
|
|
53529
|
+
},
|
|
53530
|
+
{
|
|
53531
|
+
id: "ctxlint/mcph-schema-conformance",
|
|
53532
|
+
shortDescription: { text: "mcph config unknown field or stale schema version" },
|
|
53533
|
+
helpUri: "https://github.com/yawlabs/ctxlint#mcph-config-linting"
|
|
53534
|
+
},
|
|
53535
|
+
{
|
|
53536
|
+
id: "ctxlint/mcph-lists",
|
|
53537
|
+
shortDescription: { text: "mcph allow/deny list conflict or duplicate entry" },
|
|
53538
|
+
helpUri: "https://github.com/yawlabs/ctxlint#mcph-config-linting"
|
|
53539
|
+
},
|
|
53540
|
+
{
|
|
53541
|
+
id: "ctxlint/mcph-gitignore",
|
|
53542
|
+
shortDescription: { text: "mcph machine-local file not covered by .gitignore" },
|
|
53543
|
+
helpUri: "https://github.com/yawlabs/ctxlint#mcph-config-linting"
|
|
53544
|
+
},
|
|
53285
53545
|
{
|
|
53286
53546
|
id: "ctxlint/session-missing-secret",
|
|
53287
53547
|
shortDescription: { text: "GitHub secret set on sibling repos but missing here" },
|
|
@@ -53335,10 +53595,24 @@ function formatIssue(issue2) {
|
|
|
53335
53595
|
}
|
|
53336
53596
|
return line;
|
|
53337
53597
|
}
|
|
53598
|
+
var GROUP_ORDER, GROUP_LABELS, GROUP_SUMMARY_NOUNS;
|
|
53338
53599
|
var init_reporter = __esm({
|
|
53339
53600
|
"src/core/reporter.ts"() {
|
|
53340
53601
|
"use strict";
|
|
53341
53602
|
init_source();
|
|
53603
|
+
GROUP_ORDER = ["context", "mcp", "mcph", "session"];
|
|
53604
|
+
GROUP_LABELS = {
|
|
53605
|
+
context: "Context Files",
|
|
53606
|
+
mcp: "MCP Configs",
|
|
53607
|
+
mcph: "mcph Configs",
|
|
53608
|
+
session: "Session Audit"
|
|
53609
|
+
};
|
|
53610
|
+
GROUP_SUMMARY_NOUNS = {
|
|
53611
|
+
context: "context file",
|
|
53612
|
+
mcp: "MCP config",
|
|
53613
|
+
mcph: "mcph config",
|
|
53614
|
+
session: "session audit"
|
|
53615
|
+
};
|
|
53342
53616
|
}
|
|
53343
53617
|
});
|
|
53344
53618
|
|
|
@@ -53437,7 +53711,7 @@ function loadConfig(projectRoot) {
|
|
|
53437
53711
|
const filePath = path11.join(projectRoot, filename);
|
|
53438
53712
|
let content;
|
|
53439
53713
|
try {
|
|
53440
|
-
content = fs8.readFileSync(filePath, "utf-8");
|
|
53714
|
+
content = stripBom(fs8.readFileSync(filePath, "utf-8"));
|
|
53441
53715
|
} catch {
|
|
53442
53716
|
continue;
|
|
53443
53717
|
}
|
|
@@ -53448,7 +53722,7 @@ function loadConfig(projectRoot) {
|
|
|
53448
53722
|
function loadConfigFromExplicitPath(configPath) {
|
|
53449
53723
|
let content;
|
|
53450
53724
|
try {
|
|
53451
|
-
content = fs8.readFileSync(configPath, "utf-8");
|
|
53725
|
+
content = stripBom(fs8.readFileSync(configPath, "utf-8"));
|
|
53452
53726
|
} catch (err) {
|
|
53453
53727
|
const detail = err instanceof Error ? err.message : String(err);
|
|
53454
53728
|
throw new Error(`could not load config from ${configPath}: ${detail}`, { cause: err });
|
|
@@ -53460,6 +53734,7 @@ var init_config2 = __esm({
|
|
|
53460
53734
|
"src/core/config.ts"() {
|
|
53461
53735
|
"use strict";
|
|
53462
53736
|
import_fast_levenshtein2 = __toESM(require_levenshtein(), 1);
|
|
53737
|
+
init_fs();
|
|
53463
53738
|
levenshtein2 = import_fast_levenshtein2.default.get;
|
|
53464
53739
|
KNOWN_CONFIG_KEYS = [
|
|
53465
53740
|
"checks",
|
|
@@ -53511,74 +53786,7 @@ async function runCli() {
|
|
|
53511
53786
|
false
|
|
53512
53787
|
).option("--session", "Run session audit checks (cross-project consistency)", false).option("--session-only", "Run only session checks, skip context and MCP checks", false).option("--watch", "Re-lint on context file changes", false).action(async (projectPath, opts) => {
|
|
53513
53788
|
const resolvedPath = path12.resolve(projectPath);
|
|
53514
|
-
const
|
|
53515
|
-
const config2 = configPath ? loadConfigFromPath(configPath) : loadConfig(resolvedPath);
|
|
53516
|
-
const mcpGlobal = opts.mcpGlobal || config2?.mcpGlobal || false;
|
|
53517
|
-
const mcpOnly = opts.mcpOnly || config2?.mcpOnly || false;
|
|
53518
|
-
const mcpFlag = opts.mcp || mcpGlobal || mcpOnly || config2?.mcp || false;
|
|
53519
|
-
const mcphGlobal = opts.mcphGlobal || config2?.mcphGlobal || false;
|
|
53520
|
-
const mcphOnly = opts.mcphOnly || config2?.mcphOnly || false;
|
|
53521
|
-
const mcphFlag = opts.mcph || mcphGlobal || mcphOnly || config2?.mcph || false;
|
|
53522
|
-
const mcphStrictEnvToken = opts.mcphStrictEnvToken || config2?.mcphStrictEnvToken || false;
|
|
53523
|
-
const sessionOnly = opts.sessionOnly || config2?.sessionOnly || false;
|
|
53524
|
-
const sessionFlag = opts.session || sessionOnly || config2?.session || false;
|
|
53525
|
-
let explicitChecks = opts.checks ? validateCheckNames(
|
|
53526
|
-
opts.checks.split(",").map((c3) => c3.trim()),
|
|
53527
|
-
"--checks"
|
|
53528
|
-
) : null;
|
|
53529
|
-
if (explicitChecks?.length === 0) explicitChecks = null;
|
|
53530
|
-
const hasMcpInChecks = explicitChecks?.some((c3) => c3.startsWith("mcp-")) || false;
|
|
53531
|
-
const hasMcphInChecks = explicitChecks?.some((c3) => c3.startsWith("mcph-")) || false;
|
|
53532
|
-
const hasSessionInChecks = explicitChecks?.some((c3) => c3.startsWith("session-")) || false;
|
|
53533
|
-
const effectiveMcp = mcpFlag || hasMcpInChecks;
|
|
53534
|
-
const effectiveMcph = mcphFlag || hasMcphInChecks;
|
|
53535
|
-
const effectiveSession = sessionFlag || sessionOnly || hasSessionInChecks;
|
|
53536
|
-
let checks;
|
|
53537
|
-
if (explicitChecks) {
|
|
53538
|
-
checks = explicitChecks;
|
|
53539
|
-
} else if (sessionOnly) {
|
|
53540
|
-
checks = ALL_SESSION_CHECKS;
|
|
53541
|
-
} else if (mcpOnly) {
|
|
53542
|
-
checks = ALL_MCP_CHECKS;
|
|
53543
|
-
} else if (mcphOnly) {
|
|
53544
|
-
checks = ALL_MCPH_CHECKS;
|
|
53545
|
-
} else {
|
|
53546
|
-
const base = config2?.checks || ALL_CHECKS;
|
|
53547
|
-
checks = [
|
|
53548
|
-
...base,
|
|
53549
|
-
...effectiveMcp ? ALL_MCP_CHECKS : [],
|
|
53550
|
-
...effectiveMcph ? ALL_MCPH_CHECKS : [],
|
|
53551
|
-
...effectiveSession ? ALL_SESSION_CHECKS : []
|
|
53552
|
-
];
|
|
53553
|
-
}
|
|
53554
|
-
const options = {
|
|
53555
|
-
projectPath: resolvedPath,
|
|
53556
|
-
checks,
|
|
53557
|
-
strict: opts.strict || config2?.strict || false,
|
|
53558
|
-
format: opts.format,
|
|
53559
|
-
verbose: opts.verbose,
|
|
53560
|
-
fix: opts.fix,
|
|
53561
|
-
ignore: opts.ignore ? validateCheckNames(
|
|
53562
|
-
opts.ignore.split(",").map((c3) => c3.trim()),
|
|
53563
|
-
"--ignore"
|
|
53564
|
-
) : config2?.ignore || [],
|
|
53565
|
-
tokensOnly: opts.tokens,
|
|
53566
|
-
quiet: opts.quiet,
|
|
53567
|
-
depth: Math.max(0, Math.min(parseInt(opts.depth, 10) || 2, 10)),
|
|
53568
|
-
mcp: effectiveMcp,
|
|
53569
|
-
mcpOnly,
|
|
53570
|
-
mcpGlobal,
|
|
53571
|
-
mcph: effectiveMcph,
|
|
53572
|
-
mcphOnly,
|
|
53573
|
-
mcphGlobal,
|
|
53574
|
-
mcphStrictEnvToken,
|
|
53575
|
-
session: effectiveSession,
|
|
53576
|
-
sessionOnly
|
|
53577
|
-
};
|
|
53578
|
-
if (config2?.tokenThresholds) {
|
|
53579
|
-
setTokenThresholds(config2.tokenThresholds);
|
|
53580
|
-
}
|
|
53581
|
-
const activeChecks = options.checks.filter((c3) => !options.ignore.includes(c3));
|
|
53789
|
+
const { config: config2, options, activeChecks } = resolveSession(resolvedPath, opts);
|
|
53582
53790
|
const spinner = options.format === "text" && !options.quiet ? ora("Scanning for context files...").start() : void 0;
|
|
53583
53791
|
try {
|
|
53584
53792
|
if (spinner) spinner.text = "Running checks...";
|
|
@@ -53706,7 +53914,16 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
53706
53914
|
path12.join(resolvedPath, "GEMINI.md"),
|
|
53707
53915
|
path12.join(resolvedPath, "replit.md"),
|
|
53708
53916
|
path12.join(resolvedPath, "package.json"),
|
|
53709
|
-
path12.join(resolvedPath, ".mcp.json")
|
|
53917
|
+
path12.join(resolvedPath, ".mcp.json"),
|
|
53918
|
+
// mcph configs — without these, edits to the .mcph.json the user is
|
|
53919
|
+
// actively iterating on don't trigger a re-lint.
|
|
53920
|
+
path12.join(resolvedPath, ".mcph.json"),
|
|
53921
|
+
path12.join(resolvedPath, ".mcph.local.json"),
|
|
53922
|
+
// ctxlint config — re-resolve on edit so threshold changes,
|
|
53923
|
+
// ignore-list edits, and check-list overrides take effect without
|
|
53924
|
+
// restarting the watcher.
|
|
53925
|
+
path12.join(resolvedPath, ".ctxlintrc"),
|
|
53926
|
+
path12.join(resolvedPath, ".ctxlintrc.json")
|
|
53710
53927
|
];
|
|
53711
53928
|
const watchDirs = [
|
|
53712
53929
|
path12.join(resolvedPath, ".claude"),
|
|
@@ -53727,30 +53944,46 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
53727
53944
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
53728
53945
|
debounceTimer = setTimeout(async () => {
|
|
53729
53946
|
if (process.stdout.isTTY) console.clear();
|
|
53947
|
+
let liveConfig = config2;
|
|
53948
|
+
let liveOptions = options;
|
|
53949
|
+
let liveActiveChecks = activeChecks;
|
|
53950
|
+
try {
|
|
53951
|
+
const resolved = resolveSession(resolvedPath, opts);
|
|
53952
|
+
liveConfig = resolved.config;
|
|
53953
|
+
liveOptions = resolved.options;
|
|
53954
|
+
liveActiveChecks = resolved.activeChecks;
|
|
53955
|
+
} catch (err) {
|
|
53956
|
+
console.error("Error reloading config:", err instanceof Error ? err.message : err);
|
|
53957
|
+
}
|
|
53730
53958
|
try {
|
|
53731
|
-
|
|
53732
|
-
|
|
53733
|
-
|
|
53734
|
-
|
|
53735
|
-
|
|
53736
|
-
|
|
53737
|
-
|
|
53738
|
-
|
|
53739
|
-
|
|
53740
|
-
|
|
53741
|
-
|
|
53742
|
-
|
|
53959
|
+
if (liveConfig?.tokenThresholds) {
|
|
53960
|
+
setTokenThresholds(liveConfig.tokenThresholds);
|
|
53961
|
+
} else {
|
|
53962
|
+
resetTokenThresholds();
|
|
53963
|
+
}
|
|
53964
|
+
const result = await runAudit(resolvedPath, liveActiveChecks, {
|
|
53965
|
+
depth: liveOptions.depth,
|
|
53966
|
+
extraPatterns: liveConfig?.contextFiles,
|
|
53967
|
+
mcp: liveOptions.mcp,
|
|
53968
|
+
mcpGlobal: liveOptions.mcpGlobal,
|
|
53969
|
+
mcpOnly: liveOptions.mcpOnly,
|
|
53970
|
+
mcph: liveOptions.mcph,
|
|
53971
|
+
mcphGlobal: liveOptions.mcphGlobal,
|
|
53972
|
+
mcphOnly: liveOptions.mcphOnly,
|
|
53973
|
+
mcphStrictEnvToken: liveOptions.mcphStrictEnvToken,
|
|
53974
|
+
session: liveOptions.session,
|
|
53975
|
+
sessionOnly: liveOptions.sessionOnly
|
|
53743
53976
|
});
|
|
53744
53977
|
if (result.files.length === 0) {
|
|
53745
53978
|
console.log("\nNo context files found.\n");
|
|
53746
|
-
} else if (
|
|
53979
|
+
} else if (liveOptions.tokensOnly) {
|
|
53747
53980
|
console.log(formatTokenReport(result));
|
|
53748
|
-
} else if (
|
|
53981
|
+
} else if (liveOptions.format === "json") {
|
|
53749
53982
|
console.log(formatJson(result));
|
|
53750
|
-
} else if (
|
|
53983
|
+
} else if (liveOptions.format === "sarif") {
|
|
53751
53984
|
console.log(formatSarif(result));
|
|
53752
53985
|
} else {
|
|
53753
|
-
console.log(formatText(result,
|
|
53986
|
+
console.log(formatText(result, liveOptions.verbose));
|
|
53754
53987
|
}
|
|
53755
53988
|
} catch (err) {
|
|
53756
53989
|
console.error("Error:", err instanceof Error ? err.message : err);
|
|
@@ -53760,7 +53993,6 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
53760
53993
|
resetPathsCache();
|
|
53761
53994
|
resetPackageJsonCache();
|
|
53762
53995
|
resetTokenThresholds();
|
|
53763
|
-
if (config2?.tokenThresholds) setTokenThresholds(config2.tokenThresholds);
|
|
53764
53996
|
}
|
|
53765
53997
|
console.log(chalk2.dim("\nWatching for changes... (Ctrl+C to stop)\n"));
|
|
53766
53998
|
}, 300);
|
|
@@ -53839,6 +54071,77 @@ async function promptYesNo(question) {
|
|
|
53839
54071
|
rl.close();
|
|
53840
54072
|
}
|
|
53841
54073
|
}
|
|
54074
|
+
function resolveSession(resolvedPath, opts) {
|
|
54075
|
+
const configPath = opts.config ? path12.resolve(opts.config) : void 0;
|
|
54076
|
+
const config2 = configPath ? loadConfigFromPath(configPath) : loadConfig(resolvedPath);
|
|
54077
|
+
const mcpGlobal = opts.mcpGlobal || config2?.mcpGlobal || false;
|
|
54078
|
+
const mcpOnly = opts.mcpOnly || config2?.mcpOnly || false;
|
|
54079
|
+
const mcpFlag = opts.mcp || mcpGlobal || mcpOnly || config2?.mcp || false;
|
|
54080
|
+
const mcphGlobal = opts.mcphGlobal || config2?.mcphGlobal || false;
|
|
54081
|
+
const mcphOnly = opts.mcphOnly || config2?.mcphOnly || false;
|
|
54082
|
+
const mcphFlag = opts.mcph || mcphGlobal || mcphOnly || config2?.mcph || false;
|
|
54083
|
+
const mcphStrictEnvToken = opts.mcphStrictEnvToken || config2?.mcphStrictEnvToken || false;
|
|
54084
|
+
const sessionOnly = opts.sessionOnly || config2?.sessionOnly || false;
|
|
54085
|
+
const sessionFlag = opts.session || sessionOnly || config2?.session || false;
|
|
54086
|
+
let explicitChecks = opts.checks ? validateCheckNames(
|
|
54087
|
+
opts.checks.split(",").map((c3) => c3.trim()),
|
|
54088
|
+
"--checks"
|
|
54089
|
+
) : null;
|
|
54090
|
+
if (explicitChecks?.length === 0) explicitChecks = null;
|
|
54091
|
+
const hasMcpInChecks = explicitChecks?.some((c3) => c3.startsWith("mcp-") && !c3.startsWith("mcph-")) || false;
|
|
54092
|
+
const hasMcphInChecks = explicitChecks?.some((c3) => c3.startsWith("mcph-")) || false;
|
|
54093
|
+
const hasSessionInChecks = explicitChecks?.some((c3) => c3.startsWith("session-")) || false;
|
|
54094
|
+
const effectiveMcp = mcpFlag || hasMcpInChecks;
|
|
54095
|
+
const effectiveMcph = mcphFlag || hasMcphInChecks;
|
|
54096
|
+
const effectiveSession = sessionFlag || sessionOnly || hasSessionInChecks;
|
|
54097
|
+
let checks;
|
|
54098
|
+
if (explicitChecks) {
|
|
54099
|
+
checks = explicitChecks;
|
|
54100
|
+
} else if (sessionOnly) {
|
|
54101
|
+
checks = ALL_SESSION_CHECKS;
|
|
54102
|
+
} else if (mcpOnly) {
|
|
54103
|
+
checks = ALL_MCP_CHECKS;
|
|
54104
|
+
} else if (mcphOnly) {
|
|
54105
|
+
checks = ALL_MCPH_CHECKS;
|
|
54106
|
+
} else {
|
|
54107
|
+
const base = config2?.checks || ALL_CHECKS;
|
|
54108
|
+
checks = [
|
|
54109
|
+
...base,
|
|
54110
|
+
...effectiveMcp ? ALL_MCP_CHECKS : [],
|
|
54111
|
+
...effectiveMcph ? ALL_MCPH_CHECKS : [],
|
|
54112
|
+
...effectiveSession ? ALL_SESSION_CHECKS : []
|
|
54113
|
+
];
|
|
54114
|
+
}
|
|
54115
|
+
const options = {
|
|
54116
|
+
projectPath: resolvedPath,
|
|
54117
|
+
checks,
|
|
54118
|
+
strict: opts.strict || config2?.strict || false,
|
|
54119
|
+
format: opts.format,
|
|
54120
|
+
verbose: opts.verbose,
|
|
54121
|
+
fix: opts.fix,
|
|
54122
|
+
ignore: opts.ignore ? validateCheckNames(
|
|
54123
|
+
opts.ignore.split(",").map((c3) => c3.trim()),
|
|
54124
|
+
"--ignore"
|
|
54125
|
+
) : config2?.ignore || [],
|
|
54126
|
+
tokensOnly: opts.tokens,
|
|
54127
|
+
quiet: opts.quiet,
|
|
54128
|
+
depth: Math.max(0, Math.min(parseInt(opts.depth, 10) || 2, 10)),
|
|
54129
|
+
mcp: effectiveMcp,
|
|
54130
|
+
mcpOnly,
|
|
54131
|
+
mcpGlobal,
|
|
54132
|
+
mcph: effectiveMcph,
|
|
54133
|
+
mcphOnly,
|
|
54134
|
+
mcphGlobal,
|
|
54135
|
+
mcphStrictEnvToken,
|
|
54136
|
+
session: effectiveSession,
|
|
54137
|
+
sessionOnly
|
|
54138
|
+
};
|
|
54139
|
+
if (config2?.tokenThresholds) {
|
|
54140
|
+
setTokenThresholds(config2.tokenThresholds);
|
|
54141
|
+
}
|
|
54142
|
+
const activeChecks = options.checks.filter((c3) => !options.ignore.includes(c3));
|
|
54143
|
+
return { config: config2, options, activeChecks };
|
|
54144
|
+
}
|
|
53842
54145
|
function loadConfigFromPath(configPath) {
|
|
53843
54146
|
try {
|
|
53844
54147
|
return loadConfigFromExplicitPath(configPath);
|
|
@@ -53876,7 +54179,8 @@ var init_cli = __esm({
|
|
|
53876
54179
|
// src/index.ts
|
|
53877
54180
|
var args = process.argv.slice(2);
|
|
53878
54181
|
if (args[0] === "serve" || args.includes("--mcp-server")) {
|
|
53879
|
-
await
|
|
54182
|
+
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server3(), server_exports));
|
|
54183
|
+
await startServer2();
|
|
53880
54184
|
} else {
|
|
53881
54185
|
const { runCli: runCli2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
53882
54186
|
await runCli2();
|