@chimix/agentscan 0.4.0 → 0.6.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 +105 -53
  2. package/dist/cli.js +1081 -782
  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: "warning",
7124
+ message: `Agent name "${agent.frontmatterName}" is not a valid identifier`,
7125
+ reason: "The subagent reference specifies a unique identifier in lowercase letters and hyphens. A name outside that shape is not documented to load reliably, and a `:` in it is documented not to load at all. See docs/spec/agents.md.",
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);
@@ -7215,6 +7156,18 @@ function safeDetail(detail) {
7215
7156
  }
7216
7157
  function checkConfigErrors(facts) {
7217
7158
  return facts.configErrors.map((err) => {
7159
+ if (err.kind === "truncated") {
7160
+ return make("scan.truncated", `scan:${err.path}`, {
7161
+ action: "warn",
7162
+ severity: "info",
7163
+ message: `${basename2(err.path)} is larger than the scan cap — only its first bytes were read`,
7164
+ reason: "The file itself is fine; agentscan reads a bounded prefix of it. Checks that read the body — skill.broken-reference, and the AGENTS.md / CLAUDE.md line counts — see only that prefix, so they can undercount on this file.",
7165
+ evidence: [
7166
+ { kind: "config", value: err.path },
7167
+ { kind: "detail", value: err.detail }
7168
+ ]
7169
+ });
7170
+ }
7218
7171
  const what = err.kind === "invalid-json" ? "is not valid JSON" : err.kind === "unreadable" ? "could not be read" : "has an unexpected shape";
7219
7172
  return make("config.unreadable", `config:${err.path}#${err.kind}:${safeDetail(err.detail)}`, {
7220
7173
  action: "warn",
@@ -7229,6 +7182,8 @@ function checkConfigErrors(facts) {
7229
7182
  });
7230
7183
  });
7231
7184
  }
7185
+
7186
+ // src/checks/hooks.ts
7232
7187
  var KNOWN_HOOK_EVENTS = new Set([
7233
7188
  "SessionStart",
7234
7189
  "Setup",
@@ -7314,6 +7269,85 @@ function checkHooks(facts) {
7314
7269
  }
7315
7270
  return out;
7316
7271
  }
7272
+
7273
+ // src/checks/mcp.ts
7274
+ var SECRET_PATTERNS = [
7275
+ { label: "Anthropic key", re: /\bsk-ant-[A-Za-z0-9_-]{16,}/ },
7276
+ { label: "OpenAI project key", re: /\bsk-proj-[A-Za-z0-9_-]{16,}/ },
7277
+ { label: "OpenAI service-account key", re: /\bsk-svcacct-[A-Za-z0-9_-]{16,}/ },
7278
+ { label: "OpenAI-style key", re: /\bsk-[A-Za-z0-9_]{20,}\b/ },
7279
+ { label: "GitHub token", re: /\bgh[pousr]_[A-Za-z0-9]{20,}/ },
7280
+ { label: "Google API key", re: /\bAIza[A-Za-z0-9_-]{20,}/ },
7281
+ { label: "Slack token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}/ },
7282
+ { label: "AWS access key id", re: /\bAKIA[0-9A-Z]{16}\b/ }
7283
+ ];
7284
+ function checkMcp(facts) {
7285
+ const out = [];
7286
+ for (const server of facts.mcp) {
7287
+ if (!(server.hasCommand || server.hasUrl)) {
7288
+ out.push(make("mcp.no-launch", `mcp:${server.name}@${server.path}`, {
7289
+ action: "warn",
7290
+ severity: "error",
7291
+ message: `MCP server "${server.name}" declares neither command nor url`,
7292
+ 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.",
7293
+ evidence: [{ kind: "mcp", value: `${server.name} @ ${server.path}` }],
7294
+ suggest: `Add a command (or url) for "${server.name}", or remove it`
7295
+ }));
7296
+ }
7297
+ if (server.command !== undefined && server.commandExists === false) {
7298
+ out.push(make("mcp.command-missing", `mcp:${server.name}@${server.path}`, {
7299
+ action: "warn",
7300
+ severity: "error",
7301
+ message: `MCP server "${server.name}" points at a command path that does not exist: ${server.command}`,
7302
+ 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.",
7303
+ evidence: [
7304
+ { kind: "mcp", value: `${server.name} @ ${server.path}` },
7305
+ { kind: "command", value: server.command }
7306
+ ],
7307
+ suggest: `Restore ${server.command} or fix the command path for "${server.name}"`
7308
+ }));
7309
+ }
7310
+ if (server.hasUrl && server.transport === undefined) {
7311
+ out.push(make("mcp.url-without-type", `mcp:${server.name}@${server.path}`, {
7312
+ action: "warn",
7313
+ severity: "error",
7314
+ message: `MCP server "${server.name}" has a url but no transport type`,
7315
+ 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.",
7316
+ evidence: [{ kind: "mcp", value: `${server.name} @ ${server.path}` }],
7317
+ suggest: `Add "type": "http" (or "sse" / "ws") to "${server.name}"`
7318
+ }));
7319
+ }
7320
+ const hit = SECRET_PATTERNS.find((p) => p.re.test(server.raw));
7321
+ if (hit !== undefined) {
7322
+ out.push(make("mcp.hardcoded-secret", `mcp:${server.name}@${server.path}`, {
7323
+ action: "warn",
7324
+ severity: "error",
7325
+ message: `MCP server "${server.name}" contains what looks like a hardcoded credential (${hit.label})`,
7326
+ 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.",
7327
+ evidence: [{ kind: "mcp", value: `${server.name} @ ${server.path}` }],
7328
+ suggest: "Rotate the credential, then replace the literal with ${ENV_VAR}"
7329
+ }));
7330
+ continue;
7331
+ }
7332
+ if (server.literalEnvKeys.length > 0) {
7333
+ out.push(make("mcp.literal-env", `mcp:${server.name}@${server.path}`, {
7334
+ action: "warn",
7335
+ severity: "warning",
7336
+ message: `MCP server "${server.name}" has literal env values: ${server.literalEnvKeys.join(", ")}`,
7337
+ reason: "Long literal env values in config are usually secrets that should be indirected through ${VAR} so they are never committed.",
7338
+ evidence: [
7339
+ { kind: "mcp", value: `${server.name} @ ${server.path}` },
7340
+ { kind: "env", value: server.literalEnvKeys.join(",") }
7341
+ ],
7342
+ suggest: "Replace the literals with ${ENV_VAR} references"
7343
+ }));
7344
+ }
7345
+ }
7346
+ return out;
7347
+ }
7348
+
7349
+ // src/checks/skills.ts
7350
+ import { dirname } from "node:path";
7317
7351
  function skillEvidence(skill, value) {
7318
7352
  const evidence = [{ kind: "skill", value }];
7319
7353
  if (skill.source === "global") {
@@ -7321,6 +7355,12 @@ function skillEvidence(skill, value) {
7321
7355
  }
7322
7356
  return evidence;
7323
7357
  }
7358
+ function skillSubject(skill) {
7359
+ return `skill:${skill.instanceId ?? skill.id}`;
7360
+ }
7361
+ function skillRoot(skill) {
7362
+ return dirname(skill.path.replaceAll("\\", "/"));
7363
+ }
7324
7364
  function checkSkillStructure(facts) {
7325
7365
  const out = [];
7326
7366
  for (const skill of facts.skills) {
@@ -7328,7 +7368,7 @@ function checkSkillStructure(facts) {
7328
7368
  continue;
7329
7369
  }
7330
7370
  if (!skill.hasSkillMd) {
7331
- out.push(make("skill.missing-skill-md", `skill:${skill.id}`, {
7371
+ out.push(make("skill.missing-skill-md", skillSubject(skill), {
7332
7372
  action: "warn",
7333
7373
  severity: "warning",
7334
7374
  message: `Skill directory has no SKILL.md`,
@@ -7341,8 +7381,8 @@ function checkSkillStructure(facts) {
7341
7381
  if (skill.unparseableFrontmatter === true) {
7342
7382
  continue;
7343
7383
  }
7344
- if (!skill.hasFrontmatter) {
7345
- out.push(make("skill.missing-frontmatter", `skill:${skill.id}`, {
7384
+ if (skill.runtime !== "agents" && !skill.hasFrontmatter) {
7385
+ out.push(make("skill.missing-frontmatter", skillSubject(skill), {
7346
7386
  action: "warn",
7347
7387
  severity: "warning",
7348
7388
  message: "SKILL.md has no YAML frontmatter block",
@@ -7353,10 +7393,10 @@ function checkSkillStructure(facts) {
7353
7393
  continue;
7354
7394
  }
7355
7395
  const broken = skill.brokenReferences;
7356
- if (broken !== undefined && broken.length > 0) {
7396
+ if (skill.runtime !== "agents" && broken !== undefined && broken.length > 0) {
7357
7397
  const shown = broken.slice(0, 3).join(", ");
7358
7398
  const rest = broken.length - Math.min(3, broken.length);
7359
- out.push(make("skill.broken-reference", `skill:${skill.id}`, {
7399
+ out.push(make("skill.broken-reference", skillSubject(skill), {
7360
7400
  action: "warn",
7361
7401
  severity: "warning",
7362
7402
  message: `SKILL.md points at ${broken.length} file${broken.length === 1 ? "" : "s"} that do not exist: ${shown}${rest > 0 ? ` (+${rest} more)` : ""}`,
@@ -7368,8 +7408,8 @@ function checkSkillStructure(facts) {
7368
7408
  suggest: `Restore the missing files under ${skill.path}, or drop the references from SKILL.md`
7369
7409
  }));
7370
7410
  }
7371
- if (skill.description === undefined) {
7372
- out.push(make("skill.missing-description", `skill:${skill.id}`, {
7411
+ if (skill.runtime !== "agents" && skill.description === undefined) {
7412
+ out.push(make("skill.missing-description", skillSubject(skill), {
7373
7413
  action: "warn",
7374
7414
  severity: "info",
7375
7415
  message: "SKILL.md frontmatter has no `description`",
@@ -7397,13 +7437,16 @@ function checkLockIntegrity(facts, options) {
7397
7437
  }
7398
7438
  return out;
7399
7439
  }
7440
+ if (facts.skillsLockInvalid === true) {
7441
+ return out;
7442
+ }
7400
7443
  const onDisk = new Set(projectSkills.map((s) => s.id));
7401
7444
  const locked = new Map(facts.lockedSkills.map((l) => [l.id, l]));
7402
7445
  for (const skill of projectSkills) {
7403
7446
  if (locked.has(skill.id)) {
7404
7447
  continue;
7405
7448
  }
7406
- out.push(make("skill.not-in-lock", `skill:${skill.id}`, {
7449
+ out.push(make("skill.not-in-lock", skillSubject(skill), {
7407
7450
  action: "warn",
7408
7451
  severity: "info",
7409
7452
  message: "Skill is not in skills-lock.json — local and unpinned",
@@ -7437,11 +7480,18 @@ function checkDuplicateDescriptions(facts) {
7437
7480
  if (skill.source !== "project" || skill.description === undefined) {
7438
7481
  continue;
7439
7482
  }
7440
- const key = skill.description.trim().replace(/\s+/g, " ").toLowerCase();
7483
+ if (skill.runtime === "agents") {
7484
+ continue;
7485
+ }
7486
+ const key = `${skillRoot(skill)}\x00${skill.description.trim().replace(/\s+/g, " ").toLowerCase()}`;
7441
7487
  if (key.length === 0) {
7442
7488
  continue;
7443
7489
  }
7444
- groups.set(key, [...groups.get(key) ?? [], skill.id]);
7490
+ const ids = groups.get(key);
7491
+ if (ids === undefined)
7492
+ groups.set(key, [skill.instanceId ?? skill.id]);
7493
+ else
7494
+ ids.push(skill.instanceId ?? skill.id);
7445
7495
  }
7446
7496
  const out = [];
7447
7497
  for (const ids of groups.values()) {
@@ -7466,105 +7516,141 @@ function checkDescriptionBudget(facts, options) {
7466
7516
  return [];
7467
7517
  }
7468
7518
  const project = facts.skills.filter((s) => s.source === "project" && s.hasSkillMd && s.description !== undefined);
7469
- const bytes = project.reduce((sum, s) => sum + Buffer.byteLength(s.id) + Buffer.byteLength(s.description ?? ""), 0);
7470
- if (bytes <= ceiling) {
7471
- return [];
7519
+ const byRoot = new Map;
7520
+ for (const s of project) {
7521
+ const root = skillRoot(s);
7522
+ const bucket = byRoot.get(root);
7523
+ if (bucket === undefined)
7524
+ byRoot.set(root, [s]);
7525
+ else
7526
+ bucket.push(s);
7472
7527
  }
7473
- return [
7474
- make("skill.description-budget", "skills:description-budget", {
7528
+ const out = [];
7529
+ for (const [root, skills] of byRoot) {
7530
+ const bytes = skills.reduce((sum, s) => sum + Buffer.byteLength(s.id) + Buffer.byteLength(s.description ?? ""), 0);
7531
+ if (bytes <= ceiling) {
7532
+ continue;
7533
+ }
7534
+ const where = root.startsWith(`${facts.root}/`) ? root.slice(facts.root.length + 1) : root;
7535
+ const subject = byRoot.size === 1 ? "skills:description-budget" : `skills:description-budget:${where}`;
7536
+ out.push(make("skill.description-budget", subject, {
7475
7537
  action: "warn",
7476
7538
  severity: "info",
7477
- message: `Skill descriptions total ${bytes} bytes across ${project.length} skills (over ${ceiling})`,
7539
+ message: `Skill descriptions total ${bytes} bytes across ${skills.length} skills in ${where} (over ${ceiling})`,
7478
7540
  reason: "Names and descriptions for every skill are loaded at startup, within a budget of roughly 1-2% of the context window. Past it they are truncated, and a skill whose description is cut short stops being matched for the tasks it was written for.",
7479
7541
  evidence: [
7480
7542
  { kind: "count", value: `descriptionBytes=${bytes}` },
7481
7543
  { kind: "threshold", value: String(ceiling) }
7482
7544
  ],
7483
7545
  suggest: "Shorten the longest descriptions, or remove skills this project does not use"
7484
- })
7485
- ];
7546
+ }));
7547
+ }
7548
+ return out;
7486
7549
  }
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
- }
7550
+ // src/checks/registry.ts
7551
+ var STRUCTURAL_CHECKS = [
7552
+ {
7553
+ id: "config.unreadable",
7554
+ description: "Config file is not valid JSON or cannot be read"
7555
+ },
7556
+ {
7557
+ id: "scan.truncated",
7558
+ description: "File is larger than the scan cap, so body checks read only a prefix of it"
7559
+ },
7560
+ {
7561
+ id: "hook.unknown-event",
7562
+ description: "Hook registered under an event name that is never dispatched"
7563
+ },
7564
+ {
7565
+ id: "hook.missing-script",
7566
+ description: "Hook points at a script that does not exist, so it never runs"
7567
+ },
7568
+ {
7569
+ id: "skill.missing-skill-md",
7570
+ description: "Skill directory has no SKILL.md"
7571
+ },
7572
+ {
7573
+ id: "skill.missing-frontmatter",
7574
+ description: "SKILL.md has no YAML frontmatter block"
7575
+ },
7576
+ {
7577
+ id: "skill.missing-description",
7578
+ description: "SKILL.md frontmatter has no description (recommended field)"
7579
+ },
7580
+ {
7581
+ id: "skill.not-in-lock",
7582
+ description: "Skill on disk is not tracked by skills-lock.json"
7583
+ },
7584
+ {
7585
+ id: "skill.locked-not-installed",
7586
+ description: "skills-lock.json pins a skill that is not installed"
7587
+ },
7588
+ {
7589
+ id: "skill.broken-reference",
7590
+ description: "SKILL.md points at a bundled file that does not exist"
7591
+ },
7592
+ {
7593
+ id: "skill.duplicate-description",
7594
+ description: "Two or more skills share an identical description"
7595
+ },
7596
+ {
7597
+ id: "skill.description-budget",
7598
+ description: "Skill descriptions exceed the startup character budget"
7599
+ },
7600
+ {
7601
+ id: "skill.no-lockfile",
7602
+ description: "Skills present with no skills-lock.json (requireLock only)"
7603
+ },
7604
+ {
7605
+ id: "agent.missing-frontmatter",
7606
+ description: "Agent definition has no YAML frontmatter block"
7607
+ },
7608
+ {
7609
+ id: "agent.missing-description",
7610
+ description: "Agent frontmatter has no description"
7611
+ },
7612
+ { id: "agent.missing-name", description: "Agent frontmatter has no name" },
7613
+ { id: "agent.duplicate-name", description: "Agent names are duplicated" },
7614
+ { id: "agent.invalid-name", description: "Agent frontmatter name is not a valid identifier" },
7615
+ {
7616
+ id: "mcp.no-launch",
7617
+ description: "MCP server declares neither command nor url"
7618
+ },
7619
+ {
7620
+ id: "mcp.command-missing",
7621
+ description: "MCP server command is a path that does not exist on disk"
7622
+ },
7623
+ {
7624
+ id: "mcp.url-without-type",
7625
+ description: "Remote MCP server has a url but no transport type"
7626
+ },
7627
+ {
7628
+ id: "mcp.hardcoded-secret",
7629
+ description: "MCP config contains a token-shaped literal"
7630
+ },
7631
+ {
7632
+ id: "mcp.literal-env",
7633
+ description: "MCP env value is a long literal instead of ${VAR}"
7634
+ },
7635
+ {
7636
+ id: "budget.agents-md",
7637
+ description: "AGENTS.md longer than the point where added lines stop helping (>150 lines)"
7638
+ },
7639
+ {
7640
+ id: "budget.claude-md",
7641
+ description: "CLAUDE.md longer than the instruction budget a model reliably follows (>200 lines)"
7642
+ },
7643
+ {
7644
+ id: "budget.agents",
7645
+ description: "More agent definitions than a focused set (>8 files in .claude/agents)"
7646
+ },
7647
+ {
7648
+ id: "budget.mcp",
7649
+ description: "Project MCP server count above the point where tool selection degrades"
7565
7650
  }
7566
- return out;
7567
- }
7651
+ ];
7652
+
7653
+ // src/checks/index.ts
7568
7654
  function runChecks(facts, options = {}) {
7569
7655
  return [
7570
7656
  ...checkConfigErrors(facts),
@@ -7587,113 +7673,113 @@ import { join } from "node:path";
7587
7673
  // node_modules/zod/v3/external.js
7588
7674
  var exports_external = {};
7589
7675
  __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,
7676
+ BRAND: () => BRAND,
7695
7677
  DIRTY: () => DIRTY,
7696
- BRAND: () => BRAND
7678
+ EMPTY_PATH: () => EMPTY_PATH,
7679
+ INVALID: () => INVALID,
7680
+ NEVER: () => NEVER,
7681
+ OK: () => OK,
7682
+ ParseStatus: () => ParseStatus,
7683
+ Schema: () => ZodType,
7684
+ ZodAny: () => ZodAny,
7685
+ ZodArray: () => ZodArray,
7686
+ ZodBigInt: () => ZodBigInt,
7687
+ ZodBoolean: () => ZodBoolean,
7688
+ ZodBranded: () => ZodBranded,
7689
+ ZodCatch: () => ZodCatch,
7690
+ ZodDate: () => ZodDate,
7691
+ ZodDefault: () => ZodDefault,
7692
+ ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
7693
+ ZodEffects: () => ZodEffects,
7694
+ ZodEnum: () => ZodEnum,
7695
+ ZodError: () => ZodError,
7696
+ ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
7697
+ ZodFunction: () => ZodFunction,
7698
+ ZodIntersection: () => ZodIntersection,
7699
+ ZodIssueCode: () => ZodIssueCode,
7700
+ ZodLazy: () => ZodLazy,
7701
+ ZodLiteral: () => ZodLiteral,
7702
+ ZodMap: () => ZodMap,
7703
+ ZodNaN: () => ZodNaN,
7704
+ ZodNativeEnum: () => ZodNativeEnum,
7705
+ ZodNever: () => ZodNever,
7706
+ ZodNull: () => ZodNull,
7707
+ ZodNullable: () => ZodNullable,
7708
+ ZodNumber: () => ZodNumber,
7709
+ ZodObject: () => ZodObject,
7710
+ ZodOptional: () => ZodOptional,
7711
+ ZodParsedType: () => ZodParsedType,
7712
+ ZodPipeline: () => ZodPipeline,
7713
+ ZodPromise: () => ZodPromise,
7714
+ ZodReadonly: () => ZodReadonly,
7715
+ ZodRecord: () => ZodRecord,
7716
+ ZodSchema: () => ZodType,
7717
+ ZodSet: () => ZodSet,
7718
+ ZodString: () => ZodString,
7719
+ ZodSymbol: () => ZodSymbol,
7720
+ ZodTransformer: () => ZodEffects,
7721
+ ZodTuple: () => ZodTuple,
7722
+ ZodType: () => ZodType,
7723
+ ZodUndefined: () => ZodUndefined,
7724
+ ZodUnion: () => ZodUnion,
7725
+ ZodUnknown: () => ZodUnknown,
7726
+ ZodVoid: () => ZodVoid,
7727
+ addIssueToContext: () => addIssueToContext,
7728
+ any: () => anyType,
7729
+ array: () => arrayType,
7730
+ bigint: () => bigIntType,
7731
+ boolean: () => booleanType,
7732
+ coerce: () => coerce,
7733
+ custom: () => custom,
7734
+ date: () => dateType,
7735
+ datetimeRegex: () => datetimeRegex,
7736
+ defaultErrorMap: () => en_default,
7737
+ discriminatedUnion: () => discriminatedUnionType,
7738
+ effect: () => effectsType,
7739
+ enum: () => enumType,
7740
+ function: () => functionType,
7741
+ getErrorMap: () => getErrorMap,
7742
+ getParsedType: () => getParsedType,
7743
+ instanceof: () => instanceOfType,
7744
+ intersection: () => intersectionType,
7745
+ isAborted: () => isAborted,
7746
+ isAsync: () => isAsync,
7747
+ isDirty: () => isDirty,
7748
+ isValid: () => isValid,
7749
+ late: () => late,
7750
+ lazy: () => lazyType,
7751
+ literal: () => literalType,
7752
+ makeIssue: () => makeIssue,
7753
+ map: () => mapType,
7754
+ nan: () => nanType,
7755
+ nativeEnum: () => nativeEnumType,
7756
+ never: () => neverType,
7757
+ null: () => nullType,
7758
+ nullable: () => nullableType,
7759
+ number: () => numberType,
7760
+ object: () => objectType,
7761
+ objectUtil: () => objectUtil,
7762
+ oboolean: () => oboolean,
7763
+ onumber: () => onumber,
7764
+ optional: () => optionalType,
7765
+ ostring: () => ostring,
7766
+ pipeline: () => pipelineType,
7767
+ preprocess: () => preprocessType,
7768
+ promise: () => promiseType,
7769
+ quotelessJson: () => quotelessJson,
7770
+ record: () => recordType,
7771
+ set: () => setType,
7772
+ setErrorMap: () => setErrorMap,
7773
+ strictObject: () => strictObjectType,
7774
+ string: () => stringType,
7775
+ symbol: () => symbolType,
7776
+ transformer: () => effectsType,
7777
+ tuple: () => tupleType,
7778
+ undefined: () => undefinedType,
7779
+ union: () => unionType,
7780
+ unknown: () => unknownType,
7781
+ util: () => util,
7782
+ void: () => voidType
7697
7783
  });
7698
7784
 
7699
7785
  // node_modules/zod/v3/helpers/util.js
@@ -11560,7 +11646,6 @@ var NEVER = INVALID;
11560
11646
  // src/config/schema.ts
11561
11647
  var defaultThresholds = {
11562
11648
  skillDescriptionBytes: 16000,
11563
- skills: 30,
11564
11649
  mcp: 5,
11565
11650
  agentsMdLines: 150,
11566
11651
  claudeMdLines: 200,
@@ -11580,12 +11665,12 @@ var defaultConfig = {
11580
11665
  };
11581
11666
  var thresholdsSchema = exports_external.object({
11582
11667
  skillDescriptionBytes: exports_external.number().int().positive().default(defaultThresholds.skillDescriptionBytes),
11583
- skills: exports_external.number().int().positive().default(defaultThresholds.skills),
11668
+ skills: exports_external.number().int().positive().optional(),
11584
11669
  mcp: exports_external.number().int().positive().default(defaultThresholds.mcp),
11585
11670
  agentsMdLines: exports_external.number().int().positive().default(defaultThresholds.agentsMdLines),
11586
11671
  claudeMdLines: exports_external.number().int().positive().default(defaultThresholds.claudeMdLines),
11587
11672
  agents: exports_external.number().int().positive().default(defaultThresholds.agents)
11588
- });
11673
+ }).strict();
11589
11674
  var configSchema = exports_external.object({
11590
11675
  skillPaths: exports_external.array(exports_external.string()).default(defaultConfig.skillPaths),
11591
11676
  mcpPaths: exports_external.array(exports_external.string()).default(defaultConfig.mcpPaths),
@@ -11597,7 +11682,7 @@ var configSchema = exports_external.object({
11597
11682
  includeGlobal: exports_external.boolean().default(false),
11598
11683
  requireLock: exports_external.boolean().default(false),
11599
11684
  thresholds: thresholdsSchema.default(defaultThresholds)
11600
- });
11685
+ }).strict();
11601
11686
 
11602
11687
  // src/config/load.ts
11603
11688
  function loadConfig(root, configPath) {
@@ -11617,9 +11702,16 @@ function loadConfig(root, configPath) {
11617
11702
  }
11618
11703
 
11619
11704
  // 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";
11705
+ import { homedir as homedir3 } from "node:os";
11706
+ import { join as join8, resolve as resolve3 } from "node:path";
11707
+
11708
+ // src/discover/agents.ts
11709
+ import { existsSync as existsSync3, readdirSync, statSync } from "node:fs";
11710
+ import { join as join3 } from "node:path";
11711
+
11712
+ // src/discover/shared.ts
11713
+ import { closeSync, existsSync as existsSync2, openSync, readFileSync as readFileSync2, readSync } from "node:fs";
11714
+ import { join as join2, resolve } from "node:path";
11623
11715
 
11624
11716
  // node_modules/yaml/dist/index.js
11625
11717
  var composer = require_composer();
@@ -11667,18 +11759,40 @@ var $stringify = publicApi.stringify;
11667
11759
  var $visit = visit.visit;
11668
11760
  var $visitAsync = visit.visitAsync;
11669
11761
 
11670
- // src/discover/index.ts
11762
+ // src/discover/shared.ts
11671
11763
  var POLICY_CAP = 1e5;
11672
11764
  var SKILL_MD_CAP = 65536;
11765
+ function readCapped(path, cap) {
11766
+ const fd = openSync(path, "r");
11767
+ try {
11768
+ const buf = Buffer.allocUnsafe(cap + 1);
11769
+ const bytes = readSync(fd, buf, 0, cap + 1, 0);
11770
+ return { buf: buf.subarray(0, bytes), truncated: bytes > cap };
11771
+ } finally {
11772
+ closeSync(fd);
11773
+ }
11774
+ }
11775
+ var AGENT_CONFIG_SIGNALS = [
11776
+ ".claude",
11777
+ ".agents",
11778
+ ".mcp.json",
11779
+ "mcp.json",
11780
+ "AGENTS.md",
11781
+ "CLAUDE.md",
11782
+ "skills-lock.json"
11783
+ ];
11784
+ function hasAgentConfigSignal(dir) {
11785
+ return AGENT_CONFIG_SIGNALS.some((name) => existsSync2(join2(dir, name)));
11786
+ }
11673
11787
  function resolveRoot(startDir) {
11674
11788
  let dir = resolve(startDir);
11675
11789
  for (;; ) {
11676
- if (existsSync2(join2(dir, "package.json"))) {
11790
+ if (existsSync2(join2(dir, "package.json")) || hasAgentConfigSignal(dir)) {
11677
11791
  return dir;
11678
11792
  }
11679
11793
  const parent = resolve(dir, "..");
11680
11794
  if (parent === dir) {
11681
- throw new Error(`No package.json found walking up from ${resolve(startDir)}`);
11795
+ throw new Error(`No package.json or agent configuration found walking up from ${resolve(startDir)}`);
11682
11796
  }
11683
11797
  dir = parent;
11684
11798
  }
@@ -11706,25 +11820,6 @@ function readJsonConfig(path, errors3) {
11706
11820
  return;
11707
11821
  }
11708
11822
  }
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
11823
  function scalar(value) {
11729
11824
  if (typeof value === "string") {
11730
11825
  const trimmed = value.trim();
@@ -11739,14 +11834,17 @@ function readFrontmatter(skillMdPath, errors3) {
11739
11834
  let text;
11740
11835
  let truncated = false;
11741
11836
  try {
11742
- const buf = readFileSync2(skillMdPath);
11743
- truncated = buf.length > SKILL_MD_CAP;
11744
- text = buf.subarray(0, SKILL_MD_CAP).toString("utf8");
11837
+ const result = readCapped(skillMdPath, SKILL_MD_CAP);
11838
+ truncated = result.truncated;
11839
+ text = result.buf.subarray(0, SKILL_MD_CAP).toString("utf8");
11745
11840
  if (text.charCodeAt(0) === 65279) {
11746
11841
  text = text.slice(1);
11747
11842
  }
11748
11843
  text = text.replace(/\r\n/g, `
11749
11844
  `);
11845
+ if (truncated) {
11846
+ errors3.push({ path: skillMdPath, kind: "truncated", detail: `file exceeds ${SKILL_MD_CAP} byte scan cap` });
11847
+ }
11750
11848
  } catch (err) {
11751
11849
  errors3.push({
11752
11850
  path: skillMdPath,
@@ -11803,23 +11901,12 @@ function readFrontmatter(skillMdPath, errors3) {
11803
11901
  }
11804
11902
  return out;
11805
11903
  }
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 [];
11819
- }
11904
+
11905
+ // src/discover/agents.ts
11906
+ function agentFiles(dir, errors3) {
11820
11907
  let entries;
11821
11908
  try {
11822
- entries = readdirSync(dir);
11909
+ entries = readdirSync(dir, { withFileTypes: true }).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
11823
11910
  } catch (err) {
11824
11911
  errors3.push({
11825
11912
  path: dir,
@@ -11828,144 +11915,66 @@ function discoverSkillsInDir(dir, source, errors3, root) {
11828
11915
  });
11829
11916
  return [];
11830
11917
  }
11831
- const skills = [];
11832
- for (const name of entries) {
11833
- if (name.startsWith(".") || name === "node_modules") {
11918
+ const out = [];
11919
+ for (const entry of entries) {
11920
+ if (entry.startsWith(".")) {
11834
11921
  continue;
11835
11922
  }
11836
- const skillDir = join2(dir, name);
11837
- let st;
11838
- try {
11839
- st = statSync(skillDir);
11840
- } catch {
11923
+ if (entry.endsWith("/")) {
11924
+ out.push(...agentFiles(join3(dir, entry.slice(0, -1)), errors3).map((p) => join3(entry.slice(0, -1), p)));
11841
11925
  continue;
11842
11926
  }
11843
- if (!st.isDirectory()) {
11927
+ out.push(entry);
11928
+ }
11929
+ return out;
11930
+ }
11931
+ function discoverAgents(root, errors3) {
11932
+ const dir = join3(root, ".claude", "agents");
11933
+ if (!existsSync3(dir)) {
11934
+ return [];
11935
+ }
11936
+ const entries = agentFiles(dir, errors3);
11937
+ const facts = [];
11938
+ for (const name of entries) {
11939
+ if (!name.endsWith(".md")) {
11844
11940
  continue;
11845
11941
  }
11846
- const skillMd = join2(skillDir, "SKILL.md");
11847
- let dirReadable = true;
11942
+ const filePath = join3(dir, name);
11848
11943
  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
- });
11944
+ const st = statSync(filePath);
11945
+ if (!st.isFile()) {
11946
+ continue;
11947
+ }
11948
+ } catch {
11949
+ continue;
11857
11950
  }
11858
- const hasSkillMd = dirReadable && existsSync2(skillMd);
11859
- const fm = hasSkillMd ? readFrontmatter(skillMd, errors3) : { hasFrontmatter: false };
11951
+ const fm = readFrontmatter(filePath, errors3);
11860
11952
  const fact = {
11861
- id: name,
11862
- path: skillDir,
11863
- source,
11864
- hasSkillMd,
11953
+ name: name.slice(0, -".md".length),
11954
+ path: filePath,
11865
11955
  hasFrontmatter: fm.hasFrontmatter
11866
11956
  };
11867
- if (fm.unreadable === true || !dirReadable) {
11957
+ if (fm.unreadable === true) {
11868
11958
  fact.unreadable = true;
11869
11959
  }
11870
11960
  if (fm.unparseable === true) {
11871
11961
  fact.unparseableFrontmatter = true;
11872
11962
  }
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
11963
  if (fm.name !== undefined) {
11883
11964
  fact.frontmatterName = fm.name;
11884
11965
  }
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);
11966
+ if (fm.description !== undefined) {
11967
+ fact.description = fm.description;
11965
11968
  }
11969
+ facts.push(fact);
11966
11970
  }
11967
11971
  return facts;
11968
11972
  }
11973
+
11974
+ // src/discover/hooks.ts
11975
+ import { existsSync as existsSync4, statSync as statSync2 } from "node:fs";
11976
+ import { homedir } from "node:os";
11977
+ import { isAbsolute, join as join4 } from "node:path";
11969
11978
  var INTERPRETERS = /^(?:bash|sh|zsh|python3?|node|bun|deno)$/;
11970
11979
  var INLINE_CODE_FLAGS = /^-(?:e|p|c)$/;
11971
11980
  var SHELL_METACHARS = /[|;&`]|\$\(|\|\||&&/;
@@ -11981,7 +11990,9 @@ function hookScriptPath(command) {
11981
11990
  const unquote = (t) => t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'") ? t.slice(1, -1) : t;
11982
11991
  let i = 0;
11983
11992
  const first = unquote(tokens[0]);
11993
+ let interpreted = false;
11984
11994
  if (INTERPRETERS.test(first)) {
11995
+ interpreted = true;
11985
11996
  i = 1;
11986
11997
  while (i < tokens.length) {
11987
11998
  const flag = unquote(tokens[i]);
@@ -11995,7 +12006,7 @@ function hookScriptPath(command) {
11995
12006
  }
11996
12007
  }
11997
12008
  const candidate = tokens[i] === undefined ? undefined : unquote(tokens[i]);
11998
- if (candidate === undefined || !candidate.includes("/")) {
12009
+ if (candidate === undefined || !candidate.includes("/") && !(interpreted && /\.(?:js|mjs|cjs|ts|py|sh|bash|zsh)$/i.test(candidate))) {
11999
12010
  return;
12000
12011
  }
12001
12012
  if (/^[A-Za-z]:[/\\]/.test(candidate)) {
@@ -12016,21 +12027,25 @@ function resolveHookScript(root, raw) {
12016
12027
  }
12017
12028
  let expanded = extracted;
12018
12029
  if (expanded.startsWith("~/")) {
12019
- expanded = join2(homedir(), expanded.slice(2));
12030
+ expanded = join4(homedir(), expanded.slice(2));
12020
12031
  } else if (expanded.startsWith("$")) {
12021
12032
  expanded = expanded.replace(/^\$(?:CLAUDE_PROJECT_DIR|\{CLAUDE_PROJECT_DIR\})\/?/, "");
12022
12033
  }
12023
- const abs = isAbsolute(expanded) ? expanded : join2(root, expanded);
12024
- return { scriptPath: extracted, exists: existsSync2(abs) };
12034
+ const abs = isAbsolute(expanded) ? expanded : join4(root, expanded);
12035
+ try {
12036
+ return { scriptPath: extracted, exists: statSync2(abs).isFile() };
12037
+ } catch {
12038
+ return { scriptPath: extracted, exists: false };
12039
+ }
12025
12040
  }
12026
12041
  function discoverHooks(root, errors3) {
12027
12042
  const files = [
12028
- join2(root, ".claude", "settings.json"),
12029
- join2(root, ".claude", "settings.local.json")
12043
+ join4(root, ".claude", "settings.json"),
12044
+ join4(root, ".claude", "settings.local.json")
12030
12045
  ];
12031
12046
  const facts = [];
12032
12047
  for (const filePath of files) {
12033
- if (!existsSync2(filePath)) {
12048
+ if (!existsSync4(filePath)) {
12034
12049
  continue;
12035
12050
  }
12036
12051
  const raw = readJsonConfig(filePath, errors3);
@@ -12058,6 +12073,25 @@ function discoverHooks(root, errors3) {
12058
12073
  continue;
12059
12074
  }
12060
12075
  for (const [event, groups] of Object.entries(hooks)) {
12076
+ const malformed = !Array.isArray(groups) || groups.some((group) => {
12077
+ if (group === null || typeof group !== "object" || Array.isArray(group)) {
12078
+ return true;
12079
+ }
12080
+ const inner = group.hooks;
12081
+ if (!Array.isArray(inner))
12082
+ return true;
12083
+ return inner.some((entry) => {
12084
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
12085
+ return true;
12086
+ }
12087
+ const item = entry;
12088
+ return item.command !== undefined && (typeof item.command !== "string" || item.command.length === 0) || item.type === "command" && typeof item.command !== "string";
12089
+ });
12090
+ });
12091
+ if (malformed) {
12092
+ errors3.push({ path: filePath, kind: "unexpected-shape", detail: `invalid hook groups for ${event}` });
12093
+ continue;
12094
+ }
12061
12095
  const commands = collectHookCommands(groups);
12062
12096
  if (commands.length === 0) {
12063
12097
  facts.push({ name: event, path: filePath, event });
@@ -12098,87 +12132,160 @@ function collectHookCommands(groups) {
12098
12132
  out.push(command);
12099
12133
  }
12100
12134
  }
12101
- }
12102
- return out;
12103
- }
12104
- function agentFiles(dir, errors3) {
12105
- let entries;
12106
- try {
12107
- entries = readdirSync(dir, { withFileTypes: true }).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
12108
- } catch (err) {
12109
- errors3.push({
12110
- path: dir,
12111
- kind: "unreadable",
12112
- detail: err instanceof Error ? err.message : String(err)
12135
+ }
12136
+ return out;
12137
+ }
12138
+
12139
+ // src/discover/mcp.ts
12140
+ import { existsSync as existsSync5, statSync as statSync3 } from "node:fs";
12141
+ import { homedir as homedir2 } from "node:os";
12142
+ import { isAbsolute as isAbsolute2, join as join5 } from "node:path";
12143
+ var SHELL_METACHARS2 = /[|;&`]|\$\(|\|\||&&|\s/;
12144
+ function mcpCommandPath(command) {
12145
+ const trimmed = command.trim();
12146
+ if (trimmed.length === 0 || SHELL_METACHARS2.test(trimmed)) {
12147
+ return;
12148
+ }
12149
+ if (/^[A-Za-z]:[/\\]/.test(trimmed)) {
12150
+ return;
12151
+ }
12152
+ if (trimmed.startsWith("$")) {
12153
+ return /^\$(?:CLAUDE_PROJECT_DIR\b|\{CLAUDE_PROJECT_DIR\})/.test(trimmed) ? trimmed : undefined;
12154
+ }
12155
+ if (trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~/") || trimmed.startsWith(".")) {
12156
+ return trimmed;
12157
+ }
12158
+ if (trimmed.includes("/")) {
12159
+ return trimmed;
12160
+ }
12161
+ return;
12162
+ }
12163
+ function resolveMcpCommand(root, raw) {
12164
+ const extracted = mcpCommandPath(raw);
12165
+ if (extracted === undefined) {
12166
+ return;
12167
+ }
12168
+ let expanded = extracted;
12169
+ if (expanded.startsWith("~/")) {
12170
+ expanded = join5(homedir2(), expanded.slice(2));
12171
+ } else if (expanded.startsWith("$")) {
12172
+ expanded = expanded.replace(/^\$(?:CLAUDE_PROJECT_DIR|\{CLAUDE_PROJECT_DIR\})\/?/, "");
12173
+ }
12174
+ const abs = isAbsolute2(expanded) ? expanded : join5(root, expanded);
12175
+ try {
12176
+ return { command: extracted, exists: statSync3(abs).isFile() };
12177
+ } catch {
12178
+ return { command: extracted, exists: false };
12179
+ }
12180
+ }
12181
+ function parseMcpServers(raw, filePath, root, errors3) {
12182
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
12183
+ errors3.push({
12184
+ path: filePath,
12185
+ kind: "unexpected-shape",
12186
+ detail: "MCP config root is not a JSON object"
12187
+ });
12188
+ return [];
12189
+ }
12190
+ const obj = raw;
12191
+ let servers;
12192
+ if ("mcpServers" in obj && obj.mcpServers !== null && typeof obj.mcpServers === "object" && !Array.isArray(obj.mcpServers)) {
12193
+ servers = obj.mcpServers;
12194
+ } 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)))) {
12195
+ servers = obj;
12196
+ } else {
12197
+ errors3.push({
12198
+ path: filePath,
12199
+ kind: "unexpected-shape",
12200
+ detail: "no usable `mcpServers` object and the root is not a server map"
12201
+ });
12202
+ return [];
12203
+ }
12204
+ const facts = [];
12205
+ for (const [name, value] of Object.entries(servers)) {
12206
+ let hasCommand = false;
12207
+ let hasUrl = false;
12208
+ let command;
12209
+ let commandExists;
12210
+ let transport;
12211
+ const literalEnvKeys = [];
12212
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
12213
+ const entry = value;
12214
+ hasCommand = typeof entry.command === "string" && entry.command.length > 0;
12215
+ if (typeof entry.command === "string" && entry.command.length > 0) {
12216
+ command = entry.command;
12217
+ const resolved = resolveMcpCommand(root, entry.command);
12218
+ if (resolved !== undefined) {
12219
+ commandExists = resolved.exists;
12220
+ }
12221
+ }
12222
+ hasUrl = typeof entry.url === "string" && entry.url.length > 0;
12223
+ if (typeof entry.type === "string" && entry.type.length > 0) {
12224
+ transport = entry.type;
12225
+ }
12226
+ const env = entry.env;
12227
+ if (env !== null && typeof env === "object" && !Array.isArray(env)) {
12228
+ for (const [key, val] of Object.entries(env)) {
12229
+ 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)) {
12230
+ literalEnvKeys.push(key);
12231
+ }
12232
+ }
12233
+ }
12234
+ }
12235
+ facts.push({
12236
+ name,
12237
+ path: filePath,
12238
+ hasCommand,
12239
+ hasUrl,
12240
+ ...command === undefined ? {} : { command },
12241
+ ...commandExists === undefined ? {} : { commandExists },
12242
+ ...transport === undefined ? {} : { transport },
12243
+ literalEnvKeys,
12244
+ raw: JSON.stringify(value)
12113
12245
  });
12114
- return [];
12115
- }
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;
12124
- }
12125
- out.push(entry);
12126
12246
  }
12127
- return out;
12247
+ return facts;
12128
12248
  }
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);
12249
+ function discoverMcp(root, mcpPaths, errors3) {
12135
12250
  const facts = [];
12136
- for (const name of entries) {
12137
- if (!name.endsWith(".md")) {
12251
+ const seen = new Set;
12252
+ for (const rel of mcpPaths) {
12253
+ const filePath = join5(root, rel);
12254
+ if (!existsSync5(filePath)) {
12138
12255
  continue;
12139
12256
  }
12140
- const filePath = join2(dir, name);
12141
- try {
12142
- const st = statSync(filePath);
12143
- if (!st.isFile()) {
12144
- continue;
12145
- }
12146
- } catch {
12257
+ const raw = readJsonConfig(filePath, errors3);
12258
+ if (raw === undefined) {
12147
12259
  continue;
12148
12260
  }
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;
12261
+ for (const fact of parseMcpServers(raw, filePath, root, errors3)) {
12262
+ const key = `${fact.name}@${fact.path}`;
12263
+ if (seen.has(key)) {
12264
+ continue;
12265
+ }
12266
+ seen.add(key);
12267
+ facts.push(fact);
12166
12268
  }
12167
- facts.push(fact);
12168
12269
  }
12169
12270
  return facts;
12170
12271
  }
12272
+
12273
+ // src/discover/policy.ts
12274
+ import { existsSync as existsSync6 } from "node:fs";
12275
+ import { join as join6 } from "node:path";
12171
12276
  function discoverPolicyFiles(root, policyFiles, errors3) {
12172
12277
  const out = [];
12173
12278
  for (const rel of policyFiles) {
12174
- const filePath = join2(root, rel);
12175
- if (!existsSync2(filePath)) {
12279
+ const filePath = join6(root, rel);
12280
+ if (!existsSync6(filePath)) {
12176
12281
  continue;
12177
12282
  }
12178
12283
  try {
12179
- const buf = readFileSync2(filePath);
12180
- const text = buf.subarray(0, POLICY_CAP).toString("utf8");
12284
+ const result = readCapped(filePath, POLICY_CAP);
12285
+ const text = result.buf.subarray(0, POLICY_CAP).toString("utf8");
12181
12286
  out.push({ path: filePath, text });
12287
+ if (result.truncated)
12288
+ errors3.push({ path: filePath, kind: "truncated", detail: `file exceeds ${POLICY_CAP} byte scan cap` });
12182
12289
  } catch (err) {
12183
12290
  errors3.push({
12184
12291
  path: filePath,
@@ -12190,13 +12297,13 @@ function discoverPolicyFiles(root, policyFiles, errors3) {
12190
12297
  return out;
12191
12298
  }
12192
12299
  function discoverSkillsLock(root, errors3) {
12193
- const filePath = join2(root, "skills-lock.json");
12194
- if (!existsSync2(filePath)) {
12300
+ const filePath = join6(root, "skills-lock.json");
12301
+ if (!existsSync6(filePath)) {
12195
12302
  return { locked: [], present: false };
12196
12303
  }
12197
12304
  const raw = readJsonConfig(filePath, errors3);
12198
12305
  if (raw === undefined) {
12199
- return { locked: [], present: true };
12306
+ return { locked: [], present: true, invalid: true };
12200
12307
  }
12201
12308
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
12202
12309
  errors3.push({
@@ -12204,7 +12311,7 @@ function discoverSkillsLock(root, errors3) {
12204
12311
  kind: "unexpected-shape",
12205
12312
  detail: "skills-lock.json is not a JSON object"
12206
12313
  });
12207
- return { locked: [], present: true };
12314
+ return { locked: [], present: true, invalid: true };
12208
12315
  }
12209
12316
  const skills = raw.skills;
12210
12317
  if (skills === null || typeof skills !== "object" || Array.isArray(skills)) {
@@ -12213,7 +12320,7 @@ function discoverSkillsLock(root, errors3) {
12213
12320
  kind: "unexpected-shape",
12214
12321
  detail: "skills-lock.json has no `skills` object"
12215
12322
  });
12216
- return { locked: [], present: true };
12323
+ return { locked: [], present: true, invalid: true };
12217
12324
  }
12218
12325
  const locked = [];
12219
12326
  for (const [id, value] of Object.entries(skills)) {
@@ -12234,50 +12341,219 @@ function discoverSkillsLock(root, errors3) {
12234
12341
  }
12235
12342
  return { locked, present: true };
12236
12343
  }
12344
+
12345
+ // src/discover/skills.ts
12346
+ import { existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
12347
+ import { join as join7, resolve as resolve2 } from "node:path";
12348
+ var BUNDLED = "scripts|references|assets|templates|examples";
12349
+ var REFERENCE_RE = new RegExp(`(?<![\\w/.-])(?:\\./)?((?:${BUNDLED})/[A-Za-z0-9_./-]+\\.[A-Za-z0-9]{1,10})(?![A-Za-z0-9_.-])`, "g");
12350
+ var FENCE_BLOCK = /^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^[ \t]*\1[ \t]*$|$)/gm;
12351
+ var INLINE_CODE = /`[^`\n]*`/g;
12352
+ var URLS = /\b[a-z][a-z0-9+.-]*:\/\/\S+/gi;
12353
+ function skillReferences(body) {
12354
+ 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, "");
12355
+ const out = [];
12356
+ const seen = new Set;
12357
+ for (const match of prose.matchAll(REFERENCE_RE)) {
12358
+ const path = match[1];
12359
+ if (path === undefined || seen.has(path)) {
12360
+ continue;
12361
+ }
12362
+ seen.add(path);
12363
+ out.push(path);
12364
+ }
12365
+ return out;
12366
+ }
12367
+ function brokenReferences(body, skillDir, root) {
12368
+ const isFile = (p) => {
12369
+ try {
12370
+ return statSync4(p).isFile();
12371
+ } catch {
12372
+ return false;
12373
+ }
12374
+ };
12375
+ return skillReferences(body).filter((rel) => !isFile(join7(skillDir, rel)) && !isFile(join7(root, rel)));
12376
+ }
12377
+ function discoverSkillsInDir(dir, source, errors3, root) {
12378
+ if (!existsSync7(dir)) {
12379
+ return [];
12380
+ }
12381
+ let entries;
12382
+ try {
12383
+ entries = readdirSync2(dir);
12384
+ } catch (err) {
12385
+ errors3.push({
12386
+ path: dir,
12387
+ kind: "unreadable",
12388
+ detail: err instanceof Error ? err.message : String(err)
12389
+ });
12390
+ return [];
12391
+ }
12392
+ const skills = [];
12393
+ const normalizedDir = dir.replaceAll("\\", "/");
12394
+ const runtime = normalizedDir.includes("/.claude/skills") || normalizedDir.endsWith("/.claude/skills") ? "claude" : normalizedDir.includes("/.agents/skills") || normalizedDir.endsWith("/.agents/skills") ? "agents" : "unknown";
12395
+ for (const name of entries) {
12396
+ if (name.startsWith(".") || name === "node_modules") {
12397
+ continue;
12398
+ }
12399
+ const skillDir = join7(dir, name);
12400
+ let st;
12401
+ try {
12402
+ st = statSync4(skillDir);
12403
+ } catch {
12404
+ continue;
12405
+ }
12406
+ if (!st.isDirectory()) {
12407
+ continue;
12408
+ }
12409
+ const skillMd = join7(skillDir, "SKILL.md");
12410
+ let dirReadable = true;
12411
+ try {
12412
+ readdirSync2(skillDir);
12413
+ } catch (err) {
12414
+ dirReadable = false;
12415
+ errors3.push({
12416
+ path: skillDir,
12417
+ kind: "unreadable",
12418
+ detail: err instanceof Error ? err.message : String(err)
12419
+ });
12420
+ }
12421
+ const hasSkillMd = dirReadable && existsSync7(skillMd);
12422
+ const fm = hasSkillMd ? readFrontmatter(skillMd, errors3) : { hasFrontmatter: false };
12423
+ const fact = {
12424
+ id: name,
12425
+ runtime,
12426
+ path: skillDir,
12427
+ source,
12428
+ hasSkillMd,
12429
+ hasFrontmatter: fm.hasFrontmatter
12430
+ };
12431
+ if (fm.unreadable === true || !dirReadable) {
12432
+ fact.unreadable = true;
12433
+ }
12434
+ if (fm.unparseable === true) {
12435
+ fact.unparseableFrontmatter = true;
12436
+ }
12437
+ if (fm.body !== undefined) {
12438
+ const broken = brokenReferences(fm.body, skillDir, root);
12439
+ if (broken.length > 0) {
12440
+ fact.brokenReferences = broken;
12441
+ }
12442
+ }
12443
+ if (fm.description !== undefined) {
12444
+ fact.description = fm.description;
12445
+ }
12446
+ if (fm.name !== undefined) {
12447
+ fact.frontmatterName = fm.name;
12448
+ }
12449
+ skills.push(fact);
12450
+ }
12451
+ return skills;
12452
+ }
12453
+ var NESTED_DISCOVERY_MAX_DEPTH = 8;
12454
+ var NESTED_DISCOVERY_SKIP = new Set([
12455
+ ".git",
12456
+ "node_modules",
12457
+ ".next",
12458
+ "dist",
12459
+ "build",
12460
+ "coverage",
12461
+ "vendor"
12462
+ ]);
12463
+ function discoverNestedClaudeSkills(root, configuredRoots, errors3, options = {}) {
12464
+ const out = [];
12465
+ const read = (dir) => {
12466
+ options.onDirectoryRead?.();
12467
+ try {
12468
+ return readdirSync2(dir, { withFileTypes: true });
12469
+ } catch {
12470
+ errors3.push({ path: dir, kind: "unreadable", detail: "could not read nested skill directories" });
12471
+ return;
12472
+ }
12473
+ };
12474
+ const inspectClaude = (dir) => {
12475
+ const entries = read(dir);
12476
+ if (entries === undefined)
12477
+ return;
12478
+ for (const entry of entries) {
12479
+ if (!entry.isDirectory() || entry.name !== "skills")
12480
+ continue;
12481
+ const child = join7(dir, entry.name);
12482
+ if (configuredRoots.has(resolve2(child)))
12483
+ continue;
12484
+ out.push(...discoverSkillsInDir(child, "project", errors3, root));
12485
+ }
12486
+ };
12487
+ const walk = (dir, depth) => {
12488
+ const entries = read(dir);
12489
+ if (entries === undefined)
12490
+ return;
12491
+ for (const entry of entries) {
12492
+ if (!entry.isDirectory() || NESTED_DISCOVERY_SKIP.has(entry.name))
12493
+ continue;
12494
+ if (entry.name.startsWith(".") && entry.name !== ".claude")
12495
+ continue;
12496
+ const child = join7(dir, entry.name);
12497
+ if (entry.name === ".claude") {
12498
+ inspectClaude(child);
12499
+ continue;
12500
+ }
12501
+ if (depth >= NESTED_DISCOVERY_MAX_DEPTH)
12502
+ continue;
12503
+ walk(child, depth + 1);
12504
+ }
12505
+ };
12506
+ walk(root, 0);
12507
+ return out;
12508
+ }
12509
+ function disambiguateSkills(skills, root) {
12510
+ const counts = new Map;
12511
+ for (const skill of skills)
12512
+ counts.set(skill.id, (counts.get(skill.id) ?? 0) + 1);
12513
+ return skills.map((skill) => {
12514
+ if ((counts.get(skill.id) ?? 0) < 2)
12515
+ return skill;
12516
+ const location = skill.source === "project" ? skill.path.slice(root.length + 1) : `global:${skill.path}`;
12517
+ return { ...skill, instanceId: `${skill.id}@${location}` };
12518
+ });
12519
+ }
12520
+
12521
+ // src/discover/index.ts
12237
12522
  function discoverAgentSurface(root, config, opts) {
12238
12523
  const configErrors = [];
12239
12524
  const skills = [];
12240
12525
  for (const rel of config.skillPaths) {
12241
- skills.push(...discoverSkillsInDir(join2(root, rel), "project", configErrors, root));
12526
+ skills.push(...discoverSkillsInDir(join8(root, rel), "project", configErrors, root));
12242
12527
  }
12528
+ const configuredRoots = new Set(config.skillPaths.map((rel) => resolve3(root, rel)));
12529
+ skills.push(...discoverNestedClaudeSkills(root, configuredRoots, configErrors, {
12530
+ onDirectoryRead: opts.onNestedDirectoryRead
12531
+ }));
12243
12532
  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));
12533
+ const home = homedir3();
12534
+ skills.push(...discoverSkillsInDir(join8(home, ".claude", "skills"), "global", configErrors, root));
12535
+ skills.push(...discoverSkillsInDir(join8(home, ".codex", "skills"), "global", configErrors, root));
12247
12536
  }
12248
12537
  const lock = discoverSkillsLock(root, configErrors);
12249
12538
  return {
12250
- skills: dedupeSkillsById(skills),
12539
+ skills: disambiguateSkills(skills, root),
12251
12540
  agents: discoverAgents(root, configErrors),
12252
12541
  hooks: discoverHooks(root, configErrors),
12253
12542
  mcp: discoverMcp(root, config.mcpPaths, configErrors),
12254
12543
  policyFiles: discoverPolicyFiles(root, config.policyFiles, configErrors),
12255
12544
  lockedSkills: lock.locked,
12256
12545
  hasSkillsLock: lock.present,
12546
+ skillsLockInvalid: lock.invalid,
12257
12547
  configErrors
12258
12548
  };
12259
12549
  }
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
12550
 
12275
12551
  // 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";
12552
+ import { existsSync as existsSync8, readFileSync as readFileSync3 } from "node:fs";
12553
+ import { join as join9 } from "node:path";
12278
12554
  function readPackageJson(root, errors3) {
12279
- const path = join3(root, "package.json");
12280
- if (!existsSync3(path)) {
12555
+ const path = join9(root, "package.json");
12556
+ if (!existsSync8(path)) {
12281
12557
  return {};
12282
12558
  }
12283
12559
  let text;
@@ -12315,7 +12591,7 @@ function readPackageJson(root, errors3) {
12315
12591
  if (typeof obj.packageManager === "string") {
12316
12592
  pkg.packageManager = obj.packageManager;
12317
12593
  }
12318
- for (const field of ["dependencies", "devDependencies", "scripts"]) {
12594
+ for (const field of ["dependencies", "devDependencies"]) {
12319
12595
  const raw2 = obj[field];
12320
12596
  const kept = stringEntries(raw2);
12321
12597
  if (kept === undefined) {
@@ -12337,6 +12613,19 @@ function readPackageJson(root, errors3) {
12337
12613
  }
12338
12614
  pkg[field] = kept.values;
12339
12615
  }
12616
+ const scripts = obj.scripts;
12617
+ if (scripts !== undefined) {
12618
+ const kept = stringEntries(scripts);
12619
+ if (kept === undefined) {
12620
+ errors3.push({ path, kind: "unexpected-shape", detail: "scripts is not an object" });
12621
+ } else if (kept.dropped > 0) {
12622
+ errors3.push({
12623
+ path,
12624
+ kind: "unexpected-shape",
12625
+ detail: `scripts: ${kept.dropped} entr${kept.dropped === 1 ? "y" : "ies"} ignored (value is not a string)`
12626
+ });
12627
+ }
12628
+ }
12340
12629
  return pkg;
12341
12630
  }
12342
12631
  function stringEntries(value) {
@@ -12362,95 +12651,32 @@ function inferPackageManager(root, pkg) {
12362
12651
  return prefix;
12363
12652
  }
12364
12653
  }
12365
- if (existsSync3(join3(root, "bun.lock")) || existsSync3(join3(root, "bun.lockb"))) {
12654
+ if (existsSync8(join9(root, "bun.lock")) || existsSync8(join9(root, "bun.lockb"))) {
12366
12655
  return "bun";
12367
12656
  }
12368
- if (existsSync3(join3(root, "pnpm-lock.yaml"))) {
12657
+ if (existsSync8(join9(root, "pnpm-lock.yaml"))) {
12369
12658
  return "pnpm";
12370
12659
  }
12371
- if (existsSync3(join3(root, "yarn.lock"))) {
12660
+ if (existsSync8(join9(root, "yarn.lock"))) {
12372
12661
  return "yarn";
12373
12662
  }
12374
- if (existsSync3(join3(root, "package-lock.json"))) {
12663
+ if (existsSync8(join9(root, "package-lock.json"))) {
12375
12664
  return "npm";
12376
12665
  }
12377
12666
  return "unknown";
12378
12667
  }
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
12668
  function extractFacts(root, config, opts) {
12440
12669
  const includeGlobal = opts?.includeGlobal ?? config.includeGlobal;
12441
12670
  const packageErrors = [];
12442
12671
  const pkg = readPackageJson(root, packageErrors);
12443
12672
  const dependencies = { ...pkg.dependencies ?? {} };
12444
12673
  const devDependencies = { ...pkg.devDependencies ?? {} };
12445
- const scripts = { ...pkg.scripts ?? {} };
12446
12674
  const surface = discoverAgentSurface(root, config, { includeGlobal });
12447
12675
  return {
12448
12676
  root,
12449
12677
  packageManager: inferPackageManager(root, pkg),
12450
12678
  dependencies,
12451
12679
  devDependencies,
12452
- scripts,
12453
- configs: extractConfigs(root, dependencies, devDependencies, scripts),
12454
12680
  skills: surface.skills,
12455
12681
  agents: surface.agents,
12456
12682
  hooks: surface.hooks,
@@ -12458,6 +12684,7 @@ function extractFacts(root, config, opts) {
12458
12684
  policyFiles: surface.policyFiles,
12459
12685
  lockedSkills: surface.lockedSkills,
12460
12686
  hasSkillsLock: surface.hasSkillsLock,
12687
+ skillsLockInvalid: surface.skillsLockInvalid,
12461
12688
  configErrors: [...packageErrors, ...surface.configErrors]
12462
12689
  };
12463
12690
  }
@@ -12487,7 +12714,7 @@ function compareAction(a, b) {
12487
12714
 
12488
12715
  // src/analyze.ts
12489
12716
  function analyze(options = {}) {
12490
- const requested = resolve2(options.dir ?? process.cwd());
12717
+ const requested = resolve4(options.dir ?? process.cwd());
12491
12718
  const root = resolveRoot(requested);
12492
12719
  const loaded = loadConfig(root, options.configPath);
12493
12720
  const config = {
@@ -12508,7 +12735,7 @@ function analyze(options = {}) {
12508
12735
  agents: config.thresholds.agents,
12509
12736
  mcp: config.thresholds.mcp
12510
12737
  }
12511
- }).filter((f) => !ignoredRules.has(f.ruleId) && !(f.subject.startsWith("skill:") && ignoredSkills.has(f.subject.slice("skill:".length))));
12738
+ }).filter((f) => !ignoredRules.has(f.ruleId) && !(f.subject.startsWith("skill:") && ignoredSkills.has(f.subject.slice("skill:".length).split("@", 1)[0] ?? "")));
12512
12739
  const ignoredFindings = new Set(config.ignoreFindings);
12513
12740
  const findings = sortFindings(structural.filter((f) => !ignoredFindings.has(f.id)));
12514
12741
  return {
@@ -12741,16 +12968,13 @@ function shouldColour(args) {
12741
12968
  }
12742
12969
 
12743
12970
  // src/report/text.ts
12744
- var ACTION_LABEL = {
12745
- keep: "KEEP",
12746
- delete: "DELETE",
12747
- add: "ADD",
12748
- refresh: "REFRESH",
12749
- warn: "WARN",
12750
- drift: "DRIFT"
12971
+ var SEVERITY_LABEL = {
12972
+ error: "ERROR",
12973
+ warning: "WARN",
12974
+ info: "INFO"
12751
12975
  };
12752
- function actionColumn(action) {
12753
- return ACTION_LABEL[action].padEnd(7, " ");
12976
+ function severityColumn(severity) {
12977
+ return SEVERITY_LABEL[severity].padEnd(7, " ");
12754
12978
  }
12755
12979
  function renderText(args) {
12756
12980
  const { version, facts, verbose, quiet } = args;
@@ -12775,7 +12999,7 @@ function renderText(args) {
12775
12999
  lines.push(ink.dim(`Scanned: ${formatStack(facts)}`));
12776
13000
  lines.push("");
12777
13001
  for (const group of groupByRule(visible)) {
12778
- lines.push(...formatGroup(group, facts.root, ink));
13002
+ lines.push(...formatGroup(group, facts.root, ink, verbose));
12779
13003
  lines.push("");
12780
13004
  }
12781
13005
  lines.push(summary);
@@ -12830,62 +13054,78 @@ function groupByRule(findings) {
12830
13054
  function shortPath(value, root) {
12831
13055
  return value.split(`${root}/`).join("");
12832
13056
  }
12833
- var ACTION_TONE = {
12834
- delete: "red",
12835
- warn: "yellow",
12836
- drift: "yellow",
12837
- add: "green",
12838
- refresh: "green"
13057
+ var SEVERITY_TONE = {
13058
+ error: "red",
13059
+ warning: "yellow",
13060
+ info: undefined
12839
13061
  };
12840
- function formatGroup(group, root, ink) {
13062
+ function formatGroup(group, root, ink, verbose) {
12841
13063
  const first = group[0];
12842
13064
  const count = group.length;
12843
13065
  const out = [];
12844
- const tone = ACTION_TONE[first.action];
12845
- const label = actionColumn(first.action);
13066
+ const tone = SEVERITY_TONE[first.severity];
13067
+ const label = severityColumn(first.severity);
12846
13068
  out.push(`${tone === undefined ? ink.dim(label) : ink.tone(tone, label)} ${ink.dim(`rule:${safe(first.ruleId)}`)}${count > 1 ? ink.bold(` ×${count}`) : ""}`);
12847
- out.push(` ${safe(count === 1 ? first.message : ruleHeadline(first))}`);
13069
+ out.push(` ${safe(count === 1 ? first.message : groupHeadline(group))}`);
12848
13070
  for (const f of group) {
12849
13071
  const where = f.evidence.filter((e) => e.kind !== "count" && e.kind !== "threshold").map((e) => shortPath(e.value, root));
12850
13072
  const location = where.length > 0 ? where.join(" · ") : f.subject;
12851
13073
  out.push(ink.dim(` ${safe(shortPath(location, root))}`));
13074
+ if (verbose) {
13075
+ out.push(ink.dim(` id: ${safe(f.id)}`));
13076
+ }
12852
13077
  }
12853
13078
  return out;
12854
13079
  }
12855
- function ruleHeadline(f) {
12856
- const cut = f.message.indexOf(": ");
12857
- return cut === -1 ? f.message : f.message.slice(0, cut);
13080
+ function groupHeadline(group) {
13081
+ const stripped = group.map((f) => {
13082
+ const cut = f.message.indexOf(": ");
13083
+ return cut === -1 ? f.message : f.message.slice(0, cut);
13084
+ });
13085
+ if (new Set(stripped).size === 1) {
13086
+ return stripped[0];
13087
+ }
13088
+ const deEvented = stripped.map((s) => s.replace(/^[A-Za-z][A-Za-z0-9]* hook /, "Hook "));
13089
+ if (new Set(deEvented).size === 1) {
13090
+ return deEvented[0];
13091
+ }
13092
+ return `${group.length} findings`;
12858
13093
  }
12859
13094
  function countsLine(findings, verbose) {
12860
- const parts = actionParts(findings, verbose);
13095
+ const parts = severityParts(findings, verbose);
12861
13096
  return parts.length === 0 ? "no findings" : parts.join(" · ");
12862
13097
  }
12863
- function actionParts(findings, verbose) {
13098
+ function severityParts(findings, verbose) {
12864
13099
  const counts = {
12865
- keep: 0,
12866
- delete: 0,
12867
- add: 0,
12868
- refresh: 0,
12869
- warn: 0,
12870
- drift: 0
13100
+ error: 0,
13101
+ warning: 0,
13102
+ info: 0
12871
13103
  };
12872
13104
  let infoHidden = 0;
12873
13105
  for (const f of findings) {
13106
+ if (!verbose && f.action === "keep") {
13107
+ continue;
13108
+ }
12874
13109
  if (!verbose && f.severity === "info") {
12875
13110
  infoHidden += 1;
12876
13111
  continue;
12877
13112
  }
12878
- counts[f.action] += 1;
13113
+ counts[f.severity] += 1;
12879
13114
  }
12880
- const order = ["delete", "add", "refresh", "drift", "warn", "keep"];
12881
- const parts = order.filter((action) => counts[action] > 0).map((action) => `${counts[action]} ${action}`);
13115
+ const order = ["error", "warning", "info"];
13116
+ const labels = {
13117
+ error: "error",
13118
+ warning: "warning",
13119
+ info: "info"
13120
+ };
13121
+ const parts = order.filter((severity) => counts[severity] > 0).map((severity) => `${counts[severity]} ${labels[severity]}`);
12882
13122
  if (!verbose && infoHidden > 0) {
12883
13123
  parts.push(`${infoHidden} info hidden (--verbose)`);
12884
13124
  }
12885
13125
  return parts;
12886
13126
  }
12887
13127
  function formatSummary(findings, verbose, ink) {
12888
- const parts = actionParts(findings, verbose);
13128
+ const parts = severityParts(findings, verbose);
12889
13129
  const points = score(findings);
12890
13130
  const scored = `score ${ink.tone(scoreTone(points), `${points}/100 ${scoreLabel(points)}`)}`;
12891
13131
  if (parts.length === 0) {
@@ -12895,7 +13135,7 @@ function formatSummary(findings, verbose, ink) {
12895
13135
  }
12896
13136
 
12897
13137
  // src/version.ts
12898
- var VERSION = "0.4.0";
13138
+ var VERSION = "0.6.0";
12899
13139
 
12900
13140
  // src/commands/check.ts
12901
13141
  async function runCheck(options) {
@@ -12934,6 +13174,54 @@ async function runCheck(options) {
12934
13174
  };
12935
13175
  }
12936
13176
 
13177
+ // src/commands/demo.ts
13178
+ import {
13179
+ mkdirSync,
13180
+ mkdtempSync,
13181
+ rmSync,
13182
+ writeFileSync
13183
+ } from "node:fs";
13184
+ import { tmpdir } from "node:os";
13185
+ import { join as join10 } from "node:path";
13186
+ async function runDemo() {
13187
+ const root = mkdtempSync(join10(tmpdir(), "agentscan-demo-"));
13188
+ try {
13189
+ const claude = join10(root, ".claude");
13190
+ mkdirSync(claude, { recursive: true });
13191
+ writeFileSync(join10(claude, "settings.json"), `${JSON.stringify({
13192
+ hooks: {
13193
+ PreToolUse: [
13194
+ {
13195
+ hooks: [
13196
+ {
13197
+ type: "command",
13198
+ command: ".claude/hooks/guard-destructive-bash.js"
13199
+ }
13200
+ ]
13201
+ }
13202
+ ]
13203
+ }
13204
+ }, null, 2)}
13205
+ `, "utf8");
13206
+ const result = await runCheck({
13207
+ dir: root,
13208
+ failOn: "never",
13209
+ colour: false
13210
+ });
13211
+ const intro = `Demo fixture: PreToolUse hook points at a script that is not on disk.
13212
+ ` + `This is the failure agentscan exists for — the agent starts normally; the guard does not.
13213
+
13214
+ `;
13215
+ return {
13216
+ exitCode: 0,
13217
+ stdout: intro + result.stdout,
13218
+ stderr: ""
13219
+ };
13220
+ } finally {
13221
+ rmSync(root, { recursive: true, force: true });
13222
+ }
13223
+ }
13224
+
12937
13225
  // src/commands/explain.ts
12938
13226
  async function runExplain(findingId, options = {}) {
12939
13227
  const { findings } = analyze(options);
@@ -12974,12 +13262,12 @@ function formatExplain(f) {
12974
13262
  }
12975
13263
 
12976
13264
  // src/commands/init.ts
12977
- import { existsSync as existsSync4, writeFileSync } from "node:fs";
12978
- import { join as join4, resolve as resolve3 } from "node:path";
13265
+ import { existsSync as existsSync9, writeFileSync as writeFileSync2 } from "node:fs";
13266
+ import { join as join11, resolve as resolve5 } from "node:path";
12979
13267
  async function runInit(dir, options = {}) {
12980
- const root = resolve3(dir ?? process.cwd());
12981
- const path = join4(root, ".agentscanrc.json");
12982
- if (existsSync4(path) && !options.force) {
13268
+ const root = resolve5(dir ?? process.cwd());
13269
+ const path = join11(root, ".agentscanrc.json");
13270
+ if (existsSync9(path) && !options.force) {
12983
13271
  return {
12984
13272
  exitCode: 2,
12985
13273
  stdout: "",
@@ -12989,7 +13277,7 @@ async function runInit(dir, options = {}) {
12989
13277
  }
12990
13278
  const body = `${JSON.stringify(defaultConfig, null, 2)}
12991
13279
  `;
12992
- writeFileSync(path, body, "utf8");
13280
+ writeFileSync2(path, body, "utf8");
12993
13281
  return {
12994
13282
  exitCode: 0,
12995
13283
  stdout: `Wrote ${path}
@@ -13015,7 +13303,7 @@ async function runRulesCommand(options = {}) {
13015
13303
  // src/report/clipboard.ts
13016
13304
  import { spawn } from "node:child_process";
13017
13305
  import { accessSync, constants } from "node:fs";
13018
- import { delimiter, join as join5 } from "node:path";
13306
+ import { delimiter, join as join12 } from "node:path";
13019
13307
  var TOOLS = [
13020
13308
  ["wl-copy"],
13021
13309
  ["xclip", "-selection", "clipboard"],
@@ -13024,15 +13312,15 @@ var TOOLS = [
13024
13312
  ["clip.exe"]
13025
13313
  ];
13026
13314
  var TIMEOUT_MS = 3000;
13027
- function clipboardCandidates(onPath = (name) => resolve4(name) !== null) {
13315
+ function clipboardCandidates(onPath = (name) => resolve6(name) !== null) {
13028
13316
  return TOOLS.filter((t) => onPath(t[0])).map((t) => [...t]);
13029
13317
  }
13030
- function resolve4(name) {
13318
+ function resolve6(name) {
13031
13319
  for (const dir of (process.env.PATH ?? "").split(delimiter)) {
13032
13320
  if (dir.length === 0) {
13033
13321
  continue;
13034
13322
  }
13035
- const candidate = join5(dir, name);
13323
+ const candidate = join12(dir, name);
13036
13324
  try {
13037
13325
  accessSync(candidate, constants.X_OK);
13038
13326
  return candidate;
@@ -13051,7 +13339,7 @@ async function copyToClipboard(text) {
13051
13339
  const failures = [];
13052
13340
  for (const argv of candidates) {
13053
13341
  const tool = argv[0];
13054
- const bin = resolve4(tool);
13342
+ const bin = resolve6(tool);
13055
13343
  if (bin === null) {
13056
13344
  continue;
13057
13345
  }
@@ -13087,6 +13375,7 @@ function printHelp() {
13087
13375
  Usage:
13088
13376
  agentscan scan the current directory
13089
13377
  agentscan check [dir] [options]
13378
+ agentscan demo one-shot fixture (no project required)
13090
13379
  agentscan explain <findingId> [dir]
13091
13380
  agentscan rules [dir]
13092
13381
  agentscan init [dir] [--force]
@@ -13252,6 +13541,16 @@ async function main(argv) {
13252
13541
  }
13253
13542
  return result.exitCode;
13254
13543
  }
13544
+ case "demo": {
13545
+ const result = await runDemo();
13546
+ if (result.stderr.length > 0) {
13547
+ process.stderr.write(result.stderr);
13548
+ }
13549
+ if (result.stdout.length > 0) {
13550
+ process.stdout.write(result.stdout);
13551
+ }
13552
+ return result.exitCode;
13553
+ }
13255
13554
  case "help":
13256
13555
  printHelp();
13257
13556
  return 0;