@yawlabs/ctxlint 0.12.3 → 0.13.1

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
@@ -461,8 +461,8 @@ var init_parseUtil = __esm({
461
461
  init_errors();
462
462
  init_en();
463
463
  makeIssue = (params) => {
464
- const { data, path: path14, errorMaps, issueData } = params;
465
- const fullPath = [...path14, ...issueData.path || []];
464
+ const { data, path: path17, errorMaps, issueData } = params;
465
+ const fullPath = [...path17, ...issueData.path || []];
466
466
  const fullIssue = {
467
467
  ...issueData,
468
468
  path: fullPath
@@ -745,11 +745,11 @@ var init_types = __esm({
745
745
  init_parseUtil();
746
746
  init_util();
747
747
  ParseInputLazyPath = class {
748
- constructor(parent, value, path14, key) {
748
+ constructor(parent, value, path17, key) {
749
749
  this._cachedPath = [];
750
750
  this.parent = parent;
751
751
  this.data = value;
752
- this._path = path14;
752
+ this._path = path17;
753
753
  this._key = key;
754
754
  }
755
755
  get path() {
@@ -4257,10 +4257,10 @@ function mergeDefs(...defs) {
4257
4257
  function cloneDef(schema) {
4258
4258
  return mergeDefs(schema._zod.def);
4259
4259
  }
4260
- function getElementAtPath(obj, path14) {
4261
- if (!path14)
4260
+ function getElementAtPath(obj, path17) {
4261
+ if (!path17)
4262
4262
  return obj;
4263
- return path14.reduce((acc, key) => acc?.[key], obj);
4263
+ return path17.reduce((acc, key) => acc?.[key], obj);
4264
4264
  }
4265
4265
  function promiseAllObject(promisesObj) {
4266
4266
  const keys = Object.keys(promisesObj);
@@ -4572,11 +4572,11 @@ function aborted(x3, startIndex = 0) {
4572
4572
  }
4573
4573
  return false;
4574
4574
  }
4575
- function prefixIssues(path14, issues) {
4575
+ function prefixIssues(path17, issues) {
4576
4576
  return issues.map((iss) => {
4577
4577
  var _a3;
4578
4578
  (_a3 = iss).path ?? (_a3.path = []);
4579
- iss.path.unshift(path14);
4579
+ iss.path.unshift(path17);
4580
4580
  return iss;
4581
4581
  });
4582
4582
  }
@@ -4819,7 +4819,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
4819
4819
  }
4820
4820
  function treeifyError(error49, mapper = (issue2) => issue2.message) {
4821
4821
  const result = { errors: [] };
4822
- const processError = (error50, path14 = []) => {
4822
+ const processError = (error50, path17 = []) => {
4823
4823
  var _a3, _b;
4824
4824
  for (const issue2 of error50.issues) {
4825
4825
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -4829,7 +4829,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
4829
4829
  } else if (issue2.code === "invalid_element") {
4830
4830
  processError({ issues: issue2.issues }, issue2.path);
4831
4831
  } else {
4832
- const fullpath = [...path14, ...issue2.path];
4832
+ const fullpath = [...path17, ...issue2.path];
4833
4833
  if (fullpath.length === 0) {
4834
4834
  result.errors.push(mapper(issue2));
4835
4835
  continue;
@@ -4861,8 +4861,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
4861
4861
  }
4862
4862
  function toDotPath(_path) {
4863
4863
  const segs = [];
4864
- const path14 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4865
- for (const seg of path14) {
4864
+ const path17 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4865
+ for (const seg of path17) {
4866
4866
  if (typeof seg === "number")
4867
4867
  segs.push(`[${seg}]`);
4868
4868
  else if (typeof seg === "symbol")
@@ -17890,13 +17890,13 @@ function resolveRef(ref, ctx) {
17890
17890
  if (!ref.startsWith("#")) {
17891
17891
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
17892
17892
  }
17893
- const path14 = ref.slice(1).split("/").filter(Boolean);
17894
- if (path14.length === 0) {
17893
+ const path17 = ref.slice(1).split("/").filter(Boolean);
17894
+ if (path17.length === 0) {
17895
17895
  return ctx.rootSchema;
17896
17896
  }
17897
17897
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
17898
- if (path14[0] === defsKey) {
17899
- const key = path14[1];
17898
+ if (path17[0] === defsKey) {
17899
+ const key = path17[1];
17900
17900
  if (!key || !ctx.defs[key]) {
17901
17901
  throw new Error(`Reference not found: ${ref}`);
17902
17902
  }
@@ -22404,7 +22404,7 @@ var init_protocol = __esm({
22404
22404
  return;
22405
22405
  }
22406
22406
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
22407
- await new Promise((resolve12) => setTimeout(resolve12, pollInterval));
22407
+ await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
22408
22408
  options?.signal?.throwIfAborted();
22409
22409
  }
22410
22410
  } catch (error49) {
@@ -22421,7 +22421,7 @@ var init_protocol = __esm({
22421
22421
  */
22422
22422
  request(request, resultSchema, options) {
22423
22423
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
22424
- return new Promise((resolve12, reject) => {
22424
+ return new Promise((resolve14, reject) => {
22425
22425
  const earlyReject = (error49) => {
22426
22426
  reject(error49);
22427
22427
  };
@@ -22499,7 +22499,7 @@ var init_protocol = __esm({
22499
22499
  if (!parseResult.success) {
22500
22500
  reject(parseResult.error);
22501
22501
  } else {
22502
- resolve12(parseResult.data);
22502
+ resolve14(parseResult.data);
22503
22503
  }
22504
22504
  } catch (error49) {
22505
22505
  reject(error49);
@@ -22760,12 +22760,12 @@ var init_protocol = __esm({
22760
22760
  }
22761
22761
  } catch {
22762
22762
  }
22763
- return new Promise((resolve12, reject) => {
22763
+ return new Promise((resolve14, reject) => {
22764
22764
  if (signal.aborted) {
22765
22765
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
22766
22766
  return;
22767
22767
  }
22768
- const timeoutId = setTimeout(resolve12, interval);
22768
+ const timeoutId = setTimeout(resolve14, interval);
22769
22769
  signal.addEventListener("abort", () => {
22770
22770
  clearTimeout(timeoutId);
22771
22771
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -25813,7 +25813,7 @@ var require_compile = __commonJS({
25813
25813
  const schOrFunc = root.refs[ref];
25814
25814
  if (schOrFunc)
25815
25815
  return schOrFunc;
25816
- let _sch = resolve12.call(this, root, ref);
25816
+ let _sch = resolve14.call(this, root, ref);
25817
25817
  if (_sch === void 0) {
25818
25818
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
25819
25819
  const { schemaId } = this.opts;
@@ -25840,7 +25840,7 @@ var require_compile = __commonJS({
25840
25840
  function sameSchemaEnv(s1, s2) {
25841
25841
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
25842
25842
  }
25843
- function resolve12(root, ref) {
25843
+ function resolve14(root, ref) {
25844
25844
  let sch;
25845
25845
  while (typeof (sch = this.refs[ref]) == "string")
25846
25846
  ref = sch;
@@ -26056,8 +26056,8 @@ var require_utils = __commonJS({
26056
26056
  }
26057
26057
  return ind;
26058
26058
  }
26059
- function removeDotSegments(path14) {
26060
- let input = path14;
26059
+ function removeDotSegments(path17) {
26060
+ let input = path17;
26061
26061
  const output = [];
26062
26062
  let nextSlash = -1;
26063
26063
  let len = 0;
@@ -26257,8 +26257,8 @@ var require_schemes = __commonJS({
26257
26257
  wsComponent.secure = void 0;
26258
26258
  }
26259
26259
  if (wsComponent.resourceName) {
26260
- const [path14, query] = wsComponent.resourceName.split("?");
26261
- wsComponent.path = path14 && path14 !== "/" ? path14 : void 0;
26260
+ const [path17, query] = wsComponent.resourceName.split("?");
26261
+ wsComponent.path = path17 && path17 !== "/" ? path17 : void 0;
26262
26262
  wsComponent.query = query;
26263
26263
  wsComponent.resourceName = void 0;
26264
26264
  }
@@ -26418,7 +26418,7 @@ var require_fast_uri = __commonJS({
26418
26418
  }
26419
26419
  return uri;
26420
26420
  }
26421
- function resolve12(baseURI, relativeURI, options) {
26421
+ function resolve14(baseURI, relativeURI, options) {
26422
26422
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
26423
26423
  const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
26424
26424
  schemelessOptions.skipEscape = true;
@@ -26645,7 +26645,7 @@ var require_fast_uri = __commonJS({
26645
26645
  var fastUri = {
26646
26646
  SCHEMES,
26647
26647
  normalize: normalize3,
26648
- resolve: resolve12,
26648
+ resolve: resolve14,
26649
26649
  resolveComponent,
26650
26650
  equal,
26651
26651
  serialize,
@@ -29666,12 +29666,12 @@ var require_dist = __commonJS({
29666
29666
  throw new Error(`Unknown format "${name}"`);
29667
29667
  return f;
29668
29668
  };
29669
- function addFormats(ajv, list, fs10, exportName) {
29669
+ function addFormats(ajv, list, fs12, exportName) {
29670
29670
  var _a3;
29671
29671
  var _b;
29672
29672
  (_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
29673
29673
  for (const f of list)
29674
- ajv.addFormat(f, fs10[f]);
29674
+ ajv.addFormat(f, fs12[f]);
29675
29675
  }
29676
29676
  module.exports = exports = formatsPlugin;
29677
29677
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -30827,7 +30827,7 @@ var init_mcp = __esm({
30827
30827
  let task = createTaskResult.task;
30828
30828
  const pollInterval = task.pollInterval ?? 5e3;
30829
30829
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
30830
- await new Promise((resolve12) => setTimeout(resolve12, pollInterval));
30830
+ await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
30831
30831
  const updatedTask = await extra.taskStore.getTask(taskId);
30832
30832
  if (!updatedTask) {
30833
30833
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -31423,12 +31423,12 @@ var init_stdio2 = __esm({
31423
31423
  this.onclose?.();
31424
31424
  }
31425
31425
  send(message) {
31426
- return new Promise((resolve12) => {
31426
+ return new Promise((resolve14) => {
31427
31427
  const json2 = serializeMessage(message);
31428
31428
  if (this._stdout.write(json2)) {
31429
- resolve12();
31429
+ resolve14();
31430
31430
  } else {
31431
- this._stdout.once("drain", resolve12);
31431
+ this._stdout.once("drain", resolve14);
31432
31432
  }
31433
31433
  });
31434
31434
  }
@@ -35539,7 +35539,7 @@ var require_supports_color = __commonJS({
35539
35539
  "node_modules/.pnpm/supports-color@7.2.0/node_modules/supports-color/index.js"(exports, module) {
35540
35540
  "use strict";
35541
35541
  init_define_WEB_FIRST_SEGMENTS();
35542
- var os3 = __require("os");
35542
+ var os5 = __require("os");
35543
35543
  var tty3 = __require("tty");
35544
35544
  var hasFlag2 = require_has_flag();
35545
35545
  var { env: env2 } = process;
@@ -35587,7 +35587,7 @@ var require_supports_color = __commonJS({
35587
35587
  return min;
35588
35588
  }
35589
35589
  if (process.platform === "win32") {
35590
- const osRelease = os3.release().split(".");
35590
+ const osRelease = os5.release().split(".");
35591
35591
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
35592
35592
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
35593
35593
  }
@@ -35836,10 +35836,10 @@ var require_src2 = __commonJS({
35836
35836
  var fs_1 = __require("fs");
35837
35837
  var debug_1 = __importDefault(require_src());
35838
35838
  var log = debug_1.default("@kwsites/file-exists");
35839
- function check2(path14, isFile, isDirectory2) {
35840
- log(`checking %s`, path14);
35839
+ function check2(path17, isFile, isDirectory2) {
35840
+ log(`checking %s`, path17);
35841
35841
  try {
35842
- const stat3 = fs_1.statSync(path14);
35842
+ const stat3 = fs_1.statSync(path17);
35843
35843
  if (stat3.isFile() && isFile) {
35844
35844
  log(`[OK] path represents a file`);
35845
35845
  return true;
@@ -35859,8 +35859,8 @@ var require_src2 = __commonJS({
35859
35859
  throw e;
35860
35860
  }
35861
35861
  }
35862
- function exists2(path14, type = exports.READABLE) {
35863
- return check2(path14, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
35862
+ function exists2(path17, type = exports.READABLE) {
35863
+ return check2(path17, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
35864
35864
  }
35865
35865
  exports.exists = exists2;
35866
35866
  exports.FILE = 1;
@@ -36453,8 +36453,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
36453
36453
  function forEachLineWithContent(input, callback) {
36454
36454
  return toLinesWithContent(input, true).map((line) => callback(line));
36455
36455
  }
36456
- function folderExists(path14) {
36457
- return (0, import_file_exists.exists)(path14, import_file_exists.FOLDER);
36456
+ function folderExists(path17) {
36457
+ return (0, import_file_exists.exists)(path17, import_file_exists.FOLDER);
36458
36458
  }
36459
36459
  function append(target, item) {
36460
36460
  if (Array.isArray(target)) {
@@ -36645,8 +36645,8 @@ function checkIsRepoRootTask() {
36645
36645
  commands,
36646
36646
  format: "utf-8",
36647
36647
  onError,
36648
- parser(path14) {
36649
- return /^\.(git)?$/.test(path14.trim());
36648
+ parser(path17) {
36649
+ return /^\.(git)?$/.test(path17.trim());
36650
36650
  }
36651
36651
  };
36652
36652
  }
@@ -36902,11 +36902,11 @@ function parseGrep(grep) {
36902
36902
  const paths = /* @__PURE__ */ new Set();
36903
36903
  const results = {};
36904
36904
  forEachLineWithContent(grep, (input) => {
36905
- const [path14, line, preview] = input.split(NULL);
36906
- paths.add(path14);
36907
- (results[path14] = results[path14] || []).push({
36905
+ const [path17, line, preview] = input.split(NULL);
36906
+ paths.add(path17);
36907
+ (results[path17] = results[path17] || []).push({
36908
36908
  line: asNumber(line),
36909
- path: path14,
36909
+ path: path17,
36910
36910
  preview
36911
36911
  });
36912
36912
  });
@@ -37203,14 +37203,14 @@ function hashObjectTask(filePath, write) {
37203
37203
  }
37204
37204
  return straightThroughStringTask(commands, true);
37205
37205
  }
37206
- function parseInit(bare, path14, text) {
37206
+ function parseInit(bare, path17, text) {
37207
37207
  const response = String(text).trim();
37208
37208
  let result;
37209
37209
  if (result = initResponseRegex.exec(response)) {
37210
- return new InitSummary(bare, path14, false, result[1]);
37210
+ return new InitSummary(bare, path17, false, result[1]);
37211
37211
  }
37212
37212
  if (result = reInitResponseRegex.exec(response)) {
37213
- return new InitSummary(bare, path14, true, result[1]);
37213
+ return new InitSummary(bare, path17, true, result[1]);
37214
37214
  }
37215
37215
  let gitDir = "";
37216
37216
  const tokens = response.split(" ");
@@ -37221,12 +37221,12 @@ function parseInit(bare, path14, text) {
37221
37221
  break;
37222
37222
  }
37223
37223
  }
37224
- return new InitSummary(bare, path14, /^re/i.test(response), gitDir);
37224
+ return new InitSummary(bare, path17, /^re/i.test(response), gitDir);
37225
37225
  }
37226
37226
  function hasBareCommand(command) {
37227
37227
  return command.includes(bareCommand);
37228
37228
  }
37229
- function initTask(bare = false, path14, customArgs) {
37229
+ function initTask(bare = false, path17, customArgs) {
37230
37230
  const commands = ["init", ...customArgs];
37231
37231
  if (bare && !hasBareCommand(commands)) {
37232
37232
  commands.splice(1, 0, bareCommand);
@@ -37235,7 +37235,7 @@ function initTask(bare = false, path14, customArgs) {
37235
37235
  commands,
37236
37236
  format: "utf-8",
37237
37237
  parser(text) {
37238
- return parseInit(commands.includes("--bare"), path14, text);
37238
+ return parseInit(commands.includes("--bare"), path17, text);
37239
37239
  }
37240
37240
  };
37241
37241
  }
@@ -37514,14 +37514,14 @@ function splitLine(result, lineStr) {
37514
37514
  default:
37515
37515
  return;
37516
37516
  }
37517
- function data(index, workingDir, path14) {
37517
+ function data(index, workingDir, path17) {
37518
37518
  const raw = `${index}${workingDir}`;
37519
37519
  const handler = parsers6.get(raw);
37520
37520
  if (handler) {
37521
- handler(result, path14);
37521
+ handler(result, path17);
37522
37522
  }
37523
37523
  if (raw !== "##" && raw !== "!!") {
37524
- result.files.push(new FileStatusSummary(path14, index, workingDir));
37524
+ result.files.push(new FileStatusSummary(path17, index, workingDir));
37525
37525
  }
37526
37526
  }
37527
37527
  }
@@ -37708,8 +37708,8 @@ function deleteBranchTask(branch, forceDelete = false) {
37708
37708
  return task;
37709
37709
  }
37710
37710
  function toPath(input) {
37711
- const path14 = input.trim().replace(/^["']|["']$/g, "");
37712
- return path14 && normalize2(path14);
37711
+ const path17 = input.trim().replace(/^["']|["']$/g, "");
37712
+ return path17 && normalize2(path17);
37713
37713
  }
37714
37714
  function checkIgnoreTask(paths) {
37715
37715
  return {
@@ -37848,8 +37848,8 @@ function stashListTask(opt = {}, customArgs) {
37848
37848
  parser: parser4
37849
37849
  };
37850
37850
  }
37851
- function addSubModuleTask(repo, path14) {
37852
- return subModuleTask(["add", repo, path14]);
37851
+ function addSubModuleTask(repo, path17) {
37852
+ return subModuleTask(["add", repo, path17]);
37853
37853
  }
37854
37854
  function initSubModuleTask(customArgs) {
37855
37855
  return subModuleTask(["init", ...customArgs]);
@@ -39112,9 +39112,9 @@ var init_esm2 = __esm({
39112
39112
  "src/lib/responses/InitSummary.ts"() {
39113
39113
  "use strict";
39114
39114
  InitSummary = class {
39115
- constructor(bare, path14, existing, gitDir) {
39115
+ constructor(bare, path17, existing, gitDir) {
39116
39116
  this.bare = bare;
39117
- this.path = path14;
39117
+ this.path = path17;
39118
39118
  this.existing = existing;
39119
39119
  this.gitDir = gitDir;
39120
39120
  }
@@ -39647,12 +39647,12 @@ var init_esm2 = __esm({
39647
39647
  "use strict";
39648
39648
  fromPathRegex = /^(.+)\0(.+)$/;
39649
39649
  FileStatusSummary = class {
39650
- constructor(path14, index, working_dir) {
39651
- this.path = path14;
39650
+ constructor(path17, index, working_dir) {
39651
+ this.path = path17;
39652
39652
  this.index = index;
39653
39653
  this.working_dir = working_dir;
39654
39654
  if (index === "R" || working_dir === "R") {
39655
- const detail = fromPathRegex.exec(path14) || [null, path14, path14];
39655
+ const detail = fromPathRegex.exec(path17) || [null, path17, path17];
39656
39656
  this.from = detail[2] || "";
39657
39657
  this.path = detail[1] || "";
39658
39658
  }
@@ -39914,9 +39914,9 @@ var init_esm2 = __esm({
39914
39914
  next
39915
39915
  );
39916
39916
  }
39917
- hashObject(path14, write) {
39917
+ hashObject(path17, write) {
39918
39918
  return this._runTask(
39919
- hashObjectTask(path14, write === true),
39919
+ hashObjectTask(path17, write === true),
39920
39920
  trailingFunctionArgument(arguments)
39921
39921
  );
39922
39922
  }
@@ -40553,8 +40553,8 @@ var init_esm2 = __esm({
40553
40553
  }
40554
40554
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
40555
40555
  };
40556
- Git2.prototype.submoduleAdd = function(repo, path14, then) {
40557
- return this._runTask(addSubModuleTask2(repo, path14), trailingFunctionArgument2(arguments));
40556
+ Git2.prototype.submoduleAdd = function(repo, path17, then) {
40557
+ return this._runTask(addSubModuleTask2(repo, path17), trailingFunctionArgument2(arguments));
40558
40558
  };
40559
40559
  Git2.prototype.submoduleUpdate = function(args2, then) {
40560
40560
  return this._runTask(
@@ -40905,6 +40905,45 @@ async function getCommitsSinceBatch(projectRoot, paths, since) {
40905
40905
  }
40906
40906
  return out;
40907
40907
  }
40908
+ function normalizeRenamePath(p2) {
40909
+ return p2.replace(/\\/g, "/").replace(/^\.\//, "");
40910
+ }
40911
+ function parseRenameLog(result, targetPath) {
40912
+ if (!result.trim()) return null;
40913
+ const want = targetPath ? normalizeRenamePath(targetPath) : void 0;
40914
+ const wantBase = want ? want.split("/").pop() ?? want : void 0;
40915
+ let currentHash = "unknown";
40916
+ let currentDateStr;
40917
+ let basenameFallback = null;
40918
+ const lines = result.trim().split("\n");
40919
+ for (const line of lines) {
40920
+ const headerMatch = line.match(/^([a-f0-9]{7,40})\s+(.+)$/);
40921
+ if (headerMatch) {
40922
+ currentHash = headerMatch[1].substring(0, 7);
40923
+ currentDateStr = headerMatch[2];
40924
+ continue;
40925
+ }
40926
+ if (line.startsWith("R")) {
40927
+ const parts = line.split(" ");
40928
+ if (parts.length >= 3) {
40929
+ const daysAgo = currentDateStr ? Math.floor((Date.now() - new Date(currentDateStr).getTime()) / (1e3 * 60 * 60 * 24)) : 0;
40930
+ const info2 = {
40931
+ oldPath: parts[1],
40932
+ newPath: parts[2],
40933
+ commitHash: currentHash,
40934
+ daysAgo
40935
+ };
40936
+ if (!want) return info2;
40937
+ const oldNorm = normalizeRenamePath(info2.oldPath);
40938
+ if (oldNorm === want) return info2;
40939
+ if (!basenameFallback && wantBase && (oldNorm.split("/").pop() ?? oldNorm) === wantBase) {
40940
+ basenameFallback = info2;
40941
+ }
40942
+ }
40943
+ }
40944
+ }
40945
+ return basenameFallback;
40946
+ }
40908
40947
  async function findRenames(projectRoot, filePath) {
40909
40948
  try {
40910
40949
  const git = getGit(projectRoot);
@@ -40914,35 +40953,9 @@ async function findRenames(projectRoot, filePath) {
40914
40953
  "--find-renames",
40915
40954
  "--name-status",
40916
40955
  "--format=%H %ai",
40917
- "-10",
40918
- "--",
40919
- filePath
40956
+ "-50"
40920
40957
  ]);
40921
- if (!result.trim()) return null;
40922
- let currentHash = "unknown";
40923
- let currentDateStr;
40924
- const lines = result.trim().split("\n");
40925
- for (const line of lines) {
40926
- const headerMatch = line.match(/^([a-f0-9]{7,40})\s+(.+)$/);
40927
- if (headerMatch) {
40928
- currentHash = headerMatch[1].substring(0, 7);
40929
- currentDateStr = headerMatch[2];
40930
- continue;
40931
- }
40932
- if (line.startsWith("R")) {
40933
- const parts = line.split(" ");
40934
- if (parts.length >= 3) {
40935
- const daysAgo = currentDateStr ? Math.floor((Date.now() - new Date(currentDateStr).getTime()) / (1e3 * 60 * 60 * 24)) : 0;
40936
- return {
40937
- oldPath: parts[1],
40938
- newPath: parts[2],
40939
- commitHash: currentHash,
40940
- daysAgo
40941
- };
40942
- }
40943
- }
40944
- }
40945
- return null;
40958
+ return parseRenameLog(result, filePath);
40946
40959
  } catch {
40947
40960
  return null;
40948
40961
  }
@@ -41629,6 +41642,37 @@ function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
41629
41642
  }
41630
41643
  return result;
41631
41644
  }
41645
+ function findNodeAtLocation(root, path17) {
41646
+ if (!root) {
41647
+ return void 0;
41648
+ }
41649
+ let node = root;
41650
+ for (let segment of path17) {
41651
+ if (typeof segment === "string") {
41652
+ if (node.type !== "object" || !Array.isArray(node.children)) {
41653
+ return void 0;
41654
+ }
41655
+ let found = false;
41656
+ for (const propertyNode of node.children) {
41657
+ if (Array.isArray(propertyNode.children) && propertyNode.children[0].value === segment && propertyNode.children.length === 2) {
41658
+ node = propertyNode.children[1];
41659
+ found = true;
41660
+ break;
41661
+ }
41662
+ }
41663
+ if (!found) {
41664
+ return void 0;
41665
+ }
41666
+ } else {
41667
+ const index = segment;
41668
+ if (node.type !== "array" || index < 0 || !Array.isArray(node.children) || index >= node.children.length) {
41669
+ return void 0;
41670
+ }
41671
+ node = node.children[index];
41672
+ }
41673
+ }
41674
+ return node;
41675
+ }
41632
41676
  function getNodeValue(node) {
41633
41677
  switch (node.type) {
41634
41678
  case "array":
@@ -42001,7 +42045,7 @@ var init_edit = __esm({
42001
42045
  });
42002
42046
 
42003
42047
  // node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/main.js
42004
- var ScanError, SyntaxKind, parseTree2, getNodeValue2, ParseErrorCode;
42048
+ var ScanError, SyntaxKind, parseTree2, findNodeAtLocation2, getNodeValue2, ParseErrorCode;
42005
42049
  var init_main = __esm({
42006
42050
  "node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/main.js"() {
42007
42051
  "use strict";
@@ -42039,6 +42083,7 @@ var init_main = __esm({
42039
42083
  SyntaxKind2[SyntaxKind2["EOF"] = 17] = "EOF";
42040
42084
  })(SyntaxKind || (SyntaxKind = {}));
42041
42085
  parseTree2 = parseTree;
42086
+ findNodeAtLocation2 = findNodeAtLocation;
42042
42087
  getNodeValue2 = getNodeValue;
42043
42088
  (function(ParseErrorCode2) {
42044
42089
  ParseErrorCode2[ParseErrorCode2["InvalidSymbol"] = 1] = "InvalidSymbol";
@@ -45978,6 +46023,487 @@ var init_content_secrets = __esm({
45978
46023
  }
45979
46024
  });
45980
46025
 
46026
+ // src/core/checks/hook-coverage.ts
46027
+ import * as fs7 from "node:fs";
46028
+ import * as os2 from "node:os";
46029
+ import * as path11 from "node:path";
46030
+ function loadSettingsSources2(projectRoot, homeDir, includeUserGlobal) {
46031
+ const home2 = homeDir;
46032
+ const candidates = [
46033
+ {
46034
+ filePath: path11.join(projectRoot, ".claude", "settings.json"),
46035
+ displayPath: path11.join(".claude", "settings.json"),
46036
+ isUserGlobal: false
46037
+ },
46038
+ {
46039
+ filePath: path11.join(projectRoot, ".claude", "settings.local.json"),
46040
+ displayPath: path11.join(".claude", "settings.local.json"),
46041
+ isUserGlobal: false
46042
+ }
46043
+ ];
46044
+ if (includeUserGlobal) {
46045
+ candidates.push({
46046
+ filePath: path11.join(home2, ".claude", "settings.json"),
46047
+ displayPath: "~/.claude/settings.json",
46048
+ isUserGlobal: true
46049
+ });
46050
+ }
46051
+ const sources = [];
46052
+ for (const c3 of candidates) {
46053
+ let content;
46054
+ try {
46055
+ content = stripBom(fs7.readFileSync(c3.filePath, "utf-8"));
46056
+ } catch {
46057
+ continue;
46058
+ }
46059
+ const tree = parseTree2(content, [], { allowTrailingComma: true });
46060
+ sources.push({ ...c3, content, tree });
46061
+ }
46062
+ return sources;
46063
+ }
46064
+ function expandPath(raw, projectRoot, homeDir) {
46065
+ let s = raw;
46066
+ if (s === "~" || s.startsWith("~/") || s.startsWith("~\\")) {
46067
+ s = path11.join(homeDir, s.slice(1));
46068
+ }
46069
+ const replacements = {
46070
+ CLAUDE_PROJECT_DIR: projectRoot,
46071
+ CLAUDE_CONFIG_DIR: path11.join(homeDir, ".claude"),
46072
+ HOME: homeDir,
46073
+ USERPROFILE: homeDir
46074
+ };
46075
+ s = s.replace(
46076
+ /\$\{?([A-Z_][A-Z0-9_]*)\}?/g,
46077
+ (m, name) => name in replacements ? replacements[name] : m
46078
+ );
46079
+ if (/\$\{?[A-Za-z_]/.test(s)) return null;
46080
+ if (!path11.isAbsolute(s)) s = path11.resolve(projectRoot, s);
46081
+ return s;
46082
+ }
46083
+ function looksLikePath(token) {
46084
+ const hasSep = token.includes("/") || token.includes("\\");
46085
+ if (PATH_PREFIX.test(token)) return true;
46086
+ return hasSep && SCRIPT_EXT.test(token);
46087
+ }
46088
+ function extractCommandPaths(command, projectRoot, homeDir) {
46089
+ const tokens = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
46090
+ const out = [];
46091
+ for (const t2 of tokens) {
46092
+ const token = t2.replace(/^['"]|['"]$/g, "");
46093
+ if (!looksLikePath(token)) continue;
46094
+ out.push({ raw: token, resolved: expandPath(token, projectRoot, homeDir) });
46095
+ }
46096
+ return out;
46097
+ }
46098
+ function extractPermissionPaths(entry, projectRoot, homeDir) {
46099
+ const inner = entry.replace(/^[A-Za-z]+\((.*)\)$/, "$1");
46100
+ return extractCommandPaths(inner, projectRoot, homeDir);
46101
+ }
46102
+ function lineForPath(source, jsonPath) {
46103
+ if (!source.tree) return 1;
46104
+ const node = findNodeAtLocation2(source.tree, jsonPath);
46105
+ if (!node) return 1;
46106
+ return offsetToPosition(source.content, node.offset).line;
46107
+ }
46108
+ function checkSource(source, projectRoot, homeDir) {
46109
+ if (!source.tree) return [];
46110
+ const data = getNodeValue2(source.tree);
46111
+ if (!data || typeof data !== "object") return [];
46112
+ const issues = [];
46113
+ const report = (cand, line, origin) => {
46114
+ if (cand.resolved === null) return;
46115
+ if (fileExists(cand.resolved)) return;
46116
+ const where = source.isUserGlobal ? ` (in ${source.displayPath})` : "";
46117
+ issues.push({
46118
+ severity: "warning",
46119
+ check: "hook-coverage",
46120
+ ruleId: "hook-coverage/dead-hook",
46121
+ line,
46122
+ message: `${origin} references "${cand.raw}" which does not exist on disk${where} \u2014 the gate silently no-ops`,
46123
+ suggestion: source.isUserGlobal ? `Restore the script at ${cand.resolved}, or remove the dead entry from ${source.displayPath}.` : `Restore the script, fix the path, or remove the dead entry. A PreToolUse hook pointing at a missing script does not block anything.`
46124
+ });
46125
+ };
46126
+ const hooks = data.hooks ?? {};
46127
+ for (const [event, matchers] of Object.entries(hooks)) {
46128
+ if (!Array.isArray(matchers)) continue;
46129
+ matchers.forEach((matcher, mi2) => {
46130
+ const subHooks = matcher?.hooks;
46131
+ if (!Array.isArray(subHooks)) return;
46132
+ subHooks.forEach((h2, hi2) => {
46133
+ if (!h2 || typeof h2.command !== "string") return;
46134
+ const line = lineForPath(source, ["hooks", event, mi2, "hooks", hi2, "command"]);
46135
+ for (const cand of extractCommandPaths(h2.command, projectRoot, homeDir)) {
46136
+ report(cand, line, `${event} hook`);
46137
+ }
46138
+ });
46139
+ });
46140
+ }
46141
+ const perms = data.permissions ?? {};
46142
+ for (const listName of ["allow", "deny", "ask"]) {
46143
+ const list = perms[listName];
46144
+ if (!Array.isArray(list)) continue;
46145
+ list.forEach((entry, i2) => {
46146
+ if (typeof entry !== "string") return;
46147
+ const line = lineForPath(source, ["permissions", listName, i2]);
46148
+ for (const cand of extractPermissionPaths(entry, projectRoot, homeDir)) {
46149
+ report(cand, line, `permissions.${listName} entry`);
46150
+ }
46151
+ });
46152
+ }
46153
+ return issues;
46154
+ }
46155
+ async function checkHookCoverage(projectRoot, homeDir = os2.homedir(), options = {}) {
46156
+ const sources = loadSettingsSources2(projectRoot, homeDir, options.userGlobal ?? false);
46157
+ const issues = [];
46158
+ for (const source of sources) {
46159
+ issues.push(...checkSource(source, projectRoot, homeDir));
46160
+ }
46161
+ return issues;
46162
+ }
46163
+ var SCRIPT_EXT, PATH_PREFIX;
46164
+ var init_hook_coverage = __esm({
46165
+ "src/core/checks/hook-coverage.ts"() {
46166
+ "use strict";
46167
+ init_define_WEB_FIRST_SEGMENTS();
46168
+ init_main();
46169
+ init_fs();
46170
+ init_positions();
46171
+ SCRIPT_EXT = /\.(sh|bash|zsh|fish|js|mjs|cjs|ts|py|rb|pl|ps1|cmd|bat|exe)$/i;
46172
+ PATH_PREFIX = /^(~|\.\.?[/\\]|[/\\]|\$\{?[A-Za-z_]|[A-Za-z]:[/\\])/;
46173
+ }
46174
+ });
46175
+
46176
+ // src/core/skill-scanner.ts
46177
+ import * as fs8 from "node:fs";
46178
+ import * as os3 from "node:os";
46179
+ import * as path12 from "node:path";
46180
+ function defaultHome() {
46181
+ return process.env.HOME || process.env.USERPROFILE || os3.homedir();
46182
+ }
46183
+ function scanSkillFiles(homeDir = defaultHome()) {
46184
+ const files = [];
46185
+ const orphanedSkillDirs = [];
46186
+ const claudeDir = path12.join(homeDir, ".claude");
46187
+ const skillsRoot = path12.join(claudeDir, "skills");
46188
+ for (const name of safeReadDir(skillsRoot, "dir")) {
46189
+ const skillMd = path12.join(skillsRoot, name, "SKILL.md");
46190
+ const content = safeRead(skillMd);
46191
+ if (content === null) {
46192
+ orphanedSkillDirs.push({ name, displayPath: `~/.claude/skills/${name}/` });
46193
+ continue;
46194
+ }
46195
+ files.push({
46196
+ filePath: skillMd,
46197
+ displayPath: `~/.claude/skills/${name}/SKILL.md`,
46198
+ kind: "skill",
46199
+ name,
46200
+ content
46201
+ });
46202
+ }
46203
+ const agentsRoot = path12.join(claudeDir, "agents");
46204
+ for (const fileName of safeReadDir(agentsRoot, "file")) {
46205
+ if (!fileName.endsWith(".md")) continue;
46206
+ const agentMd = path12.join(agentsRoot, fileName);
46207
+ const content = safeRead(agentMd);
46208
+ if (content === null) continue;
46209
+ files.push({
46210
+ filePath: agentMd,
46211
+ displayPath: `~/.claude/agents/${fileName}`,
46212
+ kind: "agent",
46213
+ name: fileName.replace(/\.md$/, ""),
46214
+ content
46215
+ });
46216
+ }
46217
+ return { files, orphanedSkillDirs };
46218
+ }
46219
+ function safeReadDir(dir, want) {
46220
+ let entries;
46221
+ try {
46222
+ entries = fs8.readdirSync(dir, { withFileTypes: true });
46223
+ } catch {
46224
+ return [];
46225
+ }
46226
+ return entries.filter((e) => want === "dir" ? e.isDirectory() : e.isFile()).map((e) => e.name).sort();
46227
+ }
46228
+ function safeRead(file2) {
46229
+ try {
46230
+ return stripBom(fs8.readFileSync(file2, "utf-8"));
46231
+ } catch {
46232
+ return null;
46233
+ }
46234
+ }
46235
+ var init_skill_scanner = __esm({
46236
+ "src/core/skill-scanner.ts"() {
46237
+ "use strict";
46238
+ init_define_WEB_FIRST_SEGMENTS();
46239
+ init_fs();
46240
+ }
46241
+ });
46242
+
46243
+ // src/core/checks/skills.ts
46244
+ import * as path13 from "node:path";
46245
+ function parseFrontmatter3(content) {
46246
+ const lines = content.split("\n");
46247
+ if (lines[0]?.trim() !== "---") {
46248
+ return { found: false, unclosed: false, fields: {}, endLine: 0 };
46249
+ }
46250
+ const fields = {};
46251
+ let arrayKey = "";
46252
+ let endLine = 0;
46253
+ for (let i2 = 1; i2 < lines.length; i2++) {
46254
+ const line = lines[i2].trim();
46255
+ if (line === "---") {
46256
+ endLine = i2 + 1;
46257
+ break;
46258
+ }
46259
+ if (line.startsWith("- ") && arrayKey) {
46260
+ const item = line.slice(2).trim().replace(/^["']|["']$/g, "");
46261
+ fields[arrayKey] = fields[arrayKey] ? `${fields[arrayKey]}, ${item}` : item;
46262
+ continue;
46263
+ }
46264
+ arrayKey = "";
46265
+ const m = line.match(/^([\w-]+)\s*:\s*(.*)$/);
46266
+ if (m) {
46267
+ let val = m[2].trim();
46268
+ const inline = val.match(/^\[(.*)\]$/);
46269
+ if (inline) {
46270
+ val = inline[1].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean).join(", ");
46271
+ }
46272
+ fields[m[1]] = val;
46273
+ if (!m[2].trim()) arrayKey = m[1];
46274
+ }
46275
+ }
46276
+ if (endLine === 0) return { found: true, unclosed: true, fields, endLine: lines.length };
46277
+ return { found: true, unclosed: false, fields, endLine };
46278
+ }
46279
+ function checkMissingFrontmatter(file2, fm) {
46280
+ if (fm.unclosed) {
46281
+ return [
46282
+ {
46283
+ severity: "error",
46284
+ check: "skill-frontmatter",
46285
+ ruleId: "skill/missing-frontmatter",
46286
+ line: 1,
46287
+ message: `${file2.displayPath}: frontmatter opens with \`---\` but is never closed`,
46288
+ suggestion: "Add a matching `---` line after the last frontmatter field."
46289
+ }
46290
+ ];
46291
+ }
46292
+ if (!fm.found) {
46293
+ return [
46294
+ {
46295
+ severity: "error",
46296
+ check: "skill-frontmatter",
46297
+ ruleId: "skill/missing-frontmatter",
46298
+ line: 1,
46299
+ message: `${file2.displayPath}: missing YAML frontmatter`,
46300
+ suggestion: `Add frontmatter with ${REQUIRED_FIELDS[file2.kind].join(" + ")}.`
46301
+ }
46302
+ ];
46303
+ }
46304
+ const issues = [];
46305
+ for (const field of REQUIRED_FIELDS[file2.kind]) {
46306
+ if (!fm.fields[field]) {
46307
+ issues.push({
46308
+ severity: "warning",
46309
+ check: "skill-frontmatter",
46310
+ ruleId: "skill/missing-frontmatter",
46311
+ line: 1,
46312
+ message: `${file2.displayPath}: missing required frontmatter field "${field}"`,
46313
+ suggestion: field === "description" ? "A skill/agent with no description is never selected \u2014 add one describing when to use it." : `Add a "${field}" field to the frontmatter.`
46314
+ });
46315
+ }
46316
+ }
46317
+ return issues;
46318
+ }
46319
+ function checkBrokenRefs(file2, fmEndLine) {
46320
+ const issues = [];
46321
+ const skillDir = path13.dirname(file2.filePath);
46322
+ const lines = file2.content.split("\n");
46323
+ let inCode = false;
46324
+ let codeLang = "";
46325
+ for (let i2 = 0; i2 < lines.length; i2++) {
46326
+ if (i2 + 1 <= fmEndLine) continue;
46327
+ const raw = lines[i2];
46328
+ if (raw.trimStart().startsWith("```")) {
46329
+ inCode = !inCode;
46330
+ codeLang = inCode ? raw.trimStart().slice(3).trim().toLowerCase() : "";
46331
+ continue;
46332
+ }
46333
+ if (inCode && isExampleLang(codeLang)) continue;
46334
+ BODY_PATH.lastIndex = 0;
46335
+ let m;
46336
+ while ((m = BODY_PATH.exec(raw)) !== null) {
46337
+ let value = m[1];
46338
+ if (PATH_SKIP.test(value)) continue;
46339
+ if (!value.startsWith("./") && !value.startsWith("../")) continue;
46340
+ while (/[.,;:]$/.test(value)) value = value.slice(0, -1);
46341
+ const resolved = path13.resolve(skillDir, value);
46342
+ if (fileExists(resolved) || isDirectory(resolved)) continue;
46343
+ issues.push({
46344
+ severity: "warning",
46345
+ check: "skill-broken-ref",
46346
+ ruleId: "skill/broken-ref",
46347
+ line: i2 + 1,
46348
+ message: `${file2.displayPath}: references "${value}" which does not exist relative to the skill directory`,
46349
+ suggestion: "Fix the path, or use a path that exists relative to the skill/agent file."
46350
+ });
46351
+ }
46352
+ }
46353
+ return issues;
46354
+ }
46355
+ function isExampleLang(lang) {
46356
+ return [
46357
+ "js",
46358
+ "javascript",
46359
+ "ts",
46360
+ "typescript",
46361
+ "tsx",
46362
+ "jsx",
46363
+ "py",
46364
+ "python",
46365
+ "go",
46366
+ "rust",
46367
+ "java",
46368
+ "c",
46369
+ "cpp",
46370
+ "ruby",
46371
+ "php",
46372
+ "json",
46373
+ "yaml",
46374
+ "yml",
46375
+ "toml",
46376
+ // Shell fences hold command/example snippets, not in-repo refs. A `./foo.sh`
46377
+ // inside a bash block is an invocation example (often a cross-repo script or
46378
+ // a placeholder), not a path that should resolve relative to the skill dir.
46379
+ // Matches the rule's "prefer a false negative over a false positive" posture.
46380
+ "bash",
46381
+ "sh",
46382
+ "shell",
46383
+ "shellscript",
46384
+ "zsh",
46385
+ "console"
46386
+ ].includes(lang);
46387
+ }
46388
+ function extractTriggers(fm) {
46389
+ const phrases = /* @__PURE__ */ new Set();
46390
+ const desc = fm.fields["description"] ?? "";
46391
+ for (const q3 of desc.matchAll(/["']([^"']{3,})["']/g)) {
46392
+ phrases.add(normalizeTrigger(q3[1]));
46393
+ }
46394
+ for (const key of ["trigger", "triggers"]) {
46395
+ const val = fm.fields[key];
46396
+ if (val) for (const part of val.split(",")) phrases.add(normalizeTrigger(part));
46397
+ }
46398
+ phrases.delete("");
46399
+ return [...phrases];
46400
+ }
46401
+ function normalizeTrigger(s) {
46402
+ return s.toLowerCase().trim().replace(/\s+/g, " ");
46403
+ }
46404
+ function checkTriggerCollisions(parsed) {
46405
+ const byTrigger = /* @__PURE__ */ new Map();
46406
+ for (const { file: file2, fm } of parsed) {
46407
+ for (const t2 of extractTriggers(fm)) {
46408
+ const list = byTrigger.get(t2) ?? [];
46409
+ list.push(file2);
46410
+ byTrigger.set(t2, list);
46411
+ }
46412
+ }
46413
+ const issues = [];
46414
+ for (const [trigger, owners] of byTrigger) {
46415
+ const distinct = [...new Set(owners.map((o2) => o2.displayPath))];
46416
+ if (distinct.length > 1) {
46417
+ issues.push({
46418
+ severity: "warning",
46419
+ check: "skill-trigger-collision",
46420
+ ruleId: "skill/trigger-collision",
46421
+ line: 1,
46422
+ message: `Trigger phrase "${trigger}" is declared by ${distinct.length} skills/agents \u2014 only one will win`,
46423
+ detail: distinct.map((d) => ` - ${d}`).join("\n"),
46424
+ suggestion: "Make trigger phrases unique so invocation is deterministic."
46425
+ });
46426
+ }
46427
+ }
46428
+ return issues;
46429
+ }
46430
+ function checkOrphaned(ctx) {
46431
+ return ctx.orphanedSkillDirs.map((o2) => ({
46432
+ severity: "warning",
46433
+ check: "skill-orphaned",
46434
+ ruleId: "skill/orphaned",
46435
+ line: 1,
46436
+ message: `${o2.displayPath}: skill directory has no SKILL.md \u2014 Claude Code has nothing to load`,
46437
+ suggestion: `Add a SKILL.md, or remove the empty ${o2.displayPath} directory.`
46438
+ }));
46439
+ }
46440
+ function checkDeadToolRestrictions(file2, fm) {
46441
+ if (file2.kind !== "agent") return [];
46442
+ const raw = fm.fields["tools"] ?? fm.fields["allowed-tools"] ?? fm.fields["allowedTools"];
46443
+ if (!raw) return [];
46444
+ const issues = [];
46445
+ for (const entry of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
46446
+ if (entry.startsWith("mcp__") || entry.includes("*")) continue;
46447
+ const base = entry.replace(/\(.*\)$/, "").trim();
46448
+ if (KNOWN_TOOLS.has(base)) continue;
46449
+ issues.push({
46450
+ severity: "warning",
46451
+ check: "skill-dead-tool-restriction",
46452
+ ruleId: "skill/dead-tool-restriction",
46453
+ line: 1,
46454
+ message: `${file2.displayPath}: tool restriction lists "${entry}" which is not a known Claude Code tool`,
46455
+ suggestion: `Known built-in tools: ${[...KNOWN_TOOLS].join(", ")}. MCP tools use the mcp__server__tool form.`
46456
+ });
46457
+ }
46458
+ return issues;
46459
+ }
46460
+ function checkSkills(ctx, sel) {
46461
+ const issues = [];
46462
+ const parsed = ctx.files.map((file2) => ({ file: file2, fm: parseFrontmatter3(file2.content) }));
46463
+ for (const { file: file2, fm } of parsed) {
46464
+ if (sel.frontmatter) issues.push(...checkMissingFrontmatter(file2, fm));
46465
+ if (sel.brokenRef) issues.push(...checkBrokenRefs(file2, fm.found ? fm.endLine : 0));
46466
+ if (sel.deadToolRestriction) issues.push(...checkDeadToolRestrictions(file2, fm));
46467
+ }
46468
+ if (sel.triggerCollision) issues.push(...checkTriggerCollisions(parsed));
46469
+ if (sel.orphaned) issues.push(...checkOrphaned(ctx));
46470
+ return issues;
46471
+ }
46472
+ var REQUIRED_FIELDS, BODY_PATH, PATH_SKIP, KNOWN_TOOLS;
46473
+ var init_skills = __esm({
46474
+ "src/core/checks/skills.ts"() {
46475
+ "use strict";
46476
+ init_define_WEB_FIRST_SEGMENTS();
46477
+ init_fs();
46478
+ REQUIRED_FIELDS = {
46479
+ skill: ["name", "description"],
46480
+ agent: ["name", "description"]
46481
+ };
46482
+ BODY_PATH = /(?:^|[\s`"'(])((?:\.{1,2}\/)[\w@./-]+|(?:[\w@-]+\/)+[\w.-]+)(?=[\s`"'),;:]|$)/gm;
46483
+ PATH_SKIP = /^(https?:\/\/|mailto:|n\/a|e\.g\.|i\.e\.)/i;
46484
+ KNOWN_TOOLS = /* @__PURE__ */ new Set([
46485
+ "Agent",
46486
+ "Bash",
46487
+ "BashOutput",
46488
+ "Edit",
46489
+ "ExitPlanMode",
46490
+ "Glob",
46491
+ "Grep",
46492
+ "KillShell",
46493
+ "MultiEdit",
46494
+ "NotebookEdit",
46495
+ "Read",
46496
+ "Skill",
46497
+ "SlashCommand",
46498
+ "Task",
46499
+ "TodoWrite",
46500
+ "WebFetch",
46501
+ "WebSearch",
46502
+ "Write"
46503
+ ]);
46504
+ }
46505
+ });
46506
+
45981
46507
  // src/core/ignore-rules.ts
45982
46508
  function compileRules(rules) {
45983
46509
  return rules.map((r2) => ({
@@ -46044,25 +46570,26 @@ var init_ignore_rules = __esm({
46044
46570
  });
46045
46571
 
46046
46572
  // src/core/types.ts
46047
- var SESSION_AUDIT_LABEL, SESSION_AUDIT_PATH_MARKER;
46573
+ var SESSION_AUDIT_LABEL, SESSION_AUDIT_PATH_MARKER, SKILL_AUDIT_LABEL;
46048
46574
  var init_types3 = __esm({
46049
46575
  "src/core/types.ts"() {
46050
46576
  "use strict";
46051
46577
  init_define_WEB_FIRST_SEGMENTS();
46052
46578
  SESSION_AUDIT_LABEL = "~/.claude/ (session audit)";
46053
46579
  SESSION_AUDIT_PATH_MARKER = "(session audit)";
46580
+ SKILL_AUDIT_LABEL = "~/.claude/ (skill audit)";
46054
46581
  }
46055
46582
  });
46056
46583
 
46057
46584
  // src/version.ts
46058
- import { readFileSync as readFileSync5 } from "node:fs";
46059
- import { resolve as resolve9, dirname as dirname4 } from "node:path";
46585
+ import { readFileSync as readFileSync7 } from "node:fs";
46586
+ import { resolve as resolve11, dirname as dirname5 } from "node:path";
46060
46587
  import { fileURLToPath as fileURLToPath2 } from "node:url";
46061
46588
  function loadVersion() {
46062
- if (true) return "0.12.2";
46063
- const __dir = dirname4(fileURLToPath2(import.meta.url));
46064
- const pkgPath = resolve9(__dir, "../package.json");
46065
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
46589
+ if (true) return "0.13.0";
46590
+ const __dir = dirname5(fileURLToPath2(import.meta.url));
46591
+ const pkgPath = resolve11(__dir, "../package.json");
46592
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
46066
46593
  return pkg.version;
46067
46594
  }
46068
46595
  var VERSION;
@@ -46084,6 +46611,9 @@ function hasMcphChecks(checks) {
46084
46611
  function hasSessionChecks(checks) {
46085
46612
  return checks.some((c3) => c3.startsWith("session-"));
46086
46613
  }
46614
+ function hasSkillChecks(checks) {
46615
+ return checks.some((c3) => c3.startsWith("skill-"));
46616
+ }
46087
46617
  function deriveChecksToRun(activeChecks, prefix, enabled, allChecks) {
46088
46618
  const filtered = activeChecks.filter((c3) => c3.startsWith(prefix));
46089
46619
  if (filtered.length > 0) return filtered;
@@ -46092,10 +46622,11 @@ function deriveChecksToRun(activeChecks, prefix, enabled, allChecks) {
46092
46622
  async function runAudit(projectRoot, activeChecks, options = {}) {
46093
46623
  const fileResults = [];
46094
46624
  const thresholds = resolveTokenThresholds(options.tokenThresholds);
46095
- const shouldRunContextChecks = !options.mcpOnly && !options.mcphOnly && !options.sessionOnly;
46625
+ const shouldRunContextChecks = !options.mcpOnly && !options.mcphOnly && !options.sessionOnly && !options.skillsOnly;
46096
46626
  const shouldRunMcpChecks = options.mcp || options.mcpGlobal || options.mcpOnly || hasMcpChecks(activeChecks);
46097
46627
  const shouldRunMcphChecks = options.mcph || options.mcphGlobal || options.mcphOnly || hasMcphChecks(activeChecks);
46098
46628
  const shouldRunSessionChecks = options.session || options.sessionOnly || hasSessionChecks(activeChecks);
46629
+ const shouldRunSkillChecks = options.skills || options.skillsOnly || hasSkillChecks(activeChecks);
46099
46630
  if (shouldRunContextChecks) {
46100
46631
  const discovered = await scanForContextFiles(projectRoot, {
46101
46632
  depth: options.depth,
@@ -46152,6 +46683,13 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
46152
46683
  if (activeChecks.includes("ci-secrets")) {
46153
46684
  crossFileIssues.push(...await checkCiSecrets(parsed, projectRoot));
46154
46685
  }
46686
+ if (activeChecks.includes("hook-coverage")) {
46687
+ crossFileIssues.push(
46688
+ ...await checkHookCoverage(projectRoot, void 0, {
46689
+ userGlobal: options.hooksGlobal
46690
+ })
46691
+ );
46692
+ }
46155
46693
  if (crossFileIssues.length > 0) {
46156
46694
  fileResults.push({
46157
46695
  path: "(project)",
@@ -46322,6 +46860,31 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
46322
46860
  });
46323
46861
  }
46324
46862
  }
46863
+ if (shouldRunSkillChecks) {
46864
+ const skillChecksToRun = deriveChecksToRun(
46865
+ activeChecks,
46866
+ "skill-",
46867
+ Boolean(options.skills || options.skillsOnly),
46868
+ ALL_SKILL_CHECKS
46869
+ );
46870
+ if (skillChecksToRun.length > 0) {
46871
+ const skillCtx = scanSkillFiles();
46872
+ const skillIssues = checkSkills(skillCtx, {
46873
+ frontmatter: skillChecksToRun.includes("skill-frontmatter"),
46874
+ brokenRef: skillChecksToRun.includes("skill-broken-ref"),
46875
+ triggerCollision: skillChecksToRun.includes("skill-trigger-collision"),
46876
+ orphaned: skillChecksToRun.includes("skill-orphaned"),
46877
+ deadToolRestriction: skillChecksToRun.includes("skill-dead-tool-restriction")
46878
+ });
46879
+ fileResults.push({
46880
+ path: SKILL_AUDIT_LABEL,
46881
+ isSymlink: false,
46882
+ tokens: 0,
46883
+ lines: 0,
46884
+ issues: skillIssues
46885
+ });
46886
+ }
46887
+ }
46325
46888
  let ignoreReport;
46326
46889
  if (options.ignoreRules && options.ignoreRules.length > 0) {
46327
46890
  const flat = [];
@@ -46383,7 +46946,7 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
46383
46946
  }
46384
46947
  return result;
46385
46948
  }
46386
- var ALL_CHECKS, ALL_MCP_CHECKS, ALL_MCPH_CHECKS, ALL_SESSION_CHECKS;
46949
+ var ALL_CHECKS, ALL_MCP_CHECKS, ALL_MCPH_CHECKS, ALL_SESSION_CHECKS, ALL_SKILL_CHECKS;
46387
46950
  var init_audit = __esm({
46388
46951
  "src/core/audit.ts"() {
46389
46952
  "use strict";
@@ -46425,6 +46988,9 @@ var init_audit = __esm({
46425
46988
  init_ci_coverage();
46426
46989
  init_ci_secrets();
46427
46990
  init_content_secrets();
46991
+ init_hook_coverage();
46992
+ init_skill_scanner();
46993
+ init_skills();
46428
46994
  init_ignore_rules();
46429
46995
  init_types3();
46430
46996
  init_version2();
@@ -46439,7 +47005,8 @@ var init_audit = __esm({
46439
47005
  "frontmatter",
46440
47006
  "ci-coverage",
46441
47007
  "ci-secrets",
46442
- "content-secrets"
47008
+ "content-secrets",
47009
+ "hook-coverage"
46443
47010
  ];
46444
47011
  ALL_MCP_CHECKS = [
46445
47012
  "mcp-schema",
@@ -46467,6 +47034,13 @@ var init_audit = __esm({
46467
47034
  "session-loop-detection",
46468
47035
  "session-memory-index-overflow"
46469
47036
  ];
47037
+ ALL_SKILL_CHECKS = [
47038
+ "skill-frontmatter",
47039
+ "skill-broken-ref",
47040
+ "skill-trigger-collision",
47041
+ "skill-orphaned",
47042
+ "skill-dead-tool-restriction"
47043
+ ];
46470
47044
  }
46471
47045
  });
46472
47046
 
@@ -46664,7 +47238,7 @@ var init_ansi_styles = __esm({
46664
47238
 
46665
47239
  // node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
46666
47240
  import process4 from "node:process";
46667
- import os2 from "node:os";
47241
+ import os4 from "node:os";
46668
47242
  import tty from "node:tty";
46669
47243
  function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process4.argv) {
46670
47244
  const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
@@ -46722,7 +47296,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
46722
47296
  return min;
46723
47297
  }
46724
47298
  if (process4.platform === "win32") {
46725
- const osRelease = os2.release().split(".");
47299
+ const osRelease = os4.release().split(".");
46726
47300
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
46727
47301
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
46728
47302
  }
@@ -47013,7 +47587,7 @@ var init_source = __esm({
47013
47587
  });
47014
47588
 
47015
47589
  // src/core/fixer.ts
47016
- import * as fs7 from "node:fs";
47590
+ import * as fs9 from "node:fs";
47017
47591
  function applyFixes(result, options = {}) {
47018
47592
  const log = options.quiet ? () => {
47019
47593
  } : console.log.bind(console);
@@ -47055,7 +47629,7 @@ function applyFixes(result, options = {}) {
47055
47629
  let totalFixes = 0;
47056
47630
  const filesModified = [];
47057
47631
  for (const [filePath, fixes] of fixesByFile) {
47058
- const content = fs7.readFileSync(filePath, "utf-8");
47632
+ const content = fs9.readFileSync(filePath, "utf-8");
47059
47633
  const lines = content.split("\n");
47060
47634
  let modified = false;
47061
47635
  const fixesByLine = /* @__PURE__ */ new Map();
@@ -47097,7 +47671,7 @@ function applyFixes(result, options = {}) {
47097
47671
  }
47098
47672
  }
47099
47673
  if (!dryRun) {
47100
- fs7.writeFileSync(filePath, newContent, "utf-8");
47674
+ fs9.writeFileSync(filePath, newContent, "utf-8");
47101
47675
  }
47102
47676
  filesModified.push(filePath);
47103
47677
  totalFixes += perFileFixCount;
@@ -47115,8 +47689,8 @@ var init_fixer = __esm({
47115
47689
  });
47116
47690
 
47117
47691
  // src/core/config.ts
47118
- import * as fs8 from "node:fs";
47119
- import * as path11 from "node:path";
47692
+ import * as fs10 from "node:fs";
47693
+ import * as path14 from "node:path";
47120
47694
  function formatJsonError(content, err) {
47121
47695
  const msg = err instanceof Error ? err.message : String(err);
47122
47696
  const posMatch = msg.match(/position (\d+)/);
@@ -47214,10 +47788,10 @@ function warnIgnoreRulePathPatternMisuse(config2, source) {
47214
47788
  }
47215
47789
  function loadConfig(projectRoot) {
47216
47790
  for (const filename of CONFIG_FILENAMES) {
47217
- const filePath = path11.join(projectRoot, filename);
47791
+ const filePath = path14.join(projectRoot, filename);
47218
47792
  let content;
47219
47793
  try {
47220
- content = stripBom(fs8.readFileSync(filePath, "utf-8"));
47794
+ content = stripBom(fs10.readFileSync(filePath, "utf-8"));
47221
47795
  } catch {
47222
47796
  continue;
47223
47797
  }
@@ -47228,7 +47802,7 @@ function loadConfig(projectRoot) {
47228
47802
  function loadConfigFromExplicitPath(configPath) {
47229
47803
  let content;
47230
47804
  try {
47231
- content = stripBom(fs8.readFileSync(configPath, "utf-8"));
47805
+ content = stripBom(fs10.readFileSync(configPath, "utf-8"));
47232
47806
  } catch (err) {
47233
47807
  const detail = err instanceof Error ? err.message : String(err);
47234
47808
  throw new Error(`could not load config from ${configPath}: ${detail}`, { cause: err });
@@ -47259,7 +47833,10 @@ var init_config2 = __esm({
47259
47833
  "mcphGlobal",
47260
47834
  "mcphStrictEnvToken",
47261
47835
  "session",
47262
- "sessionOnly"
47836
+ "sessionOnly",
47837
+ "skills",
47838
+ "skillsOnly",
47839
+ "hooksGlobal"
47263
47840
  ];
47264
47841
  CONFIG_FILENAMES = [".ctxlintrc", ".ctxlintrc.json"];
47265
47842
  }
@@ -47271,7 +47848,7 @@ __export(server_exports, {
47271
47848
  server: () => server,
47272
47849
  startServer: () => startServer
47273
47850
  });
47274
- import * as path12 from "node:path";
47851
+ import * as path15 from "node:path";
47275
47852
  function describeDisallowed(rawPath) {
47276
47853
  const m = rawPath.match(PATH_DISALLOWED);
47277
47854
  if (!m) return "unknown";
@@ -47284,7 +47861,7 @@ function validateProjectPath(rawPath) {
47284
47861
  `projectPath contains disallowed character ${describeDisallowed(rawPath)} (control chars and shell metacharacters are rejected)`
47285
47862
  );
47286
47863
  }
47287
- const resolved = path12.resolve(rawPath);
47864
+ const resolved = path15.resolve(rawPath);
47288
47865
  if (!isDirectory(resolved)) {
47289
47866
  throw new Error("projectPath is not an existing directory");
47290
47867
  }
@@ -47305,10 +47882,10 @@ function validateFilePathInput(rawPath) {
47305
47882
  }
47306
47883
  }
47307
47884
  function resolveWithinRoot(filePath, root) {
47308
- const resolvedRoot = path12.resolve(root);
47309
- const resolved = path12.resolve(resolvedRoot, filePath);
47310
- const rel = path12.relative(resolvedRoot, resolved);
47311
- if (rel.startsWith("..") || path12.isAbsolute(rel)) {
47885
+ const resolvedRoot = path15.resolve(root);
47886
+ const resolved = path15.resolve(resolvedRoot, filePath);
47887
+ const rel = path15.relative(resolvedRoot, resolved);
47888
+ if (rel.startsWith("..") || path15.isAbsolute(rel)) {
47312
47889
  throw new Error("path escapes the project root");
47313
47890
  }
47314
47891
  return resolved;
@@ -48823,8 +49400,8 @@ var require_command = __commonJS({
48823
49400
  init_define_WEB_FIRST_SEGMENTS();
48824
49401
  var EventEmitter2 = __require("node:events").EventEmitter;
48825
49402
  var childProcess = __require("node:child_process");
48826
- var path14 = __require("node:path");
48827
- var fs10 = __require("node:fs");
49403
+ var path17 = __require("node:path");
49404
+ var fs12 = __require("node:fs");
48828
49405
  var process11 = __require("node:process");
48829
49406
  var { Argument: Argument2, humanReadableArgName } = require_argument();
48830
49407
  var { CommanderError: CommanderError2 } = require_error();
@@ -49818,7 +50395,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
49818
50395
  * @param {string} subcommandName
49819
50396
  */
49820
50397
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
49821
- if (fs10.existsSync(executableFile)) return;
50398
+ if (fs12.existsSync(executableFile)) return;
49822
50399
  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";
49823
50400
  const executableMissing = `'${executableFile}' does not exist
49824
50401
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -49836,11 +50413,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
49836
50413
  let launchWithNode = false;
49837
50414
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
49838
50415
  function findFile(baseDir, baseName) {
49839
- const localBin = path14.resolve(baseDir, baseName);
49840
- if (fs10.existsSync(localBin)) return localBin;
49841
- if (sourceExt.includes(path14.extname(baseName))) return void 0;
50416
+ const localBin = path17.resolve(baseDir, baseName);
50417
+ if (fs12.existsSync(localBin)) return localBin;
50418
+ if (sourceExt.includes(path17.extname(baseName))) return void 0;
49842
50419
  const foundExt = sourceExt.find(
49843
- (ext) => fs10.existsSync(`${localBin}${ext}`)
50420
+ (ext) => fs12.existsSync(`${localBin}${ext}`)
49844
50421
  );
49845
50422
  if (foundExt) return `${localBin}${foundExt}`;
49846
50423
  return void 0;
@@ -49852,21 +50429,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
49852
50429
  if (this._scriptPath) {
49853
50430
  let resolvedScriptPath;
49854
50431
  try {
49855
- resolvedScriptPath = fs10.realpathSync(this._scriptPath);
50432
+ resolvedScriptPath = fs12.realpathSync(this._scriptPath);
49856
50433
  } catch {
49857
50434
  resolvedScriptPath = this._scriptPath;
49858
50435
  }
49859
- executableDir = path14.resolve(
49860
- path14.dirname(resolvedScriptPath),
50436
+ executableDir = path17.resolve(
50437
+ path17.dirname(resolvedScriptPath),
49861
50438
  executableDir
49862
50439
  );
49863
50440
  }
49864
50441
  if (executableDir) {
49865
50442
  let localFile = findFile(executableDir, executableFile);
49866
50443
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
49867
- const legacyName = path14.basename(
50444
+ const legacyName = path17.basename(
49868
50445
  this._scriptPath,
49869
- path14.extname(this._scriptPath)
50446
+ path17.extname(this._scriptPath)
49870
50447
  );
49871
50448
  if (legacyName !== this._name) {
49872
50449
  localFile = findFile(
@@ -49877,7 +50454,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
49877
50454
  }
49878
50455
  executableFile = localFile || executableFile;
49879
50456
  }
49880
- launchWithNode = sourceExt.includes(path14.extname(executableFile));
50457
+ launchWithNode = sourceExt.includes(path17.extname(executableFile));
49881
50458
  let proc;
49882
50459
  if (process11.platform !== "win32") {
49883
50460
  if (launchWithNode) {
@@ -50792,7 +51369,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
50792
51369
  * @return {Command}
50793
51370
  */
50794
51371
  nameFromFilename(filename) {
50795
- this._name = path14.basename(filename, path14.extname(filename));
51372
+ this._name = path17.basename(filename, path17.extname(filename));
50796
51373
  return this;
50797
51374
  }
50798
51375
  /**
@@ -50806,9 +51383,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
50806
51383
  * @param {string} [path]
50807
51384
  * @return {(string|null|Command)}
50808
51385
  */
50809
- executableDir(path15) {
50810
- if (path15 === void 0) return this._executableDir;
50811
- this._executableDir = path15;
51386
+ executableDir(path18) {
51387
+ if (path18 === void 0) return this._executableDir;
51388
+ this._executableDir = path18;
50812
51389
  return this;
50813
51390
  }
50814
51391
  /**
@@ -54161,8 +54738,10 @@ function classifyFile(f) {
54161
54738
  if (f.path === "(project)") return "context";
54162
54739
  if (f.path === "(mcp)") return "mcp";
54163
54740
  if (f.path.includes(SESSION_AUDIT_PATH_MARKER)) return "session";
54741
+ if (f.path.includes(SKILL_AUDIT_PATH_MARKER)) return "skill";
54164
54742
  for (const issue2 of f.issues) {
54165
54743
  if (issue2.check.startsWith("session-")) return "session";
54744
+ if (issue2.check.startsWith("skill-")) return "skill";
54166
54745
  if (issue2.check.startsWith("mcph-")) return "mcph";
54167
54746
  if (issue2.check.startsWith("mcp-")) return "mcp";
54168
54747
  }
@@ -54174,7 +54753,7 @@ function classifyFile(f) {
54174
54753
  return "context";
54175
54754
  }
54176
54755
  function isSyntheticBucket(p2) {
54177
- return p2.startsWith("(") || p2.includes(SESSION_AUDIT_PATH_MARKER);
54756
+ return p2.startsWith("(") || p2.includes(SESSION_AUDIT_PATH_MARKER) || p2.includes(SKILL_AUDIT_PATH_MARKER);
54178
54757
  }
54179
54758
  function formatText(result, verbose = false) {
54180
54759
  const lines = [];
@@ -54187,7 +54766,8 @@ function formatText(result, verbose = false) {
54187
54766
  context: [],
54188
54767
  mcp: [],
54189
54768
  mcph: [],
54190
- session: []
54769
+ session: [],
54770
+ skill: []
54191
54771
  };
54192
54772
  for (const f of result.files) {
54193
54773
  groups[classifyFile(f)].push(f);
@@ -54464,6 +55044,11 @@ function buildRuleDescriptors() {
54464
55044
  shortDescription: { text: "Inline-pasted secret detected in a context file" },
54465
55045
  helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
54466
55046
  },
55047
+ {
55048
+ id: "ctxlint/hook-coverage",
55049
+ shortDescription: { text: "Hook/permission entry references a script that no longer exists" },
55050
+ helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
55051
+ },
54467
55052
  {
54468
55053
  id: "ctxlint/mcp-schema",
54469
55054
  shortDescription: { text: "MCP config structural validation error" },
@@ -54565,6 +55150,31 @@ function buildRuleDescriptors() {
54565
55150
  text: "MEMORY.md exceeds Claude Code's 200-line / 25KB session-load cap"
54566
55151
  },
54567
55152
  helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
55153
+ },
55154
+ {
55155
+ id: "ctxlint/skill-frontmatter",
55156
+ shortDescription: { text: "Skill/agent definition missing required frontmatter" },
55157
+ helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
55158
+ },
55159
+ {
55160
+ id: "ctxlint/skill-broken-ref",
55161
+ shortDescription: { text: "Skill/agent body references a path that does not exist" },
55162
+ helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
55163
+ },
55164
+ {
55165
+ id: "ctxlint/skill-trigger-collision",
55166
+ shortDescription: { text: "Two skills/agents declare the same trigger phrase" },
55167
+ helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
55168
+ },
55169
+ {
55170
+ id: "ctxlint/skill-orphaned",
55171
+ shortDescription: { text: "Skill directory has no SKILL.md" },
55172
+ helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
55173
+ },
55174
+ {
55175
+ id: "ctxlint/skill-dead-tool-restriction",
55176
+ shortDescription: { text: "Agent tool restriction names an unknown Claude Code tool" },
55177
+ helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
54568
55178
  }
54569
55179
  ];
54570
55180
  }
@@ -54582,25 +55192,28 @@ function formatIssue(issue2) {
54582
55192
  }
54583
55193
  return line;
54584
55194
  }
54585
- var GROUP_ORDER, GROUP_LABELS, GROUP_SUMMARY_NOUNS;
55195
+ var SKILL_AUDIT_PATH_MARKER, GROUP_ORDER, GROUP_LABELS, GROUP_SUMMARY_NOUNS;
54586
55196
  var init_reporter = __esm({
54587
55197
  "src/core/reporter.ts"() {
54588
55198
  "use strict";
54589
55199
  init_define_WEB_FIRST_SEGMENTS();
54590
55200
  init_source();
54591
55201
  init_types3();
54592
- GROUP_ORDER = ["context", "mcp", "mcph", "session"];
55202
+ SKILL_AUDIT_PATH_MARKER = "(skill audit)";
55203
+ GROUP_ORDER = ["context", "mcp", "mcph", "session", "skill"];
54593
55204
  GROUP_LABELS = {
54594
55205
  context: "Context Files",
54595
55206
  mcp: "MCP Configs",
54596
55207
  mcph: "mcph Configs",
54597
- session: "Session Audit"
55208
+ session: "Session Audit",
55209
+ skill: "Skill Audit"
54598
55210
  };
54599
55211
  GROUP_SUMMARY_NOUNS = {
54600
55212
  context: "context file",
54601
55213
  mcp: "MCP config",
54602
55214
  mcph: "mcph config",
54603
- session: "session audit"
55215
+ session: "session audit",
55216
+ skill: "skill audit"
54604
55217
  };
54605
55218
  }
54606
55219
  });
@@ -54610,8 +55223,8 @@ var cli_exports = {};
54610
55223
  __export(cli_exports, {
54611
55224
  runCli: () => runCli
54612
55225
  });
54613
- import * as fs9 from "node:fs";
54614
- import * as path13 from "node:path";
55226
+ import * as fs11 from "node:fs";
55227
+ import * as path16 from "node:path";
54615
55228
  function validateCheckNames(names, source) {
54616
55229
  const invalid = names.filter((n7) => n7 && !VALID_CHECKS.has(n7));
54617
55230
  if (invalid.length > 0) {
@@ -54633,8 +55246,12 @@ async function runCli() {
54633
55246
  "--mcph-strict-env-token",
54634
55247
  "Upgrade mcph-config/prefer-env-token from warning to error (strict env-var-only posture)",
54635
55248
  false
54636
- ).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) => {
54637
- const resolvedPath = path13.resolve(projectPath);
55249
+ ).option("--session", "Run session audit checks (cross-project consistency)", false).option("--session-only", "Run only session checks, skip context and MCP checks", false).option("--skills", "Run agent-skill checks (~/.claude/skills + ~/.claude/agents)", false).option("--skills-only", "Run only agent-skill checks, skip everything else", false).option(
55250
+ "--hooks-global",
55251
+ "Also scan the user-global ~/.claude/settings.json in the dead-hook check (off by default; the standard run only reads project .claude/settings.json[.local])",
55252
+ false
55253
+ ).option("--watch", "Re-lint on context file changes", false).action(async (projectPath, opts) => {
55254
+ const resolvedPath = path16.resolve(projectPath);
54638
55255
  const { config: config2, options, activeChecks } = resolveSession(resolvedPath, opts);
54639
55256
  const spinner = options.format === "text" && !options.quiet ? ora("Scanning for context files...").start() : void 0;
54640
55257
  try {
@@ -54651,6 +55268,9 @@ async function runCli() {
54651
55268
  mcphStrictEnvToken: options.mcphStrictEnvToken,
54652
55269
  session: options.session,
54653
55270
  sessionOnly: options.sessionOnly,
55271
+ skills: options.skills,
55272
+ skillsOnly: options.skillsOnly,
55273
+ hooksGlobal: options.hooksGlobal,
54654
55274
  tokenThresholds: config2?.tokenThresholds,
54655
55275
  ignoreRules: config2?.ignoreRules
54656
55276
  });
@@ -54750,43 +55370,43 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
54750
55370
  const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
54751
55371
  console.log(chalk2.dim("\nWatching for changes... (Ctrl+C to stop)\n"));
54752
55372
  const watchPaths = [
54753
- path13.join(resolvedPath, "CLAUDE.md"),
54754
- path13.join(resolvedPath, "CLAUDE.local.md"),
54755
- path13.join(resolvedPath, "AGENTS.md"),
54756
- path13.join(resolvedPath, "AGENT.md"),
54757
- path13.join(resolvedPath, ".cursorrules"),
54758
- path13.join(resolvedPath, ".windsurfrules"),
54759
- path13.join(resolvedPath, ".clinerules"),
54760
- path13.join(resolvedPath, ".aiderules"),
54761
- path13.join(resolvedPath, ".continuerules"),
54762
- path13.join(resolvedPath, ".rules"),
54763
- path13.join(resolvedPath, ".goosehints"),
54764
- path13.join(resolvedPath, "GEMINI.md"),
54765
- path13.join(resolvedPath, "replit.md"),
54766
- path13.join(resolvedPath, "package.json"),
54767
- path13.join(resolvedPath, ".mcp.json"),
55373
+ path16.join(resolvedPath, "CLAUDE.md"),
55374
+ path16.join(resolvedPath, "CLAUDE.local.md"),
55375
+ path16.join(resolvedPath, "AGENTS.md"),
55376
+ path16.join(resolvedPath, "AGENT.md"),
55377
+ path16.join(resolvedPath, ".cursorrules"),
55378
+ path16.join(resolvedPath, ".windsurfrules"),
55379
+ path16.join(resolvedPath, ".clinerules"),
55380
+ path16.join(resolvedPath, ".aiderules"),
55381
+ path16.join(resolvedPath, ".continuerules"),
55382
+ path16.join(resolvedPath, ".rules"),
55383
+ path16.join(resolvedPath, ".goosehints"),
55384
+ path16.join(resolvedPath, "GEMINI.md"),
55385
+ path16.join(resolvedPath, "replit.md"),
55386
+ path16.join(resolvedPath, "package.json"),
55387
+ path16.join(resolvedPath, ".mcp.json"),
54768
55388
  // mcph configs — without these, edits to the .mcph.json the user is
54769
55389
  // actively iterating on don't trigger a re-lint.
54770
- path13.join(resolvedPath, ".mcph.json"),
54771
- path13.join(resolvedPath, ".mcph.local.json"),
55390
+ path16.join(resolvedPath, ".mcph.json"),
55391
+ path16.join(resolvedPath, ".mcph.local.json"),
54772
55392
  // ctxlint config — re-resolve on edit so threshold changes,
54773
55393
  // ignore-list edits, and check-list overrides take effect without
54774
55394
  // restarting the watcher.
54775
- path13.join(resolvedPath, ".ctxlintrc"),
54776
- path13.join(resolvedPath, ".ctxlintrc.json")
55395
+ path16.join(resolvedPath, ".ctxlintrc"),
55396
+ path16.join(resolvedPath, ".ctxlintrc.json")
54777
55397
  ];
54778
55398
  const watchDirs = [
54779
- path13.join(resolvedPath, ".claude"),
54780
- path13.join(resolvedPath, ".cursor"),
54781
- path13.join(resolvedPath, ".github"),
54782
- path13.join(resolvedPath, ".windsurf"),
54783
- path13.join(resolvedPath, ".aide"),
54784
- path13.join(resolvedPath, ".amazonq"),
54785
- path13.join(resolvedPath, ".goose"),
54786
- path13.join(resolvedPath, ".junie"),
54787
- path13.join(resolvedPath, ".aiassistant"),
54788
- path13.join(resolvedPath, ".continue"),
54789
- path13.join(resolvedPath, ".vscode")
55399
+ path16.join(resolvedPath, ".claude"),
55400
+ path16.join(resolvedPath, ".cursor"),
55401
+ path16.join(resolvedPath, ".github"),
55402
+ path16.join(resolvedPath, ".windsurf"),
55403
+ path16.join(resolvedPath, ".aide"),
55404
+ path16.join(resolvedPath, ".amazonq"),
55405
+ path16.join(resolvedPath, ".goose"),
55406
+ path16.join(resolvedPath, ".junie"),
55407
+ path16.join(resolvedPath, ".aiassistant"),
55408
+ path16.join(resolvedPath, ".continue"),
55409
+ path16.join(resolvedPath, ".vscode")
54790
55410
  ];
54791
55411
  const watchers = [];
54792
55412
  let debounceTimer = null;
@@ -54818,6 +55438,9 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
54818
55438
  mcphStrictEnvToken: liveOptions.mcphStrictEnvToken,
54819
55439
  session: liveOptions.session,
54820
55440
  sessionOnly: liveOptions.sessionOnly,
55441
+ skills: liveOptions.skills,
55442
+ skillsOnly: liveOptions.skillsOnly,
55443
+ hooksGlobal: liveOptions.hooksGlobal,
54821
55444
  tokenThresholds: liveConfig?.tokenThresholds,
54822
55445
  ignoreRules: liveConfig?.ignoreRules
54823
55446
  });
@@ -54845,13 +55468,13 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
54845
55468
  };
54846
55469
  for (const filePath of watchPaths) {
54847
55470
  try {
54848
- watchers.push(fs9.watch(filePath, rerun));
55471
+ watchers.push(fs11.watch(filePath, rerun));
54849
55472
  } catch {
54850
55473
  }
54851
55474
  }
54852
55475
  for (const dir of watchDirs) {
54853
55476
  try {
54854
- watchers.push(fs9.watch(dir, { recursive: true }, rerun));
55477
+ watchers.push(fs11.watch(dir, { recursive: true }, rerun));
54855
55478
  } catch {
54856
55479
  }
54857
55480
  }
@@ -54872,17 +55495,17 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
54872
55495
  }
54873
55496
  });
54874
55497
  program2.command("init").description("Set up a git pre-commit hook that runs ctxlint --strict").argument("[path]", "Project directory", ".").action(async (projectPath) => {
54875
- const resolvedRoot = path13.resolve(projectPath);
54876
- const gitDir = path13.join(resolvedRoot, ".git");
54877
- const hooksDir = path13.join(gitDir, "hooks");
54878
- if (!fs9.existsSync(gitDir)) {
55498
+ const resolvedRoot = path16.resolve(projectPath);
55499
+ const gitDir = path16.join(resolvedRoot, ".git");
55500
+ const hooksDir = path16.join(gitDir, "hooks");
55501
+ if (!fs11.existsSync(gitDir)) {
54879
55502
  console.error('Error: not a git repository. Run "git init" first.');
54880
55503
  process.exit(1);
54881
55504
  }
54882
- if (!fs9.existsSync(hooksDir)) {
54883
- fs9.mkdirSync(hooksDir, { recursive: true });
55505
+ if (!fs11.existsSync(hooksDir)) {
55506
+ fs11.mkdirSync(hooksDir, { recursive: true });
54884
55507
  }
54885
- const hookPath = path13.join(hooksDir, "pre-commit");
55508
+ const hookPath = path16.join(hooksDir, "pre-commit");
54886
55509
  const fullHookContent = `#!/bin/sh
54887
55510
  # ctxlint pre-commit hook
54888
55511
  npx @yawlabs/ctxlint@${VERSION} --strict
@@ -54891,16 +55514,16 @@ npx @yawlabs/ctxlint@${VERSION} --strict
54891
55514
  # ctxlint pre-commit hook
54892
55515
  npx @yawlabs/ctxlint@${VERSION} --strict
54893
55516
  `;
54894
- if (fs9.existsSync(hookPath)) {
54895
- const existing = fs9.readFileSync(hookPath, "utf-8");
55517
+ if (fs11.existsSync(hookPath)) {
55518
+ const existing = fs11.readFileSync(hookPath, "utf-8");
54896
55519
  if (existing.includes("ctxlint")) {
54897
55520
  console.log("Pre-commit hook already includes ctxlint.");
54898
55521
  return;
54899
55522
  }
54900
- fs9.appendFileSync(hookPath, appendHookContent);
55523
+ fs11.appendFileSync(hookPath, appendHookContent);
54901
55524
  console.log("Added ctxlint to existing pre-commit hook.");
54902
55525
  } else {
54903
- fs9.writeFileSync(hookPath, fullHookContent, { mode: 493 });
55526
+ fs11.writeFileSync(hookPath, fullHookContent, { mode: 493 });
54904
55527
  console.log("Created pre-commit hook at .git/hooks/pre-commit");
54905
55528
  }
54906
55529
  console.log("ctxlint will now run automatically before each commit.");
@@ -54918,7 +55541,7 @@ async function promptYesNo(question) {
54918
55541
  }
54919
55542
  }
54920
55543
  function resolveSession(resolvedPath, opts) {
54921
- const configPath = opts.config ? path13.resolve(opts.config) : void 0;
55544
+ const configPath = opts.config ? path16.resolve(opts.config) : void 0;
54922
55545
  const config2 = configPath ? loadConfigFromPath(configPath) : loadConfig(resolvedPath);
54923
55546
  const mcpGlobal = opts.mcpGlobal || config2?.mcpGlobal || false;
54924
55547
  const mcpOnly = opts.mcpOnly || config2?.mcpOnly || false;
@@ -54929,6 +55552,9 @@ function resolveSession(resolvedPath, opts) {
54929
55552
  const mcphStrictEnvToken = opts.mcphStrictEnvToken || config2?.mcphStrictEnvToken || false;
54930
55553
  const sessionOnly = opts.sessionOnly || config2?.sessionOnly || false;
54931
55554
  const sessionFlag = opts.session || sessionOnly || config2?.session || false;
55555
+ const skillsOnly = opts.skillsOnly || config2?.skillsOnly || false;
55556
+ const skillsFlag = opts.skills || skillsOnly || config2?.skills || false;
55557
+ const hooksGlobal = opts.hooksGlobal || config2?.hooksGlobal || false;
54932
55558
  let explicitChecks = opts.checks ? validateCheckNames(
54933
55559
  opts.checks.split(",").map((c3) => c3.trim()),
54934
55560
  "--checks"
@@ -54937,14 +55563,18 @@ function resolveSession(resolvedPath, opts) {
54937
55563
  const hasMcpInChecks = explicitChecks?.some((c3) => c3.startsWith("mcp-") && !c3.startsWith("mcph-")) || false;
54938
55564
  const hasMcphInChecks = explicitChecks?.some((c3) => c3.startsWith("mcph-")) || false;
54939
55565
  const hasSessionInChecks = explicitChecks?.some((c3) => c3.startsWith("session-")) || false;
55566
+ const hasSkillInChecks = explicitChecks?.some((c3) => c3.startsWith("skill-")) || false;
54940
55567
  const effectiveMcp = mcpFlag || hasMcpInChecks;
54941
55568
  const effectiveMcph = mcphFlag || hasMcphInChecks;
54942
55569
  const effectiveSession = sessionFlag || sessionOnly || hasSessionInChecks;
55570
+ const effectiveSkills = skillsFlag || skillsOnly || hasSkillInChecks;
54943
55571
  let checks;
54944
55572
  if (explicitChecks) {
54945
55573
  checks = explicitChecks;
54946
55574
  } else if (sessionOnly) {
54947
55575
  checks = ALL_SESSION_CHECKS;
55576
+ } else if (skillsOnly) {
55577
+ checks = ALL_SKILL_CHECKS;
54948
55578
  } else if (mcpOnly) {
54949
55579
  checks = ALL_MCP_CHECKS;
54950
55580
  } else if (mcphOnly) {
@@ -54955,7 +55585,8 @@ function resolveSession(resolvedPath, opts) {
54955
55585
  ...base,
54956
55586
  ...effectiveMcp ? ALL_MCP_CHECKS : [],
54957
55587
  ...effectiveMcph ? ALL_MCPH_CHECKS : [],
54958
- ...effectiveSession ? ALL_SESSION_CHECKS : []
55588
+ ...effectiveSession ? ALL_SESSION_CHECKS : [],
55589
+ ...effectiveSkills ? ALL_SKILL_CHECKS : []
54959
55590
  ];
54960
55591
  }
54961
55592
  const options = {
@@ -54980,7 +55611,10 @@ function resolveSession(resolvedPath, opts) {
54980
55611
  mcphGlobal,
54981
55612
  mcphStrictEnvToken,
54982
55613
  session: effectiveSession,
54983
- sessionOnly
55614
+ sessionOnly,
55615
+ skills: effectiveSkills,
55616
+ skillsOnly,
55617
+ hooksGlobal
54984
55618
  };
54985
55619
  const activeChecks = options.checks.filter((c3) => !options.ignore.includes(c3));
54986
55620
  return { config: config2, options, activeChecks };
@@ -55014,7 +55648,8 @@ var init_cli = __esm({
55014
55648
  ...ALL_CHECKS,
55015
55649
  ...ALL_MCP_CHECKS,
55016
55650
  ...ALL_MCPH_CHECKS,
55017
- ...ALL_SESSION_CHECKS
55651
+ ...ALL_SESSION_CHECKS,
55652
+ ...ALL_SKILL_CHECKS
55018
55653
  ]);
55019
55654
  }
55020
55655
  });