@yawlabs/ctxlint 0.9.6 → 0.9.8
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 +253 -78
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34322,29 +34322,24 @@ async function scanForContextFiles(projectRoot, options = {}) {
|
|
|
34322
34322
|
}
|
|
34323
34323
|
}
|
|
34324
34324
|
collectDirs(projectRoot, 0);
|
|
34325
|
-
|
|
34326
|
-
|
|
34327
|
-
|
|
34328
|
-
|
|
34329
|
-
|
|
34330
|
-
|
|
34331
|
-
|
|
34325
|
+
const perDirMatches = await Promise.all(
|
|
34326
|
+
dirsToScan.map((dir) => Ze(patterns, { cwd: dir, absolute: true, nodir: true, dot: true }))
|
|
34327
|
+
);
|
|
34328
|
+
for (const matches of perDirMatches) {
|
|
34329
|
+
for (const match of matches) {
|
|
34330
|
+
const normalized = path2.normalize(match);
|
|
34331
|
+
if (seen.has(normalized)) continue;
|
|
34332
|
+
seen.add(normalized);
|
|
34333
|
+
const relativePath = path2.relative(projectRoot, normalized);
|
|
34334
|
+
const symlink = isSymlink(normalized);
|
|
34335
|
+
const target = symlink ? readSymlinkTarget(normalized) : void 0;
|
|
34336
|
+
found.push({
|
|
34337
|
+
absolutePath: normalized,
|
|
34338
|
+
relativePath: relativePath.replace(/\\/g, "/"),
|
|
34339
|
+
isSymlink: symlink,
|
|
34340
|
+
symlinkTarget: target,
|
|
34341
|
+
type: "context"
|
|
34332
34342
|
});
|
|
34333
|
-
for (const match of matches) {
|
|
34334
|
-
const normalized = path2.normalize(match);
|
|
34335
|
-
if (seen.has(normalized)) continue;
|
|
34336
|
-
seen.add(normalized);
|
|
34337
|
-
const relativePath = path2.relative(projectRoot, normalized);
|
|
34338
|
-
const symlink = isSymlink(normalized);
|
|
34339
|
-
const target = symlink ? readSymlinkTarget(normalized) : void 0;
|
|
34340
|
-
found.push({
|
|
34341
|
-
absolutePath: normalized,
|
|
34342
|
-
relativePath: relativePath.replace(/\\/g, "/"),
|
|
34343
|
-
isSymlink: symlink,
|
|
34344
|
-
symlinkTarget: target,
|
|
34345
|
-
type: "context"
|
|
34346
|
-
});
|
|
34347
|
-
}
|
|
34348
34343
|
}
|
|
34349
34344
|
}
|
|
34350
34345
|
return found.sort((a, b2) => a.relativePath.localeCompare(b2.relativePath));
|
|
@@ -34622,9 +34617,14 @@ function extractPathReferences(lines, sections) {
|
|
|
34622
34617
|
if (/^[A-Z][\w.-]*\/[A-Z][\w.-]*$/.test(value) && !value.includes(".")) {
|
|
34623
34618
|
continue;
|
|
34624
34619
|
}
|
|
34620
|
+
let cleanValue = value;
|
|
34621
|
+
while (/[.,;:]$/.test(cleanValue) && cleanValue.includes("/")) {
|
|
34622
|
+
cleanValue = cleanValue.slice(0, -1);
|
|
34623
|
+
}
|
|
34624
|
+
if (!cleanValue.includes("/")) continue;
|
|
34625
34625
|
const column = match.index + match[0].length - match[1].length + 1;
|
|
34626
34626
|
paths.push({
|
|
34627
|
-
value,
|
|
34627
|
+
value: cleanValue,
|
|
34628
34628
|
line: i2 + 1,
|
|
34629
34629
|
// 1-indexed
|
|
34630
34630
|
column,
|
|
@@ -40501,17 +40501,54 @@ async function getFileLastModified(projectRoot, filePath) {
|
|
|
40501
40501
|
return null;
|
|
40502
40502
|
}
|
|
40503
40503
|
}
|
|
40504
|
-
async function
|
|
40504
|
+
async function getCommitsSinceBatch(projectRoot, paths, since) {
|
|
40505
|
+
const counts = /* @__PURE__ */ new Map();
|
|
40506
|
+
for (const p2 of paths) counts.set(p2, 0);
|
|
40507
|
+
if (paths.length === 0) return counts;
|
|
40505
40508
|
try {
|
|
40506
40509
|
const git = getGit(projectRoot);
|
|
40507
|
-
const
|
|
40508
|
-
|
|
40509
|
-
"
|
|
40510
|
-
|
|
40511
|
-
|
|
40510
|
+
const SENTINEL = "___CTXLINT_COMMIT___";
|
|
40511
|
+
const raw = await git.raw([
|
|
40512
|
+
"log",
|
|
40513
|
+
`--since=${since.toISOString()}`,
|
|
40514
|
+
"--name-only",
|
|
40515
|
+
`--format=${SENTINEL}`
|
|
40516
|
+
]);
|
|
40517
|
+
const normalize3 = (p2) => p2.replace(/\\/g, "/");
|
|
40518
|
+
const requested = new Set(paths.map(normalize3));
|
|
40519
|
+
const lines = raw.split("\n");
|
|
40520
|
+
let inCommit = false;
|
|
40521
|
+
const seenThisCommit = /* @__PURE__ */ new Set();
|
|
40522
|
+
const flush = () => {
|
|
40523
|
+
for (const p2 of seenThisCommit) {
|
|
40524
|
+
counts.set(p2, (counts.get(p2) ?? 0) + 1);
|
|
40525
|
+
}
|
|
40526
|
+
seenThisCommit.clear();
|
|
40527
|
+
};
|
|
40528
|
+
for (const line of lines) {
|
|
40529
|
+
if (line === SENTINEL) {
|
|
40530
|
+
if (inCommit) flush();
|
|
40531
|
+
inCommit = true;
|
|
40532
|
+
continue;
|
|
40533
|
+
}
|
|
40534
|
+
if (!inCommit) continue;
|
|
40535
|
+
const trimmed2 = line.trim();
|
|
40536
|
+
if (!trimmed2) continue;
|
|
40537
|
+
const changed = normalize3(trimmed2);
|
|
40538
|
+
for (const req of requested) {
|
|
40539
|
+
if (changed === req || changed.startsWith(req.endsWith("/") ? req : req + "/")) {
|
|
40540
|
+
seenThisCommit.add(req);
|
|
40541
|
+
}
|
|
40542
|
+
}
|
|
40543
|
+
}
|
|
40544
|
+
if (inCommit) flush();
|
|
40512
40545
|
} catch {
|
|
40513
|
-
return 0;
|
|
40514
40546
|
}
|
|
40547
|
+
const out = /* @__PURE__ */ new Map();
|
|
40548
|
+
for (const p2 of paths) {
|
|
40549
|
+
out.set(p2, counts.get(p2.replace(/\\/g, "/")) ?? 0);
|
|
40550
|
+
}
|
|
40551
|
+
return out;
|
|
40515
40552
|
}
|
|
40516
40553
|
async function findRenames(projectRoot, filePath) {
|
|
40517
40554
|
try {
|
|
@@ -40853,7 +40890,7 @@ var require_levenshtein = __commonJS({
|
|
|
40853
40890
|
} catch (err) {
|
|
40854
40891
|
console.log("Collator could not be initialized and wouldn't be used");
|
|
40855
40892
|
}
|
|
40856
|
-
var
|
|
40893
|
+
var levenshtein3 = require_mod();
|
|
40857
40894
|
var prevRow = [], str2Char = [];
|
|
40858
40895
|
var Levenshtein = {
|
|
40859
40896
|
/**
|
|
@@ -40898,7 +40935,7 @@ var require_levenshtein = __commonJS({
|
|
|
40898
40935
|
}
|
|
40899
40936
|
return nextCol;
|
|
40900
40937
|
}
|
|
40901
|
-
return
|
|
40938
|
+
return levenshtein3.distance(str1, str2);
|
|
40902
40939
|
}
|
|
40903
40940
|
};
|
|
40904
40941
|
if (typeof define !== "undefined" && define !== null && define.amd) {
|
|
@@ -41193,11 +41230,14 @@ async function checkStaleness(file2, projectRoot) {
|
|
|
41193
41230
|
for (const ref of file2.references.paths) {
|
|
41194
41231
|
referencedPaths.add(ref.value);
|
|
41195
41232
|
}
|
|
41233
|
+
if (referencedPaths.size === 0) {
|
|
41234
|
+
return issues;
|
|
41235
|
+
}
|
|
41236
|
+
const counts = await getCommitsSinceBatch(projectRoot, [...referencedPaths], lastModified);
|
|
41196
41237
|
let totalCommits = 0;
|
|
41197
41238
|
let mostActiveRef = "";
|
|
41198
41239
|
let mostActiveCommits = 0;
|
|
41199
|
-
for (const refPath of
|
|
41200
|
-
const commits = await getCommitsSince(projectRoot, refPath, lastModified);
|
|
41240
|
+
for (const [refPath, commits] of counts) {
|
|
41201
41241
|
totalCommits += commits;
|
|
41202
41242
|
if (commits > mostActiveCommits) {
|
|
41203
41243
|
mostActiveCommits = commits;
|
|
@@ -41688,54 +41728,88 @@ function detectDirectives(file2) {
|
|
|
41688
41728
|
function checkContradictions(files) {
|
|
41689
41729
|
if (files.length < 2) return [];
|
|
41690
41730
|
const issues = [];
|
|
41691
|
-
const allDirectives = [];
|
|
41692
|
-
for (const file2 of files) {
|
|
41693
|
-
allDirectives.push(...detectDirectives(file2));
|
|
41694
|
-
}
|
|
41695
41731
|
const byCategory = /* @__PURE__ */ new Map();
|
|
41696
|
-
|
|
41697
|
-
|
|
41698
|
-
|
|
41699
|
-
|
|
41732
|
+
const directiveIndex = /* @__PURE__ */ new Map();
|
|
41733
|
+
for (const file2 of files) {
|
|
41734
|
+
for (const d of detectDirectives(file2)) {
|
|
41735
|
+
let list = byCategory.get(d.category);
|
|
41736
|
+
if (!list) {
|
|
41737
|
+
list = [];
|
|
41738
|
+
byCategory.set(d.category, list);
|
|
41739
|
+
}
|
|
41740
|
+
list.push(d);
|
|
41741
|
+
let idx = directiveIndex.get(d.category);
|
|
41742
|
+
if (!idx) {
|
|
41743
|
+
idx = /* @__PURE__ */ new Map();
|
|
41744
|
+
directiveIndex.set(d.category, idx);
|
|
41745
|
+
}
|
|
41746
|
+
const key = `${d.file}::${d.label}`;
|
|
41747
|
+
if (!idx.has(key)) idx.set(key, d);
|
|
41748
|
+
}
|
|
41700
41749
|
}
|
|
41701
41750
|
for (const [category, directives] of byCategory) {
|
|
41702
|
-
const byFile = /* @__PURE__ */ new Map();
|
|
41703
|
-
for (const d of directives) {
|
|
41704
|
-
const existing = byFile.get(d.file) || [];
|
|
41705
|
-
existing.push(d);
|
|
41706
|
-
byFile.set(d.file, existing);
|
|
41707
|
-
}
|
|
41708
41751
|
const labels = new Set(directives.map((d) => d.label));
|
|
41709
41752
|
if (labels.size <= 1) continue;
|
|
41710
41753
|
const fileLabels = /* @__PURE__ */ new Map();
|
|
41711
41754
|
for (const d of directives) {
|
|
41712
|
-
|
|
41755
|
+
let existing = fileLabels.get(d.file);
|
|
41756
|
+
if (!existing) {
|
|
41757
|
+
existing = /* @__PURE__ */ new Set();
|
|
41758
|
+
fileLabels.set(d.file, existing);
|
|
41759
|
+
}
|
|
41713
41760
|
existing.add(d.label);
|
|
41714
|
-
|
|
41715
|
-
|
|
41716
|
-
|
|
41717
|
-
|
|
41718
|
-
|
|
41719
|
-
const
|
|
41720
|
-
|
|
41721
|
-
|
|
41722
|
-
|
|
41723
|
-
|
|
41724
|
-
|
|
41725
|
-
|
|
41726
|
-
|
|
41727
|
-
|
|
41728
|
-
|
|
41729
|
-
|
|
41730
|
-
|
|
41731
|
-
|
|
41732
|
-
|
|
41733
|
-
|
|
41734
|
-
|
|
41735
|
-
|
|
41736
|
-
|
|
41761
|
+
}
|
|
41762
|
+
const conflictingFiles = [...fileLabels.keys()].filter((f) => {
|
|
41763
|
+
const myLabels = fileLabels.get(f);
|
|
41764
|
+
for (const [otherFile, otherLabels] of fileLabels) {
|
|
41765
|
+
if (otherFile === f) continue;
|
|
41766
|
+
for (const l of myLabels) {
|
|
41767
|
+
if (!otherLabels.has(l)) return true;
|
|
41768
|
+
}
|
|
41769
|
+
}
|
|
41770
|
+
return false;
|
|
41771
|
+
});
|
|
41772
|
+
if (conflictingFiles.length < 2) continue;
|
|
41773
|
+
const idx = directiveIndex.get(category);
|
|
41774
|
+
if (conflictingFiles.length === 2) {
|
|
41775
|
+
const [fileA, fileB] = conflictingFiles;
|
|
41776
|
+
const labelsA = fileLabels.get(fileA);
|
|
41777
|
+
const labelsB = fileLabels.get(fileB);
|
|
41778
|
+
for (const labelA of labelsA) {
|
|
41779
|
+
for (const labelB of labelsB) {
|
|
41780
|
+
if (labelA === labelB) continue;
|
|
41781
|
+
const directiveA = idx.get(`${fileA}::${labelA}`);
|
|
41782
|
+
const directiveB = idx.get(`${fileB}::${labelB}`);
|
|
41783
|
+
issues.push({
|
|
41784
|
+
severity: "warning",
|
|
41785
|
+
check: "contradictions",
|
|
41786
|
+
ruleId: "contradictions/conflict",
|
|
41787
|
+
line: directiveA.line,
|
|
41788
|
+
message: `${category} conflict: "${directiveA.label}" in ${fileA} vs "${directiveB.label}" in ${fileB}`,
|
|
41789
|
+
suggestion: `Align on one ${category} across all context files`,
|
|
41790
|
+
detail: `${fileA}:${directiveA.line} says "${directiveA.text}" but ${fileB}:${directiveB.line} says "${directiveB.text}"`
|
|
41791
|
+
});
|
|
41737
41792
|
}
|
|
41738
41793
|
}
|
|
41794
|
+
} else {
|
|
41795
|
+
const entries = [];
|
|
41796
|
+
for (const f of conflictingFiles) {
|
|
41797
|
+
for (const l of fileLabels.get(f)) {
|
|
41798
|
+
entries.push(idx.get(`${f}::${l}`));
|
|
41799
|
+
}
|
|
41800
|
+
}
|
|
41801
|
+
const firstEntry = entries[0];
|
|
41802
|
+
const summary = entries.map((e) => `"${e.label}" in ${e.file}`).join(", ");
|
|
41803
|
+
const detail = entries.map((e) => `${e.file}:${e.line} says "${e.text}"`).join("\n");
|
|
41804
|
+
issues.push({
|
|
41805
|
+
severity: "warning",
|
|
41806
|
+
check: "contradictions",
|
|
41807
|
+
ruleId: "contradictions/conflict",
|
|
41808
|
+
line: firstEntry.line,
|
|
41809
|
+
message: `${category} conflict across ${conflictingFiles.length} files: ${summary}`,
|
|
41810
|
+
suggestion: `Align on one ${category} across all context files`,
|
|
41811
|
+
detail
|
|
41812
|
+
});
|
|
41739
41813
|
}
|
|
41740
41814
|
}
|
|
41741
41815
|
return issues;
|
|
@@ -42413,6 +42487,7 @@ async function checkMcpCommands(config2, projectRoot) {
|
|
|
42413
42487
|
}
|
|
42414
42488
|
if (server2.args) {
|
|
42415
42489
|
for (const arg of server2.args) {
|
|
42490
|
+
if (URL_PREFIX.test(arg)) continue;
|
|
42416
42491
|
if (LOCAL_PATH_PATTERN.test(arg) || FILE_PATH_PATTERN.test(arg)) {
|
|
42417
42492
|
const resolved = path8.resolve(projectRoot, arg);
|
|
42418
42493
|
if (!fileExistsSafe(resolved)) {
|
|
@@ -42438,12 +42513,13 @@ function fileExistsSafe(filePath) {
|
|
|
42438
42513
|
return false;
|
|
42439
42514
|
}
|
|
42440
42515
|
}
|
|
42441
|
-
var LOCAL_PATH_PATTERN, FILE_PATH_PATTERN;
|
|
42516
|
+
var LOCAL_PATH_PATTERN, FILE_PATH_PATTERN, URL_PREFIX;
|
|
42442
42517
|
var init_commands2 = __esm({
|
|
42443
42518
|
"src/core/checks/mcp/commands.ts"() {
|
|
42444
42519
|
"use strict";
|
|
42445
42520
|
LOCAL_PATH_PATTERN = /^\.\.?\//;
|
|
42446
42521
|
FILE_PATH_PATTERN = /^[^-].*\/.*\.\w+$/;
|
|
42522
|
+
URL_PREFIX = /^(https?|file|s3|gs|ssh|git):\/\//i;
|
|
42447
42523
|
}
|
|
42448
42524
|
});
|
|
42449
42525
|
|
|
@@ -43766,7 +43842,7 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
43766
43842
|
import { resolve as resolve8, dirname as dirname3 } from "node:path";
|
|
43767
43843
|
import { fileURLToPath } from "node:url";
|
|
43768
43844
|
function loadVersion() {
|
|
43769
|
-
if (true) return "0.9.
|
|
43845
|
+
if (true) return "0.9.8";
|
|
43770
43846
|
const __dir = dirname3(fileURLToPath(import.meta.url));
|
|
43771
43847
|
const pkgPath = resolve8(__dir, "../package.json");
|
|
43772
43848
|
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
@@ -44597,6 +44673,7 @@ function applyFixes(result, options = {}) {
|
|
|
44597
44673
|
}
|
|
44598
44674
|
}
|
|
44599
44675
|
const fixesByFile = /* @__PURE__ */ new Map();
|
|
44676
|
+
const dedupeKeys = /* @__PURE__ */ new Map();
|
|
44600
44677
|
const skippedSymlinks = /* @__PURE__ */ new Set();
|
|
44601
44678
|
for (const file2 of result.files) {
|
|
44602
44679
|
for (const issue2 of file2.issues) {
|
|
@@ -44605,6 +44682,14 @@ function applyFixes(result, options = {}) {
|
|
|
44605
44682
|
skippedSymlinks.add(file2.path);
|
|
44606
44683
|
continue;
|
|
44607
44684
|
}
|
|
44685
|
+
const key = `${issue2.fix.line}:${issue2.fix.oldText}:${issue2.fix.newText}`;
|
|
44686
|
+
let seenInFile = dedupeKeys.get(issue2.fix.file);
|
|
44687
|
+
if (!seenInFile) {
|
|
44688
|
+
seenInFile = /* @__PURE__ */ new Set();
|
|
44689
|
+
dedupeKeys.set(issue2.fix.file, seenInFile);
|
|
44690
|
+
}
|
|
44691
|
+
if (seenInFile.has(key)) continue;
|
|
44692
|
+
seenInFile.add(key);
|
|
44608
44693
|
const existing = fixesByFile.get(issue2.fix.file) || [];
|
|
44609
44694
|
existing.push(issue2.fix);
|
|
44610
44695
|
fixesByFile.set(issue2.fix.file, existing);
|
|
@@ -51751,6 +51836,76 @@ var init_reporter = __esm({
|
|
|
51751
51836
|
// src/core/config.ts
|
|
51752
51837
|
import * as fs8 from "node:fs";
|
|
51753
51838
|
import * as path10 from "node:path";
|
|
51839
|
+
function formatJsonError(content, err) {
|
|
51840
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
51841
|
+
const posMatch = msg.match(/position (\d+)/);
|
|
51842
|
+
if (posMatch) {
|
|
51843
|
+
return `${msg} (${posToLineCol(content, Number(posMatch[1]))})`;
|
|
51844
|
+
}
|
|
51845
|
+
if (/line \d+/.test(msg)) return msg;
|
|
51846
|
+
const pos = findFirstErrorPos(content, msg);
|
|
51847
|
+
if (pos !== null) {
|
|
51848
|
+
return `${msg} (${posToLineCol(content, pos)})`;
|
|
51849
|
+
}
|
|
51850
|
+
return msg;
|
|
51851
|
+
}
|
|
51852
|
+
function posToLineCol(content, pos) {
|
|
51853
|
+
let line = 1;
|
|
51854
|
+
let col = 1;
|
|
51855
|
+
for (let i2 = 0; i2 < pos && i2 < content.length; i2++) {
|
|
51856
|
+
if (content[i2] === "\n") {
|
|
51857
|
+
line++;
|
|
51858
|
+
col = 1;
|
|
51859
|
+
} else {
|
|
51860
|
+
col++;
|
|
51861
|
+
}
|
|
51862
|
+
}
|
|
51863
|
+
return `line ${line}, column ${col}`;
|
|
51864
|
+
}
|
|
51865
|
+
function findFirstErrorPos(content, errMsg) {
|
|
51866
|
+
const snippetMatch = errMsg.match(/\.\.\."([^"]+(?:"[^"]*)*?)"\s+is not valid JSON/);
|
|
51867
|
+
if (snippetMatch) {
|
|
51868
|
+
const snippet = snippetMatch[1];
|
|
51869
|
+
const needle = snippet.split(/\s/).find((s) => s.length > 1);
|
|
51870
|
+
if (needle) {
|
|
51871
|
+
const idx = content.indexOf(needle);
|
|
51872
|
+
if (idx !== -1) return idx;
|
|
51873
|
+
}
|
|
51874
|
+
}
|
|
51875
|
+
const tokenMatch = errMsg.match(/Unexpected token\s+['"]?([^'",]+?)['"]?,/);
|
|
51876
|
+
if (tokenMatch) {
|
|
51877
|
+
const tok = tokenMatch[1];
|
|
51878
|
+
const idx = content.indexOf(tok);
|
|
51879
|
+
if (idx !== -1) return idx;
|
|
51880
|
+
}
|
|
51881
|
+
return null;
|
|
51882
|
+
}
|
|
51883
|
+
function suggestKey(unknown2) {
|
|
51884
|
+
let best = null;
|
|
51885
|
+
let bestDist = Infinity;
|
|
51886
|
+
for (const known of KNOWN_CONFIG_KEYS) {
|
|
51887
|
+
const d = levenshtein2(unknown2, known);
|
|
51888
|
+
if (d < bestDist) {
|
|
51889
|
+
bestDist = d;
|
|
51890
|
+
best = known;
|
|
51891
|
+
}
|
|
51892
|
+
}
|
|
51893
|
+
if (best && bestDist <= Math.max(2, Math.floor(unknown2.length / 3))) {
|
|
51894
|
+
return best;
|
|
51895
|
+
}
|
|
51896
|
+
return null;
|
|
51897
|
+
}
|
|
51898
|
+
function warnUnknownKeys(config2, source) {
|
|
51899
|
+
if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) return;
|
|
51900
|
+
const keys = Object.keys(config2);
|
|
51901
|
+
const known = new Set(KNOWN_CONFIG_KEYS);
|
|
51902
|
+
for (const k3 of keys) {
|
|
51903
|
+
if (known.has(k3)) continue;
|
|
51904
|
+
const hint = suggestKey(k3);
|
|
51905
|
+
const suggestion = hint ? ` \u2014 did you mean "${hint}"?` : "";
|
|
51906
|
+
console.error(`Warning: unknown config key "${k3}" in ${source}${suggestion}`);
|
|
51907
|
+
}
|
|
51908
|
+
}
|
|
51754
51909
|
function loadConfig(projectRoot) {
|
|
51755
51910
|
for (const filename of CONFIG_FILENAMES) {
|
|
51756
51911
|
const filePath = path10.join(projectRoot, filename);
|
|
@@ -51760,19 +51915,39 @@ function loadConfig(projectRoot) {
|
|
|
51760
51915
|
} catch {
|
|
51761
51916
|
continue;
|
|
51762
51917
|
}
|
|
51918
|
+
let parsed;
|
|
51763
51919
|
try {
|
|
51764
|
-
|
|
51920
|
+
parsed = JSON.parse(content);
|
|
51765
51921
|
} catch (err) {
|
|
51766
|
-
|
|
51767
|
-
|
|
51922
|
+
throw new Error(`Invalid JSON in ${filePath}: ${formatJsonError(content, err)}`, {
|
|
51923
|
+
cause: err
|
|
51924
|
+
});
|
|
51768
51925
|
}
|
|
51926
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
51927
|
+
throw new Error(
|
|
51928
|
+
`Invalid config in ${filePath}: expected a JSON object at the root, got ${Array.isArray(parsed) ? "an array" : typeof parsed}`
|
|
51929
|
+
);
|
|
51930
|
+
}
|
|
51931
|
+
warnUnknownKeys(parsed, filePath);
|
|
51932
|
+
return parsed;
|
|
51769
51933
|
}
|
|
51770
51934
|
return null;
|
|
51771
51935
|
}
|
|
51772
|
-
var CONFIG_FILENAMES;
|
|
51936
|
+
var import_fast_levenshtein2, levenshtein2, KNOWN_CONFIG_KEYS, CONFIG_FILENAMES;
|
|
51773
51937
|
var init_config2 = __esm({
|
|
51774
51938
|
"src/core/config.ts"() {
|
|
51775
51939
|
"use strict";
|
|
51940
|
+
import_fast_levenshtein2 = __toESM(require_levenshtein(), 1);
|
|
51941
|
+
levenshtein2 = import_fast_levenshtein2.default.get;
|
|
51942
|
+
KNOWN_CONFIG_KEYS = [
|
|
51943
|
+
"checks",
|
|
51944
|
+
"ignore",
|
|
51945
|
+
"strict",
|
|
51946
|
+
"tokenThresholds",
|
|
51947
|
+
"contextFiles",
|
|
51948
|
+
"mcp",
|
|
51949
|
+
"mcpGlobal"
|
|
51950
|
+
];
|
|
51776
51951
|
CONFIG_FILENAMES = [".ctxlintrc", ".ctxlintrc.json"];
|
|
51777
51952
|
}
|
|
51778
51953
|
});
|