@yawlabs/ctxlint 0.9.17 → 0.9.19

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 CHANGED
@@ -448,8 +448,8 @@ var init_parseUtil = __esm({
448
448
  init_errors();
449
449
  init_en();
450
450
  makeIssue = (params) => {
451
- const { data, path: path14, errorMaps, issueData } = params;
452
- const fullPath = [...path14, ...issueData.path || []];
451
+ const { data, path: path13, errorMaps, issueData } = params;
452
+ const fullPath = [...path13, ...issueData.path || []];
453
453
  const fullIssue = {
454
454
  ...issueData,
455
455
  path: fullPath
@@ -729,11 +729,11 @@ var init_types = __esm({
729
729
  init_parseUtil();
730
730
  init_util();
731
731
  ParseInputLazyPath = class {
732
- constructor(parent, value, path14, key) {
732
+ constructor(parent, value, path13, key) {
733
733
  this._cachedPath = [];
734
734
  this.parent = parent;
735
735
  this.data = value;
736
- this._path = path14;
736
+ this._path = path13;
737
737
  this._key = key;
738
738
  }
739
739
  get path() {
@@ -4238,10 +4238,10 @@ function mergeDefs(...defs) {
4238
4238
  function cloneDef(schema) {
4239
4239
  return mergeDefs(schema._zod.def);
4240
4240
  }
4241
- function getElementAtPath(obj, path14) {
4242
- if (!path14)
4241
+ function getElementAtPath(obj, path13) {
4242
+ if (!path13)
4243
4243
  return obj;
4244
- return path14.reduce((acc, key) => acc?.[key], obj);
4244
+ return path13.reduce((acc, key) => acc?.[key], obj);
4245
4245
  }
4246
4246
  function promiseAllObject(promisesObj) {
4247
4247
  const keys = Object.keys(promisesObj);
@@ -4553,11 +4553,11 @@ function aborted(x3, startIndex = 0) {
4553
4553
  }
4554
4554
  return false;
4555
4555
  }
4556
- function prefixIssues(path14, issues) {
4556
+ function prefixIssues(path13, issues) {
4557
4557
  return issues.map((iss) => {
4558
4558
  var _a3;
4559
4559
  (_a3 = iss).path ?? (_a3.path = []);
4560
- iss.path.unshift(path14);
4560
+ iss.path.unshift(path13);
4561
4561
  return iss;
4562
4562
  });
4563
4563
  }
@@ -4799,7 +4799,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
4799
4799
  }
4800
4800
  function treeifyError(error49, mapper = (issue2) => issue2.message) {
4801
4801
  const result = { errors: [] };
4802
- const processError = (error50, path14 = []) => {
4802
+ const processError = (error50, path13 = []) => {
4803
4803
  var _a3, _b;
4804
4804
  for (const issue2 of error50.issues) {
4805
4805
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -4809,7 +4809,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
4809
4809
  } else if (issue2.code === "invalid_element") {
4810
4810
  processError({ issues: issue2.issues }, issue2.path);
4811
4811
  } else {
4812
- const fullpath = [...path14, ...issue2.path];
4812
+ const fullpath = [...path13, ...issue2.path];
4813
4813
  if (fullpath.length === 0) {
4814
4814
  result.errors.push(mapper(issue2));
4815
4815
  continue;
@@ -4841,8 +4841,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
4841
4841
  }
4842
4842
  function toDotPath(_path) {
4843
4843
  const segs = [];
4844
- const path14 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4845
- for (const seg of path14) {
4844
+ const path13 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4845
+ for (const seg of path13) {
4846
4846
  if (typeof seg === "number")
4847
4847
  segs.push(`[${seg}]`);
4848
4848
  else if (typeof seg === "symbol")
@@ -17792,13 +17792,13 @@ function resolveRef(ref, ctx) {
17792
17792
  if (!ref.startsWith("#")) {
17793
17793
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
17794
17794
  }
17795
- const path14 = ref.slice(1).split("/").filter(Boolean);
17796
- if (path14.length === 0) {
17795
+ const path13 = ref.slice(1).split("/").filter(Boolean);
17796
+ if (path13.length === 0) {
17797
17797
  return ctx.rootSchema;
17798
17798
  }
17799
17799
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
17800
- if (path14[0] === defsKey) {
17801
- const key = path14[1];
17800
+ if (path13[0] === defsKey) {
17801
+ const key = path13[1];
17802
17802
  if (!key || !ctx.defs[key]) {
17803
17803
  throw new Error(`Reference not found: ${ref}`);
17804
17804
  }
@@ -25888,8 +25888,8 @@ var require_utils = __commonJS({
25888
25888
  }
25889
25889
  return ind;
25890
25890
  }
25891
- function removeDotSegments(path14) {
25892
- let input = path14;
25891
+ function removeDotSegments(path13) {
25892
+ let input = path13;
25893
25893
  const output = [];
25894
25894
  let nextSlash = -1;
25895
25895
  let len = 0;
@@ -26088,8 +26088,8 @@ var require_schemes = __commonJS({
26088
26088
  wsComponent.secure = void 0;
26089
26089
  }
26090
26090
  if (wsComponent.resourceName) {
26091
- const [path14, query] = wsComponent.resourceName.split("?");
26092
- wsComponent.path = path14 && path14 !== "/" ? path14 : void 0;
26091
+ const [path13, query] = wsComponent.resourceName.split("?");
26092
+ wsComponent.path = path13 && path13 !== "/" ? path13 : void 0;
26093
26093
  wsComponent.query = query;
26094
26094
  wsComponent.resourceName = void 0;
26095
26095
  }
@@ -29451,12 +29451,12 @@ var require_dist = __commonJS({
29451
29451
  throw new Error(`Unknown format "${name}"`);
29452
29452
  return f;
29453
29453
  };
29454
- function addFormats(ajv, list, fs11, exportName) {
29454
+ function addFormats(ajv, list, fs10, exportName) {
29455
29455
  var _a3;
29456
29456
  var _b;
29457
29457
  (_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
29458
29458
  for (const f of list)
29459
- ajv.addFormat(f, fs11[f]);
29459
+ ajv.addFormat(f, fs10[f]);
29460
29460
  }
29461
29461
  module.exports = exports = formatsPlugin;
29462
29462
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -35561,10 +35561,10 @@ var require_src2 = __commonJS({
35561
35561
  var fs_1 = __require("fs");
35562
35562
  var debug_1 = __importDefault(require_src());
35563
35563
  var log = debug_1.default("@kwsites/file-exists");
35564
- function check2(path14, isFile, isDirectory2) {
35565
- log(`checking %s`, path14);
35564
+ function check2(path13, isFile, isDirectory2) {
35565
+ log(`checking %s`, path13);
35566
35566
  try {
35567
- const stat3 = fs_1.statSync(path14);
35567
+ const stat3 = fs_1.statSync(path13);
35568
35568
  if (stat3.isFile() && isFile) {
35569
35569
  log(`[OK] path represents a file`);
35570
35570
  return true;
@@ -35584,8 +35584,8 @@ var require_src2 = __commonJS({
35584
35584
  throw e;
35585
35585
  }
35586
35586
  }
35587
- function exists2(path14, type = exports.READABLE) {
35588
- return check2(path14, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
35587
+ function exists2(path13, type = exports.READABLE) {
35588
+ return check2(path13, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
35589
35589
  }
35590
35590
  exports.exists = exists2;
35591
35591
  exports.FILE = 1;
@@ -36174,8 +36174,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
36174
36174
  function forEachLineWithContent(input, callback) {
36175
36175
  return toLinesWithContent(input, true).map((line) => callback(line));
36176
36176
  }
36177
- function folderExists(path14) {
36178
- return (0, import_file_exists.exists)(path14, import_file_exists.FOLDER);
36177
+ function folderExists(path13) {
36178
+ return (0, import_file_exists.exists)(path13, import_file_exists.FOLDER);
36179
36179
  }
36180
36180
  function append(target, item) {
36181
36181
  if (Array.isArray(target)) {
@@ -36366,8 +36366,8 @@ function checkIsRepoRootTask() {
36366
36366
  commands,
36367
36367
  format: "utf-8",
36368
36368
  onError,
36369
- parser(path14) {
36370
- return /^\.(git)?$/.test(path14.trim());
36369
+ parser(path13) {
36370
+ return /^\.(git)?$/.test(path13.trim());
36371
36371
  }
36372
36372
  };
36373
36373
  }
@@ -36623,11 +36623,11 @@ function parseGrep(grep) {
36623
36623
  const paths = /* @__PURE__ */ new Set();
36624
36624
  const results = {};
36625
36625
  forEachLineWithContent(grep, (input) => {
36626
- const [path14, line, preview] = input.split(NULL);
36627
- paths.add(path14);
36628
- (results[path14] = results[path14] || []).push({
36626
+ const [path13, line, preview] = input.split(NULL);
36627
+ paths.add(path13);
36628
+ (results[path13] = results[path13] || []).push({
36629
36629
  line: asNumber(line),
36630
- path: path14,
36630
+ path: path13,
36631
36631
  preview
36632
36632
  });
36633
36633
  });
@@ -36924,14 +36924,14 @@ function hashObjectTask(filePath, write) {
36924
36924
  }
36925
36925
  return straightThroughStringTask(commands, true);
36926
36926
  }
36927
- function parseInit(bare, path14, text) {
36927
+ function parseInit(bare, path13, text) {
36928
36928
  const response = String(text).trim();
36929
36929
  let result;
36930
36930
  if (result = initResponseRegex.exec(response)) {
36931
- return new InitSummary(bare, path14, false, result[1]);
36931
+ return new InitSummary(bare, path13, false, result[1]);
36932
36932
  }
36933
36933
  if (result = reInitResponseRegex.exec(response)) {
36934
- return new InitSummary(bare, path14, true, result[1]);
36934
+ return new InitSummary(bare, path13, true, result[1]);
36935
36935
  }
36936
36936
  let gitDir = "";
36937
36937
  const tokens = response.split(" ");
@@ -36942,12 +36942,12 @@ function parseInit(bare, path14, text) {
36942
36942
  break;
36943
36943
  }
36944
36944
  }
36945
- return new InitSummary(bare, path14, /^re/i.test(response), gitDir);
36945
+ return new InitSummary(bare, path13, /^re/i.test(response), gitDir);
36946
36946
  }
36947
36947
  function hasBareCommand(command) {
36948
36948
  return command.includes(bareCommand);
36949
36949
  }
36950
- function initTask(bare = false, path14, customArgs) {
36950
+ function initTask(bare = false, path13, customArgs) {
36951
36951
  const commands = ["init", ...customArgs];
36952
36952
  if (bare && !hasBareCommand(commands)) {
36953
36953
  commands.splice(1, 0, bareCommand);
@@ -36956,7 +36956,7 @@ function initTask(bare = false, path14, customArgs) {
36956
36956
  commands,
36957
36957
  format: "utf-8",
36958
36958
  parser(text) {
36959
- return parseInit(commands.includes("--bare"), path14, text);
36959
+ return parseInit(commands.includes("--bare"), path13, text);
36960
36960
  }
36961
36961
  };
36962
36962
  }
@@ -37235,14 +37235,14 @@ function splitLine(result, lineStr) {
37235
37235
  default:
37236
37236
  return;
37237
37237
  }
37238
- function data(index, workingDir, path14) {
37238
+ function data(index, workingDir, path13) {
37239
37239
  const raw = `${index}${workingDir}`;
37240
37240
  const handler = parsers6.get(raw);
37241
37241
  if (handler) {
37242
- handler(result, path14);
37242
+ handler(result, path13);
37243
37243
  }
37244
37244
  if (raw !== "##" && raw !== "!!") {
37245
- result.files.push(new FileStatusSummary(path14, index, workingDir));
37245
+ result.files.push(new FileStatusSummary(path13, index, workingDir));
37246
37246
  }
37247
37247
  }
37248
37248
  }
@@ -37429,8 +37429,8 @@ function deleteBranchTask(branch, forceDelete = false) {
37429
37429
  return task;
37430
37430
  }
37431
37431
  function toPath(input) {
37432
- const path14 = input.trim().replace(/^["']|["']$/g, "");
37433
- return path14 && normalize2(path14);
37432
+ const path13 = input.trim().replace(/^["']|["']$/g, "");
37433
+ return path13 && normalize2(path13);
37434
37434
  }
37435
37435
  function checkIgnoreTask(paths) {
37436
37436
  return {
@@ -37569,8 +37569,8 @@ function stashListTask(opt = {}, customArgs) {
37569
37569
  parser: parser4
37570
37570
  };
37571
37571
  }
37572
- function addSubModuleTask(repo, path14) {
37573
- return subModuleTask(["add", repo, path14]);
37572
+ function addSubModuleTask(repo, path13) {
37573
+ return subModuleTask(["add", repo, path13]);
37574
37574
  }
37575
37575
  function initSubModuleTask(customArgs) {
37576
37576
  return subModuleTask(["init", ...customArgs]);
@@ -38832,9 +38832,9 @@ var init_esm2 = __esm({
38832
38832
  "src/lib/responses/InitSummary.ts"() {
38833
38833
  "use strict";
38834
38834
  InitSummary = class {
38835
- constructor(bare, path14, existing, gitDir) {
38835
+ constructor(bare, path13, existing, gitDir) {
38836
38836
  this.bare = bare;
38837
- this.path = path14;
38837
+ this.path = path13;
38838
38838
  this.existing = existing;
38839
38839
  this.gitDir = gitDir;
38840
38840
  }
@@ -39367,12 +39367,12 @@ var init_esm2 = __esm({
39367
39367
  "use strict";
39368
39368
  fromPathRegex = /^(.+)\0(.+)$/;
39369
39369
  FileStatusSummary = class {
39370
- constructor(path14, index, working_dir) {
39371
- this.path = path14;
39370
+ constructor(path13, index, working_dir) {
39371
+ this.path = path13;
39372
39372
  this.index = index;
39373
39373
  this.working_dir = working_dir;
39374
39374
  if (index === "R" || working_dir === "R") {
39375
- const detail = fromPathRegex.exec(path14) || [null, path14, path14];
39375
+ const detail = fromPathRegex.exec(path13) || [null, path13, path13];
39376
39376
  this.from = detail[2] || "";
39377
39377
  this.path = detail[1] || "";
39378
39378
  }
@@ -39634,9 +39634,9 @@ var init_esm2 = __esm({
39634
39634
  next
39635
39635
  );
39636
39636
  }
39637
- hashObject(path14, write) {
39637
+ hashObject(path13, write) {
39638
39638
  return this._runTask(
39639
- hashObjectTask(path14, write === true),
39639
+ hashObjectTask(path13, write === true),
39640
39640
  trailingFunctionArgument(arguments)
39641
39641
  );
39642
39642
  }
@@ -40273,8 +40273,8 @@ var init_esm2 = __esm({
40273
40273
  }
40274
40274
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
40275
40275
  };
40276
- Git2.prototype.submoduleAdd = function(repo, path14, then) {
40277
- return this._runTask(addSubModuleTask2(repo, path14), trailingFunctionArgument2(arguments));
40276
+ Git2.prototype.submoduleAdd = function(repo, path13, then) {
40277
+ return this._runTask(addSubModuleTask2(repo, path13), trailingFunctionArgument2(arguments));
40278
40278
  };
40279
40279
  Git2.prototype.submoduleUpdate = function(args2, then) {
40280
40280
  return this._runTask(
@@ -40617,21 +40617,24 @@ async function findRenames(projectRoot, filePath) {
40617
40617
  filePath
40618
40618
  ]);
40619
40619
  if (!result.trim()) return null;
40620
+ let currentHash = "unknown";
40621
+ let currentDateStr;
40620
40622
  const lines = result.trim().split("\n");
40621
- for (let i2 = 0; i2 < lines.length; i2++) {
40622
- const line = lines[i2];
40623
+ for (const line of lines) {
40624
+ const headerMatch = line.match(/^([a-f0-9]{7,40})\s+(.+)$/);
40625
+ if (headerMatch) {
40626
+ currentHash = headerMatch[1].substring(0, 7);
40627
+ currentDateStr = headerMatch[2];
40628
+ continue;
40629
+ }
40623
40630
  if (line.startsWith("R")) {
40624
40631
  const parts = line.split(" ");
40625
40632
  if (parts.length >= 3) {
40626
- const hashLine = lines[i2 - 1] || "";
40627
- const hashMatch = hashLine.match(/^([a-f0-9]+)\s+(.+)/);
40628
- const commitHash = hashMatch?.[1]?.substring(0, 7) || "unknown";
40629
- const dateStr = hashMatch?.[2];
40630
- const daysAgo = dateStr ? Math.floor((Date.now() - new Date(dateStr).getTime()) / (1e3 * 60 * 60 * 24)) : 0;
40633
+ const daysAgo = currentDateStr ? Math.floor((Date.now() - new Date(currentDateStr).getTime()) / (1e3 * 60 * 60 * 24)) : 0;
40631
40634
  return {
40632
40635
  oldPath: parts[1],
40633
40636
  newPath: parts[2],
40634
- commitHash,
40637
+ commitHash: currentHash,
40635
40638
  daysAgo
40636
40639
  };
40637
40640
  }
@@ -41313,6 +41316,28 @@ function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
41313
41316
  }
41314
41317
  return result;
41315
41318
  }
41319
+ function getNodeValue(node) {
41320
+ switch (node.type) {
41321
+ case "array":
41322
+ return node.children.map(getNodeValue);
41323
+ case "object":
41324
+ const obj = /* @__PURE__ */ Object.create(null);
41325
+ for (let prop of node.children) {
41326
+ const valueNode = prop.children[1];
41327
+ if (valueNode) {
41328
+ obj[prop.children[0].value] = getNodeValue(valueNode);
41329
+ }
41330
+ }
41331
+ return obj;
41332
+ case "null":
41333
+ case "string":
41334
+ case "number":
41335
+ case "boolean":
41336
+ return node.value;
41337
+ default:
41338
+ return void 0;
41339
+ }
41340
+ }
41316
41341
  function visit(text, visitor, options = ParseOptions.DEFAULT) {
41317
41342
  const _scanner = createScanner(text, false);
41318
41343
  const _jsonPath = [];
@@ -41661,7 +41686,7 @@ var init_edit = __esm({
41661
41686
  });
41662
41687
 
41663
41688
  // node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/main.js
41664
- var ScanError, SyntaxKind, parseTree2, ParseErrorCode;
41689
+ var ScanError, SyntaxKind, parseTree2, getNodeValue2, ParseErrorCode;
41665
41690
  var init_main = __esm({
41666
41691
  "node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/main.js"() {
41667
41692
  "use strict";
@@ -41698,6 +41723,7 @@ var init_main = __esm({
41698
41723
  SyntaxKind2[SyntaxKind2["EOF"] = 17] = "EOF";
41699
41724
  })(SyntaxKind || (SyntaxKind = {}));
41700
41725
  parseTree2 = parseTree;
41726
+ getNodeValue2 = getNodeValue;
41701
41727
  (function(ParseErrorCode2) {
41702
41728
  ParseErrorCode2[ParseErrorCode2["InvalidSymbol"] = 1] = "InvalidSymbol";
41703
41729
  ParseErrorCode2[ParseErrorCode2["InvalidNumberFormat"] = 2] = "InvalidNumberFormat";
@@ -41720,13 +41746,11 @@ var init_main = __esm({
41720
41746
  });
41721
41747
 
41722
41748
  // src/core/mcph-parser.ts
41723
- import * as fs4 from "node:fs";
41724
- import * as path3 from "node:path";
41725
41749
  async function parseMchpConfig(file2, projectRoot, scopeOverride) {
41726
41750
  const content = readFileContent(file2.absolutePath);
41727
41751
  const scope = scopeOverride ?? detectScope2(file2.relativePath);
41728
41752
  const isGitTracked = await checkGitTracked2(file2.absolutePath, projectRoot);
41729
- const isGitignored = checkGitignored(file2.absolutePath, projectRoot);
41753
+ const isGitignored = await checkGitignored(file2.absolutePath, projectRoot);
41730
41754
  const result = {
41731
41755
  filePath: file2.absolutePath,
41732
41756
  relativePath: file2.relativePath,
@@ -41753,11 +41777,7 @@ async function parseMchpConfig(file2, projectRoot, scopeOverride) {
41753
41777
  result.parseErrors.push(".mcph.json must be a JSON object at the root");
41754
41778
  return result;
41755
41779
  }
41756
- try {
41757
- result.raw = JSON.parse(stripComments2(content));
41758
- } catch {
41759
- return result;
41760
- }
41780
+ result.raw = getNodeValue2(tree);
41761
41781
  const rootProps = tree.children ?? [];
41762
41782
  for (const prop of rootProps) {
41763
41783
  if (prop.type !== "property" || !prop.children || prop.children.length < 2) continue;
@@ -41815,46 +41835,6 @@ function offsetToPosition(content, offset) {
41815
41835
  }
41816
41836
  return { line, column };
41817
41837
  }
41818
- function stripComments2(src) {
41819
- let out = "";
41820
- let i2 = 0;
41821
- let inString = false;
41822
- let escape2 = false;
41823
- while (i2 < src.length) {
41824
- const ch = src[i2];
41825
- if (inString) {
41826
- out += ch;
41827
- if (escape2) {
41828
- escape2 = false;
41829
- } else if (ch === "\\") {
41830
- escape2 = true;
41831
- } else if (ch === '"') {
41832
- inString = false;
41833
- }
41834
- i2++;
41835
- continue;
41836
- }
41837
- if (ch === '"') {
41838
- inString = true;
41839
- out += ch;
41840
- i2++;
41841
- continue;
41842
- }
41843
- if (ch === "/" && src[i2 + 1] === "/") {
41844
- while (i2 < src.length && src[i2] !== "\n") i2++;
41845
- continue;
41846
- }
41847
- if (ch === "/" && src[i2 + 1] === "*") {
41848
- i2 += 2;
41849
- while (i2 < src.length && !(src[i2] === "*" && src[i2 + 1] === "/")) i2++;
41850
- i2 += 2;
41851
- continue;
41852
- }
41853
- out += ch;
41854
- i2++;
41855
- }
41856
- return out;
41857
- }
41858
41838
  async function checkGitTracked2(filePath, projectRoot) {
41859
41839
  try {
41860
41840
  const git = getGit(projectRoot);
@@ -41864,17 +41844,14 @@ async function checkGitTracked2(filePath, projectRoot) {
41864
41844
  return false;
41865
41845
  }
41866
41846
  }
41867
- function checkGitignored(filePath, projectRoot) {
41868
- const gitignorePath = path3.join(projectRoot, ".gitignore");
41869
- let content;
41847
+ async function checkGitignored(filePath, projectRoot) {
41870
41848
  try {
41871
- content = fs4.readFileSync(gitignorePath, "utf-8");
41849
+ const git = getGit(projectRoot);
41850
+ const result = await git.raw(["check-ignore", "--", filePath]);
41851
+ return result.trim().length > 0;
41872
41852
  } catch {
41873
41853
  return false;
41874
41854
  }
41875
- const basename5 = path3.basename(filePath);
41876
- const patterns = content.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
41877
- return patterns.some((p2) => p2 === basename5 || p2 === `/${basename5}` || p2 === `./${basename5}`);
41878
41855
  }
41879
41856
  var KNOWN_FIELDS;
41880
41857
  var init_mcph_parser = __esm({
@@ -42111,7 +42088,7 @@ var require_levenshtein = __commonJS({
42111
42088
  });
42112
42089
 
42113
42090
  // src/core/checks/paths.ts
42114
- import * as path4 from "node:path";
42091
+ import * as path3 from "node:path";
42115
42092
  function getProjectFiles(projectRoot) {
42116
42093
  if (cachedProjectFiles?.root === projectRoot) return cachedProjectFiles.files;
42117
42094
  const files = getAllProjectFiles(projectRoot);
@@ -42124,10 +42101,10 @@ function resetPathsCache() {
42124
42101
  async function checkPaths(file2, projectRoot) {
42125
42102
  const issues = [];
42126
42103
  const projectFiles = getProjectFiles(projectRoot);
42127
- const contextDir = path4.dirname(file2.filePath);
42104
+ const contextDir = path3.dirname(file2.filePath);
42128
42105
  for (const ref of file2.references.paths) {
42129
42106
  const baseDir = ref.value.startsWith("./") || ref.value.startsWith("../") ? contextDir : projectRoot;
42130
- const resolvedPath = path4.resolve(baseDir, ref.value);
42107
+ const resolvedPath = path3.resolve(baseDir, ref.value);
42131
42108
  const normalizedRef = ref.value.replace(/\\/g, "/");
42132
42109
  if (normalizedRef.includes("*")) {
42133
42110
  const matches = await Ze(normalizedRef, { cwd: baseDir, nodir: false });
@@ -42145,7 +42122,7 @@ async function checkPaths(file2, projectRoot) {
42145
42122
  }
42146
42123
  const isDir = normalizedRef.endsWith("/");
42147
42124
  if (isDir) {
42148
- const dirPath = path4.resolve(baseDir, normalizedRef);
42125
+ const dirPath = path3.resolve(baseDir, normalizedRef);
42149
42126
  if (!isDirectory(dirPath)) {
42150
42127
  issues.push({
42151
42128
  severity: "error",
@@ -42190,12 +42167,12 @@ async function checkPaths(file2, projectRoot) {
42190
42167
  }
42191
42168
  function findClosestMatch(target, files) {
42192
42169
  const targetNorm = target.replace(/\\/g, "/");
42193
- const targetBase = path4.basename(targetNorm);
42170
+ const targetBase = path3.basename(targetNorm);
42194
42171
  let bestMatch = null;
42195
42172
  let bestDistance = Infinity;
42196
42173
  for (const file2 of files) {
42197
42174
  const fileNorm = file2.replace(/\\/g, "/");
42198
- if (path4.basename(fileNorm) === targetBase && fileNorm !== targetNorm) {
42175
+ if (path3.basename(fileNorm) === targetBase && fileNorm !== targetNorm) {
42199
42176
  const dist = levenshtein(targetNorm, fileNorm);
42200
42177
  if (dist < bestDistance) {
42201
42178
  bestDistance = dist;
@@ -42229,8 +42206,8 @@ var init_paths = __esm({
42229
42206
  });
42230
42207
 
42231
42208
  // src/core/checks/commands.ts
42232
- import * as fs5 from "node:fs";
42233
- import * as path5 from "node:path";
42209
+ import * as fs4 from "node:fs";
42210
+ import * as path4 from "node:path";
42234
42211
  async function checkCommands(file2, projectRoot) {
42235
42212
  const issues = [];
42236
42213
  const pkgJson = loadPackageJson(projectRoot);
@@ -42280,9 +42257,9 @@ async function checkCommands(file2, projectRoot) {
42280
42257
  ...pkgJson.optionalDependencies
42281
42258
  };
42282
42259
  if (!(pkgName in allDeps)) {
42283
- const binPath = path5.join(projectRoot, "node_modules", ".bin", pkgName);
42260
+ const binPath = path4.join(projectRoot, "node_modules", ".bin", pkgName);
42284
42261
  try {
42285
- fs5.accessSync(binPath);
42262
+ fs4.accessSync(binPath);
42286
42263
  } catch {
42287
42264
  issues.push({
42288
42265
  severity: "warning",
@@ -42328,9 +42305,9 @@ async function checkCommands(file2, projectRoot) {
42328
42305
  ...pkgJson.optionalDependencies
42329
42306
  };
42330
42307
  if (!(tool in allDeps)) {
42331
- const binPath = path5.join(projectRoot, "node_modules", ".bin", tool);
42308
+ const binPath = path4.join(projectRoot, "node_modules", ".bin", tool);
42332
42309
  try {
42333
- fs5.accessSync(binPath);
42310
+ fs4.accessSync(binPath);
42334
42311
  } catch {
42335
42312
  issues.push({
42336
42313
  severity: "warning",
@@ -42347,7 +42324,7 @@ async function checkCommands(file2, projectRoot) {
42347
42324
  }
42348
42325
  function loadMakefile(projectRoot) {
42349
42326
  try {
42350
- return fs5.readFileSync(path5.join(projectRoot, "Makefile"), "utf-8");
42327
+ return fs4.readFileSync(path4.join(projectRoot, "Makefile"), "utf-8");
42351
42328
  } catch {
42352
42329
  return null;
42353
42330
  }
@@ -42368,13 +42345,13 @@ var init_commands = __esm({
42368
42345
  });
42369
42346
 
42370
42347
  // src/core/checks/staleness.ts
42371
- import * as path6 from "node:path";
42348
+ import * as path5 from "node:path";
42372
42349
  async function checkStaleness(file2, projectRoot) {
42373
42350
  const issues = [];
42374
42351
  if (!await isGitRepo(projectRoot)) {
42375
42352
  return issues;
42376
42353
  }
42377
- const relativePath = path6.relative(projectRoot, file2.filePath).replace(/\\/g, "/");
42354
+ const relativePath = path5.relative(projectRoot, file2.filePath).replace(/\\/g, "/");
42378
42355
  const lastModified = await getFileLastModified(projectRoot, relativePath);
42379
42356
  if (!lastModified || isNaN(lastModified.getTime())) {
42380
42357
  return issues;
@@ -42507,8 +42484,8 @@ var init_tokens2 = __esm({
42507
42484
  });
42508
42485
 
42509
42486
  // src/core/checks/tier-tokens.ts
42510
- import * as fs6 from "node:fs";
42511
- import * as path7 from "node:path";
42487
+ import * as fs5 from "node:fs";
42488
+ import * as path6 from "node:path";
42512
42489
  function hasPathsFrontmatter(content) {
42513
42490
  const lines = content.split("\n");
42514
42491
  if (lines[0]?.trim() !== "---") return false;
@@ -42537,8 +42514,8 @@ function isAlwaysLoaded(file2) {
42537
42514
  if (rel.includes("/rules/")) {
42538
42515
  return !hasPathsFrontmatter(file2.content);
42539
42516
  }
42540
- const basename5 = rel.split("/").pop() ?? "";
42541
- return ALWAYS_LOADED_BASENAMES.has(basename5);
42517
+ const basename4 = rel.split("/").pop() ?? "";
42518
+ return ALWAYS_LOADED_BASENAMES.has(basename4);
42542
42519
  }
42543
42520
  function computeSectionCosts(file2) {
42544
42521
  if (file2.sections.length === 0) return [];
@@ -42553,14 +42530,14 @@ function computeSectionCosts(file2) {
42553
42530
  function loadSettingsSources(projectRoot) {
42554
42531
  const sources = [];
42555
42532
  const candidates = [
42556
- path7.join(projectRoot, ".claude", "settings.json"),
42557
- path7.join(projectRoot, ".claude", "settings.local.json"),
42558
- path7.join(process.env.HOME || process.env.USERPROFILE || "", ".claude", "settings.json")
42533
+ path6.join(projectRoot, ".claude", "settings.json"),
42534
+ path6.join(projectRoot, ".claude", "settings.local.json"),
42535
+ path6.join(process.env.HOME || process.env.USERPROFILE || "", ".claude", "settings.json")
42559
42536
  ];
42560
42537
  for (const p2 of candidates) {
42561
42538
  let content;
42562
42539
  try {
42563
- content = fs6.readFileSync(p2, "utf-8");
42540
+ content = fs5.readFileSync(p2, "utf-8");
42564
42541
  } catch {
42565
42542
  continue;
42566
42543
  }
@@ -42687,7 +42664,7 @@ var init_tier_tokens = __esm({
42687
42664
  });
42688
42665
 
42689
42666
  // src/core/checks/redundancy.ts
42690
- import * as path8 from "node:path";
42667
+ import * as path7 from "node:path";
42691
42668
  function compilePatterns(allDeps) {
42692
42669
  const compiled = [];
42693
42670
  for (const [pkg, mentions] of Object.entries(PACKAGE_TECH_MAP)) {
@@ -42752,7 +42729,7 @@ async function checkRedundancy(file2, projectRoot) {
42752
42729
  );
42753
42730
  if (dirMatch) {
42754
42731
  const dir = dirMatch[1].replace(/[`"]/g, "");
42755
- const fullPath = path8.resolve(projectRoot, dir);
42732
+ const fullPath = path7.resolve(projectRoot, dir);
42756
42733
  if (isDirectory(fullPath)) {
42757
42734
  issues.push({
42758
42735
  severity: "info",
@@ -43621,8 +43598,8 @@ var init_security = __esm({
43621
43598
  });
43622
43599
 
43623
43600
  // src/core/checks/mcp/commands.ts
43624
- import * as fs7 from "node:fs";
43625
- import * as path9 from "node:path";
43601
+ import * as fs6 from "node:fs";
43602
+ import * as path8 from "node:path";
43626
43603
  async function checkMcpCommands(config2, projectRoot) {
43627
43604
  const issues = [];
43628
43605
  if (config2.parseErrors.length > 0) return issues;
@@ -43639,7 +43616,7 @@ async function checkMcpCommands(config2, projectRoot) {
43639
43616
  });
43640
43617
  }
43641
43618
  if (LOCAL_PATH_PATTERN.test(server2.command)) {
43642
- const resolved = path9.resolve(projectRoot, server2.command);
43619
+ const resolved = path8.resolve(projectRoot, server2.command);
43643
43620
  if (!fileExistsSafe(resolved)) {
43644
43621
  issues.push({
43645
43622
  severity: "warning",
@@ -43654,7 +43631,7 @@ async function checkMcpCommands(config2, projectRoot) {
43654
43631
  for (const arg of server2.args) {
43655
43632
  if (URL_PREFIX.test(arg)) continue;
43656
43633
  if (LOCAL_PATH_PATTERN.test(arg) || FILE_PATH_PATTERN.test(arg)) {
43657
- const resolved = path9.resolve(projectRoot, arg);
43634
+ const resolved = path8.resolve(projectRoot, arg);
43658
43635
  if (!fileExistsSafe(resolved)) {
43659
43636
  issues.push({
43660
43637
  severity: "warning",
@@ -43672,7 +43649,7 @@ async function checkMcpCommands(config2, projectRoot) {
43672
43649
  }
43673
43650
  function fileExistsSafe(filePath) {
43674
43651
  try {
43675
- fs7.accessSync(filePath);
43652
+ fs6.accessSync(filePath);
43676
43653
  return true;
43677
43654
  } catch {
43678
43655
  return false;
@@ -43826,17 +43803,19 @@ async function checkMcpEnv(config2, _projectRoot) {
43826
43803
  }
43827
43804
  }
43828
43805
  }
43829
- for (const value of allValues) {
43830
- const refs = extractEnvVarRefs(value);
43831
- for (const ref of refs) {
43832
- if (!(ref.varName in process.env)) {
43833
- issues.push({
43834
- severity: "info",
43835
- check: "mcp-env",
43836
- ruleId: "unset-variable",
43837
- line: server2.line,
43838
- message: `Server "${server2.name}": environment variable "${ref.varName}" is not set`
43839
- });
43806
+ if (config2.client !== "continue") {
43807
+ for (const value of allValues) {
43808
+ const refs = extractEnvVarRefs(value);
43809
+ for (const ref of refs) {
43810
+ if (!(ref.varName in process.env)) {
43811
+ issues.push({
43812
+ severity: "info",
43813
+ check: "mcp-env",
43814
+ ruleId: "unset-variable",
43815
+ line: server2.line,
43816
+ message: `Server "${server2.name}": environment variable "${ref.varName}" is not set`
43817
+ });
43818
+ }
43840
43819
  }
43841
43820
  }
43842
43821
  }
@@ -44342,27 +44321,18 @@ var init_lists = __esm({
44342
44321
  });
44343
44322
 
44344
44323
  // src/core/checks/mcph/gitignore.ts
44345
- import * as path10 from "node:path";
44324
+ import * as path9 from "node:path";
44346
44325
  async function checkMcphGitignore(config2, _projectRoot) {
44347
44326
  const issues = [];
44348
44327
  if (config2.scope === "project-local" && !config2.isGitignored) {
44349
- const basename5 = path10.basename(config2.filePath);
44328
+ const basename4 = path9.basename(config2.filePath);
44350
44329
  issues.push({
44351
44330
  severity: "error",
44352
44331
  check: "mcph-gitignore",
44353
44332
  ruleId: "mcph-config/local-file-not-gitignored",
44354
44333
  line: 1,
44355
- message: `${basename5} is not covered by .gitignore \u2014 machine-local overrides can leak via git`,
44356
- suggestion: `Add "${basename5}" to .gitignore in your project root.`,
44357
- fix: {
44358
- file: path10.join(path10.dirname(config2.filePath), ".gitignore"),
44359
- line: 0,
44360
- oldText: "",
44361
- newText: `
44362
- # mcph CLI local overrides (machine-specific, never commit)
44363
- ${basename5}
44364
- `
44365
- }
44334
+ message: `${basename4} is not covered by .gitignore \u2014 machine-local overrides can leak via git`,
44335
+ suggestion: `Add "${basename4}" to .gitignore in your project root.`
44366
44336
  });
44367
44337
  }
44368
44338
  return issues;
@@ -44444,7 +44414,7 @@ var init_session_parser = __esm({
44444
44414
  import { readdir, stat } from "node:fs/promises";
44445
44415
  import { createReadStream } from "node:fs";
44446
44416
  import { createInterface } from "node:readline";
44447
- import { dirname as dirname3, join as join7, resolve as resolve4 } from "node:path";
44417
+ import { dirname as dirname2, join as join5, resolve as resolve4 } from "node:path";
44448
44418
  import { existsSync } from "node:fs";
44449
44419
  function detectProviders() {
44450
44420
  if (!home) return [];
@@ -44469,7 +44439,7 @@ async function parseJsonlFiltered(filePath, filter) {
44469
44439
  return results;
44470
44440
  }
44471
44441
  async function readClaudeHistory() {
44472
- const historyPath = join7(home, ".claude", "history.jsonl");
44442
+ const historyPath = join5(home, ".claude", "history.jsonl");
44473
44443
  return parseJsonlFiltered(historyPath, (entry) => {
44474
44444
  if (!entry.display || !entry.project) return null;
44475
44445
  return {
@@ -44482,7 +44452,7 @@ async function readClaudeHistory() {
44482
44452
  });
44483
44453
  }
44484
44454
  async function readCodexHistory() {
44485
- const historyPath = join7(home, ".codex", "history.jsonl");
44455
+ const historyPath = join5(home, ".codex", "history.jsonl");
44486
44456
  return parseJsonlFiltered(historyPath, (entry) => {
44487
44457
  if (!entry.display && !entry.command) return null;
44488
44458
  return {
@@ -44495,18 +44465,18 @@ async function readCodexHistory() {
44495
44465
  });
44496
44466
  }
44497
44467
  async function readClaudeMemories() {
44498
- const projectsDir = join7(home, ".claude", "projects");
44468
+ const projectsDir = join5(home, ".claude", "projects");
44499
44469
  if (!existsSync(projectsDir)) return [];
44500
44470
  const memories = [];
44501
44471
  const projectDirs = await readdir(projectsDir).catch(() => []);
44502
44472
  for (const projDir of projectDirs) {
44503
- const memoryDir = join7(projectsDir, projDir, "memory");
44473
+ const memoryDir = join5(projectsDir, projDir, "memory");
44504
44474
  if (!existsSync(memoryDir)) continue;
44505
44475
  const files = await readdir(memoryDir).catch(() => []);
44506
44476
  for (const file2 of files) {
44507
44477
  if (!file2.endsWith(".md") || file2 === "MEMORY.md") continue;
44508
44478
  try {
44509
- const entry = await parseMemoryFile(join7(memoryDir, file2), projDir);
44479
+ const entry = await parseMemoryFile(join5(memoryDir, file2), projDir);
44510
44480
  memories.push(entry);
44511
44481
  } catch {
44512
44482
  }
@@ -44516,14 +44486,14 @@ async function readClaudeMemories() {
44516
44486
  }
44517
44487
  function detectAiderInSiblings(siblings) {
44518
44488
  for (const sib of siblings) {
44519
- if (existsSync(join7(sib.path, ".aider.chat.history.md"))) {
44489
+ if (existsSync(join5(sib.path, ".aider.chat.history.md"))) {
44520
44490
  return "aider";
44521
44491
  }
44522
44492
  }
44523
44493
  return null;
44524
44494
  }
44525
44495
  async function detectSiblings(projectRoot) {
44526
- const parentDir = dirname3(resolve4(projectRoot));
44496
+ const parentDir = dirname2(resolve4(projectRoot));
44527
44497
  let entries;
44528
44498
  try {
44529
44499
  entries = await readdir(parentDir);
@@ -44533,7 +44503,7 @@ async function detectSiblings(projectRoot) {
44533
44503
  const candidates = [];
44534
44504
  for (const entry of entries) {
44535
44505
  if (entry.startsWith(".") || entry === "node_modules") continue;
44536
- const fullPath = join7(parentDir, entry);
44506
+ const fullPath = join5(parentDir, entry);
44537
44507
  const entryPath = resolve4(fullPath);
44538
44508
  if (entryPath === resolve4(projectRoot)) continue;
44539
44509
  try {
@@ -44542,7 +44512,7 @@ async function detectSiblings(projectRoot) {
44542
44512
  } catch {
44543
44513
  continue;
44544
44514
  }
44545
- const isProject = existsSync(join7(fullPath, ".git")) || existsSync(join7(fullPath, "package.json")) || existsSync(join7(fullPath, "Cargo.toml")) || existsSync(join7(fullPath, "go.mod")) || existsSync(join7(fullPath, "pyproject.toml"));
44515
+ const isProject = existsSync(join5(fullPath, ".git")) || existsSync(join5(fullPath, "package.json")) || existsSync(join5(fullPath, "Cargo.toml")) || existsSync(join5(fullPath, "go.mod")) || existsSync(join5(fullPath, "pyproject.toml"));
44546
44516
  if (!isProject) continue;
44547
44517
  candidates.push({ fullPath, entryPath, name: entry });
44548
44518
  }
@@ -44556,7 +44526,7 @@ async function detectSiblings(projectRoot) {
44556
44526
  if (orgMatch) currentOrg = orgMatch[1];
44557
44527
  } catch {
44558
44528
  }
44559
- const gitCandidates = candidates.filter((c3) => existsSync(join7(c3.fullPath, ".git")));
44529
+ const gitCandidates = candidates.filter((c3) => existsSync(join5(c3.fullPath, ".git")));
44560
44530
  const results = await Promise.all(
44561
44531
  gitCandidates.map(async (c3) => {
44562
44532
  const sibling = { path: c3.entryPath.replace(/\\/g, "/"), name: c3.name };
@@ -44628,28 +44598,28 @@ var init_session_scanner = __esm({
44628
44598
  init_session_parser();
44629
44599
  home = process.env.HOME || process.env.USERPROFILE || "";
44630
44600
  AGENT_DIRS = [
44631
- { provider: "claude-code", dir: join7(home, ".claude"), historyFile: "history.jsonl" },
44632
- { provider: "codex-cli", dir: join7(home, ".codex"), historyFile: "history.jsonl" },
44633
- { provider: "vibe-cli", dir: join7(home, ".vibe") },
44601
+ { provider: "claude-code", dir: join5(home, ".claude"), historyFile: "history.jsonl" },
44602
+ { provider: "codex-cli", dir: join5(home, ".codex"), historyFile: "history.jsonl" },
44603
+ { provider: "vibe-cli", dir: join5(home, ".vibe") },
44634
44604
  {
44635
44605
  provider: "amazon-q",
44636
- dir: join7(home, ".aws", "amazonq")
44606
+ dir: join5(home, ".aws", "amazonq")
44637
44607
  },
44638
44608
  {
44639
44609
  provider: "goose",
44640
- dir: process.platform === "win32" ? join7(process.env.APPDATA || "", "Block", "goose") : join7(home, ".config", "goose")
44610
+ dir: process.platform === "win32" ? join5(process.env.APPDATA || "", "Block", "goose") : join5(home, ".config", "goose")
44641
44611
  },
44642
- { provider: "continue", dir: join7(home, ".continue") },
44612
+ { provider: "continue", dir: join5(home, ".continue") },
44643
44613
  {
44644
44614
  provider: "windsurf",
44645
- dir: join7(home, ".windsurf")
44615
+ dir: join5(home, ".windsurf")
44646
44616
  }
44647
44617
  ];
44648
44618
  }
44649
44619
  });
44650
44620
 
44651
44621
  // src/core/checks/session/missing-secret.ts
44652
- import { resolve as resolve5, basename as basename4 } from "node:path";
44622
+ import { resolve as resolve5, basename as basename3 } from "node:path";
44653
44623
  function normalizePath(p2) {
44654
44624
  return resolve5(p2).replace(/\\/g, "/").toLowerCase();
44655
44625
  }
@@ -44673,7 +44643,7 @@ async function checkMissingSecret(ctx) {
44673
44643
  if (s.repo) byName.get(s.name).add(s.repo);
44674
44644
  }
44675
44645
  const currentNorm = normalizePath(ctx.currentProject);
44676
- const currentBase = basename4(currentNorm);
44646
+ const currentBase = basename3(currentNorm);
44677
44647
  for (const [secretName, projects] of byName) {
44678
44648
  const normalizedProjects = [...projects].map(normalizePath);
44679
44649
  const currentHas = normalizedProjects.some((p2) => {
@@ -44684,7 +44654,7 @@ async function checkMissingSecret(ctx) {
44684
44654
  if (currentHas) continue;
44685
44655
  const siblingMatches = ctx.siblings.filter((sib) => {
44686
44656
  const sibNorm = normalizePath(sib.path);
44687
- const sibBase = basename4(sibNorm);
44657
+ const sibBase = basename3(sibNorm);
44688
44658
  return normalizedProjects.some((p2) => {
44689
44659
  if (p2 === sibNorm) return true;
44690
44660
  const lastSegment = p2.split("/").pop() || "";
@@ -44717,25 +44687,28 @@ var init_missing_secret = __esm({
44717
44687
 
44718
44688
  // src/core/checks/session/diverged-file.ts
44719
44689
  import { readFile as readFile2 } from "node:fs/promises";
44720
- import { join as join8 } from "node:path";
44690
+ import { join as join6 } from "node:path";
44721
44691
  import { existsSync as existsSync2 } from "node:fs";
44722
44692
  function calculateOverlap(a, b2) {
44723
- const linesA = a.split("\n").map((l) => l.trim()).filter((l) => l.length > 3);
44693
+ const linesA = new Set(
44694
+ a.split("\n").map((l) => l.trim()).filter((l) => l.length > 3)
44695
+ );
44724
44696
  const linesB = new Set(
44725
44697
  b2.split("\n").map((l) => l.trim()).filter((l) => l.length > 3)
44726
44698
  );
44727
- if (linesA.length === 0 && linesB.size === 0) return 1;
44728
- if (linesA.length === 0 || linesB.size === 0) return 0;
44729
- let matches = 0;
44699
+ if (linesA.size === 0 && linesB.size === 0) return 1;
44700
+ if (linesA.size === 0 || linesB.size === 0) return 0;
44701
+ let intersection2 = 0;
44730
44702
  for (const line of linesA) {
44731
- if (linesB.has(line)) matches++;
44703
+ if (linesB.has(line)) intersection2++;
44732
44704
  }
44733
- return matches / Math.max(linesA.length, linesB.size);
44705
+ const unionSize = linesA.size + linesB.size - intersection2;
44706
+ return intersection2 / unionSize;
44734
44707
  }
44735
44708
  async function checkDivergedFile(ctx) {
44736
44709
  const issues = [];
44737
44710
  for (const fileName of CANONICAL_FILES) {
44738
- const currentPath = join8(ctx.currentProject, fileName);
44711
+ const currentPath = join6(ctx.currentProject, fileName);
44739
44712
  if (!existsSync2(currentPath)) continue;
44740
44713
  let currentContent;
44741
44714
  try {
@@ -44745,7 +44718,7 @@ async function checkDivergedFile(ctx) {
44745
44718
  }
44746
44719
  const diverged = [];
44747
44720
  for (const sib of ctx.siblings) {
44748
- const sibPath = join8(sib.path, fileName);
44721
+ const sibPath = join6(sib.path, fileName);
44749
44722
  if (!existsSync2(sibPath)) continue;
44750
44723
  try {
44751
44724
  const sibContent = await readFile2(sibPath, "utf-8");
@@ -44791,12 +44764,12 @@ var init_diverged_file = __esm({
44791
44764
 
44792
44765
  // src/core/checks/session/missing-workflow.ts
44793
44766
  import { readdir as readdir2 } from "node:fs/promises";
44794
- import { join as join9 } from "node:path";
44767
+ import { join as join7 } from "node:path";
44795
44768
  import { existsSync as existsSync3 } from "node:fs";
44796
44769
  async function checkMissingWorkflow(ctx) {
44797
44770
  const issues = [];
44798
- const currentWorkflowDir = join9(ctx.currentProject, ".github", "workflows");
44799
- if (!existsSync3(join9(ctx.currentProject, ".github"))) return issues;
44771
+ const currentWorkflowDir = join7(ctx.currentProject, ".github", "workflows");
44772
+ if (!existsSync3(join7(ctx.currentProject, ".github"))) return issues;
44800
44773
  const currentWorkflows = /* @__PURE__ */ new Set();
44801
44774
  if (existsSync3(currentWorkflowDir)) {
44802
44775
  try {
@@ -44809,7 +44782,7 @@ async function checkMissingWorkflow(ctx) {
44809
44782
  }
44810
44783
  const workflowMap = /* @__PURE__ */ new Map();
44811
44784
  for (const sib of ctx.siblings) {
44812
- const sibWorkflowDir = join9(sib.path, ".github", "workflows");
44785
+ const sibWorkflowDir = join7(sib.path, ".github", "workflows");
44813
44786
  if (!existsSync3(sibWorkflowDir)) continue;
44814
44787
  try {
44815
44788
  const files = await readdir2(sibWorkflowDir);
@@ -44894,16 +44867,19 @@ var init_stale_memory = __esm({
44894
44867
 
44895
44868
  // src/core/checks/session/duplicate-memory.ts
44896
44869
  function calculateLineOverlap2(a, b2) {
44897
- const linesA = a.split("\n").map((l) => l.trim()).filter((l) => l.length > 5);
44870
+ const linesA = new Set(
44871
+ a.split("\n").map((l) => l.trim()).filter((l) => l.length > 5)
44872
+ );
44898
44873
  const linesB = new Set(
44899
44874
  b2.split("\n").map((l) => l.trim()).filter((l) => l.length > 5)
44900
44875
  );
44901
- if (linesA.length === 0 || linesB.size === 0) return 0;
44902
- let matches = 0;
44876
+ if (linesA.size === 0 || linesB.size === 0) return 0;
44877
+ let intersection2 = 0;
44903
44878
  for (const line of linesA) {
44904
- if (linesB.has(line)) matches++;
44879
+ if (linesB.has(line)) intersection2++;
44905
44880
  }
44906
- return matches / Math.max(linesA.length, linesB.size);
44881
+ const unionSize = linesA.size + linesB.size - intersection2;
44882
+ return intersection2 / unionSize;
44907
44883
  }
44908
44884
  async function checkDuplicateMemory(ctx) {
44909
44885
  const issues = [];
@@ -44913,6 +44889,9 @@ async function checkDuplicateMemory(ctx) {
44913
44889
  const a = ctx.memories[i2];
44914
44890
  const b2 = ctx.memories[j3];
44915
44891
  if (a.projectDir === b2.projectDir) continue;
44892
+ const aIsCurrent = projectDirMatchesPath(a.projectDir, ctx.currentProject);
44893
+ const bIsCurrent = projectDirMatchesPath(b2.projectDir, ctx.currentProject);
44894
+ if (!aIsCurrent && !bIsCurrent) continue;
44916
44895
  if (a.content.length < 50 || b2.content.length < 50) continue;
44917
44896
  const overlap = calculateLineOverlap2(a.content, b2.content);
44918
44897
  if (overlap < 0.6) continue;
@@ -44939,6 +44918,7 @@ async function checkDuplicateMemory(ctx) {
44939
44918
  var init_duplicate_memory = __esm({
44940
44919
  "src/core/checks/session/duplicate-memory.ts"() {
44941
44920
  "use strict";
44921
+ init_session_parser();
44942
44922
  }
44943
44923
  });
44944
44924
 
@@ -45010,7 +44990,7 @@ async function checkLoopDetection(ctx) {
45010
44990
  }
45011
44991
  const cycles = findCyclicPatterns(displays);
45012
44992
  for (const { cycle, repeats: reps } of cycles) {
45013
- const cycleStr = cycle.map((c3) => c3.length > 40 ? c3.slice(0, 37) + "..." : c3).join(" \u2192 ");
44993
+ const cycleStr = cycle.map((c3) => c3.length > 40 ? c3.slice(0, 37) + "..." : c3).join(" -> ");
45014
44994
  issues.push({
45015
44995
  severity: "warning",
45016
44996
  check: "session-loop-detection",
@@ -45034,12 +45014,12 @@ var init_loop_detection = __esm({
45034
45014
 
45035
45015
  // src/core/checks/session/memory-index-overflow.ts
45036
45016
  import { readFile as readFile3, stat as stat2 } from "node:fs/promises";
45037
- import { join as join10 } from "node:path";
45017
+ import { join as join8 } from "node:path";
45038
45018
  async function checkMemoryIndexOverflow(ctx) {
45039
45019
  const home2 = process.env.HOME || process.env.USERPROFILE || "";
45040
45020
  if (!home2) return [];
45041
45021
  const encoded = encodeProjectDir(ctx.currentProject);
45042
- const memoryFile = join10(home2, ".claude", "projects", encoded, "memory", "MEMORY.md");
45022
+ const memoryFile = join8(home2, ".claude", "projects", encoded, "memory", "MEMORY.md");
45043
45023
  let stats;
45044
45024
  try {
45045
45025
  stats = await stat2(memoryFile);
@@ -45090,10 +45070,10 @@ var init_memory_index_overflow = __esm({
45090
45070
 
45091
45071
  // src/core/checks/ci-coverage.ts
45092
45072
  import { readdir as readdir3, readFile as readFile4 } from "node:fs/promises";
45093
- import { join as join11 } from "node:path";
45073
+ import { join as join9 } from "node:path";
45094
45074
  import { existsSync as existsSync5 } from "node:fs";
45095
45075
  async function findReleaseWorkflows(projectRoot) {
45096
- const workflowDir = join11(projectRoot, ".github", "workflows");
45076
+ const workflowDir = join9(projectRoot, ".github", "workflows");
45097
45077
  if (!existsSync5(workflowDir)) return [];
45098
45078
  let files;
45099
45079
  try {
@@ -45109,7 +45089,7 @@ async function findReleaseWorkflows(projectRoot) {
45109
45089
  continue;
45110
45090
  }
45111
45091
  try {
45112
- const content = await readFile4(join11(workflowDir, f), "utf-8");
45092
+ const content = await readFile4(join9(workflowDir, f), "utf-8");
45113
45093
  const nameMatch = content.match(/^name:\s*(.+)$/m);
45114
45094
  if (nameMatch && RELEASE_FILENAME_PATTERNS.some((p2) => p2.test(nameMatch[1]))) {
45115
45095
  releaseWorkflows.push(f);
@@ -45164,10 +45144,10 @@ var init_ci_coverage = __esm({
45164
45144
 
45165
45145
  // src/core/checks/ci-secrets.ts
45166
45146
  import { readdir as readdir4, readFile as readFile5 } from "node:fs/promises";
45167
- import { join as join12 } from "node:path";
45147
+ import { join as join10 } from "node:path";
45168
45148
  import { existsSync as existsSync6 } from "node:fs";
45169
45149
  async function findSecretUsages(projectRoot) {
45170
- const workflowDir = join12(projectRoot, ".github", "workflows");
45150
+ const workflowDir = join10(projectRoot, ".github", "workflows");
45171
45151
  if (!existsSync6(workflowDir)) return [];
45172
45152
  let files;
45173
45153
  try {
@@ -45180,7 +45160,7 @@ async function findSecretUsages(projectRoot) {
45180
45160
  if (!(f.endsWith(".yml") || f.endsWith(".yaml"))) continue;
45181
45161
  let content;
45182
45162
  try {
45183
- content = await readFile5(join12(workflowDir, f), "utf-8");
45163
+ content = await readFile5(join10(workflowDir, f), "utf-8");
45184
45164
  } catch {
45185
45165
  continue;
45186
45166
  }
@@ -45197,7 +45177,7 @@ async function findSecretUsages(projectRoot) {
45197
45177
  }
45198
45178
  function contextMentionsSecret(files, secretName) {
45199
45179
  const escaped = secretName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45200
- const pattern = new RegExp(escaped.replace(/_/g, "[_\\s-]?"), "i");
45180
+ const pattern = new RegExp(`\\b${escaped.replace(/_/g, "[_\\s-]")}\\b`, "i");
45201
45181
  return files.some((f) => pattern.test(f.content));
45202
45182
  }
45203
45183
  async function checkCiSecrets(files, projectRoot) {
@@ -45237,14 +45217,14 @@ var init_ci_secrets = __esm({
45237
45217
  });
45238
45218
 
45239
45219
  // src/version.ts
45240
- import { readFileSync as readFileSync5 } from "node:fs";
45241
- import { resolve as resolve8, dirname as dirname4 } from "node:path";
45220
+ import { readFileSync as readFileSync4 } from "node:fs";
45221
+ import { resolve as resolve8, dirname as dirname3 } from "node:path";
45242
45222
  import { fileURLToPath } from "node:url";
45243
45223
  function loadVersion() {
45244
- if (true) return "0.9.17";
45245
- const __dir = dirname4(fileURLToPath(import.meta.url));
45224
+ if (true) return "0.9.19";
45225
+ const __dir = dirname3(fileURLToPath(import.meta.url));
45246
45226
  const pkgPath = resolve8(__dir, "../package.json");
45247
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
45227
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
45248
45228
  return pkg.version;
45249
45229
  }
45250
45230
  var VERSION;
@@ -46138,7 +46118,7 @@ var init_source = __esm({
46138
46118
  });
46139
46119
 
46140
46120
  // src/core/fixer.ts
46141
- import * as fs8 from "node:fs";
46121
+ import * as fs7 from "node:fs";
46142
46122
  function applyFixes(result, options = {}) {
46143
46123
  const log = options.quiet ? () => {
46144
46124
  } : console.log.bind(console);
@@ -46180,16 +46160,17 @@ function applyFixes(result, options = {}) {
46180
46160
  let totalFixes = 0;
46181
46161
  const filesModified = [];
46182
46162
  for (const [filePath, fixes] of fixesByFile) {
46183
- const content = fs8.readFileSync(filePath, "utf-8");
46163
+ const content = fs7.readFileSync(filePath, "utf-8");
46184
46164
  const lines = content.split("\n");
46185
46165
  let modified = false;
46186
- const sortedFixes = [...fixes].sort((a, b2) => b2.line - a.line);
46187
46166
  const fixesByLine = /* @__PURE__ */ new Map();
46188
- for (const fix of sortedFixes) {
46167
+ for (const fix of fixes) {
46189
46168
  const existing = fixesByLine.get(fix.line) || [];
46190
46169
  existing.push(fix);
46191
46170
  fixesByLine.set(fix.line, existing);
46192
46171
  }
46172
+ let perFileFixCount = 0;
46173
+ const perFileLogs = [];
46193
46174
  for (const [lineNum, lineFixes] of fixesByLine) {
46194
46175
  const lineIdx = lineNum - 1;
46195
46176
  if (lineIdx < 0 || lineIdx >= lines.length) continue;
@@ -46197,10 +46178,10 @@ function applyFixes(result, options = {}) {
46197
46178
  for (const fix of lineFixes) {
46198
46179
  if (line.includes(fix.oldText)) {
46199
46180
  line = line.replaceAll(fix.oldText, fix.newText);
46200
- totalFixes++;
46181
+ perFileFixCount++;
46201
46182
  const prefix = dryRun ? source_default.cyan(" Would fix") : source_default.green(" Fixed");
46202
- log(
46203
- prefix + ` Line ${fix.line}: ${source_default.dim(fix.oldText)} ${source_default.dim("\u2192")} ${fix.newText}`
46183
+ perFileLogs.push(
46184
+ prefix + ` Line ${fix.line}: ${source_default.dim(fix.oldText)} ${source_default.dim("->")} ${fix.newText}`
46204
46185
  );
46205
46186
  }
46206
46187
  }
@@ -46220,9 +46201,11 @@ function applyFixes(result, options = {}) {
46220
46201
  }
46221
46202
  }
46222
46203
  if (!dryRun) {
46223
- fs8.writeFileSync(filePath, newContent, "utf-8");
46204
+ fs7.writeFileSync(filePath, newContent, "utf-8");
46224
46205
  }
46225
46206
  filesModified.push(filePath);
46207
+ totalFixes += perFileFixCount;
46208
+ for (const m of perFileLogs) log(m);
46226
46209
  }
46227
46210
  }
46228
46211
  return { totalFixes, filesModified };
@@ -46236,13 +46219,13 @@ var init_fixer = __esm({
46236
46219
 
46237
46220
  // src/mcp/server.ts
46238
46221
  var server_exports = {};
46239
- import * as path11 from "node:path";
46222
+ import * as path10 from "node:path";
46240
46223
  function validateProjectPath(rawPath) {
46241
46224
  if (!rawPath) return process.cwd();
46242
46225
  if (PATH_DISALLOWED.test(rawPath)) {
46243
46226
  throw new Error("projectPath contains disallowed characters");
46244
46227
  }
46245
- const resolved = path11.resolve(rawPath);
46228
+ const resolved = path10.resolve(rawPath);
46246
46229
  if (!isDirectory(resolved)) {
46247
46230
  throw new Error("projectPath is not an existing directory");
46248
46231
  }
@@ -46253,7 +46236,7 @@ function validateFilePathInput(rawPath) {
46253
46236
  throw new Error("path contains disallowed characters");
46254
46237
  }
46255
46238
  }
46256
- var contextCheckEnum, mcpCheckEnum, sessionCheckEnum, PATH_DISALLOWED, server, transport;
46239
+ var contextCheckEnum, mcpCheckEnum, mcphCheckEnum, sessionCheckEnum, PATH_DISALLOWED, server, transport;
46257
46240
  var init_server3 = __esm({
46258
46241
  async "src/mcp/server.ts"() {
46259
46242
  "use strict";
@@ -46272,6 +46255,7 @@ var init_server3 = __esm({
46272
46255
  init_version2();
46273
46256
  contextCheckEnum = external_exports3.enum(ALL_CHECKS);
46274
46257
  mcpCheckEnum = external_exports3.enum(ALL_MCP_CHECKS);
46258
+ mcphCheckEnum = external_exports3.enum(ALL_MCPH_CHECKS);
46275
46259
  sessionCheckEnum = external_exports3.enum(ALL_SESSION_CHECKS);
46276
46260
  PATH_DISALLOWED = /[\n\r\t;`|]|\$\(|\$\{/;
46277
46261
  server = new McpServer({
@@ -46328,7 +46312,7 @@ var init_server3 = __esm({
46328
46312
  try {
46329
46313
  validateFilePathInput(filePath);
46330
46314
  const root = validateProjectPath(projectPath);
46331
- const resolved = path11.resolve(root, filePath);
46315
+ const resolved = path10.resolve(root, filePath);
46332
46316
  const result = {
46333
46317
  path: filePath,
46334
46318
  exists: fileExists(resolved) || isDirectory(resolved)
@@ -46493,6 +46477,48 @@ var init_server3 = __esm({
46493
46477
  }
46494
46478
  }
46495
46479
  );
46480
+ server.tool(
46481
+ "ctxlint_mcph_audit",
46482
+ "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.",
46483
+ {
46484
+ projectPath: external_exports3.string().optional().describe("Path to the project root. Defaults to current working directory."),
46485
+ checks: external_exports3.array(mcphCheckEnum).optional().describe("Specific mcph checks to run (default: all mcph-* checks)."),
46486
+ includeGlobal: external_exports3.boolean().optional().describe("Also scan ~/.mcph.json (user-global config)."),
46487
+ strictEnvToken: external_exports3.boolean().optional().describe(
46488
+ "Upgrade mcph-config/prefer-env-token from warning to error (env-var-only posture)."
46489
+ )
46490
+ },
46491
+ {
46492
+ readOnlyHint: true,
46493
+ destructiveHint: false,
46494
+ idempotentHint: true,
46495
+ openWorldHint: false
46496
+ },
46497
+ async ({ projectPath, checks, includeGlobal, strictEnvToken }) => {
46498
+ try {
46499
+ const root = validateProjectPath(projectPath);
46500
+ const activeChecks = checks?.length ? checks : ALL_MCPH_CHECKS;
46501
+ const result = await runAudit(root, activeChecks, {
46502
+ mcph: true,
46503
+ mcphOnly: true,
46504
+ mcphGlobal: includeGlobal || false,
46505
+ mcphStrictEnvToken: strictEnvToken || false
46506
+ });
46507
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
46508
+ } catch (err) {
46509
+ const msg = err instanceof Error ? err.message : String(err);
46510
+ return {
46511
+ content: [{ type: "text", text: JSON.stringify({ error: msg }) }],
46512
+ isError: true
46513
+ };
46514
+ } finally {
46515
+ freeEncoder();
46516
+ resetGit();
46517
+ resetPathsCache();
46518
+ resetPackageJsonCache();
46519
+ }
46520
+ }
46521
+ );
46496
46522
  server.tool(
46497
46523
  "ctxlint_session_audit",
46498
46524
  "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.",
@@ -47698,8 +47724,8 @@ var require_command = __commonJS({
47698
47724
  "node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/command.js"(exports) {
47699
47725
  var EventEmitter2 = __require("node:events").EventEmitter;
47700
47726
  var childProcess = __require("node:child_process");
47701
- var path14 = __require("node:path");
47702
- var fs11 = __require("node:fs");
47727
+ var path13 = __require("node:path");
47728
+ var fs10 = __require("node:fs");
47703
47729
  var process11 = __require("node:process");
47704
47730
  var { Argument: Argument2, humanReadableArgName } = require_argument();
47705
47731
  var { CommanderError: CommanderError2 } = require_error();
@@ -48693,7 +48719,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
48693
48719
  * @param {string} subcommandName
48694
48720
  */
48695
48721
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
48696
- if (fs11.existsSync(executableFile)) return;
48722
+ if (fs10.existsSync(executableFile)) return;
48697
48723
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
48698
48724
  const executableMissing = `'${executableFile}' does not exist
48699
48725
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -48711,11 +48737,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
48711
48737
  let launchWithNode = false;
48712
48738
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
48713
48739
  function findFile(baseDir, baseName) {
48714
- const localBin = path14.resolve(baseDir, baseName);
48715
- if (fs11.existsSync(localBin)) return localBin;
48716
- if (sourceExt.includes(path14.extname(baseName))) return void 0;
48740
+ const localBin = path13.resolve(baseDir, baseName);
48741
+ if (fs10.existsSync(localBin)) return localBin;
48742
+ if (sourceExt.includes(path13.extname(baseName))) return void 0;
48717
48743
  const foundExt = sourceExt.find(
48718
- (ext) => fs11.existsSync(`${localBin}${ext}`)
48744
+ (ext) => fs10.existsSync(`${localBin}${ext}`)
48719
48745
  );
48720
48746
  if (foundExt) return `${localBin}${foundExt}`;
48721
48747
  return void 0;
@@ -48727,21 +48753,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
48727
48753
  if (this._scriptPath) {
48728
48754
  let resolvedScriptPath;
48729
48755
  try {
48730
- resolvedScriptPath = fs11.realpathSync(this._scriptPath);
48756
+ resolvedScriptPath = fs10.realpathSync(this._scriptPath);
48731
48757
  } catch {
48732
48758
  resolvedScriptPath = this._scriptPath;
48733
48759
  }
48734
- executableDir = path14.resolve(
48735
- path14.dirname(resolvedScriptPath),
48760
+ executableDir = path13.resolve(
48761
+ path13.dirname(resolvedScriptPath),
48736
48762
  executableDir
48737
48763
  );
48738
48764
  }
48739
48765
  if (executableDir) {
48740
48766
  let localFile = findFile(executableDir, executableFile);
48741
48767
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
48742
- const legacyName = path14.basename(
48768
+ const legacyName = path13.basename(
48743
48769
  this._scriptPath,
48744
- path14.extname(this._scriptPath)
48770
+ path13.extname(this._scriptPath)
48745
48771
  );
48746
48772
  if (legacyName !== this._name) {
48747
48773
  localFile = findFile(
@@ -48752,7 +48778,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
48752
48778
  }
48753
48779
  executableFile = localFile || executableFile;
48754
48780
  }
48755
- launchWithNode = sourceExt.includes(path14.extname(executableFile));
48781
+ launchWithNode = sourceExt.includes(path13.extname(executableFile));
48756
48782
  let proc;
48757
48783
  if (process11.platform !== "win32") {
48758
48784
  if (launchWithNode) {
@@ -49667,7 +49693,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
49667
49693
  * @return {Command}
49668
49694
  */
49669
49695
  nameFromFilename(filename) {
49670
- this._name = path14.basename(filename, path14.extname(filename));
49696
+ this._name = path13.basename(filename, path13.extname(filename));
49671
49697
  return this;
49672
49698
  }
49673
49699
  /**
@@ -49681,9 +49707,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
49681
49707
  * @param {string} [path]
49682
49708
  * @return {(string|null|Command)}
49683
49709
  */
49684
- executableDir(path15) {
49685
- if (path15 === void 0) return this._executableDir;
49686
- this._executableDir = path15;
49710
+ executableDir(path14) {
49711
+ if (path14 === void 0) return this._executableDir;
49712
+ this._executableDir = path14;
49687
49713
  return this;
49688
49714
  }
49689
49715
  /**
@@ -53008,6 +53034,24 @@ var init_ora = __esm({
53008
53034
  });
53009
53035
 
53010
53036
  // src/core/reporter.ts
53037
+ function classifyFile(f) {
53038
+ if (f.path === "(mcp)") return "mcp";
53039
+ if (f.path.includes("session audit")) return "session";
53040
+ for (const issue2 of f.issues) {
53041
+ if (issue2.check.startsWith("session-")) return "session";
53042
+ if (issue2.check.startsWith("mcph-")) return "mcph";
53043
+ if (issue2.check.startsWith("mcp-")) return "mcp";
53044
+ }
53045
+ const norm = f.path.replace(/\\/g, "/");
53046
+ if (norm.endsWith(".mcph.json") || norm.endsWith(".mcph.local.json")) return "mcph";
53047
+ 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")) {
53048
+ return "mcp";
53049
+ }
53050
+ return "context";
53051
+ }
53052
+ function isSyntheticBucket(p2) {
53053
+ return p2.startsWith("(") || p2.includes("session audit");
53054
+ }
53011
53055
  function formatText(result, verbose = false) {
53012
53056
  const lines = [];
53013
53057
  lines.push("");
@@ -53015,30 +53059,45 @@ function formatText(result, verbose = false) {
53015
53059
  lines.push("");
53016
53060
  lines.push(`Scanning ${result.projectRoot}...`);
53017
53061
  lines.push("");
53018
- const isMcpFile = (f) => f.issues.some((i2) => i2.check.startsWith("mcp-"));
53019
- const contextFiles = result.files.filter((f) => !isMcpFile(f));
53020
- const mcpFiles = result.files.filter((f) => isMcpFile(f));
53062
+ const groups = {
53063
+ context: [],
53064
+ mcp: [],
53065
+ mcph: [],
53066
+ session: []
53067
+ };
53068
+ for (const f of result.files) {
53069
+ groups[classifyFile(f)].push(f);
53070
+ }
53021
53071
  const totalTokens = result.summary.totalTokens;
53022
- if (contextFiles.length > 0) {
53072
+ let renderedAnySummary = false;
53073
+ const contextReal = groups.context.filter((f) => !isSyntheticBucket(f.path));
53074
+ if (contextReal.length > 0) {
53023
53075
  lines.push(
53024
- `Found ${contextFiles.length} context file${contextFiles.length !== 1 ? "s" : ""} (${totalTokens.toLocaleString()} tokens total)`
53076
+ `Found ${contextReal.length} context file${contextReal.length !== 1 ? "s" : ""} (${totalTokens.toLocaleString()} tokens total)`
53025
53077
  );
53026
- for (const file2 of contextFiles) {
53078
+ for (const file2 of contextReal) {
53027
53079
  let desc = ` ${file2.path} (${file2.tokens.toLocaleString()} tokens, ${file2.lines} lines)`;
53028
53080
  if (file2.isSymlink && file2.symlinkTarget) {
53029
- desc = ` ${file2.path} ${source_default.dim(`\u2192 ${file2.symlinkTarget} (symlink)`)}`;
53081
+ desc = ` ${file2.path} ${source_default.dim(`-> ${file2.symlinkTarget} (symlink)`)}`;
53030
53082
  }
53031
53083
  lines.push(desc);
53032
53084
  }
53085
+ renderedAnySummary = true;
53033
53086
  }
53034
- if (mcpFiles.length > 0) {
53035
- if (contextFiles.length > 0) lines.push("");
53036
- lines.push(`Found ${mcpFiles.length} MCP config${mcpFiles.length !== 1 ? "s" : ""}`);
53037
- for (const file2 of mcpFiles) {
53038
- lines.push(` ${file2.path}`);
53039
- }
53087
+ for (const g of ["mcp", "mcph"]) {
53088
+ const real = groups[g].filter((f) => !isSyntheticBucket(f.path));
53089
+ if (real.length === 0) continue;
53090
+ if (renderedAnySummary) lines.push("");
53091
+ lines.push(`Found ${real.length} ${GROUP_SUMMARY_NOUNS[g]}${real.length !== 1 ? "s" : ""}`);
53092
+ for (const file2 of real) lines.push(` ${file2.path}`);
53093
+ renderedAnySummary = true;
53040
53094
  }
53041
- if (contextFiles.length === 0 && mcpFiles.length === 0) {
53095
+ if (groups.session.length > 0) {
53096
+ if (renderedAnySummary) lines.push("");
53097
+ lines.push("Session audit scanned");
53098
+ renderedAnySummary = true;
53099
+ }
53100
+ if (!renderedAnySummary) {
53042
53101
  lines.push(`Found ${result.files.length} file${result.files.length !== 1 ? "s" : ""}`);
53043
53102
  }
53044
53103
  lines.push("");
@@ -53048,7 +53107,7 @@ function formatText(result, verbose = false) {
53048
53107
  if (fileIssues.length === 0 && !verbose) continue;
53049
53108
  lines.push(source_default.underline(file2.path));
53050
53109
  if (fileIssues.length === 0) {
53051
- lines.push(source_default.green(" \u2713 All checks passed"));
53110
+ lines.push(source_default.green(" [ok] All checks passed"));
53052
53111
  } else {
53053
53112
  for (const issue2 of fileIssues) {
53054
53113
  lines.push(formatIssue(issue2));
@@ -53057,16 +53116,13 @@ function formatText(result, verbose = false) {
53057
53116
  lines.push("");
53058
53117
  }
53059
53118
  };
53060
- if (contextFiles.length > 0 && mcpFiles.length > 0) {
53061
- const contextWithIssues = contextFiles.filter((f) => f.issues.length > 0 || verbose);
53062
- const mcpWithIssues = mcpFiles.filter((f) => f.issues.length > 0 || verbose);
53063
- if (contextWithIssues.length > 0) {
53064
- lines.push(source_default.bold("Context Files"));
53065
- renderFileGroup(contextFiles);
53066
- }
53067
- if (mcpWithIssues.length > 0) {
53068
- lines.push(source_default.bold("MCP Configs"));
53069
- renderFileGroup(mcpFiles);
53119
+ const groupsToRender = GROUP_ORDER.filter(
53120
+ (g) => groups[g].some((f) => f.issues.length > 0 || verbose)
53121
+ );
53122
+ if (groupsToRender.length > 1) {
53123
+ for (const g of groupsToRender) {
53124
+ lines.push(source_default.bold(GROUP_LABELS[g]));
53125
+ renderFileGroup(groups[g]);
53070
53126
  }
53071
53127
  } else {
53072
53128
  renderFileGroup(result.files);
@@ -53103,13 +53159,13 @@ function formatTokenReport(result) {
53103
53159
  lines.push(
53104
53160
  ` ${source_default.dim("File".padEnd(maxPathLen))} ${source_default.dim("Tokens".padStart(8))} ${source_default.dim("Lines".padStart(6))}`
53105
53161
  );
53106
- lines.push(` ${"\u2500".repeat(maxPathLen)} ${"\u2500".repeat(8)} ${"\u2500".repeat(6)}`);
53162
+ lines.push(` ${"-".repeat(maxPathLen)} ${"-".repeat(8)} ${"-".repeat(6)}`);
53107
53163
  for (const file2 of result.files) {
53108
53164
  const tokenStr = file2.tokens.toLocaleString().padStart(8);
53109
53165
  const lineStr = file2.lines.toString().padStart(6);
53110
53166
  lines.push(` ${file2.path.padEnd(maxPathLen)} ${tokenStr} ${lineStr}`);
53111
53167
  }
53112
- lines.push(` ${"\u2500".repeat(maxPathLen)} ${"\u2500".repeat(8)} ${"\u2500".repeat(6)}`);
53168
+ lines.push(` ${"-".repeat(maxPathLen)} ${"-".repeat(8)} ${"-".repeat(6)}`);
53113
53169
  lines.push(
53114
53170
  ` ${"Total".padEnd(maxPathLen)} ${result.summary.totalTokens.toLocaleString().padStart(8)}`
53115
53171
  );
@@ -53282,6 +53338,31 @@ function buildRuleDescriptors() {
53282
53338
  shortDescription: { text: "Redundant MCP config entry" },
53283
53339
  helpUri: "https://github.com/yawlabs/ctxlint#mcp-config-linting"
53284
53340
  },
53341
+ {
53342
+ id: "ctxlint/mcph-token-security",
53343
+ shortDescription: { text: "mcp.hosting PAT leakage or env-var posture" },
53344
+ helpUri: "https://github.com/yawlabs/ctxlint#mcph-config-linting"
53345
+ },
53346
+ {
53347
+ id: "ctxlint/mcph-apibase",
53348
+ shortDescription: { text: "mcph apiBase URL validation" },
53349
+ helpUri: "https://github.com/yawlabs/ctxlint#mcph-config-linting"
53350
+ },
53351
+ {
53352
+ id: "ctxlint/mcph-schema-conformance",
53353
+ shortDescription: { text: "mcph config unknown field or stale schema version" },
53354
+ helpUri: "https://github.com/yawlabs/ctxlint#mcph-config-linting"
53355
+ },
53356
+ {
53357
+ id: "ctxlint/mcph-lists",
53358
+ shortDescription: { text: "mcph allow/deny list conflict or duplicate entry" },
53359
+ helpUri: "https://github.com/yawlabs/ctxlint#mcph-config-linting"
53360
+ },
53361
+ {
53362
+ id: "ctxlint/mcph-gitignore",
53363
+ shortDescription: { text: "mcph machine-local file not covered by .gitignore" },
53364
+ helpUri: "https://github.com/yawlabs/ctxlint#mcph-config-linting"
53365
+ },
53285
53366
  {
53286
53367
  id: "ctxlint/session-missing-secret",
53287
53368
  shortDescription: { text: "GitHub secret set on sibling repos but missing here" },
@@ -53322,12 +53403,12 @@ function buildRuleDescriptors() {
53322
53403
  ];
53323
53404
  }
53324
53405
  function formatIssue(issue2) {
53325
- const icon = issue2.severity === "error" ? source_default.red("\u2717") : issue2.severity === "warning" ? source_default.yellow("\u26A0") : source_default.blue("\u2139");
53406
+ const icon = issue2.severity === "error" ? source_default.red("x") : issue2.severity === "warning" ? source_default.yellow("!") : source_default.blue("i");
53326
53407
  const lineRef = issue2.line > 0 ? `Line ${issue2.line}: ` : "";
53327
53408
  let line = ` ${icon} ${lineRef}${issue2.message}`;
53328
53409
  if (issue2.suggestion) {
53329
53410
  line += `
53330
- ${source_default.dim("\u2192")} ${source_default.dim(issue2.suggestion)}`;
53411
+ ${source_default.dim("->")} ${source_default.dim(issue2.suggestion)}`;
53331
53412
  }
53332
53413
  if (issue2.detail) {
53333
53414
  line += `
@@ -53335,16 +53416,30 @@ function formatIssue(issue2) {
53335
53416
  }
53336
53417
  return line;
53337
53418
  }
53419
+ var GROUP_ORDER, GROUP_LABELS, GROUP_SUMMARY_NOUNS;
53338
53420
  var init_reporter = __esm({
53339
53421
  "src/core/reporter.ts"() {
53340
53422
  "use strict";
53341
53423
  init_source();
53424
+ GROUP_ORDER = ["context", "mcp", "mcph", "session"];
53425
+ GROUP_LABELS = {
53426
+ context: "Context Files",
53427
+ mcp: "MCP Configs",
53428
+ mcph: "mcph Configs",
53429
+ session: "Session Audit"
53430
+ };
53431
+ GROUP_SUMMARY_NOUNS = {
53432
+ context: "context file",
53433
+ mcp: "MCP config",
53434
+ mcph: "mcph config",
53435
+ session: "session audit"
53436
+ };
53342
53437
  }
53343
53438
  });
53344
53439
 
53345
53440
  // src/core/config.ts
53346
- import * as fs9 from "node:fs";
53347
- import * as path12 from "node:path";
53441
+ import * as fs8 from "node:fs";
53442
+ import * as path11 from "node:path";
53348
53443
  function formatJsonError(content, err) {
53349
53444
  const msg = err instanceof Error ? err.message : String(err);
53350
53445
  const posMatch = msg.match(/position (\d+)/);
@@ -53415,33 +53510,46 @@ function warnUnknownKeys(config2, source) {
53415
53510
  console.error(`Warning: unknown config key "${k3}" in ${source}${suggestion}`);
53416
53511
  }
53417
53512
  }
53513
+ function parseConfigContent(content, source) {
53514
+ let parsed;
53515
+ try {
53516
+ parsed = JSON.parse(content);
53517
+ } catch (err) {
53518
+ throw new Error(`Invalid JSON in ${source}: ${formatJsonError(content, err)}`, {
53519
+ cause: err
53520
+ });
53521
+ }
53522
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
53523
+ throw new Error(
53524
+ `Invalid config in ${source}: expected a JSON object at the root, got ${Array.isArray(parsed) ? "an array" : typeof parsed}`
53525
+ );
53526
+ }
53527
+ warnUnknownKeys(parsed, source);
53528
+ return parsed;
53529
+ }
53418
53530
  function loadConfig(projectRoot) {
53419
53531
  for (const filename of CONFIG_FILENAMES) {
53420
- const filePath = path12.join(projectRoot, filename);
53532
+ const filePath = path11.join(projectRoot, filename);
53421
53533
  let content;
53422
53534
  try {
53423
- content = fs9.readFileSync(filePath, "utf-8");
53535
+ content = fs8.readFileSync(filePath, "utf-8");
53424
53536
  } catch {
53425
53537
  continue;
53426
53538
  }
53427
- let parsed;
53428
- try {
53429
- parsed = JSON.parse(content);
53430
- } catch (err) {
53431
- throw new Error(`Invalid JSON in ${filePath}: ${formatJsonError(content, err)}`, {
53432
- cause: err
53433
- });
53434
- }
53435
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
53436
- throw new Error(
53437
- `Invalid config in ${filePath}: expected a JSON object at the root, got ${Array.isArray(parsed) ? "an array" : typeof parsed}`
53438
- );
53439
- }
53440
- warnUnknownKeys(parsed, filePath);
53441
- return parsed;
53539
+ return parseConfigContent(content, filePath);
53442
53540
  }
53443
53541
  return null;
53444
53542
  }
53543
+ function loadConfigFromExplicitPath(configPath) {
53544
+ let content;
53545
+ try {
53546
+ content = fs8.readFileSync(configPath, "utf-8");
53547
+ } catch (err) {
53548
+ const detail = err instanceof Error ? err.message : String(err);
53549
+ throw new Error(`could not load config from ${configPath}: ${detail}`, { cause: err });
53550
+ }
53551
+ return parseConfigContent(content, configPath);
53552
+ }
53445
53553
  var import_fast_levenshtein2, levenshtein2, KNOWN_CONFIG_KEYS, CONFIG_FILENAMES;
53446
53554
  var init_config2 = __esm({
53447
53555
  "src/core/config.ts"() {
@@ -53455,7 +53563,14 @@ var init_config2 = __esm({
53455
53563
  "tokenThresholds",
53456
53564
  "contextFiles",
53457
53565
  "mcp",
53458
- "mcpGlobal"
53566
+ "mcpOnly",
53567
+ "mcpGlobal",
53568
+ "mcph",
53569
+ "mcphOnly",
53570
+ "mcphGlobal",
53571
+ "mcphStrictEnvToken",
53572
+ "session",
53573
+ "sessionOnly"
53459
53574
  ];
53460
53575
  CONFIG_FILENAMES = [".ctxlintrc", ".ctxlintrc.json"];
53461
53576
  }
@@ -53466,8 +53581,8 @@ var cli_exports = {};
53466
53581
  __export(cli_exports, {
53467
53582
  runCli: () => runCli
53468
53583
  });
53469
- import * as fs10 from "node:fs";
53470
- import * as path13 from "node:path";
53584
+ import * as fs9 from "node:fs";
53585
+ import * as path12 from "node:path";
53471
53586
  function validateCheckNames(names, source) {
53472
53587
  const invalid = names.filter((n7) => n7 && !VALID_CHECKS.has(n7));
53473
53588
  if (invalid.length > 0) {
@@ -53490,75 +53605,8 @@ async function runCli() {
53490
53605
  "Upgrade mcph-config/prefer-env-token from warning to error (strict env-var-only posture)",
53491
53606
  false
53492
53607
  ).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) => {
53493
- const resolvedPath = path13.resolve(projectPath);
53494
- const configPath = opts.config ? path13.resolve(opts.config) : void 0;
53495
- const config2 = configPath ? loadConfigFromPath(configPath) : loadConfig(resolvedPath);
53496
- const mcpGlobal = opts.mcpGlobal || false;
53497
- const mcpOnly = opts.mcpOnly || false;
53498
- const mcpFlag = opts.mcp || mcpGlobal || mcpOnly || config2?.mcp || false;
53499
- const mcphGlobal = opts.mcphGlobal || false;
53500
- const mcphOnly = opts.mcphOnly || false;
53501
- const mcphFlag = opts.mcph || mcphGlobal || mcphOnly || false;
53502
- const mcphStrictEnvToken = opts.mcphStrictEnvToken || false;
53503
- const sessionFlag = opts.session || false;
53504
- const sessionOnly = opts.sessionOnly || false;
53505
- let explicitChecks = opts.checks ? validateCheckNames(
53506
- opts.checks.split(",").map((c3) => c3.trim()),
53507
- "--checks"
53508
- ) : null;
53509
- if (explicitChecks?.length === 0) explicitChecks = null;
53510
- const hasMcpInChecks = explicitChecks?.some((c3) => c3.startsWith("mcp-")) || false;
53511
- const hasMcphInChecks = explicitChecks?.some((c3) => c3.startsWith("mcph-")) || false;
53512
- const hasSessionInChecks = explicitChecks?.some((c3) => c3.startsWith("session-")) || false;
53513
- const effectiveMcp = mcpFlag || hasMcpInChecks;
53514
- const effectiveMcph = mcphFlag || hasMcphInChecks;
53515
- const effectiveSession = sessionFlag || sessionOnly || hasSessionInChecks;
53516
- let checks;
53517
- if (explicitChecks) {
53518
- checks = explicitChecks;
53519
- } else if (sessionOnly) {
53520
- checks = ALL_SESSION_CHECKS;
53521
- } else if (mcpOnly) {
53522
- checks = ALL_MCP_CHECKS;
53523
- } else if (mcphOnly) {
53524
- checks = ALL_MCPH_CHECKS;
53525
- } else {
53526
- const base = config2?.checks || ALL_CHECKS;
53527
- checks = [
53528
- ...base,
53529
- ...effectiveMcp ? ALL_MCP_CHECKS : [],
53530
- ...effectiveMcph ? ALL_MCPH_CHECKS : [],
53531
- ...effectiveSession ? ALL_SESSION_CHECKS : []
53532
- ];
53533
- }
53534
- const options = {
53535
- projectPath: resolvedPath,
53536
- checks,
53537
- strict: opts.strict || config2?.strict || false,
53538
- format: opts.format,
53539
- verbose: opts.verbose,
53540
- fix: opts.fix,
53541
- ignore: opts.ignore ? validateCheckNames(
53542
- opts.ignore.split(",").map((c3) => c3.trim()),
53543
- "--ignore"
53544
- ) : config2?.ignore || [],
53545
- tokensOnly: opts.tokens,
53546
- quiet: opts.quiet,
53547
- depth: Math.max(0, Math.min(parseInt(opts.depth, 10) || 2, 10)),
53548
- mcp: effectiveMcp,
53549
- mcpOnly,
53550
- mcpGlobal: mcpGlobal || config2?.mcpGlobal || false,
53551
- mcph: effectiveMcph,
53552
- mcphOnly,
53553
- mcphGlobal,
53554
- mcphStrictEnvToken,
53555
- session: effectiveSession,
53556
- sessionOnly
53557
- };
53558
- if (config2?.tokenThresholds) {
53559
- setTokenThresholds(config2.tokenThresholds);
53560
- }
53561
- const activeChecks = options.checks.filter((c3) => !options.ignore.includes(c3));
53608
+ const resolvedPath = path12.resolve(projectPath);
53609
+ const { config: config2, options, activeChecks } = resolveSession(resolvedPath, opts);
53562
53610
  const spinner = options.format === "text" && !options.quiet ? ora("Scanning for context files...").start() : void 0;
53563
53611
  try {
53564
53612
  if (spinner) spinner.text = "Running checks...";
@@ -53672,34 +53720,43 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
53672
53720
  const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
53673
53721
  console.log(chalk2.dim("\nWatching for changes... (Ctrl+C to stop)\n"));
53674
53722
  const watchPaths = [
53675
- path13.join(resolvedPath, "CLAUDE.md"),
53676
- path13.join(resolvedPath, "CLAUDE.local.md"),
53677
- path13.join(resolvedPath, "AGENTS.md"),
53678
- path13.join(resolvedPath, "AGENT.md"),
53679
- path13.join(resolvedPath, ".cursorrules"),
53680
- path13.join(resolvedPath, ".windsurfrules"),
53681
- path13.join(resolvedPath, ".clinerules"),
53682
- path13.join(resolvedPath, ".aiderules"),
53683
- path13.join(resolvedPath, ".continuerules"),
53684
- path13.join(resolvedPath, ".rules"),
53685
- path13.join(resolvedPath, ".goosehints"),
53686
- path13.join(resolvedPath, "GEMINI.md"),
53687
- path13.join(resolvedPath, "replit.md"),
53688
- path13.join(resolvedPath, "package.json"),
53689
- path13.join(resolvedPath, ".mcp.json")
53723
+ path12.join(resolvedPath, "CLAUDE.md"),
53724
+ path12.join(resolvedPath, "CLAUDE.local.md"),
53725
+ path12.join(resolvedPath, "AGENTS.md"),
53726
+ path12.join(resolvedPath, "AGENT.md"),
53727
+ path12.join(resolvedPath, ".cursorrules"),
53728
+ path12.join(resolvedPath, ".windsurfrules"),
53729
+ path12.join(resolvedPath, ".clinerules"),
53730
+ path12.join(resolvedPath, ".aiderules"),
53731
+ path12.join(resolvedPath, ".continuerules"),
53732
+ path12.join(resolvedPath, ".rules"),
53733
+ path12.join(resolvedPath, ".goosehints"),
53734
+ path12.join(resolvedPath, "GEMINI.md"),
53735
+ path12.join(resolvedPath, "replit.md"),
53736
+ path12.join(resolvedPath, "package.json"),
53737
+ path12.join(resolvedPath, ".mcp.json"),
53738
+ // mcph configs — without these, edits to the .mcph.json the user is
53739
+ // actively iterating on don't trigger a re-lint.
53740
+ path12.join(resolvedPath, ".mcph.json"),
53741
+ path12.join(resolvedPath, ".mcph.local.json"),
53742
+ // ctxlint config — re-resolve on edit so threshold changes,
53743
+ // ignore-list edits, and check-list overrides take effect without
53744
+ // restarting the watcher.
53745
+ path12.join(resolvedPath, ".ctxlintrc"),
53746
+ path12.join(resolvedPath, ".ctxlintrc.json")
53690
53747
  ];
53691
53748
  const watchDirs = [
53692
- path13.join(resolvedPath, ".claude"),
53693
- path13.join(resolvedPath, ".cursor"),
53694
- path13.join(resolvedPath, ".github"),
53695
- path13.join(resolvedPath, ".windsurf"),
53696
- path13.join(resolvedPath, ".aide"),
53697
- path13.join(resolvedPath, ".amazonq"),
53698
- path13.join(resolvedPath, ".goose"),
53699
- path13.join(resolvedPath, ".junie"),
53700
- path13.join(resolvedPath, ".aiassistant"),
53701
- path13.join(resolvedPath, ".continue"),
53702
- path13.join(resolvedPath, ".vscode")
53749
+ path12.join(resolvedPath, ".claude"),
53750
+ path12.join(resolvedPath, ".cursor"),
53751
+ path12.join(resolvedPath, ".github"),
53752
+ path12.join(resolvedPath, ".windsurf"),
53753
+ path12.join(resolvedPath, ".aide"),
53754
+ path12.join(resolvedPath, ".amazonq"),
53755
+ path12.join(resolvedPath, ".goose"),
53756
+ path12.join(resolvedPath, ".junie"),
53757
+ path12.join(resolvedPath, ".aiassistant"),
53758
+ path12.join(resolvedPath, ".continue"),
53759
+ path12.join(resolvedPath, ".vscode")
53703
53760
  ];
53704
53761
  const watchers = [];
53705
53762
  let debounceTimer = null;
@@ -53707,30 +53764,46 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
53707
53764
  if (debounceTimer) clearTimeout(debounceTimer);
53708
53765
  debounceTimer = setTimeout(async () => {
53709
53766
  if (process.stdout.isTTY) console.clear();
53767
+ let liveConfig = config2;
53768
+ let liveOptions = options;
53769
+ let liveActiveChecks = activeChecks;
53770
+ try {
53771
+ const resolved = resolveSession(resolvedPath, opts);
53772
+ liveConfig = resolved.config;
53773
+ liveOptions = resolved.options;
53774
+ liveActiveChecks = resolved.activeChecks;
53775
+ } catch (err) {
53776
+ console.error("Error reloading config:", err instanceof Error ? err.message : err);
53777
+ }
53710
53778
  try {
53711
- const result = await runAudit(resolvedPath, activeChecks, {
53712
- depth: options.depth,
53713
- extraPatterns: config2?.contextFiles,
53714
- mcp: options.mcp,
53715
- mcpGlobal: options.mcpGlobal,
53716
- mcpOnly: options.mcpOnly,
53717
- mcph: options.mcph,
53718
- mcphGlobal: options.mcphGlobal,
53719
- mcphOnly: options.mcphOnly,
53720
- mcphStrictEnvToken: options.mcphStrictEnvToken,
53721
- session: options.session,
53722
- sessionOnly: options.sessionOnly
53779
+ if (liveConfig?.tokenThresholds) {
53780
+ setTokenThresholds(liveConfig.tokenThresholds);
53781
+ } else {
53782
+ resetTokenThresholds();
53783
+ }
53784
+ const result = await runAudit(resolvedPath, liveActiveChecks, {
53785
+ depth: liveOptions.depth,
53786
+ extraPatterns: liveConfig?.contextFiles,
53787
+ mcp: liveOptions.mcp,
53788
+ mcpGlobal: liveOptions.mcpGlobal,
53789
+ mcpOnly: liveOptions.mcpOnly,
53790
+ mcph: liveOptions.mcph,
53791
+ mcphGlobal: liveOptions.mcphGlobal,
53792
+ mcphOnly: liveOptions.mcphOnly,
53793
+ mcphStrictEnvToken: liveOptions.mcphStrictEnvToken,
53794
+ session: liveOptions.session,
53795
+ sessionOnly: liveOptions.sessionOnly
53723
53796
  });
53724
53797
  if (result.files.length === 0) {
53725
53798
  console.log("\nNo context files found.\n");
53726
- } else if (options.tokensOnly) {
53799
+ } else if (liveOptions.tokensOnly) {
53727
53800
  console.log(formatTokenReport(result));
53728
- } else if (options.format === "json") {
53801
+ } else if (liveOptions.format === "json") {
53729
53802
  console.log(formatJson(result));
53730
- } else if (options.format === "sarif") {
53803
+ } else if (liveOptions.format === "sarif") {
53731
53804
  console.log(formatSarif(result));
53732
53805
  } else {
53733
- console.log(formatText(result, options.verbose));
53806
+ console.log(formatText(result, liveOptions.verbose));
53734
53807
  }
53735
53808
  } catch (err) {
53736
53809
  console.error("Error:", err instanceof Error ? err.message : err);
@@ -53740,20 +53813,19 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
53740
53813
  resetPathsCache();
53741
53814
  resetPackageJsonCache();
53742
53815
  resetTokenThresholds();
53743
- if (config2?.tokenThresholds) setTokenThresholds(config2.tokenThresholds);
53744
53816
  }
53745
53817
  console.log(chalk2.dim("\nWatching for changes... (Ctrl+C to stop)\n"));
53746
53818
  }, 300);
53747
53819
  };
53748
53820
  for (const filePath of watchPaths) {
53749
53821
  try {
53750
- watchers.push(fs10.watch(filePath, rerun));
53822
+ watchers.push(fs9.watch(filePath, rerun));
53751
53823
  } catch {
53752
53824
  }
53753
53825
  }
53754
53826
  for (const dir of watchDirs) {
53755
53827
  try {
53756
- watchers.push(fs10.watch(dir, { recursive: true }, rerun));
53828
+ watchers.push(fs9.watch(dir, { recursive: true }, rerun));
53757
53829
  } catch {
53758
53830
  }
53759
53831
  }
@@ -53774,17 +53846,17 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
53774
53846
  }
53775
53847
  });
53776
53848
  program2.command("init").description("Set up a git pre-commit hook that runs ctxlint --strict").argument("[path]", "Project directory", ".").action(async (projectPath) => {
53777
- const resolvedRoot = path13.resolve(projectPath);
53778
- const gitDir = path13.join(resolvedRoot, ".git");
53779
- const hooksDir = path13.join(gitDir, "hooks");
53780
- if (!fs10.existsSync(gitDir)) {
53849
+ const resolvedRoot = path12.resolve(projectPath);
53850
+ const gitDir = path12.join(resolvedRoot, ".git");
53851
+ const hooksDir = path12.join(gitDir, "hooks");
53852
+ if (!fs9.existsSync(gitDir)) {
53781
53853
  console.error('Error: not a git repository. Run "git init" first.');
53782
53854
  process.exit(1);
53783
53855
  }
53784
- if (!fs10.existsSync(hooksDir)) {
53785
- fs10.mkdirSync(hooksDir, { recursive: true });
53856
+ if (!fs9.existsSync(hooksDir)) {
53857
+ fs9.mkdirSync(hooksDir, { recursive: true });
53786
53858
  }
53787
- const hookPath = path13.join(hooksDir, "pre-commit");
53859
+ const hookPath = path12.join(hooksDir, "pre-commit");
53788
53860
  const fullHookContent = `#!/bin/sh
53789
53861
  # ctxlint pre-commit hook
53790
53862
  npx @yawlabs/ctxlint@${VERSION} --strict
@@ -53793,16 +53865,16 @@ npx @yawlabs/ctxlint@${VERSION} --strict
53793
53865
  # ctxlint pre-commit hook
53794
53866
  npx @yawlabs/ctxlint@${VERSION} --strict
53795
53867
  `;
53796
- if (fs10.existsSync(hookPath)) {
53797
- const existing = fs10.readFileSync(hookPath, "utf-8");
53868
+ if (fs9.existsSync(hookPath)) {
53869
+ const existing = fs9.readFileSync(hookPath, "utf-8");
53798
53870
  if (existing.includes("ctxlint")) {
53799
53871
  console.log("Pre-commit hook already includes ctxlint.");
53800
53872
  return;
53801
53873
  }
53802
- fs10.appendFileSync(hookPath, appendHookContent);
53874
+ fs9.appendFileSync(hookPath, appendHookContent);
53803
53875
  console.log("Added ctxlint to existing pre-commit hook.");
53804
53876
  } else {
53805
- fs10.writeFileSync(hookPath, fullHookContent, { mode: 493 });
53877
+ fs9.writeFileSync(hookPath, fullHookContent, { mode: 493 });
53806
53878
  console.log("Created pre-commit hook at .git/hooks/pre-commit");
53807
53879
  }
53808
53880
  console.log("ctxlint will now run automatically before each commit.");
@@ -53819,13 +53891,83 @@ async function promptYesNo(question) {
53819
53891
  rl.close();
53820
53892
  }
53821
53893
  }
53894
+ function resolveSession(resolvedPath, opts) {
53895
+ const configPath = opts.config ? path12.resolve(opts.config) : void 0;
53896
+ const config2 = configPath ? loadConfigFromPath(configPath) : loadConfig(resolvedPath);
53897
+ const mcpGlobal = opts.mcpGlobal || config2?.mcpGlobal || false;
53898
+ const mcpOnly = opts.mcpOnly || config2?.mcpOnly || false;
53899
+ const mcpFlag = opts.mcp || mcpGlobal || mcpOnly || config2?.mcp || false;
53900
+ const mcphGlobal = opts.mcphGlobal || config2?.mcphGlobal || false;
53901
+ const mcphOnly = opts.mcphOnly || config2?.mcphOnly || false;
53902
+ const mcphFlag = opts.mcph || mcphGlobal || mcphOnly || config2?.mcph || false;
53903
+ const mcphStrictEnvToken = opts.mcphStrictEnvToken || config2?.mcphStrictEnvToken || false;
53904
+ const sessionOnly = opts.sessionOnly || config2?.sessionOnly || false;
53905
+ const sessionFlag = opts.session || sessionOnly || config2?.session || false;
53906
+ let explicitChecks = opts.checks ? validateCheckNames(
53907
+ opts.checks.split(",").map((c3) => c3.trim()),
53908
+ "--checks"
53909
+ ) : null;
53910
+ if (explicitChecks?.length === 0) explicitChecks = null;
53911
+ const hasMcpInChecks = explicitChecks?.some((c3) => c3.startsWith("mcp-") && !c3.startsWith("mcph-")) || false;
53912
+ const hasMcphInChecks = explicitChecks?.some((c3) => c3.startsWith("mcph-")) || false;
53913
+ const hasSessionInChecks = explicitChecks?.some((c3) => c3.startsWith("session-")) || false;
53914
+ const effectiveMcp = mcpFlag || hasMcpInChecks;
53915
+ const effectiveMcph = mcphFlag || hasMcphInChecks;
53916
+ const effectiveSession = sessionFlag || sessionOnly || hasSessionInChecks;
53917
+ let checks;
53918
+ if (explicitChecks) {
53919
+ checks = explicitChecks;
53920
+ } else if (sessionOnly) {
53921
+ checks = ALL_SESSION_CHECKS;
53922
+ } else if (mcpOnly) {
53923
+ checks = ALL_MCP_CHECKS;
53924
+ } else if (mcphOnly) {
53925
+ checks = ALL_MCPH_CHECKS;
53926
+ } else {
53927
+ const base = config2?.checks || ALL_CHECKS;
53928
+ checks = [
53929
+ ...base,
53930
+ ...effectiveMcp ? ALL_MCP_CHECKS : [],
53931
+ ...effectiveMcph ? ALL_MCPH_CHECKS : [],
53932
+ ...effectiveSession ? ALL_SESSION_CHECKS : []
53933
+ ];
53934
+ }
53935
+ const options = {
53936
+ projectPath: resolvedPath,
53937
+ checks,
53938
+ strict: opts.strict || config2?.strict || false,
53939
+ format: opts.format,
53940
+ verbose: opts.verbose,
53941
+ fix: opts.fix,
53942
+ ignore: opts.ignore ? validateCheckNames(
53943
+ opts.ignore.split(",").map((c3) => c3.trim()),
53944
+ "--ignore"
53945
+ ) : config2?.ignore || [],
53946
+ tokensOnly: opts.tokens,
53947
+ quiet: opts.quiet,
53948
+ depth: Math.max(0, Math.min(parseInt(opts.depth, 10) || 2, 10)),
53949
+ mcp: effectiveMcp,
53950
+ mcpOnly,
53951
+ mcpGlobal,
53952
+ mcph: effectiveMcph,
53953
+ mcphOnly,
53954
+ mcphGlobal,
53955
+ mcphStrictEnvToken,
53956
+ session: effectiveSession,
53957
+ sessionOnly
53958
+ };
53959
+ if (config2?.tokenThresholds) {
53960
+ setTokenThresholds(config2.tokenThresholds);
53961
+ }
53962
+ const activeChecks = options.checks.filter((c3) => !options.ignore.includes(c3));
53963
+ return { config: config2, options, activeChecks };
53964
+ }
53822
53965
  function loadConfigFromPath(configPath) {
53823
53966
  try {
53824
- const content = fs10.readFileSync(configPath, "utf-8");
53825
- return JSON.parse(content);
53967
+ return loadConfigFromExplicitPath(configPath);
53826
53968
  } catch (err) {
53827
53969
  const detail = err instanceof Error ? err.message : String(err);
53828
- console.error(`Error: could not load config from ${configPath}: ${detail}`);
53970
+ console.error(`Error: ${detail}`);
53829
53971
  process.exit(2);
53830
53972
  }
53831
53973
  }