@yawlabs/ctxlint 0.9.20 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +351 -109
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34452,6 +34452,8 @@ async function scanGlobalMcpConfigs() {
|
|
|
34452
34452
|
} catch {
|
|
34453
34453
|
continue;
|
|
34454
34454
|
}
|
|
34455
|
+
const isGeneralClaudeFile = normalized.endsWith(`${path2.sep}.claude.json`) || normalized.endsWith(`${path2.sep}.claude${path2.sep}settings.json`);
|
|
34456
|
+
if (isGeneralClaudeFile && !mcpFileHasMcpKey(normalized)) continue;
|
|
34455
34457
|
const symlink = isSymlink(normalized);
|
|
34456
34458
|
const target = symlink ? readSymlinkTarget(normalized) : void 0;
|
|
34457
34459
|
found.push({
|
|
@@ -34464,6 +34466,14 @@ async function scanGlobalMcpConfigs() {
|
|
|
34464
34466
|
}
|
|
34465
34467
|
return found.sort((a, b2) => a.relativePath.localeCompare(b2.relativePath));
|
|
34466
34468
|
}
|
|
34469
|
+
function mcpFileHasMcpKey(filePath) {
|
|
34470
|
+
try {
|
|
34471
|
+
const content = fs3.readFileSync(filePath, "utf8");
|
|
34472
|
+
return /"(mcpServers|servers)"\s*:/.test(content);
|
|
34473
|
+
} catch {
|
|
34474
|
+
return false;
|
|
34475
|
+
}
|
|
34476
|
+
}
|
|
34467
34477
|
var CONTEXT_FILE_PATTERNS, IGNORED_DIRS2, MCP_CONFIG_PATTERNS, MCPH_CONFIG_PATTERNS;
|
|
34468
34478
|
var init_scanner = __esm({
|
|
34469
34479
|
"src/core/scanner.ts"() {
|
|
@@ -40699,12 +40709,13 @@ async function parseMcpConfig(file2, projectRoot, scope) {
|
|
|
40699
40709
|
return result;
|
|
40700
40710
|
}
|
|
40701
40711
|
const lines = content.split("\n");
|
|
40712
|
+
const rootKeyLine = findRootKeyLine(lines, rootKey);
|
|
40702
40713
|
for (const [name, value] of Object.entries(serversObj)) {
|
|
40703
40714
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
40704
40715
|
continue;
|
|
40705
40716
|
}
|
|
40706
40717
|
const raw = value;
|
|
40707
|
-
const line = findServerLine(lines, name);
|
|
40718
|
+
const line = findServerLine(lines, name, rootKeyLine);
|
|
40708
40719
|
const transport = inferTransport(raw);
|
|
40709
40720
|
const entry = {
|
|
40710
40721
|
name,
|
|
@@ -40755,15 +40766,24 @@ function inferTransport(raw) {
|
|
|
40755
40766
|
if ("url" in raw) return "http";
|
|
40756
40767
|
return "unknown";
|
|
40757
40768
|
}
|
|
40758
|
-
function
|
|
40759
|
-
const escaped =
|
|
40769
|
+
function findRootKeyLine(lines, rootKey) {
|
|
40770
|
+
const escaped = rootKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
40760
40771
|
const pattern = new RegExp(`"${escaped}"\\s*:`);
|
|
40761
40772
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
40773
|
+
if (pattern.test(lines[i2])) return i2;
|
|
40774
|
+
}
|
|
40775
|
+
return -1;
|
|
40776
|
+
}
|
|
40777
|
+
function findServerLine(lines, serverName, rootKeyLine) {
|
|
40778
|
+
const escaped = serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
40779
|
+
const pattern = new RegExp(`"${escaped}"\\s*:`);
|
|
40780
|
+
const start = rootKeyLine >= 0 ? rootKeyLine + 1 : 0;
|
|
40781
|
+
for (let i2 = start; i2 < lines.length; i2++) {
|
|
40762
40782
|
if (pattern.test(lines[i2])) {
|
|
40763
40783
|
return i2 + 1;
|
|
40764
40784
|
}
|
|
40765
40785
|
}
|
|
40766
|
-
return 1;
|
|
40786
|
+
return rootKeyLine >= 0 ? rootKeyLine + 1 : 1;
|
|
40767
40787
|
}
|
|
40768
40788
|
function isStringRecord(value) {
|
|
40769
40789
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
@@ -41295,11 +41315,11 @@ function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
|
|
|
41295
41315
|
onValue({ type: getNodeType(value), offset, length, parent: currentParent, value });
|
|
41296
41316
|
ensurePropertyComplete(offset + length);
|
|
41297
41317
|
},
|
|
41298
|
-
onSeparator: (
|
|
41318
|
+
onSeparator: (sep2, offset, length) => {
|
|
41299
41319
|
if (currentParent.type === "property") {
|
|
41300
|
-
if (
|
|
41320
|
+
if (sep2 === ":") {
|
|
41301
41321
|
currentParent.colonOffset = offset;
|
|
41302
|
-
} else if (
|
|
41322
|
+
} else if (sep2 === ",") {
|
|
41303
41323
|
ensurePropertyComplete(offset);
|
|
41304
41324
|
}
|
|
41305
41325
|
}
|
|
@@ -41807,7 +41827,6 @@ async function parseMcphConfig(file2, projectRoot, scopeOverride) {
|
|
|
41807
41827
|
}
|
|
41808
41828
|
function detectScope(relativePath) {
|
|
41809
41829
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
41810
|
-
if (normalized.startsWith("~/")) return "global";
|
|
41811
41830
|
if (normalized.endsWith(".mcph.local.json")) return "project-local";
|
|
41812
41831
|
return "project";
|
|
41813
41832
|
}
|
|
@@ -42424,26 +42443,21 @@ var init_staleness = __esm({
|
|
|
42424
42443
|
});
|
|
42425
42444
|
|
|
42426
42445
|
// src/core/checks/tokens.ts
|
|
42427
|
-
function
|
|
42428
|
-
|
|
42446
|
+
function resolveTokenThresholds(overrides) {
|
|
42447
|
+
if (!overrides) return DEFAULT_TOKEN_THRESHOLDS;
|
|
42448
|
+
const merged = { ...DEFAULT_TOKEN_THRESHOLDS, ...overrides };
|
|
42429
42449
|
if (merged.info >= merged.warning || merged.warning >= merged.error) {
|
|
42430
42450
|
console.error(
|
|
42431
42451
|
`Warning: token thresholds should satisfy info < warning < error (got ${merged.info}, ${merged.warning}, ${merged.error}) \u2014 using defaults`
|
|
42432
42452
|
);
|
|
42433
|
-
return;
|
|
42453
|
+
return DEFAULT_TOKEN_THRESHOLDS;
|
|
42434
42454
|
}
|
|
42435
|
-
|
|
42455
|
+
return merged;
|
|
42436
42456
|
}
|
|
42437
|
-
function
|
|
42438
|
-
currentThresholds = DEFAULT_THRESHOLDS;
|
|
42439
|
-
}
|
|
42440
|
-
function getTokenThresholds() {
|
|
42441
|
-
return currentThresholds;
|
|
42442
|
-
}
|
|
42443
|
-
async function checkTokens(file2, _projectRoot) {
|
|
42457
|
+
async function checkTokens(file2, _projectRoot, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
42444
42458
|
const issues = [];
|
|
42445
42459
|
const tokens = file2.totalTokens;
|
|
42446
|
-
if (tokens >=
|
|
42460
|
+
if (tokens >= thresholds.error) {
|
|
42447
42461
|
issues.push({
|
|
42448
42462
|
severity: "error",
|
|
42449
42463
|
check: "tokens",
|
|
@@ -42452,7 +42466,7 @@ async function checkTokens(file2, _projectRoot) {
|
|
|
42452
42466
|
message: `${tokens.toLocaleString()} tokens \u2014 consumes significant context window space`,
|
|
42453
42467
|
suggestion: "Consider splitting into focused sections or removing redundant content."
|
|
42454
42468
|
});
|
|
42455
|
-
} else if (tokens >=
|
|
42469
|
+
} else if (tokens >= thresholds.warning) {
|
|
42456
42470
|
issues.push({
|
|
42457
42471
|
severity: "warning",
|
|
42458
42472
|
check: "tokens",
|
|
@@ -42461,7 +42475,7 @@ async function checkTokens(file2, _projectRoot) {
|
|
|
42461
42475
|
message: `${tokens.toLocaleString()} tokens \u2014 large context file`,
|
|
42462
42476
|
suggestion: "Consider trimming \u2014 research shows diminishing returns past ~300 lines."
|
|
42463
42477
|
});
|
|
42464
|
-
} else if (tokens >=
|
|
42478
|
+
} else if (tokens >= thresholds.info) {
|
|
42465
42479
|
issues.push({
|
|
42466
42480
|
severity: "info",
|
|
42467
42481
|
check: "tokens",
|
|
@@ -42472,9 +42486,9 @@ async function checkTokens(file2, _projectRoot) {
|
|
|
42472
42486
|
}
|
|
42473
42487
|
return issues;
|
|
42474
42488
|
}
|
|
42475
|
-
function checkAggregateTokens(files) {
|
|
42489
|
+
function checkAggregateTokens(files, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
42476
42490
|
const total = files.reduce((sum, f) => sum + f.tokens, 0);
|
|
42477
|
-
if (total >
|
|
42491
|
+
if (total > thresholds.aggregate && files.length > 1) {
|
|
42478
42492
|
return {
|
|
42479
42493
|
severity: "warning",
|
|
42480
42494
|
check: "tokens",
|
|
@@ -42486,11 +42500,11 @@ function checkAggregateTokens(files) {
|
|
|
42486
42500
|
}
|
|
42487
42501
|
return null;
|
|
42488
42502
|
}
|
|
42489
|
-
var
|
|
42503
|
+
var DEFAULT_TOKEN_THRESHOLDS;
|
|
42490
42504
|
var init_tokens2 = __esm({
|
|
42491
42505
|
"src/core/checks/tokens.ts"() {
|
|
42492
42506
|
"use strict";
|
|
42493
|
-
|
|
42507
|
+
DEFAULT_TOKEN_THRESHOLDS = {
|
|
42494
42508
|
info: 1e3,
|
|
42495
42509
|
warning: 3e3,
|
|
42496
42510
|
error: 8e3,
|
|
@@ -42498,7 +42512,6 @@ var init_tokens2 = __esm({
|
|
|
42498
42512
|
tierBreakdown: 1e3,
|
|
42499
42513
|
tierAggregate: 4e3
|
|
42500
42514
|
};
|
|
42501
|
-
currentThresholds = DEFAULT_THRESHOLDS;
|
|
42502
42515
|
}
|
|
42503
42516
|
});
|
|
42504
42517
|
|
|
@@ -42624,10 +42637,10 @@ function checkHardEnforcement(file2, settings) {
|
|
|
42624
42637
|
}
|
|
42625
42638
|
return issues;
|
|
42626
42639
|
}
|
|
42627
|
-
async function checkTierTokens(file2, projectRoot) {
|
|
42640
|
+
async function checkTierTokens(file2, projectRoot, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
42628
42641
|
if (!isAlwaysLoaded(file2)) return [];
|
|
42629
42642
|
const issues = [];
|
|
42630
|
-
const threshold =
|
|
42643
|
+
const threshold = thresholds.tierBreakdown;
|
|
42631
42644
|
if (file2.totalTokens >= threshold) {
|
|
42632
42645
|
const sectionCosts = computeSectionCosts(file2);
|
|
42633
42646
|
if (sectionCosts.length > 0) {
|
|
@@ -42650,11 +42663,11 @@ async function checkTierTokens(file2, projectRoot) {
|
|
|
42650
42663
|
issues.push(...checkHardEnforcement(file2, settings));
|
|
42651
42664
|
return issues;
|
|
42652
42665
|
}
|
|
42653
|
-
function checkAggregateTierTokens(files) {
|
|
42666
|
+
function checkAggregateTierTokens(files, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
42654
42667
|
const alwaysLoaded = files.filter(isAlwaysLoaded);
|
|
42655
42668
|
if (alwaysLoaded.length < 2) return null;
|
|
42656
42669
|
const total = alwaysLoaded.reduce((sum, f) => sum + f.totalTokens, 0);
|
|
42657
|
-
const threshold =
|
|
42670
|
+
const threshold = thresholds.tierAggregate;
|
|
42658
42671
|
if (total < threshold) return null;
|
|
42659
42672
|
const breakdown = alwaysLoaded.slice().sort((a, b2) => b2.totalTokens - a.totalTokens).slice(0, 5).map((f) => ` - ${f.relativePath}: ~${f.totalTokens.toLocaleString()} tokens`).join("\n");
|
|
42660
42673
|
return {
|
|
@@ -43457,7 +43470,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43457
43470
|
issues.push({
|
|
43458
43471
|
severity: "error",
|
|
43459
43472
|
check: "mcp-schema",
|
|
43460
|
-
ruleId: "invalid-json",
|
|
43473
|
+
ruleId: "mcp-schema/invalid-json",
|
|
43461
43474
|
line: 1,
|
|
43462
43475
|
message: `MCP config is not valid JSON: ${err}`
|
|
43463
43476
|
});
|
|
@@ -43468,7 +43481,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43468
43481
|
issues.push({
|
|
43469
43482
|
severity: "error",
|
|
43470
43483
|
check: "mcp-schema",
|
|
43471
|
-
ruleId: "missing-root-key",
|
|
43484
|
+
ruleId: "mcp-schema/missing-root-key",
|
|
43472
43485
|
line: 1,
|
|
43473
43486
|
message: `MCP config has no "${config2.expectedRootKey}" key`
|
|
43474
43487
|
});
|
|
@@ -43479,7 +43492,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43479
43492
|
issues.push({
|
|
43480
43493
|
severity: "error",
|
|
43481
43494
|
check: "mcp-schema",
|
|
43482
|
-
ruleId: "wrong-root-key",
|
|
43495
|
+
ruleId: "mcp-schema/wrong-root-key",
|
|
43483
43496
|
line,
|
|
43484
43497
|
message: `${config2.relativePath} must use "${config2.expectedRootKey}" as root key, not "${config2.actualRootKey}"`,
|
|
43485
43498
|
fix: {
|
|
@@ -43494,7 +43507,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43494
43507
|
issues.push({
|
|
43495
43508
|
severity: "info",
|
|
43496
43509
|
check: "mcp-schema",
|
|
43497
|
-
ruleId: "empty-servers",
|
|
43510
|
+
ruleId: "mcp-schema/empty-servers",
|
|
43498
43511
|
line: 1,
|
|
43499
43512
|
message: "MCP config has no server entries"
|
|
43500
43513
|
});
|
|
@@ -43505,7 +43518,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43505
43518
|
issues.push({
|
|
43506
43519
|
severity: "error",
|
|
43507
43520
|
check: "mcp-schema",
|
|
43508
|
-
ruleId: "no-name-field",
|
|
43521
|
+
ruleId: "mcp-schema/no-name-field",
|
|
43509
43522
|
line: server2.line,
|
|
43510
43523
|
message: "Server name cannot be empty"
|
|
43511
43524
|
});
|
|
@@ -43517,7 +43530,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43517
43530
|
issues.push({
|
|
43518
43531
|
severity: "warning",
|
|
43519
43532
|
check: "mcp-schema",
|
|
43520
|
-
ruleId: "unknown-transport",
|
|
43533
|
+
ruleId: "mcp-schema/unknown-transport",
|
|
43521
43534
|
line: server2.line,
|
|
43522
43535
|
message: `Server "${server2.name}" has unknown transport type "${typeVal}"`
|
|
43523
43536
|
});
|
|
@@ -43527,7 +43540,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43527
43540
|
issues.push({
|
|
43528
43541
|
severity: "warning",
|
|
43529
43542
|
check: "mcp-schema",
|
|
43530
|
-
ruleId: "ambiguous-transport",
|
|
43543
|
+
ruleId: "mcp-schema/ambiguous-transport",
|
|
43531
43544
|
line: server2.line,
|
|
43532
43545
|
message: `Server "${server2.name}" has both "command" and "url" \u2014 transport is ambiguous`
|
|
43533
43546
|
});
|
|
@@ -43536,7 +43549,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43536
43549
|
issues.push({
|
|
43537
43550
|
severity: "error",
|
|
43538
43551
|
check: "mcp-schema",
|
|
43539
|
-
ruleId: "missing-command",
|
|
43552
|
+
ruleId: "mcp-schema/missing-command",
|
|
43540
43553
|
line: server2.line,
|
|
43541
43554
|
message: `Server "${server2.name}" has no "command" field`
|
|
43542
43555
|
});
|
|
@@ -43545,7 +43558,7 @@ async function checkMcpSchema(config2, _projectRoot) {
|
|
|
43545
43558
|
issues.push({
|
|
43546
43559
|
severity: "error",
|
|
43547
43560
|
check: "mcp-schema",
|
|
43548
|
-
ruleId: "missing-url",
|
|
43561
|
+
ruleId: "mcp-schema/missing-url",
|
|
43549
43562
|
line: server2.line,
|
|
43550
43563
|
message: `Server "${server2.name}" has no "url" field`
|
|
43551
43564
|
});
|
|
@@ -43604,7 +43617,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43604
43617
|
issues.push({
|
|
43605
43618
|
severity: "error",
|
|
43606
43619
|
check: "mcp-security",
|
|
43607
|
-
ruleId: "hardcoded-bearer",
|
|
43620
|
+
ruleId: "mcp-security/hardcoded-bearer",
|
|
43608
43621
|
line: server2.line,
|
|
43609
43622
|
message: `Server "${server2.name}" has a hardcoded Bearer token in a git-tracked file`,
|
|
43610
43623
|
fix: {
|
|
@@ -43621,7 +43634,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43621
43634
|
issues.push({
|
|
43622
43635
|
severity: "error",
|
|
43623
43636
|
check: "mcp-security",
|
|
43624
|
-
ruleId: "hardcoded-api-key",
|
|
43637
|
+
ruleId: "mcp-security/hardcoded-api-key",
|
|
43625
43638
|
line: server2.line,
|
|
43626
43639
|
message: `Server "${server2.name}" has a hardcoded API key in a git-tracked file`
|
|
43627
43640
|
});
|
|
@@ -43636,7 +43649,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43636
43649
|
issues.push({
|
|
43637
43650
|
severity: "error",
|
|
43638
43651
|
check: "mcp-security",
|
|
43639
|
-
ruleId: "hardcoded-api-key",
|
|
43652
|
+
ruleId: "mcp-security/hardcoded-api-key",
|
|
43640
43653
|
line: server2.line,
|
|
43641
43654
|
message: `Server "${server2.name}" has a hardcoded API key in a git-tracked file`,
|
|
43642
43655
|
fix: {
|
|
@@ -43653,7 +43666,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43653
43666
|
issues.push({
|
|
43654
43667
|
severity: "error",
|
|
43655
43668
|
check: "mcp-security",
|
|
43656
|
-
ruleId: "secret-in-url",
|
|
43669
|
+
ruleId: "mcp-security/secret-in-url",
|
|
43657
43670
|
line: server2.line,
|
|
43658
43671
|
message: `Server "${server2.name}" has a secret in the URL query string`
|
|
43659
43672
|
});
|
|
@@ -43666,7 +43679,7 @@ async function checkMcpSecurity(config2, _projectRoot) {
|
|
|
43666
43679
|
issues.push({
|
|
43667
43680
|
severity: "warning",
|
|
43668
43681
|
check: "mcp-security",
|
|
43669
|
-
ruleId: "http-no-tls",
|
|
43682
|
+
ruleId: "mcp-security/http-no-tls",
|
|
43670
43683
|
line: server2.line,
|
|
43671
43684
|
message: `Server "${server2.name}" uses HTTP without TLS`
|
|
43672
43685
|
});
|
|
@@ -43738,7 +43751,7 @@ async function checkMcpCommands(config2, projectRoot) {
|
|
|
43738
43751
|
issues.push({
|
|
43739
43752
|
severity: "error",
|
|
43740
43753
|
check: "mcp-commands",
|
|
43741
|
-
ruleId: "windows-npx-no-wrapper",
|
|
43754
|
+
ruleId: "mcp-commands/windows-npx-no-wrapper",
|
|
43742
43755
|
line: server2.line,
|
|
43743
43756
|
message: `Server "${server2.name}": npx requires "cmd /c" wrapper on Windows`,
|
|
43744
43757
|
suggestion: 'Change "command" to "cmd" and prepend "/c", "npx" to args \u2014 e.g. "args": ["/c", "npx", ...]'
|
|
@@ -43750,7 +43763,7 @@ async function checkMcpCommands(config2, projectRoot) {
|
|
|
43750
43763
|
issues.push({
|
|
43751
43764
|
severity: "warning",
|
|
43752
43765
|
check: "mcp-commands",
|
|
43753
|
-
ruleId: "command-not-found",
|
|
43766
|
+
ruleId: "mcp-commands/command-not-found",
|
|
43754
43767
|
line: server2.line,
|
|
43755
43768
|
message: `Server "${server2.name}": command "${server2.command}" not found`
|
|
43756
43769
|
});
|
|
@@ -43765,7 +43778,7 @@ async function checkMcpCommands(config2, projectRoot) {
|
|
|
43765
43778
|
issues.push({
|
|
43766
43779
|
severity: "warning",
|
|
43767
43780
|
check: "mcp-commands",
|
|
43768
|
-
ruleId: "args-path-missing",
|
|
43781
|
+
ruleId: "mcp-commands/args-path-missing",
|
|
43769
43782
|
line: server2.line,
|
|
43770
43783
|
message: `Server "${server2.name}": arg "${arg}" looks like a file path but doesn't exist`
|
|
43771
43784
|
});
|
|
@@ -43804,7 +43817,7 @@ async function checkMcpDeprecated(config2, _projectRoot) {
|
|
|
43804
43817
|
issues.push({
|
|
43805
43818
|
severity: "warning",
|
|
43806
43819
|
check: "mcp-deprecated",
|
|
43807
|
-
ruleId: "sse-transport",
|
|
43820
|
+
ruleId: "mcp-deprecated/sse-transport",
|
|
43808
43821
|
line,
|
|
43809
43822
|
message: `Server "${server2.name}" uses deprecated SSE transport \u2014 use "http" (Streamable HTTP) instead`,
|
|
43810
43823
|
fix: {
|
|
@@ -43900,7 +43913,7 @@ async function checkMcpEnv(config2, _projectRoot) {
|
|
|
43900
43913
|
issues.push({
|
|
43901
43914
|
severity: "error",
|
|
43902
43915
|
check: "mcp-env",
|
|
43903
|
-
ruleId: "wrong-syntax",
|
|
43916
|
+
ruleId: "mcp-env/wrong-syntax",
|
|
43904
43917
|
line: server2.line,
|
|
43905
43918
|
message: `Server "${server2.name}": Claude Code uses \${VAR}, not \${env:VAR}`,
|
|
43906
43919
|
fix: buildSyntaxFix(config2, server2.line, value, "claude-code")
|
|
@@ -43912,7 +43925,7 @@ async function checkMcpEnv(config2, _projectRoot) {
|
|
|
43912
43925
|
issues.push({
|
|
43913
43926
|
severity: "error",
|
|
43914
43927
|
check: "mcp-env",
|
|
43915
|
-
ruleId: "wrong-syntax",
|
|
43928
|
+
ruleId: "mcp-env/wrong-syntax",
|
|
43916
43929
|
line: server2.line,
|
|
43917
43930
|
message: `Server "${server2.name}": Cursor uses \${env:VAR}, not \${VAR}`,
|
|
43918
43931
|
fix: buildSyntaxFix(config2, server2.line, value, "cursor")
|
|
@@ -43924,7 +43937,7 @@ async function checkMcpEnv(config2, _projectRoot) {
|
|
|
43924
43937
|
issues.push({
|
|
43925
43938
|
severity: "error",
|
|
43926
43939
|
check: "mcp-env",
|
|
43927
|
-
ruleId: "wrong-syntax",
|
|
43940
|
+
ruleId: "mcp-env/wrong-syntax",
|
|
43928
43941
|
line: server2.line,
|
|
43929
43942
|
message: `Server "${server2.name}": Continue uses \${{ secrets.VAR }}, not \${VAR}`,
|
|
43930
43943
|
fix: buildSyntaxFix(config2, server2.line, value, "continue")
|
|
@@ -43940,7 +43953,7 @@ async function checkMcpEnv(config2, _projectRoot) {
|
|
|
43940
43953
|
issues.push({
|
|
43941
43954
|
severity: "info",
|
|
43942
43955
|
check: "mcp-env",
|
|
43943
|
-
ruleId: "unset-variable",
|
|
43956
|
+
ruleId: "mcp-env/unset-variable",
|
|
43944
43957
|
line: server2.line,
|
|
43945
43958
|
message: `Server "${server2.name}": environment variable "${ref.varName}" is not set`
|
|
43946
43959
|
});
|
|
@@ -43952,7 +43965,7 @@ async function checkMcpEnv(config2, _projectRoot) {
|
|
|
43952
43965
|
issues.push({
|
|
43953
43966
|
severity: "info",
|
|
43954
43967
|
check: "mcp-env",
|
|
43955
|
-
ruleId: "empty-env-block",
|
|
43968
|
+
ruleId: "mcp-env/empty-env-block",
|
|
43956
43969
|
line: server2.line,
|
|
43957
43970
|
message: `Server "${server2.name}": empty "env" block can be removed`
|
|
43958
43971
|
});
|
|
@@ -44008,7 +44021,7 @@ async function checkMcpUrls(config2, _projectRoot) {
|
|
|
44008
44021
|
issues.push({
|
|
44009
44022
|
severity: "error",
|
|
44010
44023
|
check: "mcp-urls",
|
|
44011
|
-
ruleId: "malformed-url",
|
|
44024
|
+
ruleId: "mcp-urls/malformed-url",
|
|
44012
44025
|
line: server2.line,
|
|
44013
44026
|
message: `Server "${server2.name}": invalid URL "${server2.url}"`
|
|
44014
44027
|
});
|
|
@@ -44018,7 +44031,7 @@ async function checkMcpUrls(config2, _projectRoot) {
|
|
|
44018
44031
|
issues.push({
|
|
44019
44032
|
severity: "warning",
|
|
44020
44033
|
check: "mcp-urls",
|
|
44021
|
-
ruleId: "localhost-in-project-config",
|
|
44034
|
+
ruleId: "mcp-urls/localhost-in-project-config",
|
|
44022
44035
|
line: server2.line,
|
|
44023
44036
|
message: `Server "${server2.name}": localhost URL in project config won't work for teammates`
|
|
44024
44037
|
});
|
|
@@ -44027,7 +44040,7 @@ async function checkMcpUrls(config2, _projectRoot) {
|
|
|
44027
44040
|
issues.push({
|
|
44028
44041
|
severity: "info",
|
|
44029
44042
|
check: "mcp-urls",
|
|
44030
|
-
ruleId: "missing-path",
|
|
44043
|
+
ruleId: "mcp-urls/missing-path",
|
|
44031
44044
|
line: server2.line,
|
|
44032
44045
|
message: `Server "${server2.name}": URL has no path \u2014 most MCP servers expect /mcp`
|
|
44033
44046
|
});
|
|
@@ -44075,7 +44088,7 @@ async function checkMcpConsistency(configs) {
|
|
|
44075
44088
|
issues.push({
|
|
44076
44089
|
severity: "warning",
|
|
44077
44090
|
check: "mcp-consistency",
|
|
44078
|
-
ruleId: "same-server-different-config",
|
|
44091
|
+
ruleId: "mcp-consistency/same-server-different-config",
|
|
44079
44092
|
line: a.line,
|
|
44080
44093
|
message: `Server "${name}" is configured differently in ${a.config.relativePath} and ${b2.config.relativePath}`
|
|
44081
44094
|
});
|
|
@@ -44101,7 +44114,7 @@ function checkMissingFromClient(configs) {
|
|
|
44101
44114
|
issues.push({
|
|
44102
44115
|
severity: "info",
|
|
44103
44116
|
check: "mcp-consistency",
|
|
44104
|
-
ruleId: "missing-from-client",
|
|
44117
|
+
ruleId: "mcp-consistency/missing-from-client",
|
|
44105
44118
|
line: primaryServer.line,
|
|
44106
44119
|
message: `Server "${primaryServer.name}" is in .mcp.json but missing from ${other.relativePath}`
|
|
44107
44120
|
});
|
|
@@ -44178,7 +44191,7 @@ function checkSingleFileIssues(configs) {
|
|
|
44178
44191
|
issues.push({
|
|
44179
44192
|
severity: "warning",
|
|
44180
44193
|
check: "mcp-consistency",
|
|
44181
|
-
ruleId: "duplicate-server-name",
|
|
44194
|
+
ruleId: "mcp-consistency/duplicate-server-name",
|
|
44182
44195
|
line: 1,
|
|
44183
44196
|
message: `Duplicate server name "${name}" in ${config2.relativePath} \u2014 only the last definition is used`
|
|
44184
44197
|
});
|
|
@@ -44202,7 +44215,7 @@ async function checkMcpRedundancy(configs) {
|
|
|
44202
44215
|
issues.push({
|
|
44203
44216
|
severity: "info",
|
|
44204
44217
|
check: "mcp-redundancy",
|
|
44205
|
-
ruleId: "disabled-server",
|
|
44218
|
+
ruleId: "mcp-redundancy/disabled-server",
|
|
44206
44219
|
line: server2.line,
|
|
44207
44220
|
message: `Server "${server2.name}" is disabled \u2014 consider removing it if no longer needed`
|
|
44208
44221
|
});
|
|
@@ -44232,7 +44245,7 @@ async function checkMcpRedundancy(configs) {
|
|
|
44232
44245
|
issues.push({
|
|
44233
44246
|
severity: "info",
|
|
44234
44247
|
check: "mcp-redundancy",
|
|
44235
|
-
ruleId: "identical-across-scopes",
|
|
44248
|
+
ruleId: "mcp-redundancy/identical-across-scopes",
|
|
44236
44249
|
line: projectServer.line,
|
|
44237
44250
|
message: `Server "${projectServer.name}" is identically configured in both ${projectConfig.relativePath} and ${globalConfig2.relativePath}`
|
|
44238
44251
|
});
|
|
@@ -44258,7 +44271,7 @@ async function checkMcphTokenSecurity(config2, _projectRoot, options = {}) {
|
|
|
44258
44271
|
issues.push({
|
|
44259
44272
|
severity: "error",
|
|
44260
44273
|
check: "mcph-token-security",
|
|
44261
|
-
ruleId: "mcph-
|
|
44274
|
+
ruleId: "mcph-token-security/invalid-token-format",
|
|
44262
44275
|
line: tokenPos.line,
|
|
44263
44276
|
message: `"token" does not match expected format ^mcp_pat_[A-Za-z0-9_-]+$`,
|
|
44264
44277
|
suggestion: `Check that the token was copied in full. A valid mcp.hosting PAT looks like: mcp_pat_aBcDeFg123...
|
|
@@ -44269,7 +44282,7 @@ If the token was truncated or wrapped in quotes elsewhere, re-issue it from http
|
|
|
44269
44282
|
issues.push({
|
|
44270
44283
|
severity: "error",
|
|
44271
44284
|
check: "mcph-token-security",
|
|
44272
|
-
ruleId: "mcph-
|
|
44285
|
+
ruleId: "mcph-token-security/token-in-project-scope",
|
|
44273
44286
|
line: tokenPos.line,
|
|
44274
44287
|
message: `"token" in a git-tracked project-scope .mcph.json \u2014 PAT will leak via git history`,
|
|
44275
44288
|
suggestion: `Delete line ${tokenPos.line} (the "token" field) from ${config2.relativePath}.
|
|
@@ -44287,7 +44300,7 @@ If this token was already committed, ROTATE it now: https://mcp.hosting/settings
|
|
|
44287
44300
|
issues.push({
|
|
44288
44301
|
severity,
|
|
44289
44302
|
check: "mcph-token-security",
|
|
44290
|
-
ruleId: "mcph-
|
|
44303
|
+
ruleId: "mcph-token-security/prefer-env-token",
|
|
44291
44304
|
line: tokenPos.line,
|
|
44292
44305
|
message: `prefer MCPH_TOKEN env var over a file-stored token in ${config2.relativePath}`,
|
|
44293
44306
|
suggestion: `Delete line ${tokenPos.line} (the "token" field) and export instead:
|
|
@@ -44328,7 +44341,7 @@ async function checkMcphApibase(config2, _projectRoot) {
|
|
|
44328
44341
|
issues.push({
|
|
44329
44342
|
severity: "error",
|
|
44330
44343
|
check: "mcph-apibase",
|
|
44331
|
-
ruleId: "mcph-
|
|
44344
|
+
ruleId: "mcph-apibase/invalid-apibase",
|
|
44332
44345
|
line: pos.line,
|
|
44333
44346
|
message: `"apiBase" is not a valid URL: ${value}`,
|
|
44334
44347
|
suggestion: `Use an absolute http(s) URL, e.g. "https://mcp.hosting".`
|
|
@@ -44339,7 +44352,7 @@ async function checkMcphApibase(config2, _projectRoot) {
|
|
|
44339
44352
|
issues.push({
|
|
44340
44353
|
severity: "warning",
|
|
44341
44354
|
check: "mcph-apibase",
|
|
44342
|
-
ruleId: "mcph-
|
|
44355
|
+
ruleId: "mcph-apibase/insecure-apibase",
|
|
44343
44356
|
line: pos.line,
|
|
44344
44357
|
message: `"apiBase" uses plaintext HTTP to a public host (${parsed.hostname})`,
|
|
44345
44358
|
suggestion: `Use https:// instead. Plaintext HTTP exposes your MCPH_TOKEN on the wire.
|
|
@@ -44375,7 +44388,7 @@ async function checkMcphSchemaConformance(config2, _projectRoot) {
|
|
|
44375
44388
|
issues.push({
|
|
44376
44389
|
severity: "warning",
|
|
44377
44390
|
check: "mcph-schema-conformance",
|
|
44378
|
-
ruleId: "mcph-
|
|
44391
|
+
ruleId: "mcph-schema-conformance/unknown-field",
|
|
44379
44392
|
line: field.position.line,
|
|
44380
44393
|
message: `unknown field "${field.name}" \u2014 not in the mcph config schema`,
|
|
44381
44394
|
suggestion: `Known fields: $schema, version, token, apiBase, servers, blocked. Check for typos (e.g. "tokens" vs "token", "blockList" vs "blocked").`
|
|
@@ -44387,7 +44400,7 @@ async function checkMcphSchemaConformance(config2, _projectRoot) {
|
|
|
44387
44400
|
issues.push({
|
|
44388
44401
|
severity: "info",
|
|
44389
44402
|
check: "mcph-schema-conformance",
|
|
44390
|
-
ruleId: "mcph-
|
|
44403
|
+
ruleId: "mcph-schema-conformance/stale-version",
|
|
44391
44404
|
line: versionPos.line,
|
|
44392
44405
|
message: `"version": ${version2} is older than the current schema version (${CURRENT_SCHEMA_VERSION})`,
|
|
44393
44406
|
suggestion: `Update to "version": ${CURRENT_SCHEMA_VERSION}. Older versions continue to load but may miss newer fields.`
|
|
@@ -44415,7 +44428,7 @@ async function checkMcphLists(config2, _projectRoot) {
|
|
|
44415
44428
|
issues.push({
|
|
44416
44429
|
severity: "warning",
|
|
44417
44430
|
check: "mcph-lists",
|
|
44418
|
-
ruleId: "mcph-
|
|
44431
|
+
ruleId: "mcph-lists/allowlist-denylist-conflict",
|
|
44419
44432
|
line: entry.position.line,
|
|
44420
44433
|
message: `server "${entry.value}" is in both "servers" (allow-list) and "blocked" (deny-list)`,
|
|
44421
44434
|
suggestion: `Remove "${entry.value}" from one of the two lists. "blocked" wins in practice (deny > allow), so the allow-list entry is dead weight.`
|
|
@@ -44431,7 +44444,7 @@ async function checkMcphLists(config2, _projectRoot) {
|
|
|
44431
44444
|
issues.push({
|
|
44432
44445
|
severity: "info",
|
|
44433
44446
|
check: "mcph-lists",
|
|
44434
|
-
ruleId: "mcph-
|
|
44447
|
+
ruleId: "mcph-lists/duplicate-entries",
|
|
44435
44448
|
line: entry.position.line,
|
|
44436
44449
|
message: `"${entry.value}" appears multiple times in "${listName}" (first at line ${prevLine})`,
|
|
44437
44450
|
suggestion: `Remove the duplicate entry at line ${entry.position.line}.`
|
|
@@ -44458,7 +44471,7 @@ async function checkMcphGitignore(config2, _projectRoot) {
|
|
|
44458
44471
|
issues.push({
|
|
44459
44472
|
severity: "error",
|
|
44460
44473
|
check: "mcph-gitignore",
|
|
44461
|
-
ruleId: "mcph-
|
|
44474
|
+
ruleId: "mcph-gitignore/local-file-not-gitignored",
|
|
44462
44475
|
line: 1,
|
|
44463
44476
|
message: `${basename4} is not covered by .gitignore \u2014 machine-local overrides can leak via git`,
|
|
44464
44477
|
suggestion: `Add "${basename4}" to .gitignore in your project root.`
|
|
@@ -44801,7 +44814,7 @@ async function checkMissingSecret(ctx) {
|
|
|
44801
44814
|
issues.push({
|
|
44802
44815
|
severity: "error",
|
|
44803
44816
|
check: "session-missing-secret",
|
|
44804
|
-
ruleId: "session/missing-secret",
|
|
44817
|
+
ruleId: "session-missing-secret/missing-secret",
|
|
44805
44818
|
line: 0,
|
|
44806
44819
|
message: `GitHub secret "${secretName}" is set on ${siblingMatches.length} sibling repos (${sibNames}) but not on this project`,
|
|
44807
44820
|
suggestion: `Run: gh secret set ${secretName} --repo <owner>/<repo>`,
|
|
@@ -44879,7 +44892,7 @@ async function checkDivergedFile(ctx) {
|
|
|
44879
44892
|
issues.push({
|
|
44880
44893
|
severity: "warning",
|
|
44881
44894
|
check: "session-diverged-file",
|
|
44882
|
-
ruleId: "session/diverged-file",
|
|
44895
|
+
ruleId: "session-diverged-file/diverged-file",
|
|
44883
44896
|
line: 0,
|
|
44884
44897
|
message: `${fileName} has diverged from sibling repos: ${details}`,
|
|
44885
44898
|
suggestion: `Compare with sibling versions to identify unintentional drift`,
|
|
@@ -44951,7 +44964,7 @@ async function checkMissingWorkflow(ctx) {
|
|
|
44951
44964
|
issues.push({
|
|
44952
44965
|
severity: "warning",
|
|
44953
44966
|
check: "session-missing-workflow",
|
|
44954
|
-
ruleId: "session/missing-workflow",
|
|
44967
|
+
ruleId: "session-missing-workflow/missing-workflow",
|
|
44955
44968
|
line: 0,
|
|
44956
44969
|
message: `GitHub Actions workflow "${workflow}" exists in ${siblings.length} sibling repos (${sibNames}) but not in this project`,
|
|
44957
44970
|
suggestion: `Consider adding .github/workflows/${workflow} for consistency`,
|
|
@@ -44997,7 +45010,7 @@ async function checkStaleMemory(ctx) {
|
|
|
44997
45010
|
issues.push({
|
|
44998
45011
|
severity: "info",
|
|
44999
45012
|
check: "session-stale-memory",
|
|
45000
|
-
ruleId: "session/stale-memory",
|
|
45013
|
+
ruleId: "session-stale-memory/stale-memory",
|
|
45001
45014
|
line: 0,
|
|
45002
45015
|
message: `Memory "${name}" references ${brokenPaths.length} path(s) that no longer exist: ${brokenPaths.join(", ")}`,
|
|
45003
45016
|
suggestion: `Update or remove the memory file: ${mem.filePath}`,
|
|
@@ -45040,7 +45053,7 @@ async function checkDuplicateMemory(ctx) {
|
|
|
45040
45053
|
issues.push({
|
|
45041
45054
|
severity: "info",
|
|
45042
45055
|
check: "session-duplicate-memory",
|
|
45043
|
-
ruleId: "session/duplicate-memory",
|
|
45056
|
+
ruleId: "session-duplicate-memory/duplicate-memory",
|
|
45044
45057
|
line: 0,
|
|
45045
45058
|
message: `Memory "${nameA}" (${projA}) and "${nameB}" (${projB}) have ${Math.round(overlap * 100)}% overlap`,
|
|
45046
45059
|
suggestion: `Consider consolidating into a shared memory or removing the duplicate`,
|
|
@@ -45111,7 +45124,8 @@ function findCyclicPatterns(displays) {
|
|
|
45111
45124
|
async function checkLoopDetection(ctx) {
|
|
45112
45125
|
const issues = [];
|
|
45113
45126
|
const currentNorm = normalizeProject(ctx.currentProject);
|
|
45114
|
-
const
|
|
45127
|
+
const filtered = ctx.history.filter((e) => normalizeProject(e.project) === currentNorm).sort((a, b2) => a.timestamp - b2.timestamp);
|
|
45128
|
+
const entries = filtered.length > MAX_HISTORY_ENTRIES ? filtered.slice(-MAX_HISTORY_ENTRIES) : filtered;
|
|
45115
45129
|
if (entries.length < CONSECUTIVE_THRESHOLD) return issues;
|
|
45116
45130
|
const displays = entries.map((e) => e.display);
|
|
45117
45131
|
const repeats = findConsecutiveRepeats(displays);
|
|
@@ -45120,7 +45134,7 @@ async function checkLoopDetection(ctx) {
|
|
|
45120
45134
|
issues.push({
|
|
45121
45135
|
severity: "warning",
|
|
45122
45136
|
check: "session-loop-detection",
|
|
45123
|
-
ruleId: "session/consecutive-repeat",
|
|
45137
|
+
ruleId: "session-loop-detection/consecutive-repeat",
|
|
45124
45138
|
line: 0,
|
|
45125
45139
|
message: `Command run ${count} times consecutively: "${truncated}"`,
|
|
45126
45140
|
suggestion: "An agent may be looping on this command. Check history.jsonl for context on what went wrong"
|
|
@@ -45132,7 +45146,7 @@ async function checkLoopDetection(ctx) {
|
|
|
45132
45146
|
issues.push({
|
|
45133
45147
|
severity: "warning",
|
|
45134
45148
|
check: "session-loop-detection",
|
|
45135
|
-
ruleId: "session/cyclic-pattern",
|
|
45149
|
+
ruleId: "session-loop-detection/cyclic-pattern",
|
|
45136
45150
|
line: 0,
|
|
45137
45151
|
message: `Cyclic pattern repeated ${reps} times: ${cycleStr}`,
|
|
45138
45152
|
suggestion: "An agent may be stuck in a loop. Check if a context file is missing instructions for this workflow"
|
|
@@ -45140,13 +45154,14 @@ async function checkLoopDetection(ctx) {
|
|
|
45140
45154
|
}
|
|
45141
45155
|
return issues;
|
|
45142
45156
|
}
|
|
45143
|
-
var CONSECUTIVE_THRESHOLD, CYCLE_REPEAT_THRESHOLD, MAX_CYCLE_LENGTH;
|
|
45157
|
+
var CONSECUTIVE_THRESHOLD, CYCLE_REPEAT_THRESHOLD, MAX_CYCLE_LENGTH, MAX_HISTORY_ENTRIES;
|
|
45144
45158
|
var init_loop_detection = __esm({
|
|
45145
45159
|
"src/core/checks/session/loop-detection.ts"() {
|
|
45146
45160
|
"use strict";
|
|
45147
45161
|
CONSECUTIVE_THRESHOLD = 3;
|
|
45148
45162
|
CYCLE_REPEAT_THRESHOLD = 2;
|
|
45149
45163
|
MAX_CYCLE_LENGTH = 3;
|
|
45164
|
+
MAX_HISTORY_ENTRIES = 5e3;
|
|
45150
45165
|
}
|
|
45151
45166
|
});
|
|
45152
45167
|
|
|
@@ -45174,7 +45189,7 @@ async function checkMemoryIndexOverflow(ctx) {
|
|
|
45174
45189
|
issues.push({
|
|
45175
45190
|
severity: "warning",
|
|
45176
45191
|
check: "session-memory-index-overflow",
|
|
45177
|
-
ruleId: "session/memory-index-overflow",
|
|
45192
|
+
ruleId: "session-memory-index-overflow/memory-index-overflow",
|
|
45178
45193
|
line: MAX_LINES + 1,
|
|
45179
45194
|
message: `MEMORY.md has ${lineCount.toLocaleString()} lines \u2014 only the first ${MAX_LINES} are loaded. ${excess.toLocaleString()} line(s) are effectively invisible to the agent.`,
|
|
45180
45195
|
detail: `File: ${memoryFile}`,
|
|
@@ -45186,7 +45201,7 @@ async function checkMemoryIndexOverflow(ctx) {
|
|
|
45186
45201
|
issues.push({
|
|
45187
45202
|
severity: "warning",
|
|
45188
45203
|
check: "session-memory-index-overflow",
|
|
45189
|
-
ruleId: "session/memory-index-overflow",
|
|
45204
|
+
ruleId: "session-memory-index-overflow/memory-index-overflow",
|
|
45190
45205
|
line: 0,
|
|
45191
45206
|
message: `MEMORY.md is ${byteSize.toLocaleString()} bytes \u2014 only the first ${MAX_BYTES.toLocaleString()} bytes are loaded. ~${excess.toLocaleString()} bytes are effectively invisible.`,
|
|
45192
45207
|
detail: `File: ${memoryFile}`,
|
|
@@ -45253,7 +45268,7 @@ async function checkCiCoverage(files, projectRoot) {
|
|
|
45253
45268
|
{
|
|
45254
45269
|
severity: "info",
|
|
45255
45270
|
check: "ci-coverage",
|
|
45256
|
-
ruleId: "ci/no-release-docs",
|
|
45271
|
+
ruleId: "ci-coverage/no-release-docs",
|
|
45257
45272
|
line: 0,
|
|
45258
45273
|
message: `Release workflow${releaseWorkflows.length > 1 ? "s" : ""} found (${releaseWorkflows.join(", ")}) but no context file documents the release process`,
|
|
45259
45274
|
suggestion: `Document how releases work (e.g. "push a v* tag to trigger CI") in a context file so agents don't guess`
|
|
@@ -45328,7 +45343,7 @@ async function checkCiSecrets(files, projectRoot) {
|
|
|
45328
45343
|
issues.push({
|
|
45329
45344
|
severity: "info",
|
|
45330
45345
|
check: "ci-secrets",
|
|
45331
|
-
ruleId: "ci/undocumented-secret",
|
|
45346
|
+
ruleId: "ci-secrets/undocumented-secret",
|
|
45332
45347
|
line: 0,
|
|
45333
45348
|
message: `CI secret "${name}" is used in ${workflows.join(", ")} but not mentioned in any context file`,
|
|
45334
45349
|
suggestion: `Document what ${name} is and how to set it (e.g. "gh secret set ${name}") so agents don't create new tokens or guess`
|
|
@@ -45356,15 +45371,239 @@ var init_ci_secrets = __esm({
|
|
|
45356
45371
|
}
|
|
45357
45372
|
});
|
|
45358
45373
|
|
|
45374
|
+
// src/core/checks/content-secrets.ts
|
|
45375
|
+
function lineLooksLikePlaceholder(line) {
|
|
45376
|
+
const lower = line.toLowerCase();
|
|
45377
|
+
for (const tok of PLACEHOLDER_TOKENS) {
|
|
45378
|
+
if (lower.includes(tok)) return true;
|
|
45379
|
+
}
|
|
45380
|
+
return false;
|
|
45381
|
+
}
|
|
45382
|
+
function isCommentedExample(line) {
|
|
45383
|
+
if (!COMMENT_PREFIX.test(line)) return false;
|
|
45384
|
+
const lower = line.toLowerCase();
|
|
45385
|
+
return lower.includes("fake") || lower.includes("example");
|
|
45386
|
+
}
|
|
45387
|
+
function isPlaceholderWrapped(line, start, end) {
|
|
45388
|
+
const before2 = line.slice(Math.max(0, start - 2), start);
|
|
45389
|
+
const after2 = line.slice(end, end + 2);
|
|
45390
|
+
if (before2.endsWith("<") && after2.startsWith(">")) return true;
|
|
45391
|
+
if (before2.endsWith("${") && after2.startsWith("}")) return true;
|
|
45392
|
+
if (before2.endsWith("{") && after2.startsWith("}")) return true;
|
|
45393
|
+
const dollarOpen = line.lastIndexOf("${", start);
|
|
45394
|
+
if (dollarOpen !== -1) {
|
|
45395
|
+
const close = line.indexOf("}", dollarOpen);
|
|
45396
|
+
if (close !== -1 && close >= end) return true;
|
|
45397
|
+
}
|
|
45398
|
+
const angleOpen = line.lastIndexOf("<", start);
|
|
45399
|
+
if (angleOpen !== -1) {
|
|
45400
|
+
const close = line.indexOf(">", angleOpen);
|
|
45401
|
+
if (close !== -1 && close >= end) return true;
|
|
45402
|
+
}
|
|
45403
|
+
return false;
|
|
45404
|
+
}
|
|
45405
|
+
function computeFenceLanguages(lines) {
|
|
45406
|
+
const out = new Array(lines.length).fill(null);
|
|
45407
|
+
let activeMarker = null;
|
|
45408
|
+
let activeLang = null;
|
|
45409
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
45410
|
+
const line = lines[i2];
|
|
45411
|
+
const trimmed2 = line.trim();
|
|
45412
|
+
if (activeMarker === null) {
|
|
45413
|
+
const open = trimmed2.match(/^(```+|~~~+)(.*)$/);
|
|
45414
|
+
if (open) {
|
|
45415
|
+
activeMarker = open[1][0];
|
|
45416
|
+
activeLang = open[2].trim().toLowerCase();
|
|
45417
|
+
continue;
|
|
45418
|
+
}
|
|
45419
|
+
} else {
|
|
45420
|
+
out[i2] = activeLang;
|
|
45421
|
+
if (trimmed2.startsWith(activeMarker.repeat(3)) && trimmed2.replace(new RegExp(`^${activeMarker === "`" ? "`" : "~"}+`), "").trim() === "") {
|
|
45422
|
+
activeMarker = null;
|
|
45423
|
+
activeLang = null;
|
|
45424
|
+
}
|
|
45425
|
+
}
|
|
45426
|
+
}
|
|
45427
|
+
return out;
|
|
45428
|
+
}
|
|
45429
|
+
function redactedPrefix(value) {
|
|
45430
|
+
const head = value.slice(0, 6);
|
|
45431
|
+
return `${head}...`;
|
|
45432
|
+
}
|
|
45433
|
+
async function checkContentSecrets(file2, _projectRoot) {
|
|
45434
|
+
const issues = [];
|
|
45435
|
+
const lines = file2.content.split(/\r?\n/);
|
|
45436
|
+
const fenceLangs = computeFenceLanguages(lines);
|
|
45437
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
45438
|
+
const line = lines[i2];
|
|
45439
|
+
const lineNo = i2 + 1;
|
|
45440
|
+
if (isCommentedExample(line)) continue;
|
|
45441
|
+
const fenceLang = fenceLangs[i2];
|
|
45442
|
+
if (fenceLang !== null && ILLUSTRATIVE_FENCES.has(fenceLang)) continue;
|
|
45443
|
+
if (lineLooksLikePlaceholder(line)) continue;
|
|
45444
|
+
if (PRIVATE_KEY_HEADER.test(line)) {
|
|
45445
|
+
issues.push({
|
|
45446
|
+
severity: "error",
|
|
45447
|
+
check: "content-secrets",
|
|
45448
|
+
ruleId: "content-secrets/private-key-header",
|
|
45449
|
+
line: lineNo,
|
|
45450
|
+
message: `Private key header detected in ${file2.relativePath}`,
|
|
45451
|
+
suggestion: "Move the secret to a `.env` or secret manager and reference it by name. If this token is real, rotate it immediately."
|
|
45452
|
+
});
|
|
45453
|
+
continue;
|
|
45454
|
+
}
|
|
45455
|
+
const seen = /* @__PURE__ */ new Set();
|
|
45456
|
+
for (const pattern of PATTERNS) {
|
|
45457
|
+
const re2 = new RegExp(pattern.regex.source, pattern.regex.flags);
|
|
45458
|
+
let m;
|
|
45459
|
+
while ((m = re2.exec(line)) !== null) {
|
|
45460
|
+
const matched = m[0];
|
|
45461
|
+
const start = m.index;
|
|
45462
|
+
const end = start + matched.length;
|
|
45463
|
+
const key = `${start}:${pattern.ruleSlug}`;
|
|
45464
|
+
if (seen.has(key)) continue;
|
|
45465
|
+
let overlap = false;
|
|
45466
|
+
for (const k3 of seen) {
|
|
45467
|
+
const [s] = k3.split(":");
|
|
45468
|
+
if (parseInt(s, 10) === start) {
|
|
45469
|
+
overlap = true;
|
|
45470
|
+
break;
|
|
45471
|
+
}
|
|
45472
|
+
}
|
|
45473
|
+
if (overlap) continue;
|
|
45474
|
+
if (isPlaceholderWrapped(line, start, end)) continue;
|
|
45475
|
+
seen.add(key);
|
|
45476
|
+
issues.push({
|
|
45477
|
+
severity: "error",
|
|
45478
|
+
check: "content-secrets",
|
|
45479
|
+
ruleId: `content-secrets/${pattern.ruleSlug}`,
|
|
45480
|
+
line: lineNo,
|
|
45481
|
+
message: `${pattern.label} detected in ${file2.relativePath} (${redactedPrefix(matched)})`,
|
|
45482
|
+
suggestion: "Move the secret to a `.env` or secret manager and reference it by name. If this token is real, rotate it immediately."
|
|
45483
|
+
});
|
|
45484
|
+
}
|
|
45485
|
+
}
|
|
45486
|
+
}
|
|
45487
|
+
return issues;
|
|
45488
|
+
}
|
|
45489
|
+
var PATTERNS, PRIVATE_KEY_HEADER, PLACEHOLDER_TOKENS, COMMENT_PREFIX, ILLUSTRATIVE_FENCES;
|
|
45490
|
+
var init_content_secrets = __esm({
|
|
45491
|
+
"src/core/checks/content-secrets.ts"() {
|
|
45492
|
+
"use strict";
|
|
45493
|
+
PATTERNS = [
|
|
45494
|
+
// AWS access key (long-lived) -- AKIA prefix + 16 uppercase alphanum chars.
|
|
45495
|
+
{
|
|
45496
|
+
ruleSlug: "aws-access-key",
|
|
45497
|
+
label: "AWS access key",
|
|
45498
|
+
regex: /\bAKIA[0-9A-Z]{16}\b/g
|
|
45499
|
+
},
|
|
45500
|
+
// AWS STS temporary access key -- ASIA prefix.
|
|
45501
|
+
{
|
|
45502
|
+
ruleSlug: "aws-access-key",
|
|
45503
|
+
label: "AWS access key",
|
|
45504
|
+
regex: /\bASIA[0-9A-Z]{16}\b/g
|
|
45505
|
+
},
|
|
45506
|
+
// GitHub classic PAT.
|
|
45507
|
+
{
|
|
45508
|
+
ruleSlug: "github-pat",
|
|
45509
|
+
label: "GitHub personal access token",
|
|
45510
|
+
regex: /\bghp_[A-Za-z0-9]{36,}\b/g
|
|
45511
|
+
},
|
|
45512
|
+
// GitHub fine-grained PAT.
|
|
45513
|
+
{
|
|
45514
|
+
ruleSlug: "github-pat",
|
|
45515
|
+
label: "GitHub personal access token",
|
|
45516
|
+
regex: /\bgithub_pat_[A-Za-z0-9_]{82,}\b/g
|
|
45517
|
+
},
|
|
45518
|
+
// GitHub server-to-server / OAuth / user / refresh tokens.
|
|
45519
|
+
{
|
|
45520
|
+
ruleSlug: "github-pat",
|
|
45521
|
+
label: "GitHub token",
|
|
45522
|
+
regex: /\bghs_[A-Za-z0-9]{36,}\b/g
|
|
45523
|
+
},
|
|
45524
|
+
{
|
|
45525
|
+
ruleSlug: "github-pat",
|
|
45526
|
+
label: "GitHub token",
|
|
45527
|
+
regex: /\bgho_[A-Za-z0-9]{36,}\b/g
|
|
45528
|
+
},
|
|
45529
|
+
{
|
|
45530
|
+
ruleSlug: "github-pat",
|
|
45531
|
+
label: "GitHub token",
|
|
45532
|
+
regex: /\bghu_[A-Za-z0-9]{36,}\b/g
|
|
45533
|
+
},
|
|
45534
|
+
{
|
|
45535
|
+
ruleSlug: "github-pat",
|
|
45536
|
+
label: "GitHub token",
|
|
45537
|
+
regex: /\bghr_[A-Za-z0-9]{36,}\b/g
|
|
45538
|
+
},
|
|
45539
|
+
// Anthropic API keys.
|
|
45540
|
+
{
|
|
45541
|
+
ruleSlug: "anthropic-key",
|
|
45542
|
+
label: "Anthropic API key",
|
|
45543
|
+
regex: /\bsk-ant-[A-Za-z0-9\-_]{20,}\b/g
|
|
45544
|
+
},
|
|
45545
|
+
// OpenAI API keys (project-scoped or classic). Match sk- or sk-proj- with at
|
|
45546
|
+
// least 20 random chars to avoid catching `sk-...` ellipses in docs.
|
|
45547
|
+
{
|
|
45548
|
+
ruleSlug: "openai-key",
|
|
45549
|
+
label: "OpenAI API key",
|
|
45550
|
+
regex: /\bsk-(?:proj-)?[A-Za-z0-9_\-]{20,}\b/g
|
|
45551
|
+
},
|
|
45552
|
+
// npm automation tokens.
|
|
45553
|
+
{
|
|
45554
|
+
ruleSlug: "npm-token",
|
|
45555
|
+
label: "npm token",
|
|
45556
|
+
regex: /\bnpm_[A-Za-z0-9]{36,}\b/g
|
|
45557
|
+
},
|
|
45558
|
+
// Slack tokens (bot, user, app, admin, refresh).
|
|
45559
|
+
{
|
|
45560
|
+
ruleSlug: "slack-token",
|
|
45561
|
+
label: "Slack token",
|
|
45562
|
+
regex: /\bxox[bpoasr]-[A-Za-z0-9\-]{10,}\b/g
|
|
45563
|
+
},
|
|
45564
|
+
// mcp.hosting PAT -- consistent with mcph/token-security.ts.
|
|
45565
|
+
{
|
|
45566
|
+
ruleSlug: "mcph-pat",
|
|
45567
|
+
label: "mcp.hosting PAT",
|
|
45568
|
+
regex: /\bmcp_pat_[A-Za-z0-9]{32,}\b/g
|
|
45569
|
+
},
|
|
45570
|
+
// Google API keys.
|
|
45571
|
+
{
|
|
45572
|
+
ruleSlug: "google-api-key",
|
|
45573
|
+
label: "Google API key",
|
|
45574
|
+
regex: /\bAIza[0-9A-Za-z\-_]{35}\b/g
|
|
45575
|
+
},
|
|
45576
|
+
// Stripe live secret keys.
|
|
45577
|
+
{
|
|
45578
|
+
ruleSlug: "stripe-secret",
|
|
45579
|
+
label: "Stripe live secret key",
|
|
45580
|
+
regex: /\bsk_live_[0-9a-zA-Z]{24,}\b/g
|
|
45581
|
+
}
|
|
45582
|
+
];
|
|
45583
|
+
PRIVATE_KEY_HEADER = /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/;
|
|
45584
|
+
PLACEHOLDER_TOKENS = [
|
|
45585
|
+
"example",
|
|
45586
|
+
"placeholder",
|
|
45587
|
+
"your-key",
|
|
45588
|
+
"<replace",
|
|
45589
|
+
"redacted",
|
|
45590
|
+
"xxxx",
|
|
45591
|
+
"****"
|
|
45592
|
+
];
|
|
45593
|
+
COMMENT_PREFIX = /^\s*(?:#|\/\/|--|<!--)/;
|
|
45594
|
+
ILLUSTRATIVE_FENCES = /* @__PURE__ */ new Set(["text", "txt", "example", "pseudocode", "none", ""]);
|
|
45595
|
+
}
|
|
45596
|
+
});
|
|
45597
|
+
|
|
45359
45598
|
// src/version.ts
|
|
45360
|
-
import { readFileSync as
|
|
45599
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
45361
45600
|
import { resolve as resolve8, dirname as dirname3 } from "node:path";
|
|
45362
45601
|
import { fileURLToPath } from "node:url";
|
|
45363
45602
|
function loadVersion() {
|
|
45364
|
-
if (true) return "0.
|
|
45603
|
+
if (true) return "0.10.0";
|
|
45365
45604
|
const __dir = dirname3(fileURLToPath(import.meta.url));
|
|
45366
45605
|
const pkgPath = resolve8(__dir, "../package.json");
|
|
45367
|
-
const pkg = JSON.parse(
|
|
45606
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
45368
45607
|
return pkg.version;
|
|
45369
45608
|
}
|
|
45370
45609
|
var VERSION;
|
|
@@ -45392,6 +45631,7 @@ function deriveChecksToRun(activeChecks, prefix, enabled, allChecks) {
|
|
|
45392
45631
|
}
|
|
45393
45632
|
async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
45394
45633
|
const fileResults = [];
|
|
45634
|
+
const thresholds = resolveTokenThresholds(options.tokenThresholds);
|
|
45395
45635
|
const shouldRunContextChecks = !options.mcpOnly && !options.mcphOnly && !options.sessionOnly;
|
|
45396
45636
|
const shouldRunMcpChecks = options.mcp || options.mcpGlobal || options.mcpOnly || hasMcpChecks(activeChecks);
|
|
45397
45637
|
const shouldRunMcphChecks = options.mcph || options.mcphGlobal || options.mcphOnly || hasMcphChecks(activeChecks);
|
|
@@ -45407,13 +45647,16 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45407
45647
|
if (activeChecks.includes("paths")) checkPromises.push(checkPaths(file2, projectRoot));
|
|
45408
45648
|
if (activeChecks.includes("commands")) checkPromises.push(checkCommands(file2, projectRoot));
|
|
45409
45649
|
if (activeChecks.includes("staleness")) checkPromises.push(checkStaleness(file2, projectRoot));
|
|
45410
|
-
if (activeChecks.includes("tokens"))
|
|
45650
|
+
if (activeChecks.includes("tokens"))
|
|
45651
|
+
checkPromises.push(checkTokens(file2, projectRoot, thresholds));
|
|
45411
45652
|
if (activeChecks.includes("tier-tokens"))
|
|
45412
|
-
checkPromises.push(checkTierTokens(file2, projectRoot));
|
|
45653
|
+
checkPromises.push(checkTierTokens(file2, projectRoot, thresholds));
|
|
45413
45654
|
if (activeChecks.includes("redundancy"))
|
|
45414
45655
|
checkPromises.push(checkRedundancy(file2, projectRoot));
|
|
45415
45656
|
if (activeChecks.includes("frontmatter"))
|
|
45416
45657
|
checkPromises.push(checkFrontmatter(file2, projectRoot));
|
|
45658
|
+
if (activeChecks.includes("content-secrets"))
|
|
45659
|
+
checkPromises.push(checkContentSecrets(file2, projectRoot));
|
|
45417
45660
|
const results = await Promise.all(checkPromises);
|
|
45418
45661
|
const issues = results.flat();
|
|
45419
45662
|
fileResults.push({
|
|
@@ -45428,12 +45671,13 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45428
45671
|
const crossFileIssues = [];
|
|
45429
45672
|
if (activeChecks.includes("tokens")) {
|
|
45430
45673
|
const aggIssue = checkAggregateTokens(
|
|
45431
|
-
fileResults.map((f) => ({ path: f.path, tokens: f.tokens }))
|
|
45674
|
+
fileResults.map((f) => ({ path: f.path, tokens: f.tokens })),
|
|
45675
|
+
thresholds
|
|
45432
45676
|
);
|
|
45433
45677
|
if (aggIssue) crossFileIssues.push(aggIssue);
|
|
45434
45678
|
}
|
|
45435
45679
|
if (activeChecks.includes("tier-tokens")) {
|
|
45436
|
-
const tierAgg = checkAggregateTierTokens(parsed);
|
|
45680
|
+
const tierAgg = checkAggregateTierTokens(parsed, thresholds);
|
|
45437
45681
|
if (tierAgg) crossFileIssues.push(tierAgg);
|
|
45438
45682
|
}
|
|
45439
45683
|
if (activeChecks.includes("redundancy")) {
|
|
@@ -45569,7 +45813,7 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
45569
45813
|
issues.push({
|
|
45570
45814
|
severity: "error",
|
|
45571
45815
|
check: "mcph-schema-conformance",
|
|
45572
|
-
ruleId: "mcph-
|
|
45816
|
+
ruleId: "mcph-schema-conformance/parse-error",
|
|
45573
45817
|
line: 1,
|
|
45574
45818
|
message: err
|
|
45575
45819
|
});
|
|
@@ -45690,6 +45934,7 @@ var init_audit = __esm({
|
|
|
45690
45934
|
init_memory_index_overflow();
|
|
45691
45935
|
init_ci_coverage();
|
|
45692
45936
|
init_ci_secrets();
|
|
45937
|
+
init_content_secrets();
|
|
45693
45938
|
init_version2();
|
|
45694
45939
|
ALL_CHECKS = [
|
|
45695
45940
|
"paths",
|
|
@@ -45701,7 +45946,8 @@ var init_audit = __esm({
|
|
|
45701
45946
|
"contradictions",
|
|
45702
45947
|
"frontmatter",
|
|
45703
45948
|
"ci-coverage",
|
|
45704
|
-
"ci-secrets"
|
|
45949
|
+
"ci-secrets",
|
|
45950
|
+
"content-secrets"
|
|
45705
45951
|
];
|
|
45706
45952
|
ALL_MCP_CHECKS = [
|
|
45707
45953
|
"mcp-schema",
|
|
@@ -53214,7 +53460,6 @@ var init_ora = __esm({
|
|
|
53214
53460
|
function classifyFile(f) {
|
|
53215
53461
|
if (f.path === "(project)") return "context";
|
|
53216
53462
|
if (f.path === "(mcp)") return "mcp";
|
|
53217
|
-
if (f.path === "(mcph)") return "mcph";
|
|
53218
53463
|
if (f.path.includes("session audit")) return "session";
|
|
53219
53464
|
for (const issue2 of f.issues) {
|
|
53220
53465
|
if (issue2.check.startsWith("session-")) return "session";
|
|
@@ -53477,6 +53722,11 @@ function buildRuleDescriptors() {
|
|
|
53477
53722
|
shortDescription: { text: "CI secret not documented in context files" },
|
|
53478
53723
|
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
53479
53724
|
},
|
|
53725
|
+
{
|
|
53726
|
+
id: "ctxlint/content-secrets",
|
|
53727
|
+
shortDescription: { text: "Inline-pasted secret detected in a context file" },
|
|
53728
|
+
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
53729
|
+
},
|
|
53480
53730
|
{
|
|
53481
53731
|
id: "ctxlint/mcp-schema",
|
|
53482
53732
|
shortDescription: { text: "MCP config structural validation error" },
|
|
@@ -53673,7 +53923,8 @@ function suggestKey(unknown2) {
|
|
|
53673
53923
|
best = known;
|
|
53674
53924
|
}
|
|
53675
53925
|
}
|
|
53676
|
-
|
|
53926
|
+
const threshold = Math.min(4, Math.max(2, Math.floor(unknown2.length / 3)));
|
|
53927
|
+
if (best && bestDist <= threshold) {
|
|
53677
53928
|
return best;
|
|
53678
53929
|
}
|
|
53679
53930
|
return null;
|
|
@@ -53801,7 +54052,8 @@ async function runCli() {
|
|
|
53801
54052
|
mcphOnly: options.mcphOnly,
|
|
53802
54053
|
mcphStrictEnvToken: options.mcphStrictEnvToken,
|
|
53803
54054
|
session: options.session,
|
|
53804
|
-
sessionOnly: options.sessionOnly
|
|
54055
|
+
sessionOnly: options.sessionOnly,
|
|
54056
|
+
tokenThresholds: config2?.tokenThresholds
|
|
53805
54057
|
});
|
|
53806
54058
|
spinner?.stop();
|
|
53807
54059
|
if (result.files.length === 0) {
|
|
@@ -53894,7 +54146,6 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
53894
54146
|
resetGit();
|
|
53895
54147
|
resetPathsCache();
|
|
53896
54148
|
resetPackageJsonCache();
|
|
53897
|
-
resetTokenThresholds();
|
|
53898
54149
|
}
|
|
53899
54150
|
if (opts.watch) {
|
|
53900
54151
|
const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
|
|
@@ -53956,11 +54207,6 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
53956
54207
|
console.error("Error reloading config:", err instanceof Error ? err.message : err);
|
|
53957
54208
|
}
|
|
53958
54209
|
try {
|
|
53959
|
-
if (liveConfig?.tokenThresholds) {
|
|
53960
|
-
setTokenThresholds(liveConfig.tokenThresholds);
|
|
53961
|
-
} else {
|
|
53962
|
-
resetTokenThresholds();
|
|
53963
|
-
}
|
|
53964
54210
|
const result = await runAudit(resolvedPath, liveActiveChecks, {
|
|
53965
54211
|
depth: liveOptions.depth,
|
|
53966
54212
|
extraPatterns: liveConfig?.contextFiles,
|
|
@@ -53972,7 +54218,8 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
53972
54218
|
mcphOnly: liveOptions.mcphOnly,
|
|
53973
54219
|
mcphStrictEnvToken: liveOptions.mcphStrictEnvToken,
|
|
53974
54220
|
session: liveOptions.session,
|
|
53975
|
-
sessionOnly: liveOptions.sessionOnly
|
|
54221
|
+
sessionOnly: liveOptions.sessionOnly,
|
|
54222
|
+
tokenThresholds: liveConfig?.tokenThresholds
|
|
53976
54223
|
});
|
|
53977
54224
|
if (result.files.length === 0) {
|
|
53978
54225
|
console.log("\nNo context files found.\n");
|
|
@@ -53992,7 +54239,6 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
|
|
|
53992
54239
|
resetGit();
|
|
53993
54240
|
resetPathsCache();
|
|
53994
54241
|
resetPackageJsonCache();
|
|
53995
|
-
resetTokenThresholds();
|
|
53996
54242
|
}
|
|
53997
54243
|
console.log(chalk2.dim("\nWatching for changes... (Ctrl+C to stop)\n"));
|
|
53998
54244
|
}, 300);
|
|
@@ -54136,9 +54382,6 @@ function resolveSession(resolvedPath, opts) {
|
|
|
54136
54382
|
session: effectiveSession,
|
|
54137
54383
|
sessionOnly
|
|
54138
54384
|
};
|
|
54139
|
-
if (config2?.tokenThresholds) {
|
|
54140
|
-
setTokenThresholds(config2.tokenThresholds);
|
|
54141
|
-
}
|
|
54142
54385
|
const activeChecks = options.checks.filter((c3) => !options.ignore.includes(c3));
|
|
54143
54386
|
return { config: config2, options, activeChecks };
|
|
54144
54387
|
}
|
|
@@ -54158,7 +54401,6 @@ var init_cli = __esm({
|
|
|
54158
54401
|
init_esm3();
|
|
54159
54402
|
init_ora();
|
|
54160
54403
|
init_paths();
|
|
54161
|
-
init_tokens2();
|
|
54162
54404
|
init_reporter();
|
|
54163
54405
|
init_fixer();
|
|
54164
54406
|
init_tokens();
|