@chimix/agentscan 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +87 -40
  2. package/dist/cli.js +1161 -776
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -18,7 +18,7 @@ var __export = (target, all) => {
18
18
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
19
 
20
20
  // node_modules/yaml/dist/nodes/identity.js
21
- var require_identity = __commonJS((exports) => {
21
+ var require_identity = __commonJS(function(exports) {
22
22
  var ALIAS = Symbol.for("yaml.alias");
23
23
  var DOC = Symbol.for("yaml.document");
24
24
  var MAP = Symbol.for("yaml.map");
@@ -72,7 +72,7 @@ var require_identity = __commonJS((exports) => {
72
72
  });
73
73
 
74
74
  // node_modules/yaml/dist/visit.js
75
- var require_visit = __commonJS((exports) => {
75
+ var require_visit = __commonJS(function(exports) {
76
76
  var identity = require_identity();
77
77
  var BREAK = Symbol("break visit");
78
78
  var SKIP = Symbol("skip children");
@@ -227,7 +227,7 @@ var require_visit = __commonJS((exports) => {
227
227
  });
228
228
 
229
229
  // node_modules/yaml/dist/doc/directives.js
230
- var require_directives = __commonJS((exports) => {
230
+ var require_directives = __commonJS(function(exports) {
231
231
  var identity = require_identity();
232
232
  var visit = require_visit();
233
233
  var escapeChars = {
@@ -379,7 +379,7 @@ var require_directives = __commonJS((exports) => {
379
379
  });
380
380
 
381
381
  // node_modules/yaml/dist/doc/anchors.js
382
- var require_anchors = __commonJS((exports) => {
382
+ var require_anchors = __commonJS(function(exports) {
383
383
  var identity = require_identity();
384
384
  var visit = require_visit();
385
385
  function anchorIsValid(anchor) {
@@ -441,7 +441,7 @@ var require_anchors = __commonJS((exports) => {
441
441
  });
442
442
 
443
443
  // node_modules/yaml/dist/doc/applyReviver.js
444
- var require_applyReviver = __commonJS((exports) => {
444
+ var require_applyReviver = __commonJS(function(exports) {
445
445
  function applyReviver(reviver, obj, key, val) {
446
446
  if (val && typeof val === "object") {
447
447
  if (Array.isArray(val)) {
@@ -488,7 +488,7 @@ var require_applyReviver = __commonJS((exports) => {
488
488
  });
489
489
 
490
490
  // node_modules/yaml/dist/nodes/toJS.js
491
- var require_toJS = __commonJS((exports) => {
491
+ var require_toJS = __commonJS(function(exports) {
492
492
  var identity = require_identity();
493
493
  function toJS(value, arg, ctx) {
494
494
  if (Array.isArray(value))
@@ -515,7 +515,7 @@ var require_toJS = __commonJS((exports) => {
515
515
  });
516
516
 
517
517
  // node_modules/yaml/dist/nodes/Node.js
518
- var require_Node = __commonJS((exports) => {
518
+ var require_Node = __commonJS(function(exports) {
519
519
  var applyReviver = require_applyReviver();
520
520
  var identity = require_identity();
521
521
  var toJS = require_toJS();
@@ -552,7 +552,7 @@ var require_Node = __commonJS((exports) => {
552
552
  });
553
553
 
554
554
  // node_modules/yaml/dist/nodes/Alias.js
555
- var require_Alias = __commonJS((exports) => {
555
+ var require_Alias = __commonJS(function(exports) {
556
556
  var anchors = require_anchors();
557
557
  var visit = require_visit();
558
558
  var identity = require_identity();
@@ -662,7 +662,7 @@ var require_Alias = __commonJS((exports) => {
662
662
  });
663
663
 
664
664
  // node_modules/yaml/dist/nodes/Scalar.js
665
- var require_Scalar = __commonJS((exports) => {
665
+ var require_Scalar = __commonJS(function(exports) {
666
666
  var identity = require_identity();
667
667
  var Node = require_Node();
668
668
  var toJS = require_toJS();
@@ -690,7 +690,7 @@ var require_Scalar = __commonJS((exports) => {
690
690
  });
691
691
 
692
692
  // node_modules/yaml/dist/doc/createNode.js
693
- var require_createNode = __commonJS((exports) => {
693
+ var require_createNode = __commonJS(function(exports) {
694
694
  var Alias = require_Alias();
695
695
  var identity = require_identity();
696
696
  var Scalar = require_Scalar();
@@ -762,7 +762,7 @@ var require_createNode = __commonJS((exports) => {
762
762
  });
763
763
 
764
764
  // node_modules/yaml/dist/nodes/Collection.js
765
- var require_Collection = __commonJS((exports) => {
765
+ var require_Collection = __commonJS(function(exports) {
766
766
  var createNode = require_createNode();
767
767
  var identity = require_identity();
768
768
  var Node = require_Node();
@@ -877,7 +877,7 @@ var require_Collection = __commonJS((exports) => {
877
877
  });
878
878
 
879
879
  // node_modules/yaml/dist/stringify/stringifyComment.js
880
- var require_stringifyComment = __commonJS((exports) => {
880
+ var require_stringifyComment = __commonJS(function(exports) {
881
881
  var stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#");
882
882
  function indentComment(comment, indent) {
883
883
  if (/^\n+$/.test(comment))
@@ -894,7 +894,7 @@ var require_stringifyComment = __commonJS((exports) => {
894
894
  });
895
895
 
896
896
  // node_modules/yaml/dist/stringify/foldFlowLines.js
897
- var require_foldFlowLines = __commonJS((exports) => {
897
+ var require_foldFlowLines = __commonJS(function(exports) {
898
898
  var FOLD_FLOW = "flow";
899
899
  var FOLD_BLOCK = "block";
900
900
  var FOLD_QUOTED = "quoted";
@@ -1031,7 +1031,7 @@ ${indent}${text.slice(fold + 1, end2)}`;
1031
1031
  });
1032
1032
 
1033
1033
  // node_modules/yaml/dist/stringify/stringifyString.js
1034
- var require_stringifyString = __commonJS((exports) => {
1034
+ var require_stringifyString = __commonJS(function(exports) {
1035
1035
  var Scalar = require_Scalar();
1036
1036
  var foldFlowLines = require_foldFlowLines();
1037
1037
  var getFoldOptions = (ctx, isBlock) => ({
@@ -1329,7 +1329,7 @@ ${indent}`);
1329
1329
  });
1330
1330
 
1331
1331
  // node_modules/yaml/dist/stringify/stringify.js
1332
- var require_stringify = __commonJS((exports) => {
1332
+ var require_stringify = __commonJS(function(exports) {
1333
1333
  var anchors = require_anchors();
1334
1334
  var identity = require_identity();
1335
1335
  var stringifyComment = require_stringifyComment();
@@ -1450,7 +1450,7 @@ ${ctx.indent}${str}`;
1450
1450
  });
1451
1451
 
1452
1452
  // node_modules/yaml/dist/stringify/stringifyPair.js
1453
- var require_stringifyPair = __commonJS((exports) => {
1453
+ var require_stringifyPair = __commonJS(function(exports) {
1454
1454
  var identity = require_identity();
1455
1455
  var Scalar = require_Scalar();
1456
1456
  var stringify = require_stringify();
@@ -1586,7 +1586,7 @@ ${ctx.indent}`;
1586
1586
  });
1587
1587
 
1588
1588
  // node_modules/yaml/dist/log.js
1589
- var require_log = __commonJS((exports) => {
1589
+ var require_log = __commonJS(function(exports) {
1590
1590
  var node_process = __require("process");
1591
1591
  function debug(logLevel, ...messages) {
1592
1592
  if (logLevel === "debug")
@@ -1605,7 +1605,7 @@ var require_log = __commonJS((exports) => {
1605
1605
  });
1606
1606
 
1607
1607
  // node_modules/yaml/dist/schema/yaml-1.1/merge.js
1608
- var require_merge = __commonJS((exports) => {
1608
+ var require_merge = __commonJS(function(exports) {
1609
1609
  var identity = require_identity();
1610
1610
  var Scalar = require_Scalar();
1611
1611
  var MERGE_KEY = "<<";
@@ -1662,7 +1662,7 @@ var require_merge = __commonJS((exports) => {
1662
1662
  });
1663
1663
 
1664
1664
  // node_modules/yaml/dist/nodes/addPairToJSMap.js
1665
- var require_addPairToJSMap = __commonJS((exports) => {
1665
+ var require_addPairToJSMap = __commonJS(function(exports) {
1666
1666
  var log = require_log();
1667
1667
  var merge = require_merge();
1668
1668
  var stringify = require_stringify();
@@ -1723,7 +1723,7 @@ var require_addPairToJSMap = __commonJS((exports) => {
1723
1723
  });
1724
1724
 
1725
1725
  // node_modules/yaml/dist/nodes/Pair.js
1726
- var require_Pair = __commonJS((exports) => {
1726
+ var require_Pair = __commonJS(function(exports) {
1727
1727
  var createNode = require_createNode();
1728
1728
  var stringifyPair = require_stringifyPair();
1729
1729
  var addPairToJSMap = require_addPairToJSMap();
@@ -1761,7 +1761,7 @@ var require_Pair = __commonJS((exports) => {
1761
1761
  });
1762
1762
 
1763
1763
  // node_modules/yaml/dist/stringify/stringifyCollection.js
1764
- var require_stringifyCollection = __commonJS((exports) => {
1764
+ var require_stringifyCollection = __commonJS(function(exports) {
1765
1765
  var identity = require_identity();
1766
1766
  var stringify = require_stringify();
1767
1767
  var stringifyComment = require_stringifyComment();
@@ -1913,7 +1913,7 @@ ${indent}${end}`;
1913
1913
  });
1914
1914
 
1915
1915
  // node_modules/yaml/dist/nodes/YAMLMap.js
1916
- var require_YAMLMap = __commonJS((exports) => {
1916
+ var require_YAMLMap = __commonJS(function(exports) {
1917
1917
  var stringifyCollection = require_stringifyCollection();
1918
1918
  var addPairToJSMap = require_addPairToJSMap();
1919
1919
  var Collection = require_Collection();
@@ -2040,7 +2040,7 @@ var require_YAMLMap = __commonJS((exports) => {
2040
2040
  });
2041
2041
 
2042
2042
  // node_modules/yaml/dist/schema/common/map.js
2043
- var require_map = __commonJS((exports) => {
2043
+ var require_map = __commonJS(function(exports) {
2044
2044
  var identity = require_identity();
2045
2045
  var YAMLMap = require_YAMLMap();
2046
2046
  var map = {
@@ -2059,7 +2059,7 @@ var require_map = __commonJS((exports) => {
2059
2059
  });
2060
2060
 
2061
2061
  // node_modules/yaml/dist/nodes/YAMLSeq.js
2062
- var require_YAMLSeq = __commonJS((exports) => {
2062
+ var require_YAMLSeq = __commonJS(function(exports) {
2063
2063
  var createNode = require_createNode();
2064
2064
  var stringifyCollection = require_stringifyCollection();
2065
2065
  var Collection = require_Collection();
@@ -2152,7 +2152,7 @@ var require_YAMLSeq = __commonJS((exports) => {
2152
2152
  });
2153
2153
 
2154
2154
  // node_modules/yaml/dist/schema/common/seq.js
2155
- var require_seq = __commonJS((exports) => {
2155
+ var require_seq = __commonJS(function(exports) {
2156
2156
  var identity = require_identity();
2157
2157
  var YAMLSeq = require_YAMLSeq();
2158
2158
  var seq = {
@@ -2171,7 +2171,7 @@ var require_seq = __commonJS((exports) => {
2171
2171
  });
2172
2172
 
2173
2173
  // node_modules/yaml/dist/schema/common/string.js
2174
- var require_string = __commonJS((exports) => {
2174
+ var require_string = __commonJS(function(exports) {
2175
2175
  var stringifyString = require_stringifyString();
2176
2176
  var string = {
2177
2177
  identify: (value) => typeof value === "string",
@@ -2187,7 +2187,7 @@ var require_string = __commonJS((exports) => {
2187
2187
  });
2188
2188
 
2189
2189
  // node_modules/yaml/dist/schema/common/null.js
2190
- var require_null = __commonJS((exports) => {
2190
+ var require_null = __commonJS(function(exports) {
2191
2191
  var Scalar = require_Scalar();
2192
2192
  var nullTag = {
2193
2193
  identify: (value) => value == null,
@@ -2202,7 +2202,7 @@ var require_null = __commonJS((exports) => {
2202
2202
  });
2203
2203
 
2204
2204
  // node_modules/yaml/dist/schema/core/bool.js
2205
- var require_bool = __commonJS((exports) => {
2205
+ var require_bool = __commonJS(function(exports) {
2206
2206
  var Scalar = require_Scalar();
2207
2207
  var boolTag = {
2208
2208
  identify: (value) => typeof value === "boolean",
@@ -2223,7 +2223,7 @@ var require_bool = __commonJS((exports) => {
2223
2223
  });
2224
2224
 
2225
2225
  // node_modules/yaml/dist/stringify/stringifyNumber.js
2226
- var require_stringifyNumber = __commonJS((exports) => {
2226
+ var require_stringifyNumber = __commonJS(function(exports) {
2227
2227
  function stringifyNumber({ format, minFractionDigits, tag, value }) {
2228
2228
  if (typeof value === "bigint")
2229
2229
  return String(value);
@@ -2247,7 +2247,7 @@ var require_stringifyNumber = __commonJS((exports) => {
2247
2247
  });
2248
2248
 
2249
2249
  // node_modules/yaml/dist/schema/core/float.js
2250
- var require_float = __commonJS((exports) => {
2250
+ var require_float = __commonJS(function(exports) {
2251
2251
  var Scalar = require_Scalar();
2252
2252
  var stringifyNumber = require_stringifyNumber();
2253
2253
  var floatNaN = {
@@ -2290,7 +2290,7 @@ var require_float = __commonJS((exports) => {
2290
2290
  });
2291
2291
 
2292
2292
  // node_modules/yaml/dist/schema/core/int.js
2293
- var require_int = __commonJS((exports) => {
2293
+ var require_int = __commonJS(function(exports) {
2294
2294
  var stringifyNumber = require_stringifyNumber();
2295
2295
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
2296
2296
  var intResolve = (str, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix);
@@ -2332,7 +2332,7 @@ var require_int = __commonJS((exports) => {
2332
2332
  });
2333
2333
 
2334
2334
  // node_modules/yaml/dist/schema/core/schema.js
2335
- var require_schema = __commonJS((exports) => {
2335
+ var require_schema = __commonJS(function(exports) {
2336
2336
  var map = require_map();
2337
2337
  var _null = require_null();
2338
2338
  var seq = require_seq();
@@ -2357,7 +2357,7 @@ var require_schema = __commonJS((exports) => {
2357
2357
  });
2358
2358
 
2359
2359
  // node_modules/yaml/dist/schema/json/schema.js
2360
- var require_schema2 = __commonJS((exports) => {
2360
+ var require_schema2 = __commonJS(function(exports) {
2361
2361
  var Scalar = require_Scalar();
2362
2362
  var map = require_map();
2363
2363
  var seq = require_seq();
@@ -2421,7 +2421,7 @@ var require_schema2 = __commonJS((exports) => {
2421
2421
  });
2422
2422
 
2423
2423
  // node_modules/yaml/dist/schema/yaml-1.1/binary.js
2424
- var require_binary = __commonJS((exports) => {
2424
+ var require_binary = __commonJS(function(exports) {
2425
2425
  var node_buffer = __require("buffer");
2426
2426
  var Scalar = require_Scalar();
2427
2427
  var stringifyString = require_stringifyString();
@@ -2476,7 +2476,7 @@ var require_binary = __commonJS((exports) => {
2476
2476
  });
2477
2477
 
2478
2478
  // node_modules/yaml/dist/schema/yaml-1.1/pairs.js
2479
- var require_pairs = __commonJS((exports) => {
2479
+ var require_pairs = __commonJS(function(exports) {
2480
2480
  var identity = require_identity();
2481
2481
  var Pair = require_Pair();
2482
2482
  var Scalar = require_Scalar();
@@ -2551,7 +2551,7 @@ ${cn.comment}` : item.comment;
2551
2551
  });
2552
2552
 
2553
2553
  // node_modules/yaml/dist/schema/yaml-1.1/omap.js
2554
- var require_omap = __commonJS((exports) => {
2554
+ var require_omap = __commonJS(function(exports) {
2555
2555
  var identity = require_identity();
2556
2556
  var toJS = require_toJS();
2557
2557
  var YAMLMap = require_YAMLMap();
@@ -2623,7 +2623,7 @@ var require_omap = __commonJS((exports) => {
2623
2623
  });
2624
2624
 
2625
2625
  // node_modules/yaml/dist/schema/yaml-1.1/bool.js
2626
- var require_bool2 = __commonJS((exports) => {
2626
+ var require_bool2 = __commonJS(function(exports) {
2627
2627
  var Scalar = require_Scalar();
2628
2628
  function boolStringify({ value, source }, ctx) {
2629
2629
  const boolObj = value ? trueTag : falseTag;
@@ -2652,7 +2652,7 @@ var require_bool2 = __commonJS((exports) => {
2652
2652
  });
2653
2653
 
2654
2654
  // node_modules/yaml/dist/schema/yaml-1.1/float.js
2655
- var require_float2 = __commonJS((exports) => {
2655
+ var require_float2 = __commonJS(function(exports) {
2656
2656
  var Scalar = require_Scalar();
2657
2657
  var stringifyNumber = require_stringifyNumber();
2658
2658
  var floatNaN = {
@@ -2698,7 +2698,7 @@ var require_float2 = __commonJS((exports) => {
2698
2698
  });
2699
2699
 
2700
2700
  // node_modules/yaml/dist/schema/yaml-1.1/int.js
2701
- var require_int2 = __commonJS((exports) => {
2701
+ var require_int2 = __commonJS(function(exports) {
2702
2702
  var stringifyNumber = require_stringifyNumber();
2703
2703
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
2704
2704
  function intResolve(str, offset, radix, { intAsBigInt }) {
@@ -2774,7 +2774,7 @@ var require_int2 = __commonJS((exports) => {
2774
2774
  });
2775
2775
 
2776
2776
  // node_modules/yaml/dist/schema/yaml-1.1/set.js
2777
- var require_set = __commonJS((exports) => {
2777
+ var require_set = __commonJS(function(exports) {
2778
2778
  var identity = require_identity();
2779
2779
  var Pair = require_Pair();
2780
2780
  var YAMLMap = require_YAMLMap();
@@ -2857,7 +2857,7 @@ var require_set = __commonJS((exports) => {
2857
2857
  });
2858
2858
 
2859
2859
  // node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
2860
- var require_timestamp = __commonJS((exports) => {
2860
+ var require_timestamp = __commonJS(function(exports) {
2861
2861
  var stringifyNumber = require_stringifyNumber();
2862
2862
  function parseSexagesimal(str, asBigInt) {
2863
2863
  const sign = str[0];
@@ -2939,7 +2939,7 @@ var require_timestamp = __commonJS((exports) => {
2939
2939
  });
2940
2940
 
2941
2941
  // node_modules/yaml/dist/schema/yaml-1.1/schema.js
2942
- var require_schema3 = __commonJS((exports) => {
2942
+ var require_schema3 = __commonJS(function(exports) {
2943
2943
  var map = require_map();
2944
2944
  var _null = require_null();
2945
2945
  var seq = require_seq();
@@ -2980,7 +2980,7 @@ var require_schema3 = __commonJS((exports) => {
2980
2980
  });
2981
2981
 
2982
2982
  // node_modules/yaml/dist/schema/tags.js
2983
- var require_tags = __commonJS((exports) => {
2983
+ var require_tags = __commonJS(function(exports) {
2984
2984
  var map = require_map();
2985
2985
  var _null = require_null();
2986
2986
  var seq = require_seq();
@@ -3071,7 +3071,7 @@ var require_tags = __commonJS((exports) => {
3071
3071
  });
3072
3072
 
3073
3073
  // node_modules/yaml/dist/schema/Schema.js
3074
- var require_Schema = __commonJS((exports) => {
3074
+ var require_Schema = __commonJS(function(exports) {
3075
3075
  var identity = require_identity();
3076
3076
  var map = require_map();
3077
3077
  var seq = require_seq();
@@ -3101,7 +3101,7 @@ var require_Schema = __commonJS((exports) => {
3101
3101
  });
3102
3102
 
3103
3103
  // node_modules/yaml/dist/stringify/stringifyDocument.js
3104
- var require_stringifyDocument = __commonJS((exports) => {
3104
+ var require_stringifyDocument = __commonJS(function(exports) {
3105
3105
  var identity = require_identity();
3106
3106
  var stringify = require_stringify();
3107
3107
  var stringifyComment = require_stringifyComment();
@@ -3181,7 +3181,7 @@ var require_stringifyDocument = __commonJS((exports) => {
3181
3181
  });
3182
3182
 
3183
3183
  // node_modules/yaml/dist/doc/Document.js
3184
- var require_Document = __commonJS((exports) => {
3184
+ var require_Document = __commonJS(function(exports) {
3185
3185
  var Alias = require_Alias();
3186
3186
  var Collection = require_Collection();
3187
3187
  var identity = require_identity();
@@ -3416,7 +3416,7 @@ var require_Document = __commonJS((exports) => {
3416
3416
  });
3417
3417
 
3418
3418
  // node_modules/yaml/dist/errors.js
3419
- var require_errors = __commonJS((exports) => {
3419
+ var require_errors = __commonJS(function(exports) {
3420
3420
  class YAMLError extends Error {
3421
3421
  constructor(name, pos, code, message) {
3422
3422
  super();
@@ -3481,7 +3481,7 @@ ${pointer}
3481
3481
  });
3482
3482
 
3483
3483
  // node_modules/yaml/dist/compose/resolve-props.js
3484
- var require_resolve_props = __commonJS((exports) => {
3484
+ var require_resolve_props = __commonJS(function(exports) {
3485
3485
  function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) {
3486
3486
  let spaceBefore = false;
3487
3487
  let atNewline = startOnNewline;
@@ -3611,7 +3611,7 @@ var require_resolve_props = __commonJS((exports) => {
3611
3611
  });
3612
3612
 
3613
3613
  // node_modules/yaml/dist/compose/util-contains-newline.js
3614
- var require_util_contains_newline = __commonJS((exports) => {
3614
+ var require_util_contains_newline = __commonJS(function(exports) {
3615
3615
  function containsNewline(key) {
3616
3616
  if (!key)
3617
3617
  return null;
@@ -3651,7 +3651,7 @@ var require_util_contains_newline = __commonJS((exports) => {
3651
3651
  });
3652
3652
 
3653
3653
  // node_modules/yaml/dist/compose/util-flow-indent-check.js
3654
- var require_util_flow_indent_check = __commonJS((exports) => {
3654
+ var require_util_flow_indent_check = __commonJS(function(exports) {
3655
3655
  var utilContainsNewline = require_util_contains_newline();
3656
3656
  function flowIndentCheck(indent, fc, onError) {
3657
3657
  if (fc?.type === "flow-collection") {
@@ -3666,7 +3666,7 @@ var require_util_flow_indent_check = __commonJS((exports) => {
3666
3666
  });
3667
3667
 
3668
3668
  // node_modules/yaml/dist/compose/util-map-includes.js
3669
- var require_util_map_includes = __commonJS((exports) => {
3669
+ var require_util_map_includes = __commonJS(function(exports) {
3670
3670
  var identity = require_identity();
3671
3671
  function mapIncludes(ctx, items, search) {
3672
3672
  const { uniqueKeys } = ctx.options;
@@ -3679,7 +3679,7 @@ var require_util_map_includes = __commonJS((exports) => {
3679
3679
  });
3680
3680
 
3681
3681
  // node_modules/yaml/dist/compose/resolve-block-map.js
3682
- var require_resolve_block_map = __commonJS((exports) => {
3682
+ var require_resolve_block_map = __commonJS(function(exports) {
3683
3683
  var Pair = require_Pair();
3684
3684
  var YAMLMap = require_YAMLMap();
3685
3685
  var resolveProps = require_resolve_props();
@@ -3786,7 +3786,7 @@ var require_resolve_block_map = __commonJS((exports) => {
3786
3786
  });
3787
3787
 
3788
3788
  // node_modules/yaml/dist/compose/resolve-block-seq.js
3789
- var require_resolve_block_seq = __commonJS((exports) => {
3789
+ var require_resolve_block_seq = __commonJS(function(exports) {
3790
3790
  var YAMLSeq = require_YAMLSeq();
3791
3791
  var resolveProps = require_resolve_props();
3792
3792
  var utilFlowIndentCheck = require_util_flow_indent_check();
@@ -3834,7 +3834,7 @@ var require_resolve_block_seq = __commonJS((exports) => {
3834
3834
  });
3835
3835
 
3836
3836
  // node_modules/yaml/dist/compose/resolve-end.js
3837
- var require_resolve_end = __commonJS((exports) => {
3837
+ var require_resolve_end = __commonJS(function(exports) {
3838
3838
  function resolveEnd(end, offset, reqSpace, onError) {
3839
3839
  let comment = "";
3840
3840
  if (end) {
@@ -3874,7 +3874,7 @@ var require_resolve_end = __commonJS((exports) => {
3874
3874
  });
3875
3875
 
3876
3876
  // node_modules/yaml/dist/compose/resolve-flow-collection.js
3877
- var require_resolve_flow_collection = __commonJS((exports) => {
3877
+ var require_resolve_flow_collection = __commonJS(function(exports) {
3878
3878
  var identity = require_identity();
3879
3879
  var Pair = require_Pair();
3880
3880
  var YAMLMap = require_YAMLMap();
@@ -4065,7 +4065,7 @@ var require_resolve_flow_collection = __commonJS((exports) => {
4065
4065
  });
4066
4066
 
4067
4067
  // node_modules/yaml/dist/compose/compose-collection.js
4068
- var require_compose_collection = __commonJS((exports) => {
4068
+ var require_compose_collection = __commonJS(function(exports) {
4069
4069
  var identity = require_identity();
4070
4070
  var Scalar = require_Scalar();
4071
4071
  var YAMLMap = require_YAMLMap();
@@ -4127,7 +4127,7 @@ var require_compose_collection = __commonJS((exports) => {
4127
4127
  });
4128
4128
 
4129
4129
  // node_modules/yaml/dist/compose/resolve-block-scalar.js
4130
- var require_resolve_block_scalar = __commonJS((exports) => {
4130
+ var require_resolve_block_scalar = __commonJS(function(exports) {
4131
4131
  var Scalar = require_Scalar();
4132
4132
  function resolveBlockScalar(ctx, scalar, onError) {
4133
4133
  const start = scalar.offset;
@@ -4320,7 +4320,7 @@ var require_resolve_block_scalar = __commonJS((exports) => {
4320
4320
  });
4321
4321
 
4322
4322
  // node_modules/yaml/dist/compose/resolve-flow-scalar.js
4323
- var require_resolve_flow_scalar = __commonJS((exports) => {
4323
+ var require_resolve_flow_scalar = __commonJS(function(exports) {
4324
4324
  var Scalar = require_Scalar();
4325
4325
  var resolveEnd = require_resolve_end();
4326
4326
  function resolveFlowScalar(scalar, strict, onError) {
@@ -4537,7 +4537,7 @@ var require_resolve_flow_scalar = __commonJS((exports) => {
4537
4537
  });
4538
4538
 
4539
4539
  // node_modules/yaml/dist/compose/compose-scalar.js
4540
- var require_compose_scalar = __commonJS((exports) => {
4540
+ var require_compose_scalar = __commonJS(function(exports) {
4541
4541
  var identity = require_identity();
4542
4542
  var Scalar = require_Scalar();
4543
4543
  var resolveBlockScalar = require_resolve_block_scalar();
@@ -4615,7 +4615,7 @@ var require_compose_scalar = __commonJS((exports) => {
4615
4615
  });
4616
4616
 
4617
4617
  // node_modules/yaml/dist/compose/util-empty-scalar-position.js
4618
- var require_util_empty_scalar_position = __commonJS((exports) => {
4618
+ var require_util_empty_scalar_position = __commonJS(function(exports) {
4619
4619
  function emptyScalarPosition(offset, before, pos) {
4620
4620
  if (before) {
4621
4621
  pos ?? (pos = before.length);
@@ -4642,7 +4642,7 @@ var require_util_empty_scalar_position = __commonJS((exports) => {
4642
4642
  });
4643
4643
 
4644
4644
  // node_modules/yaml/dist/compose/compose-node.js
4645
- var require_compose_node = __commonJS((exports) => {
4645
+ var require_compose_node = __commonJS(function(exports) {
4646
4646
  var Alias = require_Alias();
4647
4647
  var identity = require_identity();
4648
4648
  var composeCollection = require_compose_collection();
@@ -4745,7 +4745,7 @@ var require_compose_node = __commonJS((exports) => {
4745
4745
  });
4746
4746
 
4747
4747
  // node_modules/yaml/dist/compose/compose-doc.js
4748
- var require_compose_doc = __commonJS((exports) => {
4748
+ var require_compose_doc = __commonJS(function(exports) {
4749
4749
  var Document = require_Document();
4750
4750
  var composeNode = require_compose_node();
4751
4751
  var resolveEnd = require_resolve_end();
@@ -4785,7 +4785,7 @@ var require_compose_doc = __commonJS((exports) => {
4785
4785
  });
4786
4786
 
4787
4787
  // node_modules/yaml/dist/compose/composer.js
4788
- var require_composer = __commonJS((exports) => {
4788
+ var require_composer = __commonJS(function(exports) {
4789
4789
  var node_process = __require("process");
4790
4790
  var directives = require_directives();
4791
4791
  var Document = require_Document();
@@ -4976,7 +4976,7 @@ ${end.comment}` : end.comment;
4976
4976
  });
4977
4977
 
4978
4978
  // node_modules/yaml/dist/parse/cst-scalar.js
4979
- var require_cst_scalar = __commonJS((exports) => {
4979
+ var require_cst_scalar = __commonJS(function(exports) {
4980
4980
  var resolveBlockScalar = require_resolve_block_scalar();
4981
4981
  var resolveFlowScalar = require_resolve_flow_scalar();
4982
4982
  var errors2 = require_errors();
@@ -5166,7 +5166,7 @@ var require_cst_scalar = __commonJS((exports) => {
5166
5166
  });
5167
5167
 
5168
5168
  // node_modules/yaml/dist/parse/cst-stringify.js
5169
- var require_cst_stringify = __commonJS((exports) => {
5169
+ var require_cst_stringify = __commonJS(function(exports) {
5170
5170
  var stringify = (cst) => ("type" in cst) ? stringifyToken(cst) : stringifyItem(cst);
5171
5171
  function stringifyToken(token) {
5172
5172
  switch (token.type) {
@@ -5224,7 +5224,7 @@ var require_cst_stringify = __commonJS((exports) => {
5224
5224
  });
5225
5225
 
5226
5226
  // node_modules/yaml/dist/parse/cst-visit.js
5227
- var require_cst_visit = __commonJS((exports) => {
5227
+ var require_cst_visit = __commonJS(function(exports) {
5228
5228
  var BREAK = Symbol("break visit");
5229
5229
  var SKIP = Symbol("skip children");
5230
5230
  var REMOVE = Symbol("remove item");
@@ -5283,7 +5283,7 @@ var require_cst_visit = __commonJS((exports) => {
5283
5283
  });
5284
5284
 
5285
5285
  // node_modules/yaml/dist/parse/cst.js
5286
- var require_cst = __commonJS((exports) => {
5286
+ var require_cst = __commonJS(function(exports) {
5287
5287
  var cstScalar = require_cst_scalar();
5288
5288
  var cstStringify = require_cst_stringify();
5289
5289
  var cstVisit = require_cst_visit();
@@ -5384,7 +5384,7 @@ var require_cst = __commonJS((exports) => {
5384
5384
  });
5385
5385
 
5386
5386
  // node_modules/yaml/dist/parse/lexer.js
5387
- var require_lexer = __commonJS((exports) => {
5387
+ var require_lexer = __commonJS(function(exports) {
5388
5388
  var cst = require_cst();
5389
5389
  function isEmpty(ch) {
5390
5390
  switch (ch) {
@@ -5981,7 +5981,7 @@ var require_lexer = __commonJS((exports) => {
5981
5981
  });
5982
5982
 
5983
5983
  // node_modules/yaml/dist/parse/line-counter.js
5984
- var require_line_counter = __commonJS((exports) => {
5984
+ var require_line_counter = __commonJS(function(exports) {
5985
5985
  class LineCounter {
5986
5986
  constructor() {
5987
5987
  this.lineStarts = [];
@@ -6009,7 +6009,7 @@ var require_line_counter = __commonJS((exports) => {
6009
6009
  });
6010
6010
 
6011
6011
  // node_modules/yaml/dist/parse/parser.js
6012
- var require_parser = __commonJS((exports) => {
6012
+ var require_parser = __commonJS(function(exports) {
6013
6013
  var node_process = __require("process");
6014
6014
  var cst = require_cst();
6015
6015
  var lexer = require_lexer();
@@ -6865,7 +6865,7 @@ var require_parser = __commonJS((exports) => {
6865
6865
  });
6866
6866
 
6867
6867
  // node_modules/yaml/dist/public-api.js
6868
- var require_public_api = __commonJS((exports) => {
6868
+ var require_public_api = __commonJS(function(exports) {
6869
6869
  var composer = require_composer();
6870
6870
  var Document = require_Document();
6871
6871
  var errors2 = require_errors();
@@ -6962,10 +6962,7 @@ var require_public_api = __commonJS((exports) => {
6962
6962
  import { parseArgs } from "node:util";
6963
6963
 
6964
6964
  // src/analyze.ts
6965
- import { resolve as resolve2 } from "node:path";
6966
-
6967
- // src/checks/index.ts
6968
- import { basename as basename2 } from "node:path";
6965
+ import { resolve as resolve4 } from "node:path";
6969
6966
 
6970
6967
  // src/checks/budgets.ts
6971
6968
  import { basename } from "node:path";
@@ -7042,28 +7039,6 @@ function countFinding(args) {
7042
7039
  })
7043
7040
  ];
7044
7041
  }
7045
- function packageManagerDrift(facts) {
7046
- if (facts.packageManager !== "bun") {
7047
- return [];
7048
- }
7049
- const hits = facts.policyFiles.filter((p) => p.text.includes("npm install"));
7050
- if (hits.length === 0) {
7051
- return [];
7052
- }
7053
- return [
7054
- make("policy.package-manager-drift", "policy:package-manager", {
7055
- action: "drift",
7056
- severity: "info",
7057
- message: "Policy mentions npm install but packageManager is bun",
7058
- reason: "Docs drift vs packageManager field / lockfile convention",
7059
- evidence: [
7060
- { kind: "packageManager", value: facts.packageManager },
7061
- ...hits.map((p) => ({ kind: "policy", value: p.path }))
7062
- ],
7063
- suggest: "Prefer bun install in AGENTS.md / CLAUDE.md"
7064
- })
7065
- ];
7066
- }
7067
7042
  function runBudgets(facts, options) {
7068
7043
  return [
7069
7044
  ...policyLengthFinding({
@@ -7099,114 +7074,80 @@ function runBudgets(facts, options) {
7099
7074
  message: `MCP surface above sweet spot (${facts.mcp.length} > ${options.mcp})`,
7100
7075
  reason: "What actually degrades an agent is the number of tool definitions in context, not the number of servers: benchmarks show large models near-perfect at ~20 tools and failing outright past ~100, and a typical server carries tens of tools. Server count is the only proxy available without connecting to them, so treat this as a hint to go count the tools, not as a measurement.",
7101
7076
  suggest: "Count the tools your servers actually expose; disable unused servers in mcp.json or set thresholds.mcp in .agentscanrc.json"
7102
- }),
7103
- ...packageManagerDrift(facts)
7077
+ })
7104
7078
  ];
7105
7079
  }
7106
7080
 
7107
- // src/checks/index.ts
7108
- var STRUCTURAL_CHECKS = [
7109
- {
7110
- id: "config.unreadable",
7111
- description: "Config file is not valid JSON or cannot be read"
7112
- },
7113
- {
7114
- id: "hook.unknown-event",
7115
- description: "Hook registered under an event name that is never dispatched"
7116
- },
7117
- {
7118
- id: "hook.missing-script",
7119
- description: "Hook points at a script that does not exist, so it never runs"
7120
- },
7121
- {
7122
- id: "skill.missing-skill-md",
7123
- description: "Skill directory has no SKILL.md"
7124
- },
7125
- {
7126
- id: "skill.missing-frontmatter",
7127
- description: "SKILL.md has no YAML frontmatter block"
7128
- },
7129
- {
7130
- id: "skill.missing-description",
7131
- description: "SKILL.md frontmatter has no description (recommended field)"
7132
- },
7133
- {
7134
- id: "skill.not-in-lock",
7135
- description: "Skill on disk is not tracked by skills-lock.json"
7136
- },
7137
- {
7138
- id: "skill.locked-not-installed",
7139
- description: "skills-lock.json pins a skill that is not installed"
7140
- },
7141
- {
7142
- id: "skill.broken-reference",
7143
- description: "SKILL.md points at a bundled file that does not exist"
7144
- },
7145
- {
7146
- id: "skill.duplicate-description",
7147
- description: "Two or more skills share an identical description"
7148
- },
7149
- {
7150
- id: "skill.description-budget",
7151
- description: "Skill descriptions exceed the startup character budget"
7152
- },
7153
- {
7154
- id: "skill.no-lockfile",
7155
- description: "Skills present with no skills-lock.json (requireLock only)"
7156
- },
7157
- {
7158
- id: "agent.missing-frontmatter",
7159
- description: "Agent definition has no YAML frontmatter block"
7160
- },
7161
- {
7162
- id: "agent.missing-description",
7163
- description: "Agent frontmatter has no description"
7164
- },
7165
- {
7166
- id: "mcp.no-launch",
7167
- description: "MCP server declares neither command nor url"
7168
- },
7169
- {
7170
- id: "mcp.url-without-type",
7171
- description: "Remote MCP server has a url but no transport type"
7172
- },
7173
- {
7174
- id: "mcp.hardcoded-secret",
7175
- description: "MCP config contains a token-shaped literal"
7176
- },
7177
- {
7178
- id: "mcp.literal-env",
7179
- description: "MCP env value is a long literal instead of ${VAR}"
7180
- },
7181
- {
7182
- id: "budget.agents-md",
7183
- description: "AGENTS.md longer than the point where added lines stop helping (>150 lines)"
7184
- },
7185
- {
7186
- id: "budget.claude-md",
7187
- description: "CLAUDE.md longer than the instruction budget a model reliably follows (>200 lines)"
7188
- },
7189
- {
7190
- id: "budget.agents",
7191
- description: "More agent definitions than a focused set (>8 files in .claude/agents)"
7192
- },
7193
- {
7194
- id: "budget.mcp",
7195
- description: "Project MCP server count above the point where tool selection degrades"
7196
- },
7197
- {
7198
- id: "policy.package-manager-drift",
7199
- description: "Policy mentions npm install while packageManager is bun"
7200
- }
7201
- ];
7202
- var SECRET_PATTERNS = [
7203
- { label: "Anthropic key", re: /\bsk-ant-[A-Za-z0-9_-]{16,}/ },
7204
- { label: "OpenAI-style key", re: /\bsk-[A-Za-z0-9_]{20,}\b/ },
7205
- { label: "GitHub token", re: /\bgh[pousr]_[A-Za-z0-9]{20,}/ },
7206
- { label: "Google API key", re: /\bAIza[A-Za-z0-9_-]{20,}/ },
7207
- { label: "Slack token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}/ },
7208
- { label: "AWS access key id", re: /\bAKIA[0-9A-Z]{16}\b/ }
7209
- ];
7081
+ // src/checks/agents.ts
7082
+ function checkAgents(facts) {
7083
+ const out = [];
7084
+ const names = new Map;
7085
+ for (const agent of facts.agents) {
7086
+ if (agent.unreadable === true || agent.unparseableFrontmatter === true) {
7087
+ continue;
7088
+ }
7089
+ if (!agent.hasFrontmatter) {
7090
+ out.push(make("agent.missing-frontmatter", `agent:${agent.name}`, {
7091
+ action: "warn",
7092
+ severity: "error",
7093
+ message: "Agent definition has no YAML frontmatter block",
7094
+ reason: "An agent is selected by its declared description; without a frontmatter block there is nothing to select on, and the file is unlikely to load as an agent at all.",
7095
+ evidence: [{ kind: "agent", value: agent.path }],
7096
+ suggest: "Add a --- delimited block with a description"
7097
+ }));
7098
+ continue;
7099
+ }
7100
+ if (agent.description === undefined) {
7101
+ out.push(make("agent.missing-description", `agent:${agent.name}`, {
7102
+ action: "warn",
7103
+ severity: "error",
7104
+ message: "Agent frontmatter has no `description`",
7105
+ reason: "The description is how the main session decides which agent to dispatch. Without one the agent can still be named explicitly, but it will not be chosen on its own.",
7106
+ evidence: [{ kind: "agent", value: agent.path }],
7107
+ suggest: "Add a one-line description saying when to dispatch it"
7108
+ }));
7109
+ }
7110
+ if (agent.frontmatterName === undefined) {
7111
+ out.push(make("agent.missing-name", `agent:${agent.name}`, {
7112
+ action: "warn",
7113
+ severity: "error",
7114
+ message: "Agent frontmatter has no `name`",
7115
+ reason: "Agent identity is declared by the required frontmatter name.",
7116
+ evidence: [{ kind: "agent", value: agent.path }],
7117
+ suggest: "Add a `name` field to the frontmatter"
7118
+ }));
7119
+ } else {
7120
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(agent.frontmatterName)) {
7121
+ out.push(make("agent.invalid-name", `agent:${agent.name}`, {
7122
+ action: "warn",
7123
+ severity: "error",
7124
+ message: `Agent name "${agent.frontmatterName}" is not a valid identifier`,
7125
+ reason: "Agent names use lowercase letters, numbers, and hyphens so dispatch can address them reliably.",
7126
+ evidence: [{ kind: "agent", value: agent.path }],
7127
+ suggest: "Use lowercase letters, numbers, and hyphens in the `name` field"
7128
+ }));
7129
+ }
7130
+ const list = names.get(agent.frontmatterName) ?? [];
7131
+ list.push(agent.path);
7132
+ names.set(agent.frontmatterName, list);
7133
+ }
7134
+ }
7135
+ for (const [name, paths] of names)
7136
+ if (paths.length > 1) {
7137
+ out.push(make("agent.duplicate-name", `agent:${name}`, {
7138
+ action: "warn",
7139
+ severity: "error",
7140
+ message: `Agent name "${name}" is declared by multiple files`,
7141
+ reason: "Duplicate names make dispatch ambiguous.",
7142
+ evidence: paths.map((path) => ({ kind: "agent", value: path })),
7143
+ suggest: "Give each agent a unique frontmatter name"
7144
+ }));
7145
+ }
7146
+ return out;
7147
+ }
7148
+
7149
+ // src/checks/config.ts
7150
+ import { basename as basename2 } from "node:path";
7210
7151
  function safeDetail(detail) {
7211
7152
  const position = detail.match(/\bline \d+(?:, column \d+)?/i)?.[0];
7212
7153
  const kind = detail.split(/[:(]/)[0]?.trim().slice(0, 60);
@@ -7229,6 +7170,8 @@ function checkConfigErrors(facts) {
7229
7170
  });
7230
7171
  });
7231
7172
  }
7173
+
7174
+ // src/checks/hooks.ts
7232
7175
  var KNOWN_HOOK_EVENTS = new Set([
7233
7176
  "SessionStart",
7234
7177
  "Setup",
@@ -7314,6 +7257,84 @@ function checkHooks(facts) {
7314
7257
  }
7315
7258
  return out;
7316
7259
  }
7260
+
7261
+ // src/checks/mcp.ts
7262
+ var SECRET_PATTERNS = [
7263
+ { label: "Anthropic key", re: /\bsk-ant-[A-Za-z0-9_-]{16,}/ },
7264
+ { label: "OpenAI project key", re: /\bsk-proj-[A-Za-z0-9_-]{16,}/ },
7265
+ { label: "OpenAI service-account key", re: /\bsk-svcacct-[A-Za-z0-9_-]{16,}/ },
7266
+ { label: "OpenAI-style key", re: /\bsk-[A-Za-z0-9_]{20,}\b/ },
7267
+ { label: "GitHub token", re: /\bgh[pousr]_[A-Za-z0-9]{20,}/ },
7268
+ { label: "Google API key", re: /\bAIza[A-Za-z0-9_-]{20,}/ },
7269
+ { label: "Slack token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}/ },
7270
+ { label: "AWS access key id", re: /\bAKIA[0-9A-Z]{16}\b/ }
7271
+ ];
7272
+ function checkMcp(facts) {
7273
+ const out = [];
7274
+ for (const server of facts.mcp) {
7275
+ if (!(server.hasCommand || server.hasUrl)) {
7276
+ out.push(make("mcp.no-launch", `mcp:${server.name}@${server.path}`, {
7277
+ action: "warn",
7278
+ severity: "error",
7279
+ message: `MCP server "${server.name}" declares neither command nor url`,
7280
+ reason: "Without a command or url the entry is not a launchable MCP server by schema — its tools will not be available, and the entry is dead weight that still costs a config read.",
7281
+ evidence: [{ kind: "mcp", value: `${server.name} @ ${server.path}` }],
7282
+ suggest: `Add a command (or url) for "${server.name}", or remove it`
7283
+ }));
7284
+ }
7285
+ if (server.command !== undefined && server.commandExists === false) {
7286
+ out.push(make("mcp.command-missing", `mcp:${server.name}@${server.path}`, {
7287
+ action: "warn",
7288
+ severity: "error",
7289
+ message: `MCP server "${server.name}" points at a command path that does not exist: ${server.command}`,
7290
+ reason: "The server declares a filesystem command, but that path is not a file on disk, so the entry is not launchable as configured. Bare PATH binaries are not checked — only path-like values this tool can resolve honestly.",
7291
+ evidence: [
7292
+ { kind: "mcp", value: `${server.name} @ ${server.path}` },
7293
+ { kind: "command", value: server.command }
7294
+ ],
7295
+ suggest: `Restore ${server.command} or fix the command path for "${server.name}"`
7296
+ }));
7297
+ }
7298
+ if (server.hasUrl && server.transport === undefined) {
7299
+ out.push(make("mcp.url-without-type", `mcp:${server.name}@${server.path}`, {
7300
+ action: "warn",
7301
+ severity: "error",
7302
+ message: `MCP server "${server.name}" has a url but no transport type`,
7303
+ reason: "An entry with no `type` is read as a stdio server, so a remote server declared this way is misconfigured by schema and is skipped — its tools never appear, with no error in the report you are reading. See docs/spec/mcp.md.",
7304
+ evidence: [{ kind: "mcp", value: `${server.name} @ ${server.path}` }],
7305
+ suggest: `Add "type": "http" (or "sse" / "ws") to "${server.name}"`
7306
+ }));
7307
+ }
7308
+ const hit = SECRET_PATTERNS.find((p) => p.re.test(server.raw));
7309
+ if (hit !== undefined) {
7310
+ out.push(make("mcp.hardcoded-secret", `mcp:${server.name}@${server.path}`, {
7311
+ action: "warn",
7312
+ severity: "error",
7313
+ message: `MCP server "${server.name}" contains what looks like a hardcoded credential (${hit.label})`,
7314
+ reason: "Credentials in a config file get committed, shared and cached. Treat it as leaked: rotate the key, then reference it through an environment variable.",
7315
+ evidence: [{ kind: "mcp", value: `${server.name} @ ${server.path}` }],
7316
+ suggest: "Rotate the credential, then replace the literal with ${ENV_VAR}"
7317
+ }));
7318
+ continue;
7319
+ }
7320
+ if (server.literalEnvKeys.length > 0) {
7321
+ out.push(make("mcp.literal-env", `mcp:${server.name}@${server.path}`, {
7322
+ action: "warn",
7323
+ severity: "warning",
7324
+ message: `MCP server "${server.name}" has literal env values: ${server.literalEnvKeys.join(", ")}`,
7325
+ reason: "Long literal env values in config are usually secrets that should be indirected through ${VAR} so they are never committed.",
7326
+ evidence: [
7327
+ { kind: "mcp", value: `${server.name} @ ${server.path}` },
7328
+ { kind: "env", value: server.literalEnvKeys.join(",") }
7329
+ ],
7330
+ suggest: "Replace the literals with ${ENV_VAR} references"
7331
+ }));
7332
+ }
7333
+ }
7334
+ return out;
7335
+ }
7336
+
7337
+ // src/checks/skills.ts
7317
7338
  function skillEvidence(skill, value) {
7318
7339
  const evidence = [{ kind: "skill", value }];
7319
7340
  if (skill.source === "global") {
@@ -7321,6 +7342,9 @@ function skillEvidence(skill, value) {
7321
7342
  }
7322
7343
  return evidence;
7323
7344
  }
7345
+ function skillSubject(skill) {
7346
+ return `skill:${skill.instanceId ?? skill.id}`;
7347
+ }
7324
7348
  function checkSkillStructure(facts) {
7325
7349
  const out = [];
7326
7350
  for (const skill of facts.skills) {
@@ -7328,7 +7352,7 @@ function checkSkillStructure(facts) {
7328
7352
  continue;
7329
7353
  }
7330
7354
  if (!skill.hasSkillMd) {
7331
- out.push(make("skill.missing-skill-md", `skill:${skill.id}`, {
7355
+ out.push(make("skill.missing-skill-md", skillSubject(skill), {
7332
7356
  action: "warn",
7333
7357
  severity: "warning",
7334
7358
  message: `Skill directory has no SKILL.md`,
@@ -7341,8 +7365,8 @@ function checkSkillStructure(facts) {
7341
7365
  if (skill.unparseableFrontmatter === true) {
7342
7366
  continue;
7343
7367
  }
7344
- if (!skill.hasFrontmatter) {
7345
- out.push(make("skill.missing-frontmatter", `skill:${skill.id}`, {
7368
+ if (skill.runtime !== "agents" && !skill.hasFrontmatter) {
7369
+ out.push(make("skill.missing-frontmatter", skillSubject(skill), {
7346
7370
  action: "warn",
7347
7371
  severity: "warning",
7348
7372
  message: "SKILL.md has no YAML frontmatter block",
@@ -7353,10 +7377,10 @@ function checkSkillStructure(facts) {
7353
7377
  continue;
7354
7378
  }
7355
7379
  const broken = skill.brokenReferences;
7356
- if (broken !== undefined && broken.length > 0) {
7380
+ if (skill.runtime !== "agents" && broken !== undefined && broken.length > 0) {
7357
7381
  const shown = broken.slice(0, 3).join(", ");
7358
7382
  const rest = broken.length - Math.min(3, broken.length);
7359
- out.push(make("skill.broken-reference", `skill:${skill.id}`, {
7383
+ out.push(make("skill.broken-reference", skillSubject(skill), {
7360
7384
  action: "warn",
7361
7385
  severity: "warning",
7362
7386
  message: `SKILL.md points at ${broken.length} file${broken.length === 1 ? "" : "s"} that do not exist: ${shown}${rest > 0 ? ` (+${rest} more)` : ""}`,
@@ -7368,8 +7392,8 @@ function checkSkillStructure(facts) {
7368
7392
  suggest: `Restore the missing files under ${skill.path}, or drop the references from SKILL.md`
7369
7393
  }));
7370
7394
  }
7371
- if (skill.description === undefined) {
7372
- out.push(make("skill.missing-description", `skill:${skill.id}`, {
7395
+ if (skill.runtime !== "agents" && skill.description === undefined) {
7396
+ out.push(make("skill.missing-description", skillSubject(skill), {
7373
7397
  action: "warn",
7374
7398
  severity: "info",
7375
7399
  message: "SKILL.md frontmatter has no `description`",
@@ -7397,13 +7421,16 @@ function checkLockIntegrity(facts, options) {
7397
7421
  }
7398
7422
  return out;
7399
7423
  }
7424
+ if (facts.skillsLockInvalid === true) {
7425
+ return out;
7426
+ }
7400
7427
  const onDisk = new Set(projectSkills.map((s) => s.id));
7401
7428
  const locked = new Map(facts.lockedSkills.map((l) => [l.id, l]));
7402
7429
  for (const skill of projectSkills) {
7403
7430
  if (locked.has(skill.id)) {
7404
7431
  continue;
7405
7432
  }
7406
- out.push(make("skill.not-in-lock", `skill:${skill.id}`, {
7433
+ out.push(make("skill.not-in-lock", skillSubject(skill), {
7407
7434
  action: "warn",
7408
7435
  severity: "info",
7409
7436
  message: "Skill is not in skills-lock.json — local and unpinned",
@@ -7437,11 +7464,20 @@ function checkDuplicateDescriptions(facts) {
7437
7464
  if (skill.source !== "project" || skill.description === undefined) {
7438
7465
  continue;
7439
7466
  }
7440
- const key = skill.description.trim().replace(/\s+/g, " ").toLowerCase();
7467
+ if (skill.runtime === "agents") {
7468
+ continue;
7469
+ }
7470
+ const path = skill.path.replaceAll("\\", "/");
7471
+ const namespace = path.includes(".claude/skills/") ? "claude" : path.includes(".agents/skills/") ? "agents" : skill.source;
7472
+ const key = `${namespace}\x00${skill.description.trim().replace(/\s+/g, " ").toLowerCase()}`;
7441
7473
  if (key.length === 0) {
7442
7474
  continue;
7443
7475
  }
7444
- groups.set(key, [...groups.get(key) ?? [], skill.id]);
7476
+ const ids = groups.get(key);
7477
+ if (ids === undefined)
7478
+ groups.set(key, [skill.instanceId ?? skill.id]);
7479
+ else
7480
+ ids.push(skill.instanceId ?? skill.id);
7445
7481
  }
7446
7482
  const out = [];
7447
7483
  for (const ids of groups.values()) {
@@ -7484,87 +7520,106 @@ function checkDescriptionBudget(facts, options) {
7484
7520
  })
7485
7521
  ];
7486
7522
  }
7487
- function checkAgents(facts) {
7488
- const out = [];
7489
- for (const agent of facts.agents) {
7490
- if (agent.unreadable === true || agent.unparseableFrontmatter === true) {
7491
- continue;
7492
- }
7493
- if (!agent.hasFrontmatter) {
7494
- out.push(make("agent.missing-frontmatter", `agent:${agent.name}`, {
7495
- action: "warn",
7496
- severity: "warning",
7497
- message: "Agent definition has no YAML frontmatter block",
7498
- reason: "An agent is selected by its declared description; without a frontmatter block there is nothing to select on, and the file is unlikely to load as an agent at all.",
7499
- evidence: [{ kind: "agent", value: agent.path }],
7500
- suggest: "Add a --- delimited block with a description"
7501
- }));
7502
- continue;
7503
- }
7504
- if (agent.description === undefined) {
7505
- out.push(make("agent.missing-description", `agent:${agent.name}`, {
7506
- action: "warn",
7507
- severity: "info",
7508
- message: "Agent frontmatter has no `description`",
7509
- reason: "The description is how the main session decides which agent to dispatch. Without one the agent can still be named explicitly, but it will not be chosen on its own.",
7510
- evidence: [{ kind: "agent", value: agent.path }],
7511
- suggest: "Add a one-line description saying when to dispatch it"
7512
- }));
7513
- }
7514
- }
7515
- return out;
7516
- }
7517
- function checkMcp(facts) {
7518
- const out = [];
7519
- for (const server of facts.mcp) {
7520
- if (!(server.hasCommand || server.hasUrl)) {
7521
- out.push(make("mcp.no-launch", `mcp:${server.name}@${server.path}`, {
7522
- action: "warn",
7523
- severity: "error",
7524
- message: `MCP server "${server.name}" declares neither command nor url`,
7525
- reason: "There is no way to start or reach this server, so its tools are never available — the entry is dead weight that still costs a config read.",
7526
- evidence: [{ kind: "mcp", value: `${server.name} @ ${server.path}` }],
7527
- suggest: `Add a command (or url) for "${server.name}", or remove it`
7528
- }));
7529
- }
7530
- if (server.hasUrl && server.transport === undefined) {
7531
- out.push(make("mcp.url-without-type", `mcp:${server.name}@${server.path}`, {
7532
- action: "warn",
7533
- severity: "error",
7534
- message: `MCP server "${server.name}" has a url but no transport type`,
7535
- reason: "An entry with no `type` is read as a stdio server, so a remote server declared this way fails to start and is skipped — its tools never appear, with no error in the report you are reading. See docs/spec/mcp.md.",
7536
- evidence: [{ kind: "mcp", value: `${server.name} @ ${server.path}` }],
7537
- suggest: `Add "type": "http" (or "sse" / "ws") to "${server.name}"`
7538
- }));
7539
- }
7540
- const hit = SECRET_PATTERNS.find((p) => p.re.test(server.raw));
7541
- if (hit !== undefined) {
7542
- out.push(make("mcp.hardcoded-secret", `mcp:${server.name}@${server.path}`, {
7543
- action: "warn",
7544
- severity: "error",
7545
- message: `MCP server "${server.name}" contains what looks like a hardcoded credential (${hit.label})`,
7546
- reason: "Credentials in a config file get committed, shared and cached. Treat it as leaked: rotate the key, then reference it through an environment variable.",
7547
- evidence: [{ kind: "mcp", value: `${server.name} @ ${server.path}` }],
7548
- suggest: "Rotate the credential, then replace the literal with ${ENV_VAR}"
7549
- }));
7550
- continue;
7551
- }
7552
- if (server.literalEnvKeys.length > 0) {
7553
- out.push(make("mcp.literal-env", `mcp:${server.name}@${server.path}`, {
7554
- action: "warn",
7555
- severity: "warning",
7556
- message: `MCP server "${server.name}" has literal env values: ${server.literalEnvKeys.join(", ")}`,
7557
- reason: "Long literal env values in config are usually secrets that should be indirected through ${VAR} so they are never committed.",
7558
- evidence: [
7559
- { kind: "mcp", value: `${server.name} @ ${server.path}` },
7560
- { kind: "env", value: server.literalEnvKeys.join(",") }
7561
- ],
7562
- suggest: "Replace the literals with ${ENV_VAR} references"
7563
- }));
7564
- }
7523
+ // src/checks/registry.ts
7524
+ var STRUCTURAL_CHECKS = [
7525
+ {
7526
+ id: "config.unreadable",
7527
+ description: "Config file is not valid JSON or cannot be read"
7528
+ },
7529
+ {
7530
+ id: "hook.unknown-event",
7531
+ description: "Hook registered under an event name that is never dispatched"
7532
+ },
7533
+ {
7534
+ id: "hook.missing-script",
7535
+ description: "Hook points at a script that does not exist, so it never runs"
7536
+ },
7537
+ {
7538
+ id: "skill.missing-skill-md",
7539
+ description: "Skill directory has no SKILL.md"
7540
+ },
7541
+ {
7542
+ id: "skill.missing-frontmatter",
7543
+ description: "SKILL.md has no YAML frontmatter block"
7544
+ },
7545
+ {
7546
+ id: "skill.missing-description",
7547
+ description: "SKILL.md frontmatter has no description (recommended field)"
7548
+ },
7549
+ {
7550
+ id: "skill.not-in-lock",
7551
+ description: "Skill on disk is not tracked by skills-lock.json"
7552
+ },
7553
+ {
7554
+ id: "skill.locked-not-installed",
7555
+ description: "skills-lock.json pins a skill that is not installed"
7556
+ },
7557
+ {
7558
+ id: "skill.broken-reference",
7559
+ description: "SKILL.md points at a bundled file that does not exist"
7560
+ },
7561
+ {
7562
+ id: "skill.duplicate-description",
7563
+ description: "Two or more skills share an identical description"
7564
+ },
7565
+ {
7566
+ id: "skill.description-budget",
7567
+ description: "Skill descriptions exceed the startup character budget"
7568
+ },
7569
+ {
7570
+ id: "skill.no-lockfile",
7571
+ description: "Skills present with no skills-lock.json (requireLock only)"
7572
+ },
7573
+ {
7574
+ id: "agent.missing-frontmatter",
7575
+ description: "Agent definition has no YAML frontmatter block"
7576
+ },
7577
+ {
7578
+ id: "agent.missing-description",
7579
+ description: "Agent frontmatter has no description"
7580
+ },
7581
+ { id: "agent.missing-name", description: "Agent frontmatter has no name" },
7582
+ { id: "agent.duplicate-name", description: "Agent names are duplicated" },
7583
+ { id: "agent.invalid-name", description: "Agent frontmatter name is not a valid identifier" },
7584
+ {
7585
+ id: "mcp.no-launch",
7586
+ description: "MCP server declares neither command nor url"
7587
+ },
7588
+ {
7589
+ id: "mcp.command-missing",
7590
+ description: "MCP server command is a path that does not exist on disk"
7591
+ },
7592
+ {
7593
+ id: "mcp.url-without-type",
7594
+ description: "Remote MCP server has a url but no transport type"
7595
+ },
7596
+ {
7597
+ id: "mcp.hardcoded-secret",
7598
+ description: "MCP config contains a token-shaped literal"
7599
+ },
7600
+ {
7601
+ id: "mcp.literal-env",
7602
+ description: "MCP env value is a long literal instead of ${VAR}"
7603
+ },
7604
+ {
7605
+ id: "budget.agents-md",
7606
+ description: "AGENTS.md longer than the point where added lines stop helping (>150 lines)"
7607
+ },
7608
+ {
7609
+ id: "budget.claude-md",
7610
+ description: "CLAUDE.md longer than the instruction budget a model reliably follows (>200 lines)"
7611
+ },
7612
+ {
7613
+ id: "budget.agents",
7614
+ description: "More agent definitions than a focused set (>8 files in .claude/agents)"
7615
+ },
7616
+ {
7617
+ id: "budget.mcp",
7618
+ description: "Project MCP server count above the point where tool selection degrades"
7565
7619
  }
7566
- return out;
7567
- }
7620
+ ];
7621
+
7622
+ // src/checks/index.ts
7568
7623
  function runChecks(facts, options = {}) {
7569
7624
  return [
7570
7625
  ...checkConfigErrors(facts),
@@ -7587,113 +7642,113 @@ import { join } from "node:path";
7587
7642
  // node_modules/zod/v3/external.js
7588
7643
  var exports_external = {};
7589
7644
  __export(exports_external, {
7590
- void: () => voidType,
7591
- util: () => util,
7592
- unknown: () => unknownType,
7593
- union: () => unionType,
7594
- undefined: () => undefinedType,
7595
- tuple: () => tupleType,
7596
- transformer: () => effectsType,
7597
- symbol: () => symbolType,
7598
- string: () => stringType,
7599
- strictObject: () => strictObjectType,
7600
- setErrorMap: () => setErrorMap,
7601
- set: () => setType,
7602
- record: () => recordType,
7603
- quotelessJson: () => quotelessJson,
7604
- promise: () => promiseType,
7605
- preprocess: () => preprocessType,
7606
- pipeline: () => pipelineType,
7607
- ostring: () => ostring,
7608
- optional: () => optionalType,
7609
- onumber: () => onumber,
7610
- oboolean: () => oboolean,
7611
- objectUtil: () => objectUtil,
7612
- object: () => objectType,
7613
- number: () => numberType,
7614
- nullable: () => nullableType,
7615
- null: () => nullType,
7616
- never: () => neverType,
7617
- nativeEnum: () => nativeEnumType,
7618
- nan: () => nanType,
7619
- map: () => mapType,
7620
- makeIssue: () => makeIssue,
7621
- literal: () => literalType,
7622
- lazy: () => lazyType,
7623
- late: () => late,
7624
- isValid: () => isValid,
7625
- isDirty: () => isDirty,
7626
- isAsync: () => isAsync,
7627
- isAborted: () => isAborted,
7628
- intersection: () => intersectionType,
7629
- instanceof: () => instanceOfType,
7630
- getParsedType: () => getParsedType,
7631
- getErrorMap: () => getErrorMap,
7632
- function: () => functionType,
7633
- enum: () => enumType,
7634
- effect: () => effectsType,
7635
- discriminatedUnion: () => discriminatedUnionType,
7636
- defaultErrorMap: () => en_default,
7637
- datetimeRegex: () => datetimeRegex,
7638
- date: () => dateType,
7639
- custom: () => custom,
7640
- coerce: () => coerce,
7641
- boolean: () => booleanType,
7642
- bigint: () => bigIntType,
7643
- array: () => arrayType,
7644
- any: () => anyType,
7645
- addIssueToContext: () => addIssueToContext,
7646
- ZodVoid: () => ZodVoid,
7647
- ZodUnknown: () => ZodUnknown,
7648
- ZodUnion: () => ZodUnion,
7649
- ZodUndefined: () => ZodUndefined,
7650
- ZodType: () => ZodType,
7651
- ZodTuple: () => ZodTuple,
7652
- ZodTransformer: () => ZodEffects,
7653
- ZodSymbol: () => ZodSymbol,
7654
- ZodString: () => ZodString,
7655
- ZodSet: () => ZodSet,
7656
- ZodSchema: () => ZodType,
7657
- ZodRecord: () => ZodRecord,
7658
- ZodReadonly: () => ZodReadonly,
7659
- ZodPromise: () => ZodPromise,
7660
- ZodPipeline: () => ZodPipeline,
7661
- ZodParsedType: () => ZodParsedType,
7662
- ZodOptional: () => ZodOptional,
7663
- ZodObject: () => ZodObject,
7664
- ZodNumber: () => ZodNumber,
7665
- ZodNullable: () => ZodNullable,
7666
- ZodNull: () => ZodNull,
7667
- ZodNever: () => ZodNever,
7668
- ZodNativeEnum: () => ZodNativeEnum,
7669
- ZodNaN: () => ZodNaN,
7670
- ZodMap: () => ZodMap,
7671
- ZodLiteral: () => ZodLiteral,
7672
- ZodLazy: () => ZodLazy,
7673
- ZodIssueCode: () => ZodIssueCode,
7674
- ZodIntersection: () => ZodIntersection,
7675
- ZodFunction: () => ZodFunction,
7676
- ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
7677
- ZodError: () => ZodError,
7678
- ZodEnum: () => ZodEnum,
7679
- ZodEffects: () => ZodEffects,
7680
- ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
7681
- ZodDefault: () => ZodDefault,
7682
- ZodDate: () => ZodDate,
7683
- ZodCatch: () => ZodCatch,
7684
- ZodBranded: () => ZodBranded,
7685
- ZodBoolean: () => ZodBoolean,
7686
- ZodBigInt: () => ZodBigInt,
7687
- ZodArray: () => ZodArray,
7688
- ZodAny: () => ZodAny,
7689
- Schema: () => ZodType,
7690
- ParseStatus: () => ParseStatus,
7691
- OK: () => OK,
7692
- NEVER: () => NEVER,
7693
- INVALID: () => INVALID,
7694
- EMPTY_PATH: () => EMPTY_PATH,
7645
+ BRAND: () => BRAND,
7695
7646
  DIRTY: () => DIRTY,
7696
- BRAND: () => BRAND
7647
+ EMPTY_PATH: () => EMPTY_PATH,
7648
+ INVALID: () => INVALID,
7649
+ NEVER: () => NEVER,
7650
+ OK: () => OK,
7651
+ ParseStatus: () => ParseStatus,
7652
+ Schema: () => ZodType,
7653
+ ZodAny: () => ZodAny,
7654
+ ZodArray: () => ZodArray,
7655
+ ZodBigInt: () => ZodBigInt,
7656
+ ZodBoolean: () => ZodBoolean,
7657
+ ZodBranded: () => ZodBranded,
7658
+ ZodCatch: () => ZodCatch,
7659
+ ZodDate: () => ZodDate,
7660
+ ZodDefault: () => ZodDefault,
7661
+ ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
7662
+ ZodEffects: () => ZodEffects,
7663
+ ZodEnum: () => ZodEnum,
7664
+ ZodError: () => ZodError,
7665
+ ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
7666
+ ZodFunction: () => ZodFunction,
7667
+ ZodIntersection: () => ZodIntersection,
7668
+ ZodIssueCode: () => ZodIssueCode,
7669
+ ZodLazy: () => ZodLazy,
7670
+ ZodLiteral: () => ZodLiteral,
7671
+ ZodMap: () => ZodMap,
7672
+ ZodNaN: () => ZodNaN,
7673
+ ZodNativeEnum: () => ZodNativeEnum,
7674
+ ZodNever: () => ZodNever,
7675
+ ZodNull: () => ZodNull,
7676
+ ZodNullable: () => ZodNullable,
7677
+ ZodNumber: () => ZodNumber,
7678
+ ZodObject: () => ZodObject,
7679
+ ZodOptional: () => ZodOptional,
7680
+ ZodParsedType: () => ZodParsedType,
7681
+ ZodPipeline: () => ZodPipeline,
7682
+ ZodPromise: () => ZodPromise,
7683
+ ZodReadonly: () => ZodReadonly,
7684
+ ZodRecord: () => ZodRecord,
7685
+ ZodSchema: () => ZodType,
7686
+ ZodSet: () => ZodSet,
7687
+ ZodString: () => ZodString,
7688
+ ZodSymbol: () => ZodSymbol,
7689
+ ZodTransformer: () => ZodEffects,
7690
+ ZodTuple: () => ZodTuple,
7691
+ ZodType: () => ZodType,
7692
+ ZodUndefined: () => ZodUndefined,
7693
+ ZodUnion: () => ZodUnion,
7694
+ ZodUnknown: () => ZodUnknown,
7695
+ ZodVoid: () => ZodVoid,
7696
+ addIssueToContext: () => addIssueToContext,
7697
+ any: () => anyType,
7698
+ array: () => arrayType,
7699
+ bigint: () => bigIntType,
7700
+ boolean: () => booleanType,
7701
+ coerce: () => coerce,
7702
+ custom: () => custom,
7703
+ date: () => dateType,
7704
+ datetimeRegex: () => datetimeRegex,
7705
+ defaultErrorMap: () => en_default,
7706
+ discriminatedUnion: () => discriminatedUnionType,
7707
+ effect: () => effectsType,
7708
+ enum: () => enumType,
7709
+ function: () => functionType,
7710
+ getErrorMap: () => getErrorMap,
7711
+ getParsedType: () => getParsedType,
7712
+ instanceof: () => instanceOfType,
7713
+ intersection: () => intersectionType,
7714
+ isAborted: () => isAborted,
7715
+ isAsync: () => isAsync,
7716
+ isDirty: () => isDirty,
7717
+ isValid: () => isValid,
7718
+ late: () => late,
7719
+ lazy: () => lazyType,
7720
+ literal: () => literalType,
7721
+ makeIssue: () => makeIssue,
7722
+ map: () => mapType,
7723
+ nan: () => nanType,
7724
+ nativeEnum: () => nativeEnumType,
7725
+ never: () => neverType,
7726
+ null: () => nullType,
7727
+ nullable: () => nullableType,
7728
+ number: () => numberType,
7729
+ object: () => objectType,
7730
+ objectUtil: () => objectUtil,
7731
+ oboolean: () => oboolean,
7732
+ onumber: () => onumber,
7733
+ optional: () => optionalType,
7734
+ ostring: () => ostring,
7735
+ pipeline: () => pipelineType,
7736
+ preprocess: () => preprocessType,
7737
+ promise: () => promiseType,
7738
+ quotelessJson: () => quotelessJson,
7739
+ record: () => recordType,
7740
+ set: () => setType,
7741
+ setErrorMap: () => setErrorMap,
7742
+ strictObject: () => strictObjectType,
7743
+ string: () => stringType,
7744
+ symbol: () => symbolType,
7745
+ transformer: () => effectsType,
7746
+ tuple: () => tupleType,
7747
+ undefined: () => undefinedType,
7748
+ union: () => unionType,
7749
+ unknown: () => unknownType,
7750
+ util: () => util,
7751
+ void: () => voidType
7697
7752
  });
7698
7753
 
7699
7754
  // node_modules/zod/v3/helpers/util.js
@@ -11560,7 +11615,6 @@ var NEVER = INVALID;
11560
11615
  // src/config/schema.ts
11561
11616
  var defaultThresholds = {
11562
11617
  skillDescriptionBytes: 16000,
11563
- skills: 30,
11564
11618
  mcp: 5,
11565
11619
  agentsMdLines: 150,
11566
11620
  claudeMdLines: 200,
@@ -11580,12 +11634,12 @@ var defaultConfig = {
11580
11634
  };
11581
11635
  var thresholdsSchema = exports_external.object({
11582
11636
  skillDescriptionBytes: exports_external.number().int().positive().default(defaultThresholds.skillDescriptionBytes),
11583
- skills: exports_external.number().int().positive().default(defaultThresholds.skills),
11637
+ skills: exports_external.number().int().positive().optional(),
11584
11638
  mcp: exports_external.number().int().positive().default(defaultThresholds.mcp),
11585
11639
  agentsMdLines: exports_external.number().int().positive().default(defaultThresholds.agentsMdLines),
11586
11640
  claudeMdLines: exports_external.number().int().positive().default(defaultThresholds.claudeMdLines),
11587
11641
  agents: exports_external.number().int().positive().default(defaultThresholds.agents)
11588
- });
11642
+ }).strict();
11589
11643
  var configSchema = exports_external.object({
11590
11644
  skillPaths: exports_external.array(exports_external.string()).default(defaultConfig.skillPaths),
11591
11645
  mcpPaths: exports_external.array(exports_external.string()).default(defaultConfig.mcpPaths),
@@ -11597,7 +11651,7 @@ var configSchema = exports_external.object({
11597
11651
  includeGlobal: exports_external.boolean().default(false),
11598
11652
  requireLock: exports_external.boolean().default(false),
11599
11653
  thresholds: thresholdsSchema.default(defaultThresholds)
11600
- });
11654
+ }).strict();
11601
11655
 
11602
11656
  // src/config/load.ts
11603
11657
  function loadConfig(root, configPath) {
@@ -11617,9 +11671,16 @@ function loadConfig(root, configPath) {
11617
11671
  }
11618
11672
 
11619
11673
  // src/discover/index.ts
11620
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
11621
- import { homedir } from "node:os";
11622
- import { isAbsolute, join as join2, resolve } from "node:path";
11674
+ import { homedir as homedir3 } from "node:os";
11675
+ import { join as join8, resolve as resolve3 } from "node:path";
11676
+
11677
+ // src/discover/agents.ts
11678
+ import { existsSync as existsSync3, readdirSync, statSync } from "node:fs";
11679
+ import { join as join3 } from "node:path";
11680
+
11681
+ // src/discover/shared.ts
11682
+ import { closeSync, existsSync as existsSync2, openSync, readFileSync as readFileSync2, readSync } from "node:fs";
11683
+ import { join as join2, resolve } from "node:path";
11623
11684
 
11624
11685
  // node_modules/yaml/dist/index.js
11625
11686
  var composer = require_composer();
@@ -11667,18 +11728,40 @@ var $stringify = publicApi.stringify;
11667
11728
  var $visit = visit.visit;
11668
11729
  var $visitAsync = visit.visitAsync;
11669
11730
 
11670
- // src/discover/index.ts
11731
+ // src/discover/shared.ts
11671
11732
  var POLICY_CAP = 1e5;
11672
11733
  var SKILL_MD_CAP = 65536;
11734
+ function readCapped(path, cap) {
11735
+ const fd = openSync(path, "r");
11736
+ try {
11737
+ const buf = Buffer.allocUnsafe(cap + 1);
11738
+ const bytes = readSync(fd, buf, 0, cap + 1, 0);
11739
+ return { buf: buf.subarray(0, bytes), truncated: bytes > cap };
11740
+ } finally {
11741
+ closeSync(fd);
11742
+ }
11743
+ }
11744
+ var AGENT_CONFIG_SIGNALS = [
11745
+ ".claude",
11746
+ ".agents",
11747
+ ".mcp.json",
11748
+ "mcp.json",
11749
+ "AGENTS.md",
11750
+ "CLAUDE.md",
11751
+ "skills-lock.json"
11752
+ ];
11753
+ function hasAgentConfigSignal(dir) {
11754
+ return AGENT_CONFIG_SIGNALS.some((name) => existsSync2(join2(dir, name)));
11755
+ }
11673
11756
  function resolveRoot(startDir) {
11674
11757
  let dir = resolve(startDir);
11675
11758
  for (;; ) {
11676
- if (existsSync2(join2(dir, "package.json"))) {
11759
+ if (existsSync2(join2(dir, "package.json")) || hasAgentConfigSignal(dir)) {
11677
11760
  return dir;
11678
11761
  }
11679
11762
  const parent = resolve(dir, "..");
11680
11763
  if (parent === dir) {
11681
- throw new Error(`No package.json found walking up from ${resolve(startDir)}`);
11764
+ throw new Error(`No package.json or agent configuration found walking up from ${resolve(startDir)}`);
11682
11765
  }
11683
11766
  dir = parent;
11684
11767
  }
@@ -11706,25 +11789,6 @@ function readJsonConfig(path, errors3) {
11706
11789
  return;
11707
11790
  }
11708
11791
  }
11709
- var BUNDLED = "scripts|references|assets|templates|examples";
11710
- var REFERENCE_RE = new RegExp(`(?<![\\w/.-])(?:\\./)?((?:${BUNDLED})/[A-Za-z0-9_./-]+\\.[A-Za-z0-9]{1,10})(?![A-Za-z0-9_.-])`, "g");
11711
- var FENCE_BLOCK = /^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^[ \t]*\1[ \t]*$|$)/gm;
11712
- var INLINE_CODE = /`[^`\n]*`/g;
11713
- var URLS = /\b[a-z][a-z0-9+.-]*:\/\/\S+/gi;
11714
- function skillReferences(body) {
11715
- const prose = body.replace(FENCE_BLOCK, "").replace(INLINE_CODE, "").replace(URLS, "");
11716
- const out = [];
11717
- const seen = new Set;
11718
- for (const match of prose.matchAll(REFERENCE_RE)) {
11719
- const path = match[1];
11720
- if (path === undefined || seen.has(path)) {
11721
- continue;
11722
- }
11723
- seen.add(path);
11724
- out.push(path);
11725
- }
11726
- return out;
11727
- }
11728
11792
  function scalar(value) {
11729
11793
  if (typeof value === "string") {
11730
11794
  const trimmed = value.trim();
@@ -11739,14 +11803,18 @@ function readFrontmatter(skillMdPath, errors3) {
11739
11803
  let text;
11740
11804
  let truncated = false;
11741
11805
  try {
11742
- const buf = readFileSync2(skillMdPath);
11743
- truncated = buf.length > SKILL_MD_CAP;
11744
- text = buf.subarray(0, SKILL_MD_CAP).toString("utf8");
11806
+ const result = readCapped(skillMdPath, SKILL_MD_CAP);
11807
+ truncated = result.truncated;
11808
+ text = result.buf.subarray(0, SKILL_MD_CAP).toString("utf8");
11745
11809
  if (text.charCodeAt(0) === 65279) {
11746
11810
  text = text.slice(1);
11747
11811
  }
11748
11812
  text = text.replace(/\r\n/g, `
11749
11813
  `);
11814
+ if (truncated) {
11815
+ errors3.push({ path: skillMdPath, kind: "unexpected-shape", detail: `file exceeds ${SKILL_MD_CAP} byte scan cap` });
11816
+ return { hasFrontmatter: true, unparseable: true };
11817
+ }
11750
11818
  } catch (err) {
11751
11819
  errors3.push({
11752
11820
  path: skillMdPath,
@@ -11793,33 +11861,22 @@ function readFrontmatter(skillMdPath, errors3) {
11793
11861
  }
11794
11862
  const record = block;
11795
11863
  const out = { hasFrontmatter: true, body: text.slice(end + 4) };
11796
- const name = scalar(record.name);
11797
- if (name !== undefined) {
11798
- out.name = name;
11799
- }
11800
- const description = scalar(record.description);
11801
- if (description !== undefined) {
11802
- out.description = description;
11803
- }
11804
- return out;
11805
- }
11806
- function brokenReferences(body, skillDir, root) {
11807
- const isFile = (p) => {
11808
- try {
11809
- return statSync(p).isFile();
11810
- } catch {
11811
- return false;
11812
- }
11813
- };
11814
- return skillReferences(body).filter((rel) => !isFile(join2(skillDir, rel)) && !isFile(join2(root, rel)));
11815
- }
11816
- function discoverSkillsInDir(dir, source, errors3, root) {
11817
- if (!existsSync2(dir)) {
11818
- return [];
11864
+ const name = scalar(record.name);
11865
+ if (name !== undefined) {
11866
+ out.name = name;
11867
+ }
11868
+ const description = scalar(record.description);
11869
+ if (description !== undefined) {
11870
+ out.description = description;
11819
11871
  }
11872
+ return out;
11873
+ }
11874
+
11875
+ // src/discover/agents.ts
11876
+ function agentFiles(dir, errors3) {
11820
11877
  let entries;
11821
11878
  try {
11822
- entries = readdirSync(dir);
11879
+ entries = readdirSync(dir, { withFileTypes: true }).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
11823
11880
  } catch (err) {
11824
11881
  errors3.push({
11825
11882
  path: dir,
@@ -11828,144 +11885,66 @@ function discoverSkillsInDir(dir, source, errors3, root) {
11828
11885
  });
11829
11886
  return [];
11830
11887
  }
11831
- const skills = [];
11832
- for (const name of entries) {
11833
- if (name.startsWith(".") || name === "node_modules") {
11888
+ const out = [];
11889
+ for (const entry of entries) {
11890
+ if (entry.startsWith(".")) {
11834
11891
  continue;
11835
11892
  }
11836
- const skillDir = join2(dir, name);
11837
- let st;
11838
- try {
11839
- st = statSync(skillDir);
11840
- } catch {
11893
+ if (entry.endsWith("/")) {
11894
+ out.push(...agentFiles(join3(dir, entry.slice(0, -1)), errors3).map((p) => join3(entry.slice(0, -1), p)));
11841
11895
  continue;
11842
11896
  }
11843
- if (!st.isDirectory()) {
11897
+ out.push(entry);
11898
+ }
11899
+ return out;
11900
+ }
11901
+ function discoverAgents(root, errors3) {
11902
+ const dir = join3(root, ".claude", "agents");
11903
+ if (!existsSync3(dir)) {
11904
+ return [];
11905
+ }
11906
+ const entries = agentFiles(dir, errors3);
11907
+ const facts = [];
11908
+ for (const name of entries) {
11909
+ if (!name.endsWith(".md")) {
11844
11910
  continue;
11845
11911
  }
11846
- const skillMd = join2(skillDir, "SKILL.md");
11847
- let dirReadable = true;
11912
+ const filePath = join3(dir, name);
11848
11913
  try {
11849
- readdirSync(skillDir);
11850
- } catch (err) {
11851
- dirReadable = false;
11852
- errors3.push({
11853
- path: skillDir,
11854
- kind: "unreadable",
11855
- detail: err instanceof Error ? err.message : String(err)
11856
- });
11914
+ const st = statSync(filePath);
11915
+ if (!st.isFile()) {
11916
+ continue;
11917
+ }
11918
+ } catch {
11919
+ continue;
11857
11920
  }
11858
- const hasSkillMd = dirReadable && existsSync2(skillMd);
11859
- const fm = hasSkillMd ? readFrontmatter(skillMd, errors3) : { hasFrontmatter: false };
11921
+ const fm = readFrontmatter(filePath, errors3);
11860
11922
  const fact = {
11861
- id: name,
11862
- path: skillDir,
11863
- source,
11864
- hasSkillMd,
11923
+ name: name.slice(0, -".md".length),
11924
+ path: filePath,
11865
11925
  hasFrontmatter: fm.hasFrontmatter
11866
11926
  };
11867
- if (fm.unreadable === true || !dirReadable) {
11927
+ if (fm.unreadable === true) {
11868
11928
  fact.unreadable = true;
11869
11929
  }
11870
11930
  if (fm.unparseable === true) {
11871
11931
  fact.unparseableFrontmatter = true;
11872
11932
  }
11873
- if (fm.body !== undefined) {
11874
- const broken = brokenReferences(fm.body, skillDir, root);
11875
- if (broken.length > 0) {
11876
- fact.brokenReferences = broken;
11877
- }
11878
- }
11879
- if (fm.description !== undefined) {
11880
- fact.description = fm.description;
11881
- }
11882
11933
  if (fm.name !== undefined) {
11883
11934
  fact.frontmatterName = fm.name;
11884
11935
  }
11885
- skills.push(fact);
11886
- }
11887
- return skills;
11888
- }
11889
- function parseMcpServers(raw, filePath, errors3) {
11890
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
11891
- errors3.push({
11892
- path: filePath,
11893
- kind: "unexpected-shape",
11894
- detail: "MCP config root is not a JSON object"
11895
- });
11896
- return [];
11897
- }
11898
- const obj = raw;
11899
- let servers;
11900
- if ("mcpServers" in obj && obj.mcpServers !== null && typeof obj.mcpServers === "object" && !Array.isArray(obj.mcpServers)) {
11901
- servers = obj.mcpServers;
11902
- } else if (!("mcpServers" in obj) && Object.keys(obj).length > 0 && Object.values(obj).every((v) => v !== null && typeof v === "object" && !Array.isArray(v) && (("command" in v) || ("url" in v) || ("type" in v)))) {
11903
- servers = obj;
11904
- } else {
11905
- errors3.push({
11906
- path: filePath,
11907
- kind: "unexpected-shape",
11908
- detail: "no usable `mcpServers` object and the root is not a server map"
11909
- });
11910
- return [];
11911
- }
11912
- const facts = [];
11913
- for (const [name, value] of Object.entries(servers)) {
11914
- let hasCommand = false;
11915
- let hasUrl = false;
11916
- let transport;
11917
- const literalEnvKeys = [];
11918
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
11919
- const entry = value;
11920
- hasCommand = typeof entry.command === "string" && entry.command.length > 0;
11921
- hasUrl = typeof entry.url === "string" && entry.url.length > 0;
11922
- if (typeof entry.type === "string" && entry.type.length > 0) {
11923
- transport = entry.type;
11924
- }
11925
- const env = entry.env;
11926
- if (env !== null && typeof env === "object" && !Array.isArray(env)) {
11927
- for (const [key, val] of Object.entries(env)) {
11928
- if (typeof val === "string" && val.length > 0 && !val.includes("${") && !val.startsWith("$") && /(?:TOKEN|SECRET|KEY|PASSWORD|PASSWD|CREDENTIAL)S?$/i.test(key)) {
11929
- literalEnvKeys.push(key);
11930
- }
11931
- }
11932
- }
11933
- }
11934
- facts.push({
11935
- name,
11936
- path: filePath,
11937
- hasCommand,
11938
- hasUrl,
11939
- ...transport === undefined ? {} : { transport },
11940
- literalEnvKeys,
11941
- raw: JSON.stringify(value)
11942
- });
11943
- }
11944
- return facts;
11945
- }
11946
- function discoverMcp(root, mcpPaths, errors3) {
11947
- const facts = [];
11948
- const seen = new Set;
11949
- for (const rel of mcpPaths) {
11950
- const filePath = join2(root, rel);
11951
- if (!existsSync2(filePath)) {
11952
- continue;
11953
- }
11954
- const raw = readJsonConfig(filePath, errors3);
11955
- if (raw === undefined) {
11956
- continue;
11957
- }
11958
- for (const fact of parseMcpServers(raw, filePath, errors3)) {
11959
- const key = `${fact.name}@${fact.path}`;
11960
- if (seen.has(key)) {
11961
- continue;
11962
- }
11963
- seen.add(key);
11964
- facts.push(fact);
11936
+ if (fm.description !== undefined) {
11937
+ fact.description = fm.description;
11965
11938
  }
11939
+ facts.push(fact);
11966
11940
  }
11967
11941
  return facts;
11968
11942
  }
11943
+
11944
+ // src/discover/hooks.ts
11945
+ import { existsSync as existsSync4, statSync as statSync2 } from "node:fs";
11946
+ import { homedir } from "node:os";
11947
+ import { isAbsolute, join as join4 } from "node:path";
11969
11948
  var INTERPRETERS = /^(?:bash|sh|zsh|python3?|node|bun|deno)$/;
11970
11949
  var INLINE_CODE_FLAGS = /^-(?:e|p|c)$/;
11971
11950
  var SHELL_METACHARS = /[|;&`]|\$\(|\|\||&&/;
@@ -11981,7 +11960,9 @@ function hookScriptPath(command) {
11981
11960
  const unquote = (t) => t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'") ? t.slice(1, -1) : t;
11982
11961
  let i = 0;
11983
11962
  const first = unquote(tokens[0]);
11963
+ let interpreted = false;
11984
11964
  if (INTERPRETERS.test(first)) {
11965
+ interpreted = true;
11985
11966
  i = 1;
11986
11967
  while (i < tokens.length) {
11987
11968
  const flag = unquote(tokens[i]);
@@ -11995,7 +11976,7 @@ function hookScriptPath(command) {
11995
11976
  }
11996
11977
  }
11997
11978
  const candidate = tokens[i] === undefined ? undefined : unquote(tokens[i]);
11998
- if (candidate === undefined || !candidate.includes("/")) {
11979
+ if (candidate === undefined || !candidate.includes("/") && !(interpreted && /\.(?:js|mjs|cjs|ts|py|sh|bash|zsh)$/i.test(candidate))) {
11999
11980
  return;
12000
11981
  }
12001
11982
  if (/^[A-Za-z]:[/\\]/.test(candidate)) {
@@ -12016,21 +11997,25 @@ function resolveHookScript(root, raw) {
12016
11997
  }
12017
11998
  let expanded = extracted;
12018
11999
  if (expanded.startsWith("~/")) {
12019
- expanded = join2(homedir(), expanded.slice(2));
12000
+ expanded = join4(homedir(), expanded.slice(2));
12020
12001
  } else if (expanded.startsWith("$")) {
12021
12002
  expanded = expanded.replace(/^\$(?:CLAUDE_PROJECT_DIR|\{CLAUDE_PROJECT_DIR\})\/?/, "");
12022
12003
  }
12023
- const abs = isAbsolute(expanded) ? expanded : join2(root, expanded);
12024
- return { scriptPath: extracted, exists: existsSync2(abs) };
12004
+ const abs = isAbsolute(expanded) ? expanded : join4(root, expanded);
12005
+ try {
12006
+ return { scriptPath: extracted, exists: statSync2(abs).isFile() };
12007
+ } catch {
12008
+ return { scriptPath: extracted, exists: false };
12009
+ }
12025
12010
  }
12026
12011
  function discoverHooks(root, errors3) {
12027
12012
  const files = [
12028
- join2(root, ".claude", "settings.json"),
12029
- join2(root, ".claude", "settings.local.json")
12013
+ join4(root, ".claude", "settings.json"),
12014
+ join4(root, ".claude", "settings.local.json")
12030
12015
  ];
12031
12016
  const facts = [];
12032
12017
  for (const filePath of files) {
12033
- if (!existsSync2(filePath)) {
12018
+ if (!existsSync4(filePath)) {
12034
12019
  continue;
12035
12020
  }
12036
12021
  const raw = readJsonConfig(filePath, errors3);
@@ -12058,6 +12043,25 @@ function discoverHooks(root, errors3) {
12058
12043
  continue;
12059
12044
  }
12060
12045
  for (const [event, groups] of Object.entries(hooks)) {
12046
+ const malformed = !Array.isArray(groups) || groups.some((group) => {
12047
+ if (group === null || typeof group !== "object" || Array.isArray(group)) {
12048
+ return true;
12049
+ }
12050
+ const inner = group.hooks;
12051
+ if (!Array.isArray(inner))
12052
+ return true;
12053
+ return inner.some((entry) => {
12054
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
12055
+ return true;
12056
+ }
12057
+ const item = entry;
12058
+ return item.command !== undefined && (typeof item.command !== "string" || item.command.length === 0) || item.type === "command" && typeof item.command !== "string";
12059
+ });
12060
+ });
12061
+ if (malformed) {
12062
+ errors3.push({ path: filePath, kind: "unexpected-shape", detail: `invalid hook groups for ${event}` });
12063
+ continue;
12064
+ }
12061
12065
  const commands = collectHookCommands(groups);
12062
12066
  if (commands.length === 0) {
12063
12067
  facts.push({ name: event, path: filePath, event });
@@ -12101,84 +12105,157 @@ function collectHookCommands(groups) {
12101
12105
  }
12102
12106
  return out;
12103
12107
  }
12104
- function agentFiles(dir, errors3) {
12105
- let entries;
12108
+
12109
+ // src/discover/mcp.ts
12110
+ import { existsSync as existsSync5, statSync as statSync3 } from "node:fs";
12111
+ import { homedir as homedir2 } from "node:os";
12112
+ import { isAbsolute as isAbsolute2, join as join5 } from "node:path";
12113
+ var SHELL_METACHARS2 = /[|;&`]|\$\(|\|\||&&|\s/;
12114
+ function mcpCommandPath(command) {
12115
+ const trimmed = command.trim();
12116
+ if (trimmed.length === 0 || SHELL_METACHARS2.test(trimmed)) {
12117
+ return;
12118
+ }
12119
+ if (/^[A-Za-z]:[/\\]/.test(trimmed)) {
12120
+ return;
12121
+ }
12122
+ if (trimmed.startsWith("$")) {
12123
+ return /^\$(?:CLAUDE_PROJECT_DIR\b|\{CLAUDE_PROJECT_DIR\})/.test(trimmed) ? trimmed : undefined;
12124
+ }
12125
+ if (trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~/") || trimmed.startsWith(".")) {
12126
+ return trimmed;
12127
+ }
12128
+ if (trimmed.includes("/")) {
12129
+ return trimmed;
12130
+ }
12131
+ return;
12132
+ }
12133
+ function resolveMcpCommand(root, raw) {
12134
+ const extracted = mcpCommandPath(raw);
12135
+ if (extracted === undefined) {
12136
+ return;
12137
+ }
12138
+ let expanded = extracted;
12139
+ if (expanded.startsWith("~/")) {
12140
+ expanded = join5(homedir2(), expanded.slice(2));
12141
+ } else if (expanded.startsWith("$")) {
12142
+ expanded = expanded.replace(/^\$(?:CLAUDE_PROJECT_DIR|\{CLAUDE_PROJECT_DIR\})\/?/, "");
12143
+ }
12144
+ const abs = isAbsolute2(expanded) ? expanded : join5(root, expanded);
12106
12145
  try {
12107
- entries = readdirSync(dir, { withFileTypes: true }).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
12108
- } catch (err) {
12146
+ return { command: extracted, exists: statSync3(abs).isFile() };
12147
+ } catch {
12148
+ return { command: extracted, exists: false };
12149
+ }
12150
+ }
12151
+ function parseMcpServers(raw, filePath, root, errors3) {
12152
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
12109
12153
  errors3.push({
12110
- path: dir,
12111
- kind: "unreadable",
12112
- detail: err instanceof Error ? err.message : String(err)
12154
+ path: filePath,
12155
+ kind: "unexpected-shape",
12156
+ detail: "MCP config root is not a JSON object"
12113
12157
  });
12114
12158
  return [];
12115
12159
  }
12116
- const out = [];
12117
- for (const entry of entries) {
12118
- if (entry.startsWith(".")) {
12119
- continue;
12120
- }
12121
- if (entry.endsWith("/")) {
12122
- out.push(...agentFiles(join2(dir, entry.slice(0, -1)), errors3).map((p) => join2(entry.slice(0, -1), p)));
12123
- continue;
12160
+ const obj = raw;
12161
+ let servers;
12162
+ if ("mcpServers" in obj && obj.mcpServers !== null && typeof obj.mcpServers === "object" && !Array.isArray(obj.mcpServers)) {
12163
+ servers = obj.mcpServers;
12164
+ } else if (!("mcpServers" in obj) && Object.keys(obj).length > 0 && Object.values(obj).every((v) => v !== null && typeof v === "object" && !Array.isArray(v) && (("command" in v) || ("url" in v) || ("type" in v)))) {
12165
+ servers = obj;
12166
+ } else {
12167
+ errors3.push({
12168
+ path: filePath,
12169
+ kind: "unexpected-shape",
12170
+ detail: "no usable `mcpServers` object and the root is not a server map"
12171
+ });
12172
+ return [];
12173
+ }
12174
+ const facts = [];
12175
+ for (const [name, value] of Object.entries(servers)) {
12176
+ let hasCommand = false;
12177
+ let hasUrl = false;
12178
+ let command;
12179
+ let commandExists;
12180
+ let transport;
12181
+ const literalEnvKeys = [];
12182
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
12183
+ const entry = value;
12184
+ hasCommand = typeof entry.command === "string" && entry.command.length > 0;
12185
+ if (typeof entry.command === "string" && entry.command.length > 0) {
12186
+ command = entry.command;
12187
+ const resolved = resolveMcpCommand(root, entry.command);
12188
+ if (resolved !== undefined) {
12189
+ commandExists = resolved.exists;
12190
+ }
12191
+ }
12192
+ hasUrl = typeof entry.url === "string" && entry.url.length > 0;
12193
+ if (typeof entry.type === "string" && entry.type.length > 0) {
12194
+ transport = entry.type;
12195
+ }
12196
+ const env = entry.env;
12197
+ if (env !== null && typeof env === "object" && !Array.isArray(env)) {
12198
+ for (const [key, val] of Object.entries(env)) {
12199
+ if (typeof val === "string" && val.length > 0 && !/\$\{[A-Za-z_][A-Za-z0-9_]*(?::-[^}]*)?\}/.test(val) && /(?:TOKEN|SECRET|KEY|PASSWORD|PASSWD|CREDENTIAL)S?$/i.test(key)) {
12200
+ literalEnvKeys.push(key);
12201
+ }
12202
+ }
12203
+ }
12124
12204
  }
12125
- out.push(entry);
12205
+ facts.push({
12206
+ name,
12207
+ path: filePath,
12208
+ hasCommand,
12209
+ hasUrl,
12210
+ ...command === undefined ? {} : { command },
12211
+ ...commandExists === undefined ? {} : { commandExists },
12212
+ ...transport === undefined ? {} : { transport },
12213
+ literalEnvKeys,
12214
+ raw: JSON.stringify(value)
12215
+ });
12126
12216
  }
12127
- return out;
12217
+ return facts;
12128
12218
  }
12129
- function discoverAgents(root, errors3) {
12130
- const dir = join2(root, ".claude", "agents");
12131
- if (!existsSync2(dir)) {
12132
- return [];
12133
- }
12134
- const entries = agentFiles(dir, errors3);
12219
+ function discoverMcp(root, mcpPaths, errors3) {
12135
12220
  const facts = [];
12136
- for (const name of entries) {
12137
- if (!name.endsWith(".md")) {
12221
+ const seen = new Set;
12222
+ for (const rel of mcpPaths) {
12223
+ const filePath = join5(root, rel);
12224
+ if (!existsSync5(filePath)) {
12138
12225
  continue;
12139
12226
  }
12140
- const filePath = join2(dir, name);
12141
- try {
12142
- const st = statSync(filePath);
12143
- if (!st.isFile()) {
12144
- continue;
12145
- }
12146
- } catch {
12227
+ const raw = readJsonConfig(filePath, errors3);
12228
+ if (raw === undefined) {
12147
12229
  continue;
12148
12230
  }
12149
- const fm = readFrontmatter(filePath, errors3);
12150
- const fact = {
12151
- name: name.slice(0, -".md".length),
12152
- path: filePath,
12153
- hasFrontmatter: fm.hasFrontmatter
12154
- };
12155
- if (fm.unreadable === true) {
12156
- fact.unreadable = true;
12157
- }
12158
- if (fm.unparseable === true) {
12159
- fact.unparseableFrontmatter = true;
12160
- }
12161
- if (fm.name !== undefined) {
12162
- fact.frontmatterName = fm.name;
12163
- }
12164
- if (fm.description !== undefined) {
12165
- fact.description = fm.description;
12231
+ for (const fact of parseMcpServers(raw, filePath, root, errors3)) {
12232
+ const key = `${fact.name}@${fact.path}`;
12233
+ if (seen.has(key)) {
12234
+ continue;
12235
+ }
12236
+ seen.add(key);
12237
+ facts.push(fact);
12166
12238
  }
12167
- facts.push(fact);
12168
12239
  }
12169
12240
  return facts;
12170
12241
  }
12242
+
12243
+ // src/discover/policy.ts
12244
+ import { existsSync as existsSync6 } from "node:fs";
12245
+ import { join as join6 } from "node:path";
12171
12246
  function discoverPolicyFiles(root, policyFiles, errors3) {
12172
12247
  const out = [];
12173
12248
  for (const rel of policyFiles) {
12174
- const filePath = join2(root, rel);
12175
- if (!existsSync2(filePath)) {
12249
+ const filePath = join6(root, rel);
12250
+ if (!existsSync6(filePath)) {
12176
12251
  continue;
12177
12252
  }
12178
12253
  try {
12179
- const buf = readFileSync2(filePath);
12180
- const text = buf.subarray(0, POLICY_CAP).toString("utf8");
12254
+ const result = readCapped(filePath, POLICY_CAP);
12255
+ const text = result.buf.subarray(0, POLICY_CAP).toString("utf8");
12181
12256
  out.push({ path: filePath, text });
12257
+ if (result.truncated)
12258
+ errors3.push({ path: filePath, kind: "unexpected-shape", detail: `file exceeds ${POLICY_CAP} byte scan cap` });
12182
12259
  } catch (err) {
12183
12260
  errors3.push({
12184
12261
  path: filePath,
@@ -12190,13 +12267,13 @@ function discoverPolicyFiles(root, policyFiles, errors3) {
12190
12267
  return out;
12191
12268
  }
12192
12269
  function discoverSkillsLock(root, errors3) {
12193
- const filePath = join2(root, "skills-lock.json");
12194
- if (!existsSync2(filePath)) {
12270
+ const filePath = join6(root, "skills-lock.json");
12271
+ if (!existsSync6(filePath)) {
12195
12272
  return { locked: [], present: false };
12196
12273
  }
12197
12274
  const raw = readJsonConfig(filePath, errors3);
12198
12275
  if (raw === undefined) {
12199
- return { locked: [], present: true };
12276
+ return { locked: [], present: true, invalid: true };
12200
12277
  }
12201
12278
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
12202
12279
  errors3.push({
@@ -12204,7 +12281,7 @@ function discoverSkillsLock(root, errors3) {
12204
12281
  kind: "unexpected-shape",
12205
12282
  detail: "skills-lock.json is not a JSON object"
12206
12283
  });
12207
- return { locked: [], present: true };
12284
+ return { locked: [], present: true, invalid: true };
12208
12285
  }
12209
12286
  const skills = raw.skills;
12210
12287
  if (skills === null || typeof skills !== "object" || Array.isArray(skills)) {
@@ -12213,7 +12290,7 @@ function discoverSkillsLock(root, errors3) {
12213
12290
  kind: "unexpected-shape",
12214
12291
  detail: "skills-lock.json has no `skills` object"
12215
12292
  });
12216
- return { locked: [], present: true };
12293
+ return { locked: [], present: true, invalid: true };
12217
12294
  }
12218
12295
  const locked = [];
12219
12296
  for (const [id, value] of Object.entries(skills)) {
@@ -12234,50 +12311,219 @@ function discoverSkillsLock(root, errors3) {
12234
12311
  }
12235
12312
  return { locked, present: true };
12236
12313
  }
12314
+
12315
+ // src/discover/skills.ts
12316
+ import { existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
12317
+ import { join as join7, resolve as resolve2 } from "node:path";
12318
+ var BUNDLED = "scripts|references|assets|templates|examples";
12319
+ var REFERENCE_RE = new RegExp(`(?<![\\w/.-])(?:\\./)?((?:${BUNDLED})/[A-Za-z0-9_./-]+\\.[A-Za-z0-9]{1,10})(?![A-Za-z0-9_.-])`, "g");
12320
+ var FENCE_BLOCK = /^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^[ \t]*\1[ \t]*$|$)/gm;
12321
+ var INLINE_CODE = /`[^`\n]*`/g;
12322
+ var URLS = /\b[a-z][a-z0-9+.-]*:\/\/\S+/gi;
12323
+ function skillReferences(body) {
12324
+ const prose = body.replace(FENCE_BLOCK, "").replace(INLINE_CODE, (code) => /(?:scripts|references|assets|templates|examples)\/[A-Za-z0-9_./-]+\.[A-Za-z0-9]{1,10}/.test(code) ? code : "").replace(URLS, "");
12325
+ const out = [];
12326
+ const seen = new Set;
12327
+ for (const match of prose.matchAll(REFERENCE_RE)) {
12328
+ const path = match[1];
12329
+ if (path === undefined || seen.has(path)) {
12330
+ continue;
12331
+ }
12332
+ seen.add(path);
12333
+ out.push(path);
12334
+ }
12335
+ return out;
12336
+ }
12337
+ function brokenReferences(body, skillDir, root) {
12338
+ const isFile = (p) => {
12339
+ try {
12340
+ return statSync4(p).isFile();
12341
+ } catch {
12342
+ return false;
12343
+ }
12344
+ };
12345
+ return skillReferences(body).filter((rel) => !isFile(join7(skillDir, rel)) && !isFile(join7(root, rel)));
12346
+ }
12347
+ function discoverSkillsInDir(dir, source, errors3, root) {
12348
+ if (!existsSync7(dir)) {
12349
+ return [];
12350
+ }
12351
+ let entries;
12352
+ try {
12353
+ entries = readdirSync2(dir);
12354
+ } catch (err) {
12355
+ errors3.push({
12356
+ path: dir,
12357
+ kind: "unreadable",
12358
+ detail: err instanceof Error ? err.message : String(err)
12359
+ });
12360
+ return [];
12361
+ }
12362
+ const skills = [];
12363
+ const normalizedDir = dir.replaceAll("\\", "/");
12364
+ const runtime = normalizedDir.includes("/.claude/skills") || normalizedDir.endsWith("/.claude/skills") ? "claude" : normalizedDir.includes("/.agents/skills") || normalizedDir.endsWith("/.agents/skills") ? "agents" : "unknown";
12365
+ for (const name of entries) {
12366
+ if (name.startsWith(".") || name === "node_modules") {
12367
+ continue;
12368
+ }
12369
+ const skillDir = join7(dir, name);
12370
+ let st;
12371
+ try {
12372
+ st = statSync4(skillDir);
12373
+ } catch {
12374
+ continue;
12375
+ }
12376
+ if (!st.isDirectory()) {
12377
+ continue;
12378
+ }
12379
+ const skillMd = join7(skillDir, "SKILL.md");
12380
+ let dirReadable = true;
12381
+ try {
12382
+ readdirSync2(skillDir);
12383
+ } catch (err) {
12384
+ dirReadable = false;
12385
+ errors3.push({
12386
+ path: skillDir,
12387
+ kind: "unreadable",
12388
+ detail: err instanceof Error ? err.message : String(err)
12389
+ });
12390
+ }
12391
+ const hasSkillMd = dirReadable && existsSync7(skillMd);
12392
+ const fm = hasSkillMd ? readFrontmatter(skillMd, errors3) : { hasFrontmatter: false };
12393
+ const fact = {
12394
+ id: name,
12395
+ runtime,
12396
+ path: skillDir,
12397
+ source,
12398
+ hasSkillMd,
12399
+ hasFrontmatter: fm.hasFrontmatter
12400
+ };
12401
+ if (fm.unreadable === true || !dirReadable) {
12402
+ fact.unreadable = true;
12403
+ }
12404
+ if (fm.unparseable === true) {
12405
+ fact.unparseableFrontmatter = true;
12406
+ }
12407
+ if (fm.body !== undefined) {
12408
+ const broken = brokenReferences(fm.body, skillDir, root);
12409
+ if (broken.length > 0) {
12410
+ fact.brokenReferences = broken;
12411
+ }
12412
+ }
12413
+ if (fm.description !== undefined) {
12414
+ fact.description = fm.description;
12415
+ }
12416
+ if (fm.name !== undefined) {
12417
+ fact.frontmatterName = fm.name;
12418
+ }
12419
+ skills.push(fact);
12420
+ }
12421
+ return skills;
12422
+ }
12423
+ var NESTED_DISCOVERY_MAX_DEPTH = 8;
12424
+ var NESTED_DISCOVERY_SKIP = new Set([
12425
+ ".git",
12426
+ "node_modules",
12427
+ ".next",
12428
+ "dist",
12429
+ "build",
12430
+ "coverage",
12431
+ "vendor"
12432
+ ]);
12433
+ function discoverNestedClaudeSkills(root, configuredRoots, errors3, options = {}) {
12434
+ const out = [];
12435
+ const read = (dir) => {
12436
+ options.onDirectoryRead?.();
12437
+ try {
12438
+ return readdirSync2(dir, { withFileTypes: true });
12439
+ } catch {
12440
+ errors3.push({ path: dir, kind: "unreadable", detail: "could not read nested skill directories" });
12441
+ return;
12442
+ }
12443
+ };
12444
+ const inspectClaude = (dir) => {
12445
+ const entries = read(dir);
12446
+ if (entries === undefined)
12447
+ return;
12448
+ for (const entry of entries) {
12449
+ if (!entry.isDirectory() || entry.name !== "skills")
12450
+ continue;
12451
+ const child = join7(dir, entry.name);
12452
+ if (configuredRoots.has(resolve2(child)))
12453
+ continue;
12454
+ out.push(...discoverSkillsInDir(child, "project", errors3, root));
12455
+ }
12456
+ };
12457
+ const walk = (dir, depth) => {
12458
+ const entries = read(dir);
12459
+ if (entries === undefined)
12460
+ return;
12461
+ for (const entry of entries) {
12462
+ if (!entry.isDirectory() || NESTED_DISCOVERY_SKIP.has(entry.name))
12463
+ continue;
12464
+ if (entry.name.startsWith(".") && entry.name !== ".claude")
12465
+ continue;
12466
+ const child = join7(dir, entry.name);
12467
+ if (entry.name === ".claude") {
12468
+ inspectClaude(child);
12469
+ continue;
12470
+ }
12471
+ if (depth >= NESTED_DISCOVERY_MAX_DEPTH)
12472
+ continue;
12473
+ walk(child, depth + 1);
12474
+ }
12475
+ };
12476
+ walk(root, 0);
12477
+ return out;
12478
+ }
12479
+ function disambiguateSkills(skills, root) {
12480
+ const counts = new Map;
12481
+ for (const skill of skills)
12482
+ counts.set(skill.id, (counts.get(skill.id) ?? 0) + 1);
12483
+ return skills.map((skill) => {
12484
+ if ((counts.get(skill.id) ?? 0) < 2)
12485
+ return skill;
12486
+ const location = skill.source === "project" ? skill.path.slice(root.length + 1) : `global:${skill.path}`;
12487
+ return { ...skill, instanceId: `${skill.id}@${location}` };
12488
+ });
12489
+ }
12490
+
12491
+ // src/discover/index.ts
12237
12492
  function discoverAgentSurface(root, config, opts) {
12238
12493
  const configErrors = [];
12239
12494
  const skills = [];
12240
12495
  for (const rel of config.skillPaths) {
12241
- skills.push(...discoverSkillsInDir(join2(root, rel), "project", configErrors, root));
12496
+ skills.push(...discoverSkillsInDir(join8(root, rel), "project", configErrors, root));
12242
12497
  }
12498
+ const configuredRoots = new Set(config.skillPaths.map((rel) => resolve3(root, rel)));
12499
+ skills.push(...discoverNestedClaudeSkills(root, configuredRoots, configErrors, {
12500
+ onDirectoryRead: opts.onNestedDirectoryRead
12501
+ }));
12243
12502
  if (opts.includeGlobal) {
12244
- const home = homedir();
12245
- skills.push(...discoverSkillsInDir(join2(home, ".claude", "skills"), "global", configErrors, root));
12246
- skills.push(...discoverSkillsInDir(join2(home, ".codex", "skills"), "global", configErrors, root));
12503
+ const home = homedir3();
12504
+ skills.push(...discoverSkillsInDir(join8(home, ".claude", "skills"), "global", configErrors, root));
12505
+ skills.push(...discoverSkillsInDir(join8(home, ".codex", "skills"), "global", configErrors, root));
12247
12506
  }
12248
12507
  const lock = discoverSkillsLock(root, configErrors);
12249
12508
  return {
12250
- skills: dedupeSkillsById(skills),
12509
+ skills: disambiguateSkills(skills, root),
12251
12510
  agents: discoverAgents(root, configErrors),
12252
12511
  hooks: discoverHooks(root, configErrors),
12253
12512
  mcp: discoverMcp(root, config.mcpPaths, configErrors),
12254
12513
  policyFiles: discoverPolicyFiles(root, config.policyFiles, configErrors),
12255
12514
  lockedSkills: lock.locked,
12256
12515
  hasSkillsLock: lock.present,
12516
+ skillsLockInvalid: lock.invalid,
12257
12517
  configErrors
12258
12518
  };
12259
12519
  }
12260
- function dedupeSkillsById(skills) {
12261
- const byId = new Map;
12262
- for (const skill of skills) {
12263
- const existing = byId.get(skill.id);
12264
- if (!existing) {
12265
- byId.set(skill.id, skill);
12266
- continue;
12267
- }
12268
- if (existing.source === "global" && skill.source === "project") {
12269
- byId.set(skill.id, skill);
12270
- }
12271
- }
12272
- return [...byId.values()];
12273
- }
12274
12520
 
12275
12521
  // src/facts/extract.ts
12276
- import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "node:fs";
12277
- import { join as join3 } from "node:path";
12522
+ import { existsSync as existsSync8, readFileSync as readFileSync3 } from "node:fs";
12523
+ import { join as join9 } from "node:path";
12278
12524
  function readPackageJson(root, errors3) {
12279
- const path = join3(root, "package.json");
12280
- if (!existsSync3(path)) {
12525
+ const path = join9(root, "package.json");
12526
+ if (!existsSync8(path)) {
12281
12527
  return {};
12282
12528
  }
12283
12529
  let text;
@@ -12315,7 +12561,7 @@ function readPackageJson(root, errors3) {
12315
12561
  if (typeof obj.packageManager === "string") {
12316
12562
  pkg.packageManager = obj.packageManager;
12317
12563
  }
12318
- for (const field of ["dependencies", "devDependencies", "scripts"]) {
12564
+ for (const field of ["dependencies", "devDependencies"]) {
12319
12565
  const raw2 = obj[field];
12320
12566
  const kept = stringEntries(raw2);
12321
12567
  if (kept === undefined) {
@@ -12337,6 +12583,19 @@ function readPackageJson(root, errors3) {
12337
12583
  }
12338
12584
  pkg[field] = kept.values;
12339
12585
  }
12586
+ const scripts = obj.scripts;
12587
+ if (scripts !== undefined) {
12588
+ const kept = stringEntries(scripts);
12589
+ if (kept === undefined) {
12590
+ errors3.push({ path, kind: "unexpected-shape", detail: "scripts is not an object" });
12591
+ } else if (kept.dropped > 0) {
12592
+ errors3.push({
12593
+ path,
12594
+ kind: "unexpected-shape",
12595
+ detail: `scripts: ${kept.dropped} entr${kept.dropped === 1 ? "y" : "ies"} ignored (value is not a string)`
12596
+ });
12597
+ }
12598
+ }
12340
12599
  return pkg;
12341
12600
  }
12342
12601
  function stringEntries(value) {
@@ -12362,95 +12621,32 @@ function inferPackageManager(root, pkg) {
12362
12621
  return prefix;
12363
12622
  }
12364
12623
  }
12365
- if (existsSync3(join3(root, "bun.lock")) || existsSync3(join3(root, "bun.lockb"))) {
12624
+ if (existsSync8(join9(root, "bun.lock")) || existsSync8(join9(root, "bun.lockb"))) {
12366
12625
  return "bun";
12367
12626
  }
12368
- if (existsSync3(join3(root, "pnpm-lock.yaml"))) {
12627
+ if (existsSync8(join9(root, "pnpm-lock.yaml"))) {
12369
12628
  return "pnpm";
12370
12629
  }
12371
- if (existsSync3(join3(root, "yarn.lock"))) {
12630
+ if (existsSync8(join9(root, "yarn.lock"))) {
12372
12631
  return "yarn";
12373
12632
  }
12374
- if (existsSync3(join3(root, "package-lock.json"))) {
12633
+ if (existsSync8(join9(root, "package-lock.json"))) {
12375
12634
  return "npm";
12376
12635
  }
12377
12636
  return "unknown";
12378
12637
  }
12379
- function hasNextConfig(root) {
12380
- try {
12381
- const entries = readdirSync2(root);
12382
- return entries.some((name) => name === "next.config.js" || name === "next.config.mjs" || name === "next.config.cjs" || name === "next.config.ts" || name === "next.config.mts");
12383
- } catch {
12384
- return false;
12385
- }
12386
- }
12387
- function readNextConfigText(root) {
12388
- const candidates = [
12389
- "next.config.ts",
12390
- "next.config.mjs",
12391
- "next.config.js",
12392
- "next.config.mts",
12393
- "next.config.cjs"
12394
- ];
12395
- for (const name of candidates) {
12396
- const path = join3(root, name);
12397
- if (!existsSync3(path)) {
12398
- continue;
12399
- }
12400
- try {
12401
- return readFileSync3(path, "utf8");
12402
- } catch {
12403
- return;
12404
- }
12405
- }
12406
- return;
12407
- }
12408
- function extractConfigs(root, deps, devDeps, scripts) {
12409
- const configs = {};
12410
- if (existsSync3(join3(root, "components.json"))) {
12411
- configs.shadcn = true;
12412
- }
12413
- if (existsSync3(join3(root, "biome.json")) || existsSync3(join3(root, "biome.jsonc"))) {
12414
- configs.biome = true;
12415
- }
12416
- const allDeps = { ...deps, ...devDeps };
12417
- const hasUltraciteDep = "ultracite" in allDeps;
12418
- const hasUltraciteScript = Object.values(scripts).some((s) => s.includes("ultracite"));
12419
- if (hasUltraciteDep || hasUltraciteScript) {
12420
- configs.ultracite = true;
12421
- }
12422
- if ("next" in allDeps) {
12423
- const next = {};
12424
- if (hasNextConfig(root)) {
12425
- next.appRouter = true;
12426
- const text = readNextConfigText(root);
12427
- if (text !== undefined) {
12428
- if (/\bcacheComponents\s*:\s*true\b/.test(text) || /\bcacheComponents\s*:\s*!0\b/.test(text)) {
12429
- next.cacheComponents = true;
12430
- } else if (/\bcacheComponents\s*:\s*false\b/.test(text) || /\bcacheComponents\s*:\s*!1\b/.test(text)) {
12431
- next.cacheComponents = false;
12432
- }
12433
- }
12434
- }
12435
- configs.next = next;
12436
- }
12437
- return configs;
12438
- }
12439
12638
  function extractFacts(root, config, opts) {
12440
12639
  const includeGlobal = opts?.includeGlobal ?? config.includeGlobal;
12441
12640
  const packageErrors = [];
12442
12641
  const pkg = readPackageJson(root, packageErrors);
12443
12642
  const dependencies = { ...pkg.dependencies ?? {} };
12444
12643
  const devDependencies = { ...pkg.devDependencies ?? {} };
12445
- const scripts = { ...pkg.scripts ?? {} };
12446
12644
  const surface = discoverAgentSurface(root, config, { includeGlobal });
12447
12645
  return {
12448
12646
  root,
12449
12647
  packageManager: inferPackageManager(root, pkg),
12450
12648
  dependencies,
12451
12649
  devDependencies,
12452
- scripts,
12453
- configs: extractConfigs(root, dependencies, devDependencies, scripts),
12454
12650
  skills: surface.skills,
12455
12651
  agents: surface.agents,
12456
12652
  hooks: surface.hooks,
@@ -12458,6 +12654,7 @@ function extractFacts(root, config, opts) {
12458
12654
  policyFiles: surface.policyFiles,
12459
12655
  lockedSkills: surface.lockedSkills,
12460
12656
  hasSkillsLock: surface.hasSkillsLock,
12657
+ skillsLockInvalid: surface.skillsLockInvalid,
12461
12658
  configErrors: [...packageErrors, ...surface.configErrors]
12462
12659
  };
12463
12660
  }
@@ -12487,7 +12684,7 @@ function compareAction(a, b) {
12487
12684
 
12488
12685
  // src/analyze.ts
12489
12686
  function analyze(options = {}) {
12490
- const requested = resolve2(options.dir ?? process.cwd());
12687
+ const requested = resolve4(options.dir ?? process.cwd());
12491
12688
  const root = resolveRoot(requested);
12492
12689
  const loaded = loadConfig(root, options.configPath);
12493
12690
  const config = {
@@ -12508,7 +12705,7 @@ function analyze(options = {}) {
12508
12705
  agents: config.thresholds.agents,
12509
12706
  mcp: config.thresholds.mcp
12510
12707
  }
12511
- }).filter((f) => !ignoredRules.has(f.ruleId) && !(f.subject.startsWith("skill:") && ignoredSkills.has(f.subject.slice("skill:".length))));
12708
+ }).filter((f) => !ignoredRules.has(f.ruleId) && !(f.subject.startsWith("skill:") && ignoredSkills.has(f.subject.slice("skill:".length).split("@", 1)[0] ?? "")));
12512
12709
  const ignoredFindings = new Set(config.ignoreFindings);
12513
12710
  const findings = sortFindings(structural.filter((f) => !ignoredFindings.has(f.id)));
12514
12711
  return {
@@ -12549,6 +12746,30 @@ function scoreLabel(points) {
12549
12746
  }
12550
12747
  return "broken";
12551
12748
  }
12749
+ function scoreTone(points) {
12750
+ if (points >= 90) {
12751
+ return "green";
12752
+ }
12753
+ if (points >= 50) {
12754
+ return "yellow";
12755
+ }
12756
+ return "red";
12757
+ }
12758
+ function scoreFace(points) {
12759
+ if (points === 100) {
12760
+ return ["◠ ◠", " ▽ "];
12761
+ }
12762
+ if (points >= 90) {
12763
+ return ["• •", " ‿ "];
12764
+ }
12765
+ if (points >= 70) {
12766
+ return ["• •", " ─ "];
12767
+ }
12768
+ if (points >= 50) {
12769
+ return ["◡ ◡", " ⌒ "];
12770
+ }
12771
+ return ["✕ ✕", " ⌒ "];
12772
+ }
12552
12773
 
12553
12774
  // src/report/exit-code.ts
12554
12775
  function exitCode(findings, failOn, failUnder) {
@@ -12673,37 +12894,82 @@ function headingFor(severity) {
12673
12894
 
12674
12895
  // src/report/text.ts
12675
12896
  import { basename as basename4 } from "node:path";
12676
- var ACTION_LABEL = {
12677
- keep: "KEEP",
12678
- delete: "DELETE",
12679
- add: "ADD",
12680
- refresh: "REFRESH",
12681
- warn: "WARN",
12682
- drift: "DRIFT"
12897
+
12898
+ // src/report/ansi.ts
12899
+ var CODES = {
12900
+ green: "\x1B[32m",
12901
+ yellow: "\x1B[33m",
12902
+ red: "\x1B[31m",
12903
+ dim: "\x1B[2m",
12904
+ bold: "\x1B[1m",
12905
+ reset: "\x1B[0m"
12906
+ };
12907
+ var PLAIN = {
12908
+ tone: (_tone, text) => text,
12909
+ strong: (_tone, text) => text,
12910
+ dim: (text) => text,
12911
+ bold: (text) => text,
12912
+ enabled: false
12913
+ };
12914
+ function paint(enabled) {
12915
+ if (!enabled) {
12916
+ return PLAIN;
12917
+ }
12918
+ return {
12919
+ tone: (tone, text) => `${CODES[tone]}${text}${CODES.reset}`,
12920
+ strong: (tone, text) => `${CODES.bold}${CODES[tone]}${text}${CODES.reset}`,
12921
+ dim: (text) => `${CODES.dim}${text}${CODES.reset}`,
12922
+ bold: (text) => `${CODES.bold}${text}${CODES.reset}`,
12923
+ enabled: true
12924
+ };
12925
+ }
12926
+ function shouldColour(args) {
12927
+ if (args.noColorFlag === true) {
12928
+ return false;
12929
+ }
12930
+ const { NO_COLOR, FORCE_COLOR } = args.env;
12931
+ if (NO_COLOR !== undefined && NO_COLOR !== "") {
12932
+ return false;
12933
+ }
12934
+ if (FORCE_COLOR !== undefined && FORCE_COLOR !== "" && FORCE_COLOR !== "0") {
12935
+ return true;
12936
+ }
12937
+ return args.isTTY;
12938
+ }
12939
+
12940
+ // src/report/text.ts
12941
+ var SEVERITY_LABEL = {
12942
+ error: "ERROR",
12943
+ warning: "WARN",
12944
+ info: "INFO"
12683
12945
  };
12684
- function actionColumn(action) {
12685
- return ACTION_LABEL[action].padEnd(7, " ");
12946
+ function severityColumn(severity) {
12947
+ return SEVERITY_LABEL[severity].padEnd(7, " ");
12686
12948
  }
12687
12949
  function renderText(args) {
12688
12950
  const { version, facts, verbose, quiet } = args;
12951
+ const ink = paint(args.colour === true);
12689
12952
  const sorted = sortFindings(args.findings);
12690
12953
  const visible = verbose ? sorted : sorted.filter((f) => f.action !== "keep" && f.severity !== "info");
12691
- const summary = formatSummary(sorted, verbose);
12954
+ const summary = formatSummary(sorted, verbose, ink);
12692
12955
  if (quiet) {
12693
12956
  return `${summary}
12694
12957
  `;
12695
12958
  }
12696
12959
  const lines = [];
12697
12960
  const name = projectLabel(facts.root);
12698
- lines.push(`agentscan v${version} — ${safe(name)}`);
12961
+ if (ink.enabled) {
12962
+ lines.push(...formatHeaderBox(sorted, verbose, safe(name), ink));
12963
+ }
12964
+ lines.push(ink.dim(`agentscan v${version} — ${safe(name)}`));
12699
12965
  if (args.resolvedFrom !== undefined) {
12700
12966
  lines.push(` no package.json in ${safe(args.resolvedFrom)} — scanned ${safe(facts.root)}`);
12701
12967
  }
12702
12968
  lines.push("");
12703
- lines.push(`Scanned: ${formatStack(facts)}`);
12969
+ lines.push(ink.dim(`Scanned: ${formatStack(facts)}`));
12704
12970
  lines.push("");
12705
12971
  for (const group of groupByRule(visible)) {
12706
- lines.push(...formatGroup(group, facts.root));
12972
+ lines.push(...formatGroup(group, facts.root, ink));
12707
12973
  lines.push("");
12708
12974
  }
12709
12975
  lines.push(summary);
@@ -12711,6 +12977,22 @@ function renderText(args) {
12711
12977
  return lines.join(`
12712
12978
  `);
12713
12979
  }
12980
+ var BAR_CELLS = 25;
12981
+ function formatHeaderBox(findings, verbose, name, ink) {
12982
+ const points = score(findings);
12983
+ const tone = scoreTone(points);
12984
+ const [eyes, mouth] = scoreFace(points);
12985
+ const filled = Math.round(points / 100 * BAR_CELLS);
12986
+ const bar = ink.tone(tone, "█".repeat(filled)) + ink.dim("░".repeat(BAR_CELLS - filled));
12987
+ const verdict = `${ink.strong(tone, `${points}/100`)} ${ink.tone(tone, scoreLabel(points))}${ink.dim(` · ${name}`)}`;
12988
+ return [
12989
+ ` ${ink.tone(tone, "┌─────┐")} ${verdict}`,
12990
+ ` ${ink.tone(tone, `│ ${eyes} │`)} ${bar}`,
12991
+ ` ${ink.tone(tone, `│ ${mouth} │`)} ${ink.dim(countsLine(findings, verbose))}`,
12992
+ ` ${ink.tone(tone, "└─────┘")}`,
12993
+ ""
12994
+ ];
12995
+ }
12714
12996
  function projectLabel(root) {
12715
12997
  const base = basename4(root);
12716
12998
  return base.length > 0 ? base : root;
@@ -12742,47 +13024,77 @@ function groupByRule(findings) {
12742
13024
  function shortPath(value, root) {
12743
13025
  return value.split(`${root}/`).join("");
12744
13026
  }
12745
- function formatGroup(group, root) {
13027
+ var SEVERITY_TONE = {
13028
+ error: "red",
13029
+ warning: "yellow",
13030
+ info: undefined
13031
+ };
13032
+ function formatGroup(group, root, ink) {
12746
13033
  const first = group[0];
12747
13034
  const count = group.length;
12748
13035
  const out = [];
12749
- out.push(`${actionColumn(first.action)} rule:${safe(first.ruleId)}${count > 1 ? ` ×${count}` : ""}`);
12750
- out.push(` ${safe(count === 1 ? first.message : ruleHeadline(first))}`);
13036
+ const tone = SEVERITY_TONE[first.severity];
13037
+ const label = severityColumn(first.severity);
13038
+ out.push(`${tone === undefined ? ink.dim(label) : ink.tone(tone, label)} ${ink.dim(`rule:${safe(first.ruleId)}`)}${count > 1 ? ink.bold(` ×${count}`) : ""}`);
13039
+ out.push(` ${safe(count === 1 ? first.message : groupHeadline(group))}`);
12751
13040
  for (const f of group) {
12752
13041
  const where = f.evidence.filter((e) => e.kind !== "count" && e.kind !== "threshold").map((e) => shortPath(e.value, root));
12753
13042
  const location = where.length > 0 ? where.join(" · ") : f.subject;
12754
- out.push(` ${safe(shortPath(location, root))}`);
13043
+ out.push(ink.dim(` ${safe(shortPath(location, root))}`));
12755
13044
  }
12756
13045
  return out;
12757
13046
  }
12758
- function ruleHeadline(f) {
12759
- const cut = f.message.indexOf(": ");
12760
- return cut === -1 ? f.message : f.message.slice(0, cut);
13047
+ function groupHeadline(group) {
13048
+ const stripped = group.map((f) => {
13049
+ const cut = f.message.indexOf(": ");
13050
+ return cut === -1 ? f.message : f.message.slice(0, cut);
13051
+ });
13052
+ if (new Set(stripped).size === 1) {
13053
+ return stripped[0];
13054
+ }
13055
+ const deEvented = stripped.map((s) => s.replace(/^[A-Za-z][A-Za-z0-9]* hook /, "Hook "));
13056
+ if (new Set(deEvented).size === 1) {
13057
+ return deEvented[0];
13058
+ }
13059
+ return `${group.length} findings`;
13060
+ }
13061
+ function countsLine(findings, verbose) {
13062
+ const parts = severityParts(findings, verbose);
13063
+ return parts.length === 0 ? "no findings" : parts.join(" · ");
12761
13064
  }
12762
- function formatSummary(findings, verbose) {
13065
+ function severityParts(findings, verbose) {
12763
13066
  const counts = {
12764
- keep: 0,
12765
- delete: 0,
12766
- add: 0,
12767
- refresh: 0,
12768
- warn: 0,
12769
- drift: 0
13067
+ error: 0,
13068
+ warning: 0,
13069
+ info: 0
12770
13070
  };
12771
13071
  let infoHidden = 0;
12772
13072
  for (const f of findings) {
13073
+ if (!verbose && f.action === "keep") {
13074
+ continue;
13075
+ }
12773
13076
  if (!verbose && f.severity === "info") {
12774
13077
  infoHidden += 1;
12775
13078
  continue;
12776
13079
  }
12777
- counts[f.action] += 1;
13080
+ counts[f.severity] += 1;
12778
13081
  }
12779
- const order = ["delete", "add", "refresh", "drift", "warn", "keep"];
12780
- const parts = order.filter((action) => counts[action] > 0).map((action) => `${counts[action]} ${action}`);
13082
+ const order = ["error", "warning", "info"];
13083
+ const labels = {
13084
+ error: "error",
13085
+ warning: "warning",
13086
+ info: "info"
13087
+ };
13088
+ const parts = order.filter((severity) => counts[severity] > 0).map((severity) => `${counts[severity]} ${labels[severity]}`);
12781
13089
  if (!verbose && infoHidden > 0) {
12782
13090
  parts.push(`${infoHidden} info hidden (--verbose)`);
12783
13091
  }
13092
+ return parts;
13093
+ }
13094
+ function formatSummary(findings, verbose, ink) {
13095
+ const parts = severityParts(findings, verbose);
12784
13096
  const points = score(findings);
12785
- const scored = `score ${points}/100 ${scoreLabel(points)}`;
13097
+ const scored = `score ${ink.tone(scoreTone(points), `${points}/100 ${scoreLabel(points)}`)}`;
12786
13098
  if (parts.length === 0) {
12787
13099
  return `Summary: no findings · ${scored}`;
12788
13100
  }
@@ -12790,7 +13102,7 @@ function formatSummary(findings, verbose) {
12790
13102
  }
12791
13103
 
12792
13104
  // src/version.ts
12793
- var VERSION = "0.3.0";
13105
+ var VERSION = "0.5.0";
12794
13106
 
12795
13107
  // src/commands/check.ts
12796
13108
  async function runCheck(options) {
@@ -12802,26 +13114,81 @@ async function runCheck(options) {
12802
13114
  });
12803
13115
  const format = options.output ?? (options.json === true ? "json" : "human");
12804
13116
  let stdout;
13117
+ let plain;
12805
13118
  if (format === "json") {
12806
13119
  stdout = renderJson({ version: VERSION, root, facts, findings, resolvedFrom });
12807
13120
  } else if (format === "prompt") {
12808
13121
  stdout = renderPrompt({ version: VERSION, facts, findings });
12809
13122
  } else {
12810
- stdout = renderText({
13123
+ const text = (colour) => renderText({
12811
13124
  version: VERSION,
12812
13125
  facts,
12813
13126
  findings,
12814
13127
  resolvedFrom,
12815
13128
  verbose: options.verbose ?? false,
12816
- quiet: options.quiet ?? false
13129
+ quiet: options.quiet ?? false,
13130
+ colour
12817
13131
  });
13132
+ stdout = text(options.colour === true);
13133
+ if (options.colour === true) {
13134
+ plain = text(false);
13135
+ }
12818
13136
  }
12819
13137
  return {
12820
13138
  exitCode: exitCode(findings, config.failOn, options.failUnder),
12821
- stdout
13139
+ stdout,
13140
+ ...plain === undefined ? {} : { plain }
12822
13141
  };
12823
13142
  }
12824
13143
 
13144
+ // src/commands/demo.ts
13145
+ import {
13146
+ mkdirSync,
13147
+ mkdtempSync,
13148
+ rmSync,
13149
+ writeFileSync
13150
+ } from "node:fs";
13151
+ import { tmpdir } from "node:os";
13152
+ import { join as join10 } from "node:path";
13153
+ async function runDemo() {
13154
+ const root = mkdtempSync(join10(tmpdir(), "agentscan-demo-"));
13155
+ try {
13156
+ const claude = join10(root, ".claude");
13157
+ mkdirSync(claude, { recursive: true });
13158
+ writeFileSync(join10(claude, "settings.json"), `${JSON.stringify({
13159
+ hooks: {
13160
+ PreToolUse: [
13161
+ {
13162
+ hooks: [
13163
+ {
13164
+ type: "command",
13165
+ command: ".claude/hooks/guard-destructive-bash.js"
13166
+ }
13167
+ ]
13168
+ }
13169
+ ]
13170
+ }
13171
+ }, null, 2)}
13172
+ `, "utf8");
13173
+ const result = await runCheck({
13174
+ dir: root,
13175
+ failOn: "never",
13176
+ colour: false
13177
+ });
13178
+ const intro = `Demo fixture: PreToolUse hook points at a script that is not on disk.
13179
+ ` + `This is the failure agentscan exists for — the agent starts normally; the guard does not.
13180
+
13181
+ `;
13182
+ return {
13183
+ exitCode: 0,
13184
+ stdout: intro + result.stdout,
13185
+ stderr: ""
13186
+ };
13187
+ } finally {
13188
+ rmSync(root, { recursive: true, force: true });
13189
+ }
13190
+ }
13191
+
12825
13192
  // src/commands/explain.ts
12826
13193
  async function runExplain(findingId, options = {}) {
12827
13194
  const { findings } = analyze(options);
@@ -12862,12 +13229,12 @@ function formatExplain(f) {
12862
13229
  }
12863
13230
 
12864
13231
  // src/commands/init.ts
12865
- import { existsSync as existsSync4, writeFileSync } from "node:fs";
12866
- import { join as join4, resolve as resolve3 } from "node:path";
13232
+ import { existsSync as existsSync9, writeFileSync as writeFileSync2 } from "node:fs";
13233
+ import { join as join11, resolve as resolve5 } from "node:path";
12867
13234
  async function runInit(dir, options = {}) {
12868
- const root = resolve3(dir ?? process.cwd());
12869
- const path = join4(root, ".agentscanrc.json");
12870
- if (existsSync4(path) && !options.force) {
13235
+ const root = resolve5(dir ?? process.cwd());
13236
+ const path = join11(root, ".agentscanrc.json");
13237
+ if (existsSync9(path) && !options.force) {
12871
13238
  return {
12872
13239
  exitCode: 2,
12873
13240
  stdout: "",
@@ -12877,7 +13244,7 @@ async function runInit(dir, options = {}) {
12877
13244
  }
12878
13245
  const body = `${JSON.stringify(defaultConfig, null, 2)}
12879
13246
  `;
12880
- writeFileSync(path, body, "utf8");
13247
+ writeFileSync2(path, body, "utf8");
12881
13248
  return {
12882
13249
  exitCode: 0,
12883
13250
  stdout: `Wrote ${path}
@@ -12903,7 +13270,7 @@ async function runRulesCommand(options = {}) {
12903
13270
  // src/report/clipboard.ts
12904
13271
  import { spawn } from "node:child_process";
12905
13272
  import { accessSync, constants } from "node:fs";
12906
- import { delimiter, join as join5 } from "node:path";
13273
+ import { delimiter, join as join12 } from "node:path";
12907
13274
  var TOOLS = [
12908
13275
  ["wl-copy"],
12909
13276
  ["xclip", "-selection", "clipboard"],
@@ -12912,15 +13279,15 @@ var TOOLS = [
12912
13279
  ["clip.exe"]
12913
13280
  ];
12914
13281
  var TIMEOUT_MS = 3000;
12915
- function clipboardCandidates(onPath = (name) => resolve4(name) !== null) {
13282
+ function clipboardCandidates(onPath = (name) => resolve6(name) !== null) {
12916
13283
  return TOOLS.filter((t) => onPath(t[0])).map((t) => [...t]);
12917
13284
  }
12918
- function resolve4(name) {
13285
+ function resolve6(name) {
12919
13286
  for (const dir of (process.env.PATH ?? "").split(delimiter)) {
12920
13287
  if (dir.length === 0) {
12921
13288
  continue;
12922
13289
  }
12923
- const candidate = join5(dir, name);
13290
+ const candidate = join12(dir, name);
12924
13291
  try {
12925
13292
  accessSync(candidate, constants.X_OK);
12926
13293
  return candidate;
@@ -12939,7 +13306,7 @@ async function copyToClipboard(text) {
12939
13306
  const failures = [];
12940
13307
  for (const argv of candidates) {
12941
13308
  const tool = argv[0];
12942
- const bin = resolve4(tool);
13309
+ const bin = resolve6(tool);
12943
13310
  if (bin === null) {
12944
13311
  continue;
12945
13312
  }
@@ -12975,6 +13342,7 @@ function printHelp() {
12975
13342
  Usage:
12976
13343
  agentscan scan the current directory
12977
13344
  agentscan check [dir] [options]
13345
+ agentscan demo one-shot fixture (no project required)
12978
13346
  agentscan explain <findingId> [dir]
12979
13347
  agentscan rules [dir]
12980
13348
  agentscan init [dir] [--force]
@@ -12987,6 +13355,7 @@ check options:
12987
13355
  --output <format> human (default) | json | prompt
12988
13356
  prompt: a paste-ready handoff for a fixing agent
12989
13357
  --copy Also copy the report to the system clipboard
13358
+ --no-color Never colour, even on a terminal (also: NO_COLOR=1)
12990
13359
  --quiet Summary only
12991
13360
  --verbose Show KEEP findings
12992
13361
  --fail-on <level> never | warning | error (default: never)
@@ -13016,6 +13385,7 @@ async function main(argv) {
13016
13385
  json: { type: "boolean", default: false },
13017
13386
  output: { type: "string" },
13018
13387
  copy: { type: "boolean", default: false },
13388
+ "no-color": { type: "boolean", default: false },
13019
13389
  quiet: { type: "boolean", default: false },
13020
13390
  verbose: { type: "boolean", default: false },
13021
13391
  "fail-on": { type: "string" },
@@ -13082,11 +13452,16 @@ async function main(argv) {
13082
13452
  failOn: failOnRaw,
13083
13453
  ...failUnder === undefined ? {} : { failUnder },
13084
13454
  global: values.global,
13085
- configPath: values.config
13455
+ configPath: values.config,
13456
+ colour: shouldColour({
13457
+ isTTY: process.stdout.isTTY === true,
13458
+ env: process.env,
13459
+ noColorFlag: values["no-color"]
13460
+ })
13086
13461
  });
13087
13462
  process.stdout.write(result.stdout);
13088
13463
  if (values.copy === true) {
13089
- const copied = await copyToClipboard(result.stdout);
13464
+ const copied = await copyToClipboard(result.plain ?? result.stdout);
13090
13465
  process.stderr.write(copied.ok ? `Copied to clipboard via ${copied.tool}.
13091
13466
  ` : `Could not copy: ${safe(copied.reason)}. Redirect instead: agentscan check > report.md
13092
13467
  `);
@@ -13133,6 +13508,16 @@ async function main(argv) {
13133
13508
  }
13134
13509
  return result.exitCode;
13135
13510
  }
13511
+ case "demo": {
13512
+ const result = await runDemo();
13513
+ if (result.stderr.length > 0) {
13514
+ process.stderr.write(result.stderr);
13515
+ }
13516
+ if (result.stdout.length > 0) {
13517
+ process.stdout.write(result.stdout);
13518
+ }
13519
+ return result.exitCode;
13520
+ }
13136
13521
  case "help":
13137
13522
  printHelp();
13138
13523
  return 0;