@lousy-agents/cli 5.18.2 → 5.19.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.
package/dist/index.js CHANGED
@@ -3032,7 +3032,7 @@ exports.basename = (path, { windows } = {}) => {
3032
3032
 
3033
3033
 
3034
3034
  },
3035
- 7015(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
3035
+ 4140(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
3036
3036
  // NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
3037
3037
  var constructs_namespaceObject = {};
3038
3038
  __webpack_require__.r(constructs_namespaceObject);
@@ -27697,13 +27697,13 @@ const PackageJsonSchema = schemas_object({
27697
27697
  "shared"
27698
27698
  ]
27699
27699
  },
27700
- // Doctor-only skill locations
27700
+ // Additional skill roots (SKILL.md convention, no alternate layout documented)
27701
27701
  {
27702
27702
  id: "skill-pi",
27703
27703
  path: ".pi/skills",
27704
27704
  matchKind: "directory-prefix",
27705
27705
  primaryConstruct: "skill",
27706
- lintTarget: "none",
27706
+ lintTarget: "skills",
27707
27707
  harnessHints: [
27708
27708
  "pi"
27709
27709
  ]
@@ -27713,7 +27713,7 @@ const PackageJsonSchema = schemas_object({
27713
27713
  path: ".pi/prompts",
27714
27714
  matchKind: "directory-prefix",
27715
27715
  primaryConstruct: "skill",
27716
- lintTarget: "none",
27716
+ lintTarget: "skills",
27717
27717
  harnessHints: [
27718
27718
  "pi"
27719
27719
  ]
@@ -27833,13 +27833,13 @@ const PackageJsonSchema = schemas_object({
27833
27833
  ],
27834
27834
  instructionFormat: "claude-md"
27835
27835
  },
27836
- // Doctor-only construct locations
27836
+ // Subagent lint root
27837
27837
  {
27838
27838
  id: "subagent-claude",
27839
27839
  path: ".claude/agents",
27840
27840
  matchKind: "directory-prefix",
27841
27841
  primaryConstruct: "subagent",
27842
- lintTarget: "none",
27842
+ lintTarget: "subagents",
27843
27843
  harnessHints: [
27844
27844
  "claude"
27845
27845
  ]
@@ -27875,11 +27875,14 @@ const PackageJsonSchema = schemas_object({
27875
27875
  * Normalizes a repo-relative path for catalog matching.
27876
27876
  * Collapses `.` / `..` segments. Returns "" when the path is absolute or
27877
27877
  * escapes the repository root — callers treat "" as non-matching.
27878
- */ function normalizeRepoRelativePath(path) {
27879
- const normalized = path.replaceAll("\\", "/");
27878
+ */ function isAbsoluteRepoPath(normalized) {
27880
27879
  // POSIX absolute, UNC-style leading slash after backslash conversion,
27881
27880
  // and Windows drive-letter absolute (C:/..., d:\...).
27882
- if (normalized.startsWith("/") || /^[A-Za-z]:(\/|$)/.test(normalized)) {
27881
+ return normalized.startsWith("/") || /^[A-Za-z]:(\/|$)/.test(normalized);
27882
+ }
27883
+ function normalizeRepoRelativePath(path) {
27884
+ const normalized = path.replaceAll("\\", "/");
27885
+ if (isAbsoluteRepoPath(normalized)) {
27883
27886
  return "";
27884
27887
  }
27885
27888
  const resolved = [];
@@ -27887,10 +27890,10 @@ const PackageJsonSchema = schemas_object({
27887
27890
  if (segment === "" || segment === ".") {
27888
27891
  continue;
27889
27892
  }
27893
+ if (segment === ".." && resolved.length === 0) {
27894
+ return "";
27895
+ }
27890
27896
  if (segment === "..") {
27891
- if (resolved.length === 0) {
27892
- return "";
27893
- }
27894
27897
  resolved.pop();
27895
27898
  continue;
27896
27899
  }
@@ -27938,14 +27941,15 @@ function constructTypesForPath(path, catalog = AGENTIC_LOCATION_CATALOG) {
27938
27941
  if (!best) {
27939
27942
  return [];
27940
27943
  }
27941
- if (best.secondaryConstructs && best.secondaryConstructs.length > 0) {
27944
+ const secondary = best.secondaryConstructs ?? [];
27945
+ if (secondary.length === 0) {
27942
27946
  return [
27943
- best.primaryConstruct,
27944
- ...best.secondaryConstructs
27947
+ best.primaryConstruct
27945
27948
  ];
27946
27949
  }
27947
27950
  return [
27948
- best.primaryConstruct
27951
+ best.primaryConstruct,
27952
+ ...secondary
27949
27953
  ];
27950
27954
  }
27951
27955
  function lintTargetEntries(target, catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
@@ -27957,8 +27961,14 @@ function skillDirectoryRoots(catalog = agentic_location_catalog_AGENTIC_LOCATION
27957
27961
  function agentDirectoryRoots(catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
27958
27962
  return lintTargetEntries("agents", catalog).filter((entry)=>entry.matchKind === "directory-prefix").map((entry)=>entry.path);
27959
27963
  }
27964
+ function subagentDirectoryRoots(catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
27965
+ return lintTargetEntries("subagents", catalog).filter((entry)=>entry.matchKind === "directory-prefix").map((entry)=>entry.path);
27966
+ }
27967
+ function isHookConfigEntry(entry) {
27968
+ return entry.matchKind === "exact" && entry.hookPlatform != null;
27969
+ }
27960
27970
  function hookConfigPaths(catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
27961
- return lintTargetEntries("hooks", catalog).filter((entry)=>entry.matchKind === "exact" && entry.hookPlatform != null).map((entry)=>({
27971
+ return lintTargetEntries("hooks", catalog).filter(isHookConfigEntry).map((entry)=>({
27962
27972
  relativePath: entry.path,
27963
27973
  platform: entry.hookPlatform
27964
27974
  }));
@@ -30372,26 +30382,74 @@ function parseRawEdges(content) {
30372
30382
  return edges;
30373
30383
  }
30374
30384
 
30375
- ;// CONCATENATED MODULE: ../doctor/src/gateways/mcp-config.ts
30376
-
30377
-
30378
- const MAX_MCP_CONFIG_BYTES = 1_048_576;
30379
- const MCP_CONFIG_SOURCES = [
30385
+ ;// CONCATENATED MODULE: ../core/src/entities/mcp-config-source.ts
30386
+ /**
30387
+ * Canonical list of MCP server configuration file locations.
30388
+ * Shared by doctor (inventory enumeration) and lint (mcp-servers target) so
30389
+ * the two consumers cannot drift on where MCP servers are declared.
30390
+ *
30391
+ * Deliberately kept separate from AGENTIC_LOCATION_CATALOG: one MCP config
30392
+ * file can declare many servers, so the one-path-to-one-construct model the
30393
+ * catalog/matchers use for skills, agents, hooks, and instructions does not
30394
+ * fit. This list is the single source of truth for MCP config paths instead.
30395
+ */ const MCP_CONFIG_SOURCES = [
30380
30396
  {
30381
30397
  relPath: ".mcp.json",
30382
- harness: "shared"
30398
+ harnessHint: "shared"
30383
30399
  },
30384
30400
  {
30385
30401
  relPath: ".vscode/mcp.json",
30386
- harness: "copilot"
30402
+ harnessHint: "copilot"
30387
30403
  }
30388
30404
  ];
30405
+
30406
+ ;// CONCATENATED MODULE: ../doctor/src/gateways/mcp-config.ts
30407
+
30408
+
30409
+
30410
+ const MAX_MCP_CONFIG_BYTES = 1_048_576;
30389
30411
  const McpServerEntrySchema = schemas_object({
30390
30412
  type: schemas_string().optional(),
30391
30413
  transport: schemas_string().optional()
30392
30414
  }).passthrough();
30415
+ /**
30416
+ * Property names that must never be used as MCP server names: assigning
30417
+ * them via a plain object key (as zod's `record()` does when reconstructing
30418
+ * parsed output) triggers JavaScript's `__proto__` magic setter instead of
30419
+ * creating an own property, silently dropping the entry. Rejected explicitly
30420
+ * so a config declaring one of these names is treated as invalid instead of
30421
+ * silently under-inventoried.
30422
+ */ const RESERVED_SERVER_NAMES = [
30423
+ "__proto__",
30424
+ "constructor",
30425
+ "prototype"
30426
+ ];
30427
+ const SERVER_MAP_FIELDS = [
30428
+ "mcpServers",
30429
+ "servers"
30430
+ ];
30431
+ function hasReservedServerName(parsed) {
30432
+ if (typeof parsed !== "object" || parsed === null) {
30433
+ return false;
30434
+ }
30435
+ for (const field of SERVER_MAP_FIELDS){
30436
+ const servers = parsed[field];
30437
+ if (typeof servers !== "object" || servers === null) {
30438
+ continue;
30439
+ }
30440
+ if (RESERVED_SERVER_NAMES.some((name)=>Object.hasOwn(servers, name))) {
30441
+ return true;
30442
+ }
30443
+ }
30444
+ return false;
30445
+ }
30446
+ // Accepts both Claude Code's `.mcp.json` convention (`mcpServers`) and VS
30447
+ // Code's `.vscode/mcp.json` convention (`servers`) regardless of which
30448
+ // source file is being read, since either config file could in practice
30449
+ // use either key.
30393
30450
  const McpConfigSchema = schemas_object({
30394
- mcpServers: schemas_record(schemas_string(), McpServerEntrySchema).optional()
30451
+ mcpServers: schemas_record(schemas_string(), McpServerEntrySchema).optional(),
30452
+ servers: schemas_record(schemas_string(), McpServerEntrySchema).optional()
30395
30453
  }).passthrough();
30396
30454
  async function readMcpServersFromSource(repoRoot, source) {
30397
30455
  let content;
@@ -30406,13 +30464,20 @@ async function readMcpServersFromSource(repoRoot, source) {
30406
30464
  } catch {
30407
30465
  return [];
30408
30466
  }
30467
+ if (hasReservedServerName(raw)) {
30468
+ return [];
30469
+ }
30409
30470
  const result = McpConfigSchema.safeParse(raw);
30410
- if (!result.success || result.data.mcpServers === undefined) {
30471
+ if (!result.success) {
30411
30472
  return [];
30412
30473
  }
30413
- return Object.entries(result.data.mcpServers).map(([serverName, entry])=>({
30474
+ const servers = {
30475
+ ...result.data.mcpServers ?? {},
30476
+ ...result.data.servers ?? {}
30477
+ };
30478
+ return Object.entries(servers).map(([serverName, entry])=>({
30414
30479
  serverName,
30415
- harness: source.harness,
30480
+ harness: source.harnessHint,
30416
30481
  path: source.relPath,
30417
30482
  ...entry.transport !== undefined ? {
30418
30483
  transport: entry.transport
@@ -32855,6 +32920,82 @@ function createInitHooksCommand(useCase) {
32855
32920
  }
32856
32921
  }
32857
32922
 
32923
+ ;// CONCATENATED MODULE: ../core/src/gateways/markdown-file-discovery.ts
32924
+ /**
32925
+ * Shared recursive markdown-file discovery for lint gateways that scan a
32926
+ * catalog root directory for `.md` files (agents, subagents). Guards
32927
+ * against path traversal and symlink escapes, and caps recursion depth to
32928
+ * avoid stack exhaustion on pathologically deep directory trees.
32929
+ */
32930
+
32931
+ /** Maximum subdirectory nesting the walk will descend into. */ const MAX_WALK_DEPTH = 32;
32932
+ function isUnsafeEntryName(name) {
32933
+ return name.includes("..") || name.includes("/") || name.includes("\\");
32934
+ }
32935
+ function isWithinResolvedRoot(resolvedRootDir, absoluteEntryPath) {
32936
+ const resolvedPath = (0,external_node_path_.resolve)(absoluteEntryPath);
32937
+ const rel = (0,external_node_path_.relative)(resolvedRootDir, resolvedPath);
32938
+ return !rel.startsWith("..") && !rel.startsWith(external_node_path_.sep);
32939
+ }
32940
+ async function rootExists(targetDir, rootDir) {
32941
+ try {
32942
+ return await file_system_utils_pathExistsWithinRoot(targetDir, rootDir);
32943
+ } catch {
32944
+ return false;
32945
+ }
32946
+ }
32947
+ function isDiscoverableMarkdownFile(entry, name) {
32948
+ return entry.isFile() && name.endsWith(".md");
32949
+ }
32950
+ async function collectEntryFiles(targetDir, resolvedRootDir, dir, depth, entry, deriveName) {
32951
+ const name = entry.name;
32952
+ if (isUnsafeEntryName(name)) {
32953
+ return [];
32954
+ }
32955
+ const entryPath = (0,external_node_path_.join)(dir, name);
32956
+ const absoluteEntryPath = (0,external_node_path_.join)(targetDir, entryPath);
32957
+ if (!isWithinResolvedRoot(resolvedRootDir, absoluteEntryPath)) {
32958
+ return [];
32959
+ }
32960
+ if (entry.isSymbolicLink()) {
32961
+ return [];
32962
+ }
32963
+ if (entry.isDirectory()) {
32964
+ return walkMarkdownFiles(targetDir, resolvedRootDir, entryPath, depth + 1, deriveName);
32965
+ }
32966
+ if (!isDiscoverableMarkdownFile(entry, name)) {
32967
+ return [];
32968
+ }
32969
+ return [
32970
+ {
32971
+ filePath: absoluteEntryPath,
32972
+ name: deriveName(name)
32973
+ }
32974
+ ];
32975
+ }
32976
+ async function walkMarkdownFiles(targetDir, resolvedRootDir, dir, depth, deriveName) {
32977
+ if (depth > MAX_WALK_DEPTH) {
32978
+ return [];
32979
+ }
32980
+ const entries = await file_system_utils_listDirectoryWithinRoot(targetDir, dir);
32981
+ const discovered = [];
32982
+ for (const entry of entries){
32983
+ const files = await collectEntryFiles(targetDir, resolvedRootDir, dir, depth, entry, deriveName);
32984
+ discovered.push(...files);
32985
+ }
32986
+ return discovered;
32987
+ }
32988
+ /**
32989
+ * Recursively discovers `.md` files under `rootDir` (relative to
32990
+ * `targetDir`). `deriveName` maps a filename to the construct name callers
32991
+ * use to identify the file (e.g. stripping `.md` or `.agent.md`).
32992
+ */ async function discoverMarkdownFiles(targetDir, rootDir, deriveName) {
32993
+ if (!await rootExists(targetDir, rootDir)) {
32994
+ return [];
32995
+ }
32996
+ return walkMarkdownFiles(targetDir, (0,external_node_path_.resolve)(targetDir, rootDir), rootDir, 0, deriveName);
32997
+ }
32998
+
32858
32999
  ;// CONCATENATED MODULE: ../core/src/gateways/agent-lint-gateway.ts
32859
33000
  /**
32860
33001
  * Gateway for agent lint file system operations.
@@ -32863,6 +33004,7 @@ function createInitHooksCommand(useCase) {
32863
33004
 
32864
33005
 
32865
33006
 
33007
+
32866
33008
  /** Maximum agent file size: 1 MB */ const MAX_AGENT_FILE_BYTES = 1_048_576;
32867
33009
  /** Catalog agent roots converted to OS-native relative paths. */ const AGENT_DIRECTORIES = agentDirectoryRoots().map((root)=>(0,external_node_path_.join)(...root.split("/")));
32868
33010
  /**
@@ -32877,51 +33019,11 @@ function createInitHooksCommand(useCase) {
32877
33019
  return agents;
32878
33020
  }
32879
33021
  async discoverAgentsInDir(targetDir, agentsDir) {
32880
- try {
32881
- if (!await file_system_utils_pathExistsWithinRoot(targetDir, agentsDir)) {
32882
- return [];
32883
- }
32884
- } catch {
32885
- return [];
32886
- }
32887
- const resolvedAgentsDir = (0,external_node_path_.resolve)(targetDir, agentsDir);
32888
- const agents = [];
32889
- const walk = async (dir)=>{
32890
- const entries = await file_system_utils_listDirectoryWithinRoot(targetDir, dir);
32891
- for (const entry of entries){
32892
- const name = entry.name;
32893
- if (name.includes("..") || name.includes("/") || name.includes("\\")) {
32894
- continue;
32895
- }
32896
- const entryPath = (0,external_node_path_.join)(dir, name);
32897
- const absoluteEntryPath = (0,external_node_path_.join)(targetDir, entryPath);
32898
- const resolvedPath = (0,external_node_path_.resolve)(absoluteEntryPath);
32899
- const rel = (0,external_node_path_.relative)(resolvedAgentsDir, resolvedPath);
32900
- if (rel.startsWith("..") || rel.startsWith(external_node_path_.sep)) {
32901
- continue;
32902
- }
32903
- if (entry.isSymbolicLink()) {
32904
- continue;
32905
- }
32906
- if (entry.isDirectory()) {
32907
- await walk(entryPath);
32908
- continue;
32909
- }
32910
- if (!entry.isFile()) {
32911
- continue;
32912
- }
32913
- if (!name.endsWith(".md")) {
32914
- continue;
32915
- }
32916
- const agentName = name.endsWith(".agent.md") ? (0,external_node_path_.basename)(name, ".agent.md") : (0,external_node_path_.basename)(name, ".md");
32917
- agents.push({
32918
- filePath: absoluteEntryPath,
32919
- agentName
32920
- });
32921
- }
32922
- };
32923
- await walk(agentsDir);
32924
- return agents;
33022
+ const files = await discoverMarkdownFiles(targetDir, agentsDir, (name)=>name.endsWith(".agent.md") ? (0,external_node_path_.basename)(name, ".agent.md") : (0,external_node_path_.basename)(name, ".md"));
33023
+ return files.map((file)=>({
33024
+ filePath: file.filePath,
33025
+ agentName: file.name
33026
+ }));
32925
33027
  }
32926
33028
  async readAgentFileContent(filePath) {
32927
33029
  return readFileNoFollow(filePath, MAX_AGENT_FILE_BYTES);
@@ -52594,6 +52696,120 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
52594
52696
  return new RemarkMarkdownAstGateway();
52595
52697
  }
52596
52698
 
52699
+ ;// CONCATENATED MODULE: ../core/src/gateways/mcp-lint-gateway.ts
52700
+ /**
52701
+ * Gateway for MCP server config file system operations.
52702
+ * Discovers MCP server config files at the shared MCP_CONFIG_SOURCES paths.
52703
+ */
52704
+
52705
+
52706
+ /** Maximum MCP config file size: 1 MB */ const MAX_MCP_CONFIG_FILE_BYTES = 1_048_576;
52707
+ /** Catalog MCP config paths converted to OS-native relative paths. */ const MCP_CONFIG_RELATIVE_PATHS = MCP_CONFIG_SOURCES.map((source)=>(0,external_node_path_.join)(...source.relPath.split("/")));
52708
+ const SKIPPABLE_PATH_ERROR_PREFIXES = [
52709
+ "Resolved path is outside target directory:",
52710
+ "Path contains symbolic link:"
52711
+ ];
52712
+ function isSkippablePathError(error) {
52713
+ if (!(error instanceof Error)) {
52714
+ return false;
52715
+ }
52716
+ return SKIPPABLE_PATH_ERROR_PREFIXES.some((prefix)=>error.message.startsWith(prefix));
52717
+ }
52718
+ async function pathExistsSafely(targetDir, relativePath) {
52719
+ try {
52720
+ return await file_system_utils_pathExistsWithinRoot(targetDir, relativePath);
52721
+ } catch {
52722
+ return false;
52723
+ }
52724
+ }
52725
+ async function resolveConfigPathOrNull(targetDir, relativePath) {
52726
+ try {
52727
+ return await file_system_utils_resolveSafePath(targetDir, relativePath);
52728
+ } catch (error) {
52729
+ if (isSkippablePathError(error)) {
52730
+ return null;
52731
+ }
52732
+ throw error;
52733
+ }
52734
+ }
52735
+ /**
52736
+ * Resolves a config path within the target directory, returning null when the
52737
+ * path is missing or escapes the root via traversal/symlink.
52738
+ */ async function tryResolveConfigPath(targetDir, relativePath) {
52739
+ if (!await pathExistsSafely(targetDir, relativePath)) {
52740
+ return null;
52741
+ }
52742
+ return resolveConfigPathOrNull(targetDir, relativePath);
52743
+ }
52744
+ /**
52745
+ * File system implementation of the MCP server lint gateway.
52746
+ */ class FileSystemMcpServersLintGateway {
52747
+ async discoverMcpConfigFiles(targetDir) {
52748
+ const discovered = [];
52749
+ for (const relativePath of MCP_CONFIG_RELATIVE_PATHS){
52750
+ const safePath = await tryResolveConfigPath(targetDir, relativePath);
52751
+ if (safePath === null) {
52752
+ continue;
52753
+ }
52754
+ discovered.push({
52755
+ filePath: safePath,
52756
+ relativePath
52757
+ });
52758
+ }
52759
+ return discovered;
52760
+ }
52761
+ async readFileContent(filePath) {
52762
+ return readFileNoFollow(filePath, MAX_MCP_CONFIG_FILE_BYTES);
52763
+ }
52764
+ }
52765
+ /**
52766
+ * Creates and returns the default MCP server lint gateway.
52767
+ */ function createMcpServersLintGateway() {
52768
+ return new FileSystemMcpServersLintGateway();
52769
+ }
52770
+
52771
+ ;// CONCATENATED MODULE: ../core/src/gateways/subagent-lint-gateway.ts
52772
+ /**
52773
+ * Gateway for subagent lint file system operations.
52774
+ * Discovers Claude Code subagent files and parses YAML frontmatter.
52775
+ */
52776
+
52777
+
52778
+
52779
+
52780
+ /** Maximum subagent file size: 1 MB */ const MAX_SUBAGENT_FILE_BYTES = 1_048_576;
52781
+ /** Catalog subagent roots converted to OS-native relative paths. */ const SUBAGENT_DIRECTORIES = subagentDirectoryRoots().map((root)=>(0,external_node_path_.join)(...root.split("/")));
52782
+ /**
52783
+ * File system implementation of the subagent lint gateway.
52784
+ */ class FileSystemSubagentLintGateway {
52785
+ async discoverSubagents(targetDir) {
52786
+ const subagents = [];
52787
+ for (const subagentsDir of SUBAGENT_DIRECTORIES){
52788
+ const discovered = await this.discoverSubagentsInDir(targetDir, subagentsDir);
52789
+ subagents.push(...discovered);
52790
+ }
52791
+ return subagents;
52792
+ }
52793
+ async discoverSubagentsInDir(targetDir, subagentsDir) {
52794
+ const files = await discoverMarkdownFiles(targetDir, subagentsDir, (name)=>(0,external_node_path_.basename)(name, ".md"));
52795
+ return files.map((file)=>({
52796
+ filePath: file.filePath,
52797
+ subagentName: file.name
52798
+ }));
52799
+ }
52800
+ async readSubagentFileContent(filePath) {
52801
+ return readFileNoFollow(filePath, MAX_SUBAGENT_FILE_BYTES);
52802
+ }
52803
+ parseFrontmatter(content) {
52804
+ return parseFrontmatter(content);
52805
+ }
52806
+ }
52807
+ /**
52808
+ * Creates and returns the default subagent lint gateway.
52809
+ */ function createSubagentLintGateway() {
52810
+ return new FileSystemSubagentLintGateway();
52811
+ }
52812
+
52597
52813
  ;// CONCATENATED MODULE: ../core/src/entities/lint-rules.ts
52598
52814
  /**
52599
52815
  * Lint rule registry entity.
@@ -52609,6 +52825,26 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
52609
52825
  "agent/invalid-description": "error",
52610
52826
  "agent/invalid-field": "warn"
52611
52827
  },
52828
+ subagents: {
52829
+ "subagent/missing-frontmatter": "error",
52830
+ "subagent/invalid-frontmatter": "error",
52831
+ "subagent/missing-name": "error",
52832
+ // Claude Code's documented subagent contract requires only `name`
52833
+ // and `description`; it neither specifies a name format beyond
52834
+ // "unique identifier" nor ties `name` to the filename the way
52835
+ // VS Code derives a Copilot custom agent's name from
52836
+ // `<name>.agent.md`. Defaulting these to "warn" avoids failing
52837
+ // lint for subagents that are valid per Claude Code's own docs.
52838
+ "subagent/invalid-name-format": "warn",
52839
+ "subagent/name-mismatch": "warn",
52840
+ "subagent/missing-description": "error",
52841
+ "subagent/invalid-description": "error",
52842
+ "subagent/invalid-field": "warn"
52843
+ },
52844
+ mcpServers: {
52845
+ "mcpserver/invalid-json": "error",
52846
+ "mcpserver/invalid-config": "error"
52847
+ },
52612
52848
  hooks: {
52613
52849
  "hook/invalid-json": "error",
52614
52850
  "hook/invalid-config": "error",
@@ -52650,9 +52886,11 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
52650
52886
  ]));
52651
52887
  /** Zod schema for the lint.rules section of the config */ const LintRulesConfigSchema = schemas_object({
52652
52888
  agents: RuleConfigMapSchema.optional(),
52889
+ subagents: RuleConfigMapSchema.optional(),
52653
52890
  hooks: RuleConfigMapSchema.optional(),
52654
52891
  instructions: RuleConfigMapSchema.optional(),
52655
- skills: RuleConfigMapSchema.optional()
52892
+ skills: RuleConfigMapSchema.optional(),
52893
+ mcpServers: RuleConfigMapSchema.optional()
52656
52894
  });
52657
52895
  /** Zod schema for the lint section of the config */ const LintConfigSchema = schemas_object({
52658
52896
  lint: schemas_object({
@@ -52695,9 +52933,11 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
52695
52933
  }
52696
52934
  return {
52697
52935
  agents: mergeTargetRules(DEFAULT_LINT_RULES.agents, rules.agents),
52936
+ subagents: mergeTargetRules(DEFAULT_LINT_RULES.subagents, rules.subagents),
52698
52937
  hooks: mergeTargetRules(DEFAULT_LINT_RULES.hooks, rules.hooks),
52699
52938
  instructions: mergeTargetRules(DEFAULT_LINT_RULES.instructions, rules.instructions),
52700
- skills: mergeTargetRules(DEFAULT_LINT_RULES.skills, rules.skills)
52939
+ skills: mergeTargetRules(DEFAULT_LINT_RULES.skills, rules.skills),
52940
+ mcpServers: mergeTargetRules(DEFAULT_LINT_RULES.mcpServers, rules.mcpServers)
52701
52941
  };
52702
52942
  }
52703
52943
 
@@ -52709,8 +52949,10 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
52709
52949
  */ /** Maps a lint target to its config key */ const TARGET_TO_CONFIG_KEY = {
52710
52950
  skill: "skills",
52711
52951
  agent: "agents",
52952
+ subagent: "subagents",
52712
52953
  hook: "hooks",
52713
- instruction: "instructions"
52954
+ instruction: "instructions",
52955
+ "mcp-server": "mcpServers"
52714
52956
  };
52715
52957
  /**
52716
52958
  * Maps config severity to diagnostic severity.
@@ -53110,6 +53352,172 @@ const INVALID_JSON_MESSAGE_PREFIX = "Invalid JSON in hook configuration file";
53110
53352
  }
53111
53353
  }
53112
53354
 
53355
+ ;// CONCATENATED MODULE: ../core/src/use-cases/lint-mcp-servers.ts
53356
+ /**
53357
+ * Use case for linting MCP server configuration files.
53358
+ * Validates baseline shape: the config parses as JSON and, when present,
53359
+ * `mcpServers` (Claude Code's `.mcp.json` convention) or `servers` (VS
53360
+ * Code's `.vscode/mcp.json` convention) is a map of server name to a
53361
+ * server declaration.
53362
+ */
53363
+ const lint_mcp_servers_INVALID_JSON_MESSAGE_PREFIX = "Invalid JSON in MCP server configuration file";
53364
+ /**
53365
+ * Property names that must never be used as MCP server names: assigning
53366
+ * them via a plain object key (as zod's `record()` does when reconstructing
53367
+ * parsed output) triggers JavaScript's `__proto__` magic setter instead of
53368
+ * creating an own property, silently dropping the entry from validation and
53369
+ * counts. Rejected explicitly so a config declaring one of these names
53370
+ * fails lint instead of appearing to declare fewer servers than it does.
53371
+ */ const lint_mcp_servers_RESERVED_SERVER_NAMES = [
53372
+ "__proto__",
53373
+ "constructor",
53374
+ "prototype"
53375
+ ];
53376
+ const lint_mcp_servers_SERVER_MAP_FIELDS = [
53377
+ "mcpServers",
53378
+ "servers"
53379
+ ];
53380
+ /**
53381
+ * Zod schema for a single MCP server declaration.
53382
+ * Baseline shape only: an object whose optional `type`/`transport` fields,
53383
+ * when present, must be non-empty strings. Unknown fields are preserved.
53384
+ */ const lint_mcp_servers_McpServerEntrySchema = schemas_object({
53385
+ type: schemas_string().min(1, "type must not be empty").optional(),
53386
+ transport: schemas_string().min(1, "transport must not be empty").optional()
53387
+ }).passthrough();
53388
+ /**
53389
+ * Zod schema for the MCP config file shape.
53390
+ */ const lint_mcp_servers_McpConfigSchema = schemas_object({
53391
+ mcpServers: schemas_record(schemas_string(), lint_mcp_servers_McpServerEntrySchema).optional(),
53392
+ servers: schemas_record(schemas_string(), lint_mcp_servers_McpServerEntrySchema).optional()
53393
+ }).passthrough();
53394
+ function lint_mcp_servers_isPlainObject(value) {
53395
+ return typeof value === "object" && value !== null;
53396
+ }
53397
+ function reservedNameDiagnostic(field, reservedName) {
53398
+ return {
53399
+ line: 1,
53400
+ severity: "error",
53401
+ message: `Server name '${reservedName}' is a reserved JavaScript property name and is not allowed.`,
53402
+ field: `${field}.${reservedName}`,
53403
+ ruleId: "mcpserver/invalid-config"
53404
+ };
53405
+ }
53406
+ function reservedNamesInServerMap(servers, field) {
53407
+ const diagnostics = [];
53408
+ for (const reservedName of lint_mcp_servers_RESERVED_SERVER_NAMES){
53409
+ if (Object.hasOwn(servers, reservedName)) {
53410
+ diagnostics.push(reservedNameDiagnostic(field, reservedName));
53411
+ }
53412
+ }
53413
+ return diagnostics;
53414
+ }
53415
+ /**
53416
+ * Scans the raw (pre-zod) parsed config for reserved server names. Must run
53417
+ * against the raw `JSON.parse` output, which preserves `__proto__` as an
53418
+ * ordinary own property, rather than against zod's reconstructed output.
53419
+ */ function findReservedServerNameDiagnostics(parsed) {
53420
+ if (!lint_mcp_servers_isPlainObject(parsed)) {
53421
+ return [];
53422
+ }
53423
+ const diagnostics = [];
53424
+ for (const field of lint_mcp_servers_SERVER_MAP_FIELDS){
53425
+ const servers = parsed[field];
53426
+ if (!lint_mcp_servers_isPlainObject(servers)) {
53427
+ continue;
53428
+ }
53429
+ diagnostics.push(...reservedNamesInServerMap(servers, field));
53430
+ }
53431
+ return diagnostics;
53432
+ }
53433
+ function invalidJsonResult(filePath, error) {
53434
+ const detail = error instanceof Error && error.message ? `: ${error.message}` : ".";
53435
+ return {
53436
+ filePath,
53437
+ serverCount: 0,
53438
+ diagnostics: [
53439
+ {
53440
+ line: 1,
53441
+ severity: "error",
53442
+ message: `${lint_mcp_servers_INVALID_JSON_MESSAGE_PREFIX}${detail}`,
53443
+ ruleId: "mcpserver/invalid-json"
53444
+ }
53445
+ ],
53446
+ valid: false
53447
+ };
53448
+ }
53449
+ function invalidConfigResult(filePath, diagnostics) {
53450
+ return {
53451
+ filePath,
53452
+ serverCount: 0,
53453
+ diagnostics,
53454
+ valid: false
53455
+ };
53456
+ }
53457
+ function zodIssuesToDiagnostics(issues) {
53458
+ return issues.map((issue)=>({
53459
+ line: 1,
53460
+ severity: "error",
53461
+ message: issue.message,
53462
+ field: issue.path.length > 0 ? issue.path.join(".") : undefined,
53463
+ ruleId: "mcpserver/invalid-config"
53464
+ }));
53465
+ }
53466
+ function countBySeverity(results, severity) {
53467
+ return results.reduce((sum, r)=>sum + r.diagnostics.filter((d)=>d.severity === severity).length, 0);
53468
+ }
53469
+ /**
53470
+ * Use case for linting MCP server configuration files across a repository.
53471
+ */ class LintMcpServersUseCase {
53472
+ gateway;
53473
+ constructor(gateway){
53474
+ this.gateway = gateway;
53475
+ }
53476
+ async execute(input) {
53477
+ if (!input.targetDir) {
53478
+ throw new Error("Target directory is required");
53479
+ }
53480
+ const files = await this.gateway.discoverMcpConfigFiles(input.targetDir);
53481
+ const results = [];
53482
+ for (const file of files){
53483
+ const content = await this.gateway.readFileContent(file.filePath);
53484
+ results.push(this.lintFile(file, content));
53485
+ }
53486
+ return {
53487
+ results,
53488
+ totalFiles: files.length,
53489
+ totalErrors: countBySeverity(results, "error"),
53490
+ totalWarnings: countBySeverity(results, "warning")
53491
+ };
53492
+ }
53493
+ lintFile(file, content) {
53494
+ let parsed;
53495
+ try {
53496
+ parsed = JSON.parse(content);
53497
+ } catch (error) {
53498
+ return invalidJsonResult(file.filePath, error);
53499
+ }
53500
+ const reservedNameDiagnostics = findReservedServerNameDiagnostics(parsed);
53501
+ if (reservedNameDiagnostics.length > 0) {
53502
+ return invalidConfigResult(file.filePath, reservedNameDiagnostics);
53503
+ }
53504
+ const result = lint_mcp_servers_McpConfigSchema.safeParse(parsed);
53505
+ if (!result.success) {
53506
+ return invalidConfigResult(file.filePath, zodIssuesToDiagnostics(result.error.issues));
53507
+ }
53508
+ const servers = {
53509
+ ...result.data.mcpServers ?? {},
53510
+ ...result.data.servers ?? {}
53511
+ };
53512
+ return {
53513
+ filePath: file.filePath,
53514
+ serverCount: Object.keys(servers).length,
53515
+ diagnostics: [],
53516
+ valid: true
53517
+ };
53518
+ }
53519
+ }
53520
+
53113
53521
  ;// CONCATENATED MODULE: ../core/src/use-cases/lint-skill-frontmatter.ts
53114
53522
 
53115
53523
  const AgentSkillFrontmatterSchema = schemas_object({
@@ -53265,6 +53673,138 @@ function hasFrontmatterDelimiters(content) {
53265
53673
  return false;
53266
53674
  }
53267
53675
 
53676
+ ;// CONCATENATED MODULE: ../core/src/use-cases/lint-subagent-frontmatter.ts
53677
+ /**
53678
+ * Use case for linting Claude Code subagent frontmatter.
53679
+ * Validates required fields, name format, and filename matching.
53680
+ */
53681
+ /**
53682
+ * Zod schema for validating subagent frontmatter.
53683
+ * Claude Code subagents require `name` and `description`, mirroring the
53684
+ * Copilot custom agent contract (see AgentFrontmatterSchema).
53685
+ */ const SubagentFrontmatterSchema = schemas_object({
53686
+ name: schemas_string().min(1, "Name is required").max(64, "Name must be 64 characters or fewer").regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Name must contain only lowercase letters, numbers, and hyphens"),
53687
+ description: schemas_string().min(1, "Description is required").max(1024, "Description must be 1024 characters or fewer").refine((s)=>s.trim().length > 0, {
53688
+ message: "Description cannot be empty or whitespace-only"
53689
+ })
53690
+ });
53691
+ /**
53692
+ * Use case for linting subagent frontmatter across a repository.
53693
+ */ class LintSubagentFrontmatterUseCase {
53694
+ gateway;
53695
+ constructor(gateway){
53696
+ this.gateway = gateway;
53697
+ }
53698
+ async execute(input) {
53699
+ if (!input.targetDir) {
53700
+ throw new Error("Target directory is required");
53701
+ }
53702
+ const subagents = await this.gateway.discoverSubagents(input.targetDir);
53703
+ const results = [];
53704
+ for (const subagent of subagents){
53705
+ const content = await this.gateway.readSubagentFileContent(subagent.filePath);
53706
+ const result = this.lintSubagent(subagent, content);
53707
+ results.push(result);
53708
+ }
53709
+ const totalErrors = results.reduce((sum, r)=>sum + r.diagnostics.filter((d)=>d.severity === "error").length, 0);
53710
+ const totalWarnings = results.reduce((sum, r)=>sum + r.diagnostics.filter((d)=>d.severity === "warning").length, 0);
53711
+ return {
53712
+ results,
53713
+ totalSubagents: subagents.length,
53714
+ totalErrors,
53715
+ totalWarnings
53716
+ };
53717
+ }
53718
+ lintSubagent(subagent, content) {
53719
+ let parsed = null;
53720
+ let diagnostics = [];
53721
+ try {
53722
+ parsed = this.gateway.parseFrontmatter(content);
53723
+ } catch (error) {
53724
+ const messagePrefix = "Invalid YAML frontmatter";
53725
+ const errorMessage = error instanceof Error && error.message ? `${messagePrefix}: ${error.message}` : `${messagePrefix}.`;
53726
+ diagnostics.push({
53727
+ line: 1,
53728
+ severity: "error",
53729
+ message: errorMessage,
53730
+ ruleId: "subagent/invalid-frontmatter"
53731
+ });
53732
+ }
53733
+ if (!parsed) {
53734
+ if (diagnostics.length === 0) {
53735
+ const message = lint_subagent_frontmatter_hasOpeningDelimiter(content) ? "Invalid YAML frontmatter. The content between --- delimiters could not be parsed as valid YAML." : "Missing YAML frontmatter. Subagent files must begin with --- delimited YAML frontmatter.";
53736
+ const ruleId = lint_subagent_frontmatter_hasOpeningDelimiter(content) ? "subagent/invalid-frontmatter" : "subagent/missing-frontmatter";
53737
+ diagnostics.push({
53738
+ line: 1,
53739
+ severity: "error",
53740
+ message,
53741
+ ruleId
53742
+ });
53743
+ }
53744
+ return {
53745
+ filePath: subagent.filePath,
53746
+ subagentName: subagent.subagentName,
53747
+ diagnostics,
53748
+ valid: false
53749
+ };
53750
+ }
53751
+ const frontmatterDiagnostics = this.validateFrontmatter(parsed, subagent.subagentName);
53752
+ diagnostics = diagnostics.concat(frontmatterDiagnostics);
53753
+ return {
53754
+ filePath: subagent.filePath,
53755
+ subagentName: subagent.subagentName,
53756
+ diagnostics,
53757
+ valid: diagnostics.every((d)=>d.severity !== "error")
53758
+ };
53759
+ }
53760
+ validateFrontmatter(parsed, filenameStem) {
53761
+ const diagnostics = [];
53762
+ const result = SubagentFrontmatterSchema.safeParse(parsed.data);
53763
+ if (!result.success) {
53764
+ for (const issue of result.error.issues){
53765
+ const fieldName = issue.path[0]?.toString();
53766
+ const line = fieldName ? parsed.fieldLines.get(fieldName) ?? parsed.frontmatterStartLine : parsed.frontmatterStartLine;
53767
+ const isMissing = fieldName !== undefined && parsed.data[fieldName] === undefined;
53768
+ const ruleId = this.getRuleIdForField(fieldName, isMissing);
53769
+ diagnostics.push({
53770
+ line,
53771
+ severity: "error",
53772
+ message: issue.message,
53773
+ field: fieldName,
53774
+ ruleId
53775
+ });
53776
+ }
53777
+ }
53778
+ // Check name matches filename stem
53779
+ if (result.success && result.data.name !== filenameStem) {
53780
+ const nameLine = parsed.fieldLines.get("name") ?? parsed.frontmatterStartLine;
53781
+ diagnostics.push({
53782
+ line: nameLine,
53783
+ severity: "error",
53784
+ message: `Frontmatter name '${result.data.name}' does not match filename '${filenameStem}'. Claude Code identifies subagents by the name field, not the filename, but matching them is a helpful convention.`,
53785
+ field: "name",
53786
+ ruleId: "subagent/name-mismatch"
53787
+ });
53788
+ }
53789
+ return diagnostics;
53790
+ }
53791
+ getRuleIdForField(fieldName, isMissing) {
53792
+ if (fieldName === "name") {
53793
+ return isMissing ? "subagent/missing-name" : "subagent/invalid-name-format";
53794
+ }
53795
+ if (fieldName === "description") {
53796
+ return isMissing ? "subagent/missing-description" : "subagent/invalid-description";
53797
+ }
53798
+ return "subagent/invalid-field";
53799
+ }
53800
+ }
53801
+ /**
53802
+ * Checks whether content has an opening --- frontmatter delimiter.
53803
+ */ function lint_subagent_frontmatter_hasOpeningDelimiter(content) {
53804
+ const lines = content.split("\n");
53805
+ return lines[0]?.trim() === "---";
53806
+ }
53807
+
53268
53808
  ;// CONCATENATED MODULE: ../lint/src/lint-errors.ts
53269
53809
  /**
53270
53810
  * Public lint API error types.
@@ -53405,11 +53945,17 @@ function sanitizeForErrorMessage(value) {
53405
53945
 
53406
53946
 
53407
53947
 
53948
+
53949
+
53950
+
53951
+
53408
53952
  const LintTargetsSchema = schemas_object({
53409
53953
  skills: schemas_boolean().optional(),
53410
53954
  agents: schemas_boolean().optional(),
53955
+ subagents: schemas_boolean().optional(),
53411
53956
  hooks: schemas_boolean().optional(),
53412
- instructions: schemas_boolean().optional()
53957
+ instructions: schemas_boolean().optional(),
53958
+ mcpServers: schemas_boolean().optional()
53413
53959
  }).strict().optional();
53414
53960
  const LintOptionsSchema = schemas_object({
53415
53961
  directory: schemas_string().min(1, "directory must not be empty"),
@@ -53465,6 +54011,22 @@ async function lintAgents(targetDir) {
53465
54011
  });
53466
54012
  return toLintOutput(output, "agent", output.totalAgents);
53467
54013
  }
54014
+ async function lintSubagents(targetDir) {
54015
+ const gateway = createSubagentLintGateway();
54016
+ const useCase = new LintSubagentFrontmatterUseCase(gateway);
54017
+ const output = await useCase.execute({
54018
+ targetDir
54019
+ });
54020
+ return toLintOutput(output, "subagent", output.totalSubagents);
54021
+ }
54022
+ async function lintMcpServers(targetDir) {
54023
+ const gateway = createMcpServersLintGateway();
54024
+ const useCase = new LintMcpServersUseCase(gateway);
54025
+ const output = await useCase.execute({
54026
+ targetDir
54027
+ });
54028
+ return toLintOutput(output, "mcp-server", output.totalFiles);
54029
+ }
53468
54030
  async function lintHooks(targetDir) {
53469
54031
  const gateway = createHookConfigGateway();
53470
54032
  const useCase = new LintHookConfigUseCase(gateway);
@@ -53495,10 +54057,15 @@ async function lintInstructions(targetDir, logger) {
53495
54057
  }
53496
54058
  };
53497
54059
  }
53498
- function isTargetEnabled(key, targets) {
53499
- if (!targets) return true;
54060
+ function isTargetEnabled(target, targets) {
54061
+ if (!targets) {
54062
+ return target.defaultEnabled;
54063
+ }
53500
54064
  const hasAnyEnabled = Object.values(targets).some(Boolean);
53501
- return !hasAnyEnabled || targets[key] === true;
54065
+ if (!hasAnyEnabled) {
54066
+ return target.defaultEnabled;
54067
+ }
54068
+ return targets[target.key] === true;
53502
54069
  }
53503
54070
  /**
53504
54071
  * Run lint checks on a project directory.
@@ -53506,7 +54073,10 @@ function isTargetEnabled(key, targets) {
53506
54073
  * Orchestrates all lint targets (skills, agents, hooks, instructions),
53507
54074
  * applies lint rule configuration, and returns structured results.
53508
54075
  *
53509
- * When no targets are specified (or all are false), all targets are run.
54076
+ * When no targets are specified (or all are false), the default-enabled
54077
+ * targets are run (skills, agents, hooks, instructions). `subagents` and
54078
+ * `mcpServers` are flag-only (not yet GA): they only run when explicitly
54079
+ * set to `true`, even when no other target flags are set.
53510
54080
  *
53511
54081
  * @example
53512
54082
  * ```typescript
@@ -53541,22 +54111,36 @@ function isTargetEnabled(key, targets) {
53541
54111
  const lintTargets = [
53542
54112
  {
53543
54113
  key: "skills",
54114
+ defaultEnabled: true,
53544
54115
  execute: lintSkills
53545
54116
  },
53546
54117
  {
53547
54118
  key: "agents",
54119
+ defaultEnabled: true,
53548
54120
  execute: lintAgents
53549
54121
  },
54122
+ {
54123
+ key: "subagents",
54124
+ defaultEnabled: false,
54125
+ execute: lintSubagents
54126
+ },
53550
54127
  {
53551
54128
  key: "hooks",
54129
+ defaultEnabled: true,
53552
54130
  execute: lintHooks
53553
54131
  },
53554
54132
  {
53555
54133
  key: "instructions",
54134
+ defaultEnabled: true,
53556
54135
  execute: (dir)=>lintInstructions(dir, parsed.logger)
54136
+ },
54137
+ {
54138
+ key: "mcpServers",
54139
+ defaultEnabled: false,
54140
+ execute: lintMcpServers
53557
54141
  }
53558
54142
  ];
53559
- const enabledTargets = lintTargets.filter((t)=>isTargetEnabled(t.key, parsed.targets));
54143
+ const enabledTargets = lintTargets.filter((t)=>isTargetEnabled(t, parsed.targets));
53560
54144
  const outputs = [];
53561
54145
  for (const target of enabledTargets){
53562
54146
  const rawOutput = await target.execute(targetDir);
@@ -53591,61 +54175,225 @@ function isTargetEnabled(key, targets) {
53591
54175
 
53592
54176
 
53593
54177
 
53594
- ;// CONCATENATED MODULE: ./src/commands/lint.ts
54178
+ ;// CONCATENATED MODULE: ./src/commands/lint-instruction-display.ts
53595
54179
  /**
53596
- * CLI command for linting agent skills, custom agents, and instruction files.
53597
- * Delegates to the lint package facade and handles CLI display concerns.
54180
+ * Instruction-quality display for the lint CLI command.
53598
54181
  */
54182
+ function warnSuggestions(suggestions) {
54183
+ for (const suggestion of suggestions){
54184
+ consola.warn(suggestion.message);
54185
+ }
54186
+ }
54187
+ /**
54188
+ * Displays instruction quality analysis results using consola.
54189
+ */ function displayInstructionQuality(output) {
54190
+ const result = output.qualityResult;
54191
+ if (!result) {
54192
+ return;
54193
+ }
54194
+ if (result.discoveredFiles.length === 0) {
54195
+ consola.info("No instruction files found");
54196
+ warnSuggestions(result.suggestions);
54197
+ return;
54198
+ }
54199
+ consola.info(`Discovered ${result.discoveredFiles.length} instruction file(s)`);
54200
+ for (const file of result.discoveredFiles){
54201
+ consola.info(` ${file.filePath} (${file.format})`);
54202
+ }
54203
+ consola.info(`Overall instruction quality score: ${result.overallQualityScore}%`);
54204
+ warnSuggestions(result.suggestions);
54205
+ }
53599
54206
 
54207
+ ;// CONCATENATED MODULE: ./src/commands/lint-target-labels.ts
54208
+ /**
54209
+ * Shared human-readable labels for lint target categories, used by both
54210
+ * per-file diagnostic display and the pass/fail summary line.
54211
+ */ const TARGET_LABELS = {
54212
+ skill: "skill(s)",
54213
+ agent: "agent(s)",
54214
+ subagent: "subagent(s)",
54215
+ hook: "hook config(s)",
54216
+ instruction: "instruction file(s)",
54217
+ "mcp-server": "MCP server config(s)"
54218
+ };
54219
+ function targetLabel(target) {
54220
+ return TARGET_LABELS[target] ?? target;
54221
+ }
53600
54222
 
54223
+ ;// CONCATENATED MODULE: ./src/commands/lint-display.ts
53601
54224
  /**
53602
- * Formats and displays a LintOutput using consola.
53603
- */ function displayLintOutput(output, label) {
54225
+ * Human-readable display helpers for the lint CLI command.
54226
+ */
54227
+
54228
+
54229
+
54230
+ const SEVERITY_LOGGERS = {
54231
+ error: (msg)=>consola.error(msg),
54232
+ warning: (msg)=>consola.warn(msg),
54233
+ info: (msg)=>consola.info(msg)
54234
+ };
54235
+ function logDiagnostic(d) {
54236
+ const fieldInfo = d.field ? ` [${d.field}]` : "";
54237
+ const message = `${d.filePath}:${d.line}${fieldInfo}: ${d.message}`;
54238
+ const log = SEVERITY_LOGGERS[d.severity] ?? SEVERITY_LOGGERS.info;
54239
+ log(message);
54240
+ }
54241
+ function displayOkFiles(output) {
54242
+ const filesWithDiagnostics = new Set(output.diagnostics.map((d)=>d.filePath));
54243
+ for (const file of output.filesAnalyzed){
54244
+ if (filesWithDiagnostics.has(file)) {
54245
+ continue;
54246
+ }
54247
+ consola.success(`${file}: OK`);
54248
+ }
54249
+ }
54250
+ function displayLintOutput(output, label) {
53604
54251
  if (output.summary.totalFiles === 0) {
53605
54252
  consola.info(`No ${label} found`);
53606
54253
  return;
53607
54254
  }
53608
54255
  consola.info(`Discovered ${output.summary.totalFiles} ${label}`);
53609
- const filesWithDiagnostics = new Set();
54256
+ displayOkFiles(output);
53610
54257
  for (const d of output.diagnostics){
53611
- filesWithDiagnostics.add(d.filePath);
54258
+ logDiagnostic(d);
53612
54259
  }
53613
- for (const file of output.filesAnalyzed){
53614
- if (!filesWithDiagnostics.has(file)) {
53615
- consola.success(`${file}: OK`);
54260
+ }
54261
+ function displayHumanOutputs(outputs) {
54262
+ for (const output of outputs){
54263
+ if (output.target === "instruction") {
54264
+ displayInstructionQuality(output);
54265
+ continue;
53616
54266
  }
54267
+ displayLintOutput(output, targetLabel(output.target));
53617
54268
  }
53618
- for (const d of output.diagnostics){
53619
- const prefix = `${d.filePath}:${d.line}`;
53620
- const fieldInfo = d.field ? ` [${d.field}]` : "";
53621
- if (d.severity === "error") {
53622
- consola.error(`${prefix}${fieldInfo}: ${d.message}`);
53623
- } else if (d.severity === "warning") {
53624
- consola.warn(`${prefix}${fieldInfo}: ${d.message}`);
53625
- } else {
53626
- consola.info(`${prefix}${fieldInfo}: ${d.message}`);
53627
- }
54269
+ }
54270
+ function displayFormattedOutputs(outputs, format) {
54271
+ if (format === "human") {
54272
+ displayHumanOutputs(outputs);
54273
+ return;
53628
54274
  }
54275
+ const formatted = createFormatter(format).format(outputs);
54276
+ if (!formatted) {
54277
+ return;
54278
+ }
54279
+ process.stdout.write(`${formatted}\n`);
53629
54280
  }
54281
+ function sumWarnings(outputs) {
54282
+ return outputs.reduce((sum, output)=>sum + output.summary.totalWarnings, 0);
54283
+ }
54284
+
54285
+ ;// CONCATENATED MODULE: ./src/commands/lint-report.ts
53630
54286
  /**
53631
- * Displays instruction quality analysis results using consola.
53632
- */ function displayInstructionQuality(output) {
53633
- const result = output.qualityResult;
53634
- if (!result) {
54287
+ * Pass/fail summary reporting for the lint CLI command.
54288
+ */
54289
+
54290
+ function reportFailure(outputs, totalWarnings, format) {
54291
+ process.exitCode = 1;
54292
+ if (format !== "human") {
53635
54293
  return;
53636
54294
  }
53637
- if (result.discoveredFiles.length === 0) {
53638
- consola.info("No instruction files found");
53639
- } else {
53640
- consola.info(`Discovered ${result.discoveredFiles.length} instruction file(s)`);
53641
- for (const file of result.discoveredFiles){
53642
- consola.info(` ${file.filePath} (${file.format})`);
54295
+ const totalErrors = outputs.reduce((sum, o)=>sum + o.summary.totalErrors, 0);
54296
+ consola.error(`lint failed: ${totalErrors} error(s), ${totalWarnings} warning(s)`);
54297
+ }
54298
+ function reportSuccess(outputs, totalWarnings, format) {
54299
+ if (format !== "human") {
54300
+ return;
54301
+ }
54302
+ if (totalWarnings > 0) {
54303
+ consola.warn(`Lint passed with ${totalWarnings} warning(s)`);
54304
+ return;
54305
+ }
54306
+ const targets = outputs.map((o)=>targetLabel(o.target)).join(", ");
54307
+ consola.success(`All ${targets} passed lint checks`);
54308
+ }
54309
+
54310
+ ;// CONCATENATED MODULE: ./src/commands/lint.ts
54311
+ /**
54312
+ * CLI command for linting agent skills, custom agents, and instruction files.
54313
+ * Delegates to the lint package facade and handles CLI display concerns.
54314
+ */
54315
+
54316
+
54317
+
54318
+
54319
+ const VALID_FORMATS = new Set([
54320
+ "human",
54321
+ "json",
54322
+ "rdjsonl"
54323
+ ]);
54324
+ function resolveTargetDir(context) {
54325
+ if (typeof context.data?.targetDir === "string") {
54326
+ return context.data.targetDir;
54327
+ }
54328
+ return process.cwd();
54329
+ }
54330
+ function isLessonsSubcommand(rawArgs) {
54331
+ return rawArgs.some((token, idx)=>token === "lessons" && rawArgs[idx - 1] !== "--format");
54332
+ }
54333
+ function resolveBooleanFlag(context, key) {
54334
+ return context.args?.[key] === true || context.data?.[key] === true;
54335
+ }
54336
+ function isLintFormatType(value) {
54337
+ return VALID_FORMATS.has(value);
54338
+ }
54339
+ function readRawFormat(context) {
54340
+ if (typeof context.args?.format === "string") {
54341
+ return context.args.format;
54342
+ }
54343
+ if (typeof context.data?.format === "string") {
54344
+ return context.data.format;
54345
+ }
54346
+ return "human";
54347
+ }
54348
+ function resolveFormat(context) {
54349
+ const rawFormat = readRawFormat(context);
54350
+ if (isLintFormatType(rawFormat)) {
54351
+ return rawFormat;
54352
+ }
54353
+ return "human";
54354
+ }
54355
+ function resolveLintTargets(context) {
54356
+ return {
54357
+ skills: resolveBooleanFlag(context, "skills"),
54358
+ agents: resolveBooleanFlag(context, "agents"),
54359
+ subagents: resolveBooleanFlag(context, "subagents"),
54360
+ hooks: resolveBooleanFlag(context, "hooks"),
54361
+ instructions: resolveBooleanFlag(context, "instructions"),
54362
+ mcpServers: resolveBooleanFlag(context, "mcpServers")
54363
+ };
54364
+ }
54365
+ async function executeLint(context) {
54366
+ try {
54367
+ return await runLint({
54368
+ directory: resolveTargetDir(context),
54369
+ targets: resolveLintTargets(context)
54370
+ });
54371
+ } catch (error) {
54372
+ if (error instanceof LintValidationError) {
54373
+ consola.error(`Lint failed: ${error.message}`);
54374
+ process.exitCode = 1;
54375
+ return null;
53643
54376
  }
53644
- consola.info(`Overall instruction quality score: ${result.overallQualityScore}%`);
54377
+ throw error;
53645
54378
  }
53646
- for (const suggestion of result.suggestions){
53647
- consola.warn(suggestion.message);
54379
+ }
54380
+ async function runLintCommand(context) {
54381
+ if (isLessonsSubcommand(context.rawArgs ?? [])) {
54382
+ return;
54383
+ }
54384
+ const format = resolveFormat(context);
54385
+ const result = await executeLint(context);
54386
+ if (result === null) {
54387
+ return;
53648
54388
  }
54389
+ const { outputs, hasErrors } = result;
54390
+ const totalWarnings = sumWarnings(outputs);
54391
+ displayFormattedOutputs(outputs, format);
54392
+ if (hasErrors) {
54393
+ reportFailure(outputs, totalWarnings, format);
54394
+ return;
54395
+ }
54396
+ reportSuccess(outputs, totalWarnings, format);
53649
54397
  }
53650
54398
  /**
53651
54399
  * The `lint` command for validating agent skills, custom agents, and instruction files.
@@ -53661,6 +54409,11 @@ function isTargetEnabled(key, targets) {
53661
54409
  description: "Lint custom agent frontmatter in .github/agents/",
53662
54410
  default: false
53663
54411
  },
54412
+ subagents: {
54413
+ type: "boolean",
54414
+ description: "[flag-only, not default-on] Lint Claude Code subagent frontmatter in .claude/agents/",
54415
+ default: false
54416
+ },
53664
54417
  hooks: {
53665
54418
  type: "boolean",
53666
54419
  description: "Lint pre-tool-use hook configurations in .github/hooks/agent-shell/hooks.json, .claude/settings.json, and .claude/settings.local.json",
@@ -53671,6 +54424,11 @@ function isTargetEnabled(key, targets) {
53671
54424
  description: "Analyze instruction quality across all instruction file formats",
53672
54425
  default: false
53673
54426
  },
54427
+ mcpServers: {
54428
+ type: "boolean",
54429
+ description: "[flag-only, not default-on] Lint MCP server config files (.mcp.json, .vscode/mcp.json)",
54430
+ default: false
54431
+ },
53674
54432
  format: {
53675
54433
  type: "string",
53676
54434
  description: "Output format: human (default), json, or rdjsonl",
@@ -53681,96 +54439,13 @@ function createLintCommand(lintLessonsCmd) {
53681
54439
  return defineCommand({
53682
54440
  meta: {
53683
54441
  name: "lint",
53684
- description: "Lint agent skills, custom agents, instruction files, and hook configurations. Validates frontmatter, instruction quality, and hook config schemas. Run `lint lessons` to validate lesson files."
54442
+ description: "Lint agent skills, custom agents, instruction files, and hook configurations. Validates frontmatter, instruction quality, and hook config schemas. Also supports flag-only (not default-on) subagent and MCP server config linting. Run `lint lessons` to validate lesson files."
53685
54443
  },
53686
54444
  subCommands: {
53687
54445
  lessons: lintLessonsCmd
53688
54446
  },
53689
54447
  args: lintArgs,
53690
- run: async (context)=>{
53691
- const rawTargetDir = typeof context.data?.targetDir === "string" ? context.data.targetDir : process.cwd();
53692
- const rawArgs_ = context.rawArgs ?? [];
53693
- const lessonsIsSubcommand = rawArgs_.some((token, idx)=>token === "lessons" && rawArgs_[idx - 1] !== "--format");
53694
- if (lessonsIsSubcommand) {
53695
- return;
53696
- }
53697
- const lintSkillsFlag = context.args?.skills === true || context.data?.skills === true;
53698
- const lintAgentsFlag = context.args?.agents === true || context.data?.agents === true;
53699
- const lintHooksFlag = context.args?.hooks === true || context.data?.hooks === true;
53700
- const lintInstructionsFlag = context.args?.instructions === true || context.data?.instructions === true;
53701
- const rawFormat = typeof context.args?.format === "string" ? context.args.format : typeof context.data?.format === "string" ? context.data.format : "human";
53702
- const validFormats = new Set([
53703
- "human",
53704
- "json",
53705
- "rdjsonl"
53706
- ]);
53707
- function isLintFormatType(value) {
53708
- return validFormats.has(value);
53709
- }
53710
- const format = isLintFormatType(rawFormat) ? rawFormat : "human";
53711
- let result;
53712
- try {
53713
- result = await runLint({
53714
- directory: rawTargetDir,
53715
- targets: {
53716
- skills: lintSkillsFlag,
53717
- agents: lintAgentsFlag,
53718
- hooks: lintHooksFlag,
53719
- instructions: lintInstructionsFlag
53720
- }
53721
- });
53722
- } catch (error) {
53723
- if (error instanceof LintValidationError) {
53724
- consola.error(`Lint failed: ${error.message}`);
53725
- process.exitCode = 1;
53726
- return;
53727
- }
53728
- throw error;
53729
- }
53730
- const { outputs, hasErrors } = result;
53731
- let totalWarnings = 0;
53732
- for (const output of outputs){
53733
- totalWarnings += output.summary.totalWarnings;
53734
- }
53735
- const targetLabels = {
53736
- skill: "skill(s)",
53737
- agent: "agent(s)",
53738
- hook: "hook config(s)",
53739
- instruction: "instruction file(s)"
53740
- };
53741
- if (format !== "human") {
53742
- const formatter = createFormatter(format);
53743
- const formatted = formatter.format(outputs);
53744
- if (formatted) {
53745
- process.stdout.write(`${formatted}\n`);
53746
- }
53747
- } else {
53748
- for (const output of outputs){
53749
- const label = targetLabels[output.target] ?? output.target;
53750
- if (output.target === "instruction") {
53751
- displayInstructionQuality(output);
53752
- } else {
53753
- displayLintOutput(output, label);
53754
- }
53755
- }
53756
- }
53757
- if (hasErrors) {
53758
- process.exitCode = 1;
53759
- if (format === "human") {
53760
- const totalErrors = outputs.reduce((sum, o)=>sum + o.summary.totalErrors, 0);
53761
- consola.error(`lint failed: ${totalErrors} error(s), ${totalWarnings} warning(s)`);
53762
- }
53763
- return;
53764
- }
53765
- if (format === "human") {
53766
- if (totalWarnings > 0) {
53767
- consola.warn(`Lint passed with ${totalWarnings} warning(s)`);
53768
- } else {
53769
- const targets = outputs.map((o)=>targetLabels[o.target] ?? o.target).join(", ");
53770
- consola.success(`All ${targets} passed lint checks`);
53771
- }
53772
- }
53773
- }
54448
+ run: runLintCommand
53774
54449
  });
53775
54450
  }
53776
54451
 
@@ -63036,4 +63711,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
63036
63711
  // module factories are used so entry inlining is disabled
63037
63712
  // startup
63038
63713
  // Load entry module and return exports
63039
- var __webpack_exports__ = __webpack_require__(7015);
63714
+ var __webpack_exports__ = __webpack_require__(4140);