@lousy-agents/cli 5.18.1 → 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
|
-
|
|
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);
|
|
@@ -16811,7 +16811,9 @@ class LessonContextUseCase {
|
|
|
16811
16811
|
// the second, causing incorrect matching).
|
|
16812
16812
|
const globMatchers = result.lessons.map((p)=>{
|
|
16813
16813
|
const { paths } = p.lesson.triggers;
|
|
16814
|
-
return paths.length > 0 ? picomatch_default()(
|
|
16814
|
+
return paths.length > 0 ? picomatch_default()([
|
|
16815
|
+
...paths
|
|
16816
|
+
], PICOMATCH_OPTIONS) : null;
|
|
16815
16817
|
});
|
|
16816
16818
|
matched = result.lessons.filter((p, i)=>matchesLesson(p.lesson, filePaths, fileContents, segments, normalizedPaths, globMatchers[i] ?? null));
|
|
16817
16819
|
}
|
|
@@ -19239,19 +19241,20 @@ function buildPermissionDecisionResponse(payload) {
|
|
|
19239
19241
|
/** Parses a comma-separated --files string into trimmed, non-empty path tokens. */ function parseExplicitFiles(rawFiles) {
|
|
19240
19242
|
return rawFiles.split(",").map((f)=>f.trim()).filter(Boolean);
|
|
19241
19243
|
}
|
|
19244
|
+
const contextArgs = {
|
|
19245
|
+
files: {
|
|
19246
|
+
type: "string",
|
|
19247
|
+
description: "Comma-separated list of file paths to match lessons against (PreToolUse dispatch; for debug use — real hook invocations pass paths via stdin)",
|
|
19248
|
+
default: ""
|
|
19249
|
+
}
|
|
19250
|
+
};
|
|
19242
19251
|
function createContextCommand(useCase) {
|
|
19243
19252
|
return defineCommand({
|
|
19244
19253
|
meta: {
|
|
19245
19254
|
name: "context",
|
|
19246
19255
|
description: "Inject relevant lessons into Claude hooks as additionalContext"
|
|
19247
19256
|
},
|
|
19248
|
-
args:
|
|
19249
|
-
files: {
|
|
19250
|
-
type: "string",
|
|
19251
|
-
description: "Comma-separated list of file paths to match lessons against (PreToolUse dispatch; for debug use — real hook invocations pass paths via stdin)",
|
|
19252
|
-
default: ""
|
|
19253
|
-
}
|
|
19254
|
-
},
|
|
19257
|
+
args: contextArgs,
|
|
19255
19258
|
run: async (context)=>{
|
|
19256
19259
|
const rootDir = typeof context.data?.targetDir === "string" ? context.data.targetDir : process.cwd();
|
|
19257
19260
|
const rawFiles = typeof context.args?.files === "string" ? context.args.files : "";
|
|
@@ -22477,8 +22480,9 @@ async function readVersionFileContent(targetDir, relativePath) {
|
|
|
22477
22480
|
continue;
|
|
22478
22481
|
}
|
|
22479
22482
|
if (await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.manifestFile)) {
|
|
22480
|
-
const
|
|
22481
|
-
const
|
|
22483
|
+
const { lockfile } = pmConfig;
|
|
22484
|
+
const lockfilePath = lockfile ? await file_system_utils_resolveSafePath(targetDir, lockfile) : undefined;
|
|
22485
|
+
const hasLockfile = lockfilePath && lockfile ? await file_system_utils_pathExistsWithinRoot(targetDir, lockfile) : false;
|
|
22482
22486
|
if (pmConfig.requiresLockfile && !hasLockfile) {
|
|
22483
22487
|
continue;
|
|
22484
22488
|
}
|
|
@@ -22495,8 +22499,9 @@ async function readVersionFileContent(targetDir, relativePath) {
|
|
|
22495
22499
|
const packageManagers = [];
|
|
22496
22500
|
for (const pmConfig of otherPackageManagers){
|
|
22497
22501
|
if (await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.manifestFile)) {
|
|
22498
|
-
const
|
|
22499
|
-
const
|
|
22502
|
+
const { lockfile } = pmConfig;
|
|
22503
|
+
const lockfilePath = lockfile ? await file_system_utils_resolveSafePath(targetDir, lockfile) : undefined;
|
|
22504
|
+
const hasLockfile = lockfilePath && lockfile ? await file_system_utils_pathExistsWithinRoot(targetDir, lockfile) : false;
|
|
22500
22505
|
if (pmConfig.requiresLockfile && !hasLockfile) {
|
|
22501
22506
|
continue;
|
|
22502
22507
|
}
|
|
@@ -27227,11 +27232,18 @@ function defaultExec(command, args, options) {
|
|
|
27227
27232
|
throw new Error("Not authenticated");
|
|
27228
27233
|
}
|
|
27229
27234
|
try {
|
|
27230
|
-
|
|
27235
|
+
const params = {
|
|
27231
27236
|
owner,
|
|
27232
27237
|
repo,
|
|
27233
|
-
|
|
27234
|
-
|
|
27238
|
+
name: payload.name,
|
|
27239
|
+
enforcement: payload.enforcement,
|
|
27240
|
+
target: payload.target,
|
|
27241
|
+
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
27242
|
+
bypass_actors: payload.bypass_actors,
|
|
27243
|
+
conditions: payload.conditions,
|
|
27244
|
+
rules: payload.rules
|
|
27245
|
+
};
|
|
27246
|
+
await this.octokit.rest.repos.createRepoRuleset(params);
|
|
27235
27247
|
} catch (error) {
|
|
27236
27248
|
const details = formatOctokitError(error);
|
|
27237
27249
|
throw new Error(`Failed to create ruleset for ${owner}/${repo}: ${details}`);
|
|
@@ -27685,13 +27697,13 @@ const PackageJsonSchema = schemas_object({
|
|
|
27685
27697
|
"shared"
|
|
27686
27698
|
]
|
|
27687
27699
|
},
|
|
27688
|
-
//
|
|
27700
|
+
// Additional skill roots (SKILL.md convention, no alternate layout documented)
|
|
27689
27701
|
{
|
|
27690
27702
|
id: "skill-pi",
|
|
27691
27703
|
path: ".pi/skills",
|
|
27692
27704
|
matchKind: "directory-prefix",
|
|
27693
27705
|
primaryConstruct: "skill",
|
|
27694
|
-
lintTarget: "
|
|
27706
|
+
lintTarget: "skills",
|
|
27695
27707
|
harnessHints: [
|
|
27696
27708
|
"pi"
|
|
27697
27709
|
]
|
|
@@ -27701,7 +27713,7 @@ const PackageJsonSchema = schemas_object({
|
|
|
27701
27713
|
path: ".pi/prompts",
|
|
27702
27714
|
matchKind: "directory-prefix",
|
|
27703
27715
|
primaryConstruct: "skill",
|
|
27704
|
-
lintTarget: "
|
|
27716
|
+
lintTarget: "skills",
|
|
27705
27717
|
harnessHints: [
|
|
27706
27718
|
"pi"
|
|
27707
27719
|
]
|
|
@@ -27821,13 +27833,13 @@ const PackageJsonSchema = schemas_object({
|
|
|
27821
27833
|
],
|
|
27822
27834
|
instructionFormat: "claude-md"
|
|
27823
27835
|
},
|
|
27824
|
-
//
|
|
27836
|
+
// Subagent lint root
|
|
27825
27837
|
{
|
|
27826
27838
|
id: "subagent-claude",
|
|
27827
27839
|
path: ".claude/agents",
|
|
27828
27840
|
matchKind: "directory-prefix",
|
|
27829
27841
|
primaryConstruct: "subagent",
|
|
27830
|
-
lintTarget: "
|
|
27842
|
+
lintTarget: "subagents",
|
|
27831
27843
|
harnessHints: [
|
|
27832
27844
|
"claude"
|
|
27833
27845
|
]
|
|
@@ -27863,11 +27875,14 @@ const PackageJsonSchema = schemas_object({
|
|
|
27863
27875
|
* Normalizes a repo-relative path for catalog matching.
|
|
27864
27876
|
* Collapses `.` / `..` segments. Returns "" when the path is absolute or
|
|
27865
27877
|
* escapes the repository root — callers treat "" as non-matching.
|
|
27866
|
-
*/ function
|
|
27867
|
-
const normalized = path.replaceAll("\\", "/");
|
|
27878
|
+
*/ function isAbsoluteRepoPath(normalized) {
|
|
27868
27879
|
// POSIX absolute, UNC-style leading slash after backslash conversion,
|
|
27869
27880
|
// and Windows drive-letter absolute (C:/..., d:\...).
|
|
27870
|
-
|
|
27881
|
+
return normalized.startsWith("/") || /^[A-Za-z]:(\/|$)/.test(normalized);
|
|
27882
|
+
}
|
|
27883
|
+
function normalizeRepoRelativePath(path) {
|
|
27884
|
+
const normalized = path.replaceAll("\\", "/");
|
|
27885
|
+
if (isAbsoluteRepoPath(normalized)) {
|
|
27871
27886
|
return "";
|
|
27872
27887
|
}
|
|
27873
27888
|
const resolved = [];
|
|
@@ -27875,10 +27890,10 @@ const PackageJsonSchema = schemas_object({
|
|
|
27875
27890
|
if (segment === "" || segment === ".") {
|
|
27876
27891
|
continue;
|
|
27877
27892
|
}
|
|
27893
|
+
if (segment === ".." && resolved.length === 0) {
|
|
27894
|
+
return "";
|
|
27895
|
+
}
|
|
27878
27896
|
if (segment === "..") {
|
|
27879
|
-
if (resolved.length === 0) {
|
|
27880
|
-
return "";
|
|
27881
|
-
}
|
|
27882
27897
|
resolved.pop();
|
|
27883
27898
|
continue;
|
|
27884
27899
|
}
|
|
@@ -27926,14 +27941,15 @@ function constructTypesForPath(path, catalog = AGENTIC_LOCATION_CATALOG) {
|
|
|
27926
27941
|
if (!best) {
|
|
27927
27942
|
return [];
|
|
27928
27943
|
}
|
|
27929
|
-
|
|
27944
|
+
const secondary = best.secondaryConstructs ?? [];
|
|
27945
|
+
if (secondary.length === 0) {
|
|
27930
27946
|
return [
|
|
27931
|
-
best.primaryConstruct
|
|
27932
|
-
...best.secondaryConstructs
|
|
27947
|
+
best.primaryConstruct
|
|
27933
27948
|
];
|
|
27934
27949
|
}
|
|
27935
27950
|
return [
|
|
27936
|
-
best.primaryConstruct
|
|
27951
|
+
best.primaryConstruct,
|
|
27952
|
+
...secondary
|
|
27937
27953
|
];
|
|
27938
27954
|
}
|
|
27939
27955
|
function lintTargetEntries(target, catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
|
|
@@ -27945,8 +27961,14 @@ function skillDirectoryRoots(catalog = agentic_location_catalog_AGENTIC_LOCATION
|
|
|
27945
27961
|
function agentDirectoryRoots(catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
|
|
27946
27962
|
return lintTargetEntries("agents", catalog).filter((entry)=>entry.matchKind === "directory-prefix").map((entry)=>entry.path);
|
|
27947
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
|
+
}
|
|
27948
27970
|
function hookConfigPaths(catalog = agentic_location_catalog_AGENTIC_LOCATION_CATALOG) {
|
|
27949
|
-
return lintTargetEntries("hooks", catalog).filter(
|
|
27971
|
+
return lintTargetEntries("hooks", catalog).filter(isHookConfigEntry).map((entry)=>({
|
|
27950
27972
|
relativePath: entry.path,
|
|
27951
27973
|
platform: entry.hookPlatform
|
|
27952
27974
|
}));
|
|
@@ -30360,26 +30382,74 @@ function parseRawEdges(content) {
|
|
|
30360
30382
|
return edges;
|
|
30361
30383
|
}
|
|
30362
30384
|
|
|
30363
|
-
;// CONCATENATED MODULE: ../
|
|
30364
|
-
|
|
30365
|
-
|
|
30366
|
-
|
|
30367
|
-
|
|
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 = [
|
|
30368
30396
|
{
|
|
30369
30397
|
relPath: ".mcp.json",
|
|
30370
|
-
|
|
30398
|
+
harnessHint: "shared"
|
|
30371
30399
|
},
|
|
30372
30400
|
{
|
|
30373
30401
|
relPath: ".vscode/mcp.json",
|
|
30374
|
-
|
|
30402
|
+
harnessHint: "copilot"
|
|
30375
30403
|
}
|
|
30376
30404
|
];
|
|
30405
|
+
|
|
30406
|
+
;// CONCATENATED MODULE: ../doctor/src/gateways/mcp-config.ts
|
|
30407
|
+
|
|
30408
|
+
|
|
30409
|
+
|
|
30410
|
+
const MAX_MCP_CONFIG_BYTES = 1_048_576;
|
|
30377
30411
|
const McpServerEntrySchema = schemas_object({
|
|
30378
30412
|
type: schemas_string().optional(),
|
|
30379
30413
|
transport: schemas_string().optional()
|
|
30380
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.
|
|
30381
30450
|
const McpConfigSchema = schemas_object({
|
|
30382
|
-
mcpServers: schemas_record(schemas_string(), McpServerEntrySchema).optional()
|
|
30451
|
+
mcpServers: schemas_record(schemas_string(), McpServerEntrySchema).optional(),
|
|
30452
|
+
servers: schemas_record(schemas_string(), McpServerEntrySchema).optional()
|
|
30383
30453
|
}).passthrough();
|
|
30384
30454
|
async function readMcpServersFromSource(repoRoot, source) {
|
|
30385
30455
|
let content;
|
|
@@ -30394,13 +30464,20 @@ async function readMcpServersFromSource(repoRoot, source) {
|
|
|
30394
30464
|
} catch {
|
|
30395
30465
|
return [];
|
|
30396
30466
|
}
|
|
30467
|
+
if (hasReservedServerName(raw)) {
|
|
30468
|
+
return [];
|
|
30469
|
+
}
|
|
30397
30470
|
const result = McpConfigSchema.safeParse(raw);
|
|
30398
|
-
if (!result.success
|
|
30471
|
+
if (!result.success) {
|
|
30399
30472
|
return [];
|
|
30400
30473
|
}
|
|
30401
|
-
|
|
30474
|
+
const servers = {
|
|
30475
|
+
...result.data.mcpServers ?? {},
|
|
30476
|
+
...result.data.servers ?? {}
|
|
30477
|
+
};
|
|
30478
|
+
return Object.entries(servers).map(([serverName, entry])=>({
|
|
30402
30479
|
serverName,
|
|
30403
|
-
harness: source.
|
|
30480
|
+
harness: source.harnessHint,
|
|
30404
30481
|
path: source.relPath,
|
|
30405
30482
|
...entry.transport !== undefined ? {
|
|
30406
30483
|
transport: entry.transport
|
|
@@ -32672,24 +32749,25 @@ const initCommand = defineCommand({
|
|
|
32672
32749
|
;// CONCATENATED MODULE: ./src/commands/init-hooks.ts
|
|
32673
32750
|
|
|
32674
32751
|
|
|
32752
|
+
const initHooksArgs = {
|
|
32753
|
+
force: {
|
|
32754
|
+
type: "boolean",
|
|
32755
|
+
description: "Overwrite existing hook configuration even if already present",
|
|
32756
|
+
default: false
|
|
32757
|
+
},
|
|
32758
|
+
"no-session-start": {
|
|
32759
|
+
type: "boolean",
|
|
32760
|
+
description: "Disable the SessionStart hook for invariant lesson injection (enabled by default)",
|
|
32761
|
+
default: false
|
|
32762
|
+
}
|
|
32763
|
+
};
|
|
32675
32764
|
function createInitHooksCommand(useCase) {
|
|
32676
32765
|
return defineCommand({
|
|
32677
32766
|
meta: {
|
|
32678
32767
|
name: "init-hooks",
|
|
32679
32768
|
description: "Initialize Claude Code hook configuration for lousy-agents context injection"
|
|
32680
32769
|
},
|
|
32681
|
-
args:
|
|
32682
|
-
force: {
|
|
32683
|
-
type: "boolean",
|
|
32684
|
-
description: "Overwrite existing hook configuration even if already present",
|
|
32685
|
-
default: false
|
|
32686
|
-
},
|
|
32687
|
-
"no-session-start": {
|
|
32688
|
-
type: "boolean",
|
|
32689
|
-
description: "Disable the SessionStart hook for invariant lesson injection (enabled by default)",
|
|
32690
|
-
default: false
|
|
32691
|
-
}
|
|
32692
|
-
},
|
|
32770
|
+
args: initHooksArgs,
|
|
32693
32771
|
run: async (context)=>{
|
|
32694
32772
|
const rootDir = typeof context.data?.targetDir === "string" ? context.data.targetDir : process.cwd();
|
|
32695
32773
|
const force = context.args?.force === true;
|
|
@@ -32842,6 +32920,82 @@ function createInitHooksCommand(useCase) {
|
|
|
32842
32920
|
}
|
|
32843
32921
|
}
|
|
32844
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
|
+
|
|
32845
32999
|
;// CONCATENATED MODULE: ../core/src/gateways/agent-lint-gateway.ts
|
|
32846
33000
|
/**
|
|
32847
33001
|
* Gateway for agent lint file system operations.
|
|
@@ -32850,6 +33004,7 @@ function createInitHooksCommand(useCase) {
|
|
|
32850
33004
|
|
|
32851
33005
|
|
|
32852
33006
|
|
|
33007
|
+
|
|
32853
33008
|
/** Maximum agent file size: 1 MB */ const MAX_AGENT_FILE_BYTES = 1_048_576;
|
|
32854
33009
|
/** Catalog agent roots converted to OS-native relative paths. */ const AGENT_DIRECTORIES = agentDirectoryRoots().map((root)=>(0,external_node_path_.join)(...root.split("/")));
|
|
32855
33010
|
/**
|
|
@@ -32864,51 +33019,11 @@ function createInitHooksCommand(useCase) {
|
|
|
32864
33019
|
return agents;
|
|
32865
33020
|
}
|
|
32866
33021
|
async discoverAgentsInDir(targetDir, agentsDir) {
|
|
32867
|
-
|
|
32868
|
-
|
|
32869
|
-
|
|
32870
|
-
|
|
32871
|
-
|
|
32872
|
-
return [];
|
|
32873
|
-
}
|
|
32874
|
-
const resolvedAgentsDir = (0,external_node_path_.resolve)(targetDir, agentsDir);
|
|
32875
|
-
const agents = [];
|
|
32876
|
-
const walk = async (dir)=>{
|
|
32877
|
-
const entries = await file_system_utils_listDirectoryWithinRoot(targetDir, dir);
|
|
32878
|
-
for (const entry of entries){
|
|
32879
|
-
const name = entry.name;
|
|
32880
|
-
if (name.includes("..") || name.includes("/") || name.includes("\\")) {
|
|
32881
|
-
continue;
|
|
32882
|
-
}
|
|
32883
|
-
const entryPath = (0,external_node_path_.join)(dir, name);
|
|
32884
|
-
const absoluteEntryPath = (0,external_node_path_.join)(targetDir, entryPath);
|
|
32885
|
-
const resolvedPath = (0,external_node_path_.resolve)(absoluteEntryPath);
|
|
32886
|
-
const rel = (0,external_node_path_.relative)(resolvedAgentsDir, resolvedPath);
|
|
32887
|
-
if (rel.startsWith("..") || rel.startsWith(external_node_path_.sep)) {
|
|
32888
|
-
continue;
|
|
32889
|
-
}
|
|
32890
|
-
if (entry.isSymbolicLink()) {
|
|
32891
|
-
continue;
|
|
32892
|
-
}
|
|
32893
|
-
if (entry.isDirectory()) {
|
|
32894
|
-
await walk(entryPath);
|
|
32895
|
-
continue;
|
|
32896
|
-
}
|
|
32897
|
-
if (!entry.isFile()) {
|
|
32898
|
-
continue;
|
|
32899
|
-
}
|
|
32900
|
-
if (!name.endsWith(".md")) {
|
|
32901
|
-
continue;
|
|
32902
|
-
}
|
|
32903
|
-
const agentName = name.endsWith(".agent.md") ? (0,external_node_path_.basename)(name, ".agent.md") : (0,external_node_path_.basename)(name, ".md");
|
|
32904
|
-
agents.push({
|
|
32905
|
-
filePath: absoluteEntryPath,
|
|
32906
|
-
agentName
|
|
32907
|
-
});
|
|
32908
|
-
}
|
|
32909
|
-
};
|
|
32910
|
-
await walk(agentsDir);
|
|
32911
|
-
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
|
+
}));
|
|
32912
33027
|
}
|
|
32913
33028
|
async readAgentFileContent(filePath) {
|
|
32914
33029
|
return readFileNoFollow(filePath, MAX_AGENT_FILE_BYTES);
|
|
@@ -51905,227 +52020,6 @@ function lib_isUint8Array(value) {
|
|
|
51905
52020
|
*/
|
|
51906
52021
|
const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
51907
52022
|
|
|
51908
|
-
;// CONCATENATED MODULE: ../core/src/gateways/markdown-ast-gateway.ts
|
|
51909
|
-
/**
|
|
51910
|
-
* Gateway for parsing Markdown files into AST using remark.
|
|
51911
|
-
* Extracts structural features like headings, code blocks, and paragraphs.
|
|
51912
|
-
*/
|
|
51913
|
-
|
|
51914
|
-
|
|
51915
|
-
/** Maximum instruction file size: 1 MB */ const markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES = 1_048_576;
|
|
51916
|
-
/**
|
|
51917
|
-
* Remark-based implementation of the Markdown AST gateway.
|
|
51918
|
-
*/ class RemarkMarkdownAstGateway {
|
|
51919
|
-
async parseFile(filePath) {
|
|
51920
|
-
const content = await readFileNoFollow(filePath, markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES);
|
|
51921
|
-
return this.parseContent(content);
|
|
51922
|
-
}
|
|
51923
|
-
parseContent(content) {
|
|
51924
|
-
const processor = remark();
|
|
51925
|
-
const ast = processor.parse(content);
|
|
51926
|
-
const headings = [];
|
|
51927
|
-
const codeBlocks = [];
|
|
51928
|
-
const inlineCodes = [];
|
|
51929
|
-
lib_visit(ast, "heading", (node)=>{
|
|
51930
|
-
const text = this.extractHeadingText(node);
|
|
51931
|
-
headings.push({
|
|
51932
|
-
text,
|
|
51933
|
-
depth: node.depth,
|
|
51934
|
-
position: {
|
|
51935
|
-
line: node.position?.start.line ?? 0
|
|
51936
|
-
}
|
|
51937
|
-
});
|
|
51938
|
-
});
|
|
51939
|
-
// Track code blocks with their node index in root children
|
|
51940
|
-
for(let i = 0; i < ast.children.length; i++){
|
|
51941
|
-
const child = ast.children[i];
|
|
51942
|
-
if (child.type === "code") {
|
|
51943
|
-
const codeNode = child;
|
|
51944
|
-
codeBlocks.push({
|
|
51945
|
-
value: codeNode.value,
|
|
51946
|
-
lang: codeNode.lang ?? undefined,
|
|
51947
|
-
position: {
|
|
51948
|
-
line: codeNode.position?.start.line ?? 0
|
|
51949
|
-
},
|
|
51950
|
-
nodeIndex: i
|
|
51951
|
-
});
|
|
51952
|
-
}
|
|
51953
|
-
}
|
|
51954
|
-
lib_visit(ast, "inlineCode", (node)=>{
|
|
51955
|
-
inlineCodes.push({
|
|
51956
|
-
value: node.value,
|
|
51957
|
-
position: {
|
|
51958
|
-
line: node.position?.start.line ?? 0
|
|
51959
|
-
}
|
|
51960
|
-
});
|
|
51961
|
-
});
|
|
51962
|
-
return {
|
|
51963
|
-
headings,
|
|
51964
|
-
codeBlocks,
|
|
51965
|
-
inlineCodes,
|
|
51966
|
-
ast
|
|
51967
|
-
};
|
|
51968
|
-
}
|
|
51969
|
-
findConditionalKeywordsInProximity(structure, codeBlockNodeIndex, proximityWindow, keywords) {
|
|
51970
|
-
const ast = structure.ast;
|
|
51971
|
-
const endIndex = Math.min(ast.children.length, codeBlockNodeIndex + 1 + proximityWindow);
|
|
51972
|
-
for(let i = codeBlockNodeIndex + 1; i < endIndex; i++){
|
|
51973
|
-
const sibling = ast.children[i];
|
|
51974
|
-
const text = extractTextFromNode(sibling);
|
|
51975
|
-
const lowerText = text.toLowerCase();
|
|
51976
|
-
for (const keyword of keywords){
|
|
51977
|
-
if (containsWordBoundary(lowerText, keyword.toLowerCase())) {
|
|
51978
|
-
return true;
|
|
51979
|
-
}
|
|
51980
|
-
}
|
|
51981
|
-
}
|
|
51982
|
-
return false;
|
|
51983
|
-
}
|
|
51984
|
-
extractHeadingText(node) {
|
|
51985
|
-
const parts = [];
|
|
51986
|
-
lib_visit(node, "text", (textNode)=>{
|
|
51987
|
-
parts.push(textNode.value);
|
|
51988
|
-
});
|
|
51989
|
-
return parts.join("");
|
|
51990
|
-
}
|
|
51991
|
-
}
|
|
51992
|
-
/**
|
|
51993
|
-
* Checks if text contains a keyword at a word boundary (safe string matching, no regex).
|
|
51994
|
-
*/ function containsWordBoundary(text, keyword) {
|
|
51995
|
-
const index = text.indexOf(keyword);
|
|
51996
|
-
if (index === -1) {
|
|
51997
|
-
return false;
|
|
51998
|
-
}
|
|
51999
|
-
const charBefore = index > 0 ? text[index - 1] : " ";
|
|
52000
|
-
const charAfter = index + keyword.length < text.length ? text[index + keyword.length] : " ";
|
|
52001
|
-
const isWordBoundaryBefore = !/[a-z0-9]/i.test(charBefore);
|
|
52002
|
-
const isWordBoundaryAfter = !/[a-z0-9]/i.test(charAfter);
|
|
52003
|
-
return isWordBoundaryBefore && isWordBoundaryAfter;
|
|
52004
|
-
}
|
|
52005
|
-
/**
|
|
52006
|
-
* Extracts plain text content from any AST node recursively.
|
|
52007
|
-
*/ function extractTextFromNode(node) {
|
|
52008
|
-
if (node.value && typeof node.value === "string") {
|
|
52009
|
-
return node.value;
|
|
52010
|
-
}
|
|
52011
|
-
if (Array.isArray(node.children)) {
|
|
52012
|
-
return node.children.map((child)=>extractTextFromNode(child)).join(" ");
|
|
52013
|
-
}
|
|
52014
|
-
return "";
|
|
52015
|
-
}
|
|
52016
|
-
/**
|
|
52017
|
-
* Creates and returns the default Markdown AST gateway.
|
|
52018
|
-
*/ function createMarkdownAstGateway() {
|
|
52019
|
-
return new RemarkMarkdownAstGateway();
|
|
52020
|
-
}
|
|
52021
|
-
|
|
52022
|
-
;// CONCATENATED MODULE: ../core/src/entities/lint-rules.ts
|
|
52023
|
-
/**
|
|
52024
|
-
* Lint rule registry entity.
|
|
52025
|
-
* Defines all known lint rule IDs with their default severities, organized by target.
|
|
52026
|
-
*/ /** Valid severity values for rule configuration */ /** Default severity levels for all known lint rules */ const DEFAULT_LINT_RULES = {
|
|
52027
|
-
agents: {
|
|
52028
|
-
"agent/missing-frontmatter": "error",
|
|
52029
|
-
"agent/invalid-frontmatter": "error",
|
|
52030
|
-
"agent/missing-name": "error",
|
|
52031
|
-
"agent/invalid-name-format": "error",
|
|
52032
|
-
"agent/name-mismatch": "error",
|
|
52033
|
-
"agent/missing-description": "error",
|
|
52034
|
-
"agent/invalid-description": "error",
|
|
52035
|
-
"agent/invalid-field": "warn"
|
|
52036
|
-
},
|
|
52037
|
-
hooks: {
|
|
52038
|
-
"hook/invalid-json": "error",
|
|
52039
|
-
"hook/invalid-config": "error",
|
|
52040
|
-
"hook/missing-command": "error",
|
|
52041
|
-
"hook/missing-matcher": "warn",
|
|
52042
|
-
"hook/missing-timeout": "warn"
|
|
52043
|
-
},
|
|
52044
|
-
instructions: {
|
|
52045
|
-
"instruction/parse-error": "warn",
|
|
52046
|
-
"instruction/command-not-in-code-block": "warn",
|
|
52047
|
-
"instruction/command-outside-section": "warn",
|
|
52048
|
-
"instruction/missing-error-handling": "warn",
|
|
52049
|
-
"instruction/missing-structural-heading": "warn"
|
|
52050
|
-
},
|
|
52051
|
-
skills: {
|
|
52052
|
-
"skill/invalid-frontmatter": "error",
|
|
52053
|
-
"skill/missing-frontmatter": "error",
|
|
52054
|
-
"skill/missing-name": "error",
|
|
52055
|
-
"skill/invalid-name-format": "error",
|
|
52056
|
-
"skill/name-mismatch": "error",
|
|
52057
|
-
"skill/missing-description": "error",
|
|
52058
|
-
"skill/invalid-description": "error",
|
|
52059
|
-
"skill/missing-allowed-tools": "warn",
|
|
52060
|
-
"skill/missing-argument-hint": "warn"
|
|
52061
|
-
}
|
|
52062
|
-
};
|
|
52063
|
-
|
|
52064
|
-
;// CONCATENATED MODULE: ../core/src/lib/lint-config.ts
|
|
52065
|
-
/**
|
|
52066
|
-
* Lint configuration loader.
|
|
52067
|
-
* Loads lint rule severity overrides from c12 config and merges with defaults.
|
|
52068
|
-
*/
|
|
52069
|
-
|
|
52070
|
-
|
|
52071
|
-
/** Zod schema for a rule config map: rule IDs validated with regex to prevent prototype pollution */ const RuleConfigMapSchema = schemas_record(schemas_string().regex(/^[a-z]+\/[a-z]+(?:-[a-z]+)*$/), schemas_enum([
|
|
52072
|
-
"error",
|
|
52073
|
-
"warn",
|
|
52074
|
-
"off"
|
|
52075
|
-
]));
|
|
52076
|
-
/** Zod schema for the lint.rules section of the config */ const LintRulesConfigSchema = schemas_object({
|
|
52077
|
-
agents: RuleConfigMapSchema.optional(),
|
|
52078
|
-
hooks: RuleConfigMapSchema.optional(),
|
|
52079
|
-
instructions: RuleConfigMapSchema.optional(),
|
|
52080
|
-
skills: RuleConfigMapSchema.optional()
|
|
52081
|
-
});
|
|
52082
|
-
/** Zod schema for the lint section of the config */ const LintConfigSchema = schemas_object({
|
|
52083
|
-
lint: schemas_object({
|
|
52084
|
-
rules: LintRulesConfigSchema.optional()
|
|
52085
|
-
}).optional()
|
|
52086
|
-
});
|
|
52087
|
-
/**
|
|
52088
|
-
* Merges user overrides with defaults for a single target.
|
|
52089
|
-
* Only known rule IDs (present in defaults) are applied; unknown IDs are discarded.
|
|
52090
|
-
*/ function mergeTargetRules(defaults, overrides) {
|
|
52091
|
-
if (!overrides) {
|
|
52092
|
-
return defaults;
|
|
52093
|
-
}
|
|
52094
|
-
const merged = {
|
|
52095
|
-
...defaults
|
|
52096
|
-
};
|
|
52097
|
-
for (const [ruleId, severity] of Object.entries(overrides)){
|
|
52098
|
-
if (Object.hasOwn(defaults, ruleId)) {
|
|
52099
|
-
merged[ruleId] = severity;
|
|
52100
|
-
}
|
|
52101
|
-
}
|
|
52102
|
-
return merged;
|
|
52103
|
-
}
|
|
52104
|
-
/**
|
|
52105
|
-
* Loads lint configuration from the target directory using c12.
|
|
52106
|
-
* Merges user overrides with default rule severities.
|
|
52107
|
-
* Throws on config load failures (syntax errors, permission denied, validation errors).
|
|
52108
|
-
*/ async function loadLintConfig(targetDir) {
|
|
52109
|
-
const { config } = await loadConfig({
|
|
52110
|
-
name: "lousy-agents",
|
|
52111
|
-
cwd: targetDir
|
|
52112
|
-
});
|
|
52113
|
-
if (!config) {
|
|
52114
|
-
return DEFAULT_LINT_RULES;
|
|
52115
|
-
}
|
|
52116
|
-
const parsed = LintConfigSchema.parse(config);
|
|
52117
|
-
const rules = parsed.lint?.rules;
|
|
52118
|
-
if (!rules) {
|
|
52119
|
-
return DEFAULT_LINT_RULES;
|
|
52120
|
-
}
|
|
52121
|
-
return {
|
|
52122
|
-
agents: mergeTargetRules(DEFAULT_LINT_RULES.agents, rules.agents),
|
|
52123
|
-
hooks: mergeTargetRules(DEFAULT_LINT_RULES.hooks, rules.hooks),
|
|
52124
|
-
instructions: mergeTargetRules(DEFAULT_LINT_RULES.instructions, rules.instructions),
|
|
52125
|
-
skills: mergeTargetRules(DEFAULT_LINT_RULES.skills, rules.skills)
|
|
52126
|
-
};
|
|
52127
|
-
}
|
|
52128
|
-
|
|
52129
52023
|
;// CONCATENATED MODULE: ../core/src/entities/instruction-quality.ts
|
|
52130
52024
|
/**
|
|
52131
52025
|
* Core domain entities for instruction quality analysis.
|
|
@@ -52177,6 +52071,9 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
|
|
|
52177
52071
|
* Orchestrates discovery, AST parsing, and scoring of feedback loop documentation.
|
|
52178
52072
|
*/
|
|
52179
52073
|
|
|
52074
|
+
/** Type guard narrowing an `unknown` AST child to {@link AstNodeLike}. */ function isAstNodeLike(node) {
|
|
52075
|
+
return typeof node === "object" && node !== null && "type" in node && typeof node.type === "string";
|
|
52076
|
+
}
|
|
52180
52077
|
/** Maximum number of raw heading pattern entries accepted before deduplication. */ const MAX_RAW_HEADING_PATTERNS = 1000;
|
|
52181
52078
|
/** Maximum number of heading patterns accepted in a single execute() call. */ const MAX_HEADING_PATTERNS = 50;
|
|
52182
52079
|
/** Maximum character length for a single heading pattern. */ const analyze_instruction_quality_MAX_PATTERN_LENGTH = 200;
|
|
@@ -52532,6 +52429,9 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
|
|
|
52532
52429
|
return parts.join(" ");
|
|
52533
52430
|
}
|
|
52534
52431
|
collectText(node, parts) {
|
|
52432
|
+
if (!isAstNodeLike(node)) {
|
|
52433
|
+
return;
|
|
52434
|
+
}
|
|
52535
52435
|
if (node.type === "text" && typeof node.value === "string") {
|
|
52536
52436
|
parts.push(node.value);
|
|
52537
52437
|
}
|
|
@@ -52678,6 +52578,369 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
|
|
|
52678
52578
|
}
|
|
52679
52579
|
}
|
|
52680
52580
|
|
|
52581
|
+
;// CONCATENATED MODULE: ../core/src/gateways/markdown-ast-gateway.ts
|
|
52582
|
+
/**
|
|
52583
|
+
* Gateway for parsing Markdown files into AST using remark.
|
|
52584
|
+
* Extracts structural features like headings, code blocks, and paragraphs.
|
|
52585
|
+
*/
|
|
52586
|
+
|
|
52587
|
+
|
|
52588
|
+
|
|
52589
|
+
/** Maximum instruction file size: 1 MB */ const markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES = 1_048_576;
|
|
52590
|
+
/**
|
|
52591
|
+
* Remark-based implementation of the Markdown AST gateway.
|
|
52592
|
+
*/ class RemarkMarkdownAstGateway {
|
|
52593
|
+
async parseFile(filePath) {
|
|
52594
|
+
const content = await readFileNoFollow(filePath, markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES);
|
|
52595
|
+
return this.parseContent(content);
|
|
52596
|
+
}
|
|
52597
|
+
parseContent(content) {
|
|
52598
|
+
const processor = remark();
|
|
52599
|
+
const ast = processor.parse(content);
|
|
52600
|
+
const headings = [];
|
|
52601
|
+
const codeBlocks = [];
|
|
52602
|
+
const inlineCodes = [];
|
|
52603
|
+
lib_visit(ast, "heading", (node)=>{
|
|
52604
|
+
const text = this.extractHeadingText(node);
|
|
52605
|
+
headings.push({
|
|
52606
|
+
text,
|
|
52607
|
+
depth: node.depth,
|
|
52608
|
+
position: {
|
|
52609
|
+
line: node.position?.start.line ?? 0
|
|
52610
|
+
}
|
|
52611
|
+
});
|
|
52612
|
+
});
|
|
52613
|
+
// Track code blocks with their node index in root children
|
|
52614
|
+
for(let i = 0; i < ast.children.length; i++){
|
|
52615
|
+
const child = ast.children[i];
|
|
52616
|
+
if (child.type === "code") {
|
|
52617
|
+
const codeNode = child;
|
|
52618
|
+
codeBlocks.push({
|
|
52619
|
+
value: codeNode.value,
|
|
52620
|
+
lang: codeNode.lang ?? undefined,
|
|
52621
|
+
position: {
|
|
52622
|
+
line: codeNode.position?.start.line ?? 0
|
|
52623
|
+
},
|
|
52624
|
+
nodeIndex: i
|
|
52625
|
+
});
|
|
52626
|
+
}
|
|
52627
|
+
}
|
|
52628
|
+
lib_visit(ast, "inlineCode", (node)=>{
|
|
52629
|
+
inlineCodes.push({
|
|
52630
|
+
value: node.value,
|
|
52631
|
+
position: {
|
|
52632
|
+
line: node.position?.start.line ?? 0
|
|
52633
|
+
}
|
|
52634
|
+
});
|
|
52635
|
+
});
|
|
52636
|
+
return {
|
|
52637
|
+
headings,
|
|
52638
|
+
codeBlocks,
|
|
52639
|
+
inlineCodes,
|
|
52640
|
+
ast
|
|
52641
|
+
};
|
|
52642
|
+
}
|
|
52643
|
+
findConditionalKeywordsInProximity(structure, codeBlockNodeIndex, proximityWindow, keywords) {
|
|
52644
|
+
const ast = structure.ast;
|
|
52645
|
+
const endIndex = Math.min(ast.children.length, codeBlockNodeIndex + 1 + proximityWindow);
|
|
52646
|
+
for(let i = codeBlockNodeIndex + 1; i < endIndex; i++){
|
|
52647
|
+
const sibling = ast.children[i];
|
|
52648
|
+
const text = extractTextFromNode(sibling);
|
|
52649
|
+
const lowerText = text.toLowerCase();
|
|
52650
|
+
for (const keyword of keywords){
|
|
52651
|
+
if (containsWordBoundary(lowerText, keyword.toLowerCase())) {
|
|
52652
|
+
return true;
|
|
52653
|
+
}
|
|
52654
|
+
}
|
|
52655
|
+
}
|
|
52656
|
+
return false;
|
|
52657
|
+
}
|
|
52658
|
+
extractHeadingText(node) {
|
|
52659
|
+
const parts = [];
|
|
52660
|
+
lib_visit(node, "text", (textNode)=>{
|
|
52661
|
+
parts.push(textNode.value);
|
|
52662
|
+
});
|
|
52663
|
+
return parts.join("");
|
|
52664
|
+
}
|
|
52665
|
+
}
|
|
52666
|
+
/**
|
|
52667
|
+
* Checks if text contains a keyword at a word boundary (safe string matching, no regex).
|
|
52668
|
+
*/ function containsWordBoundary(text, keyword) {
|
|
52669
|
+
const index = text.indexOf(keyword);
|
|
52670
|
+
if (index === -1) {
|
|
52671
|
+
return false;
|
|
52672
|
+
}
|
|
52673
|
+
const charBefore = index > 0 ? text[index - 1] : " ";
|
|
52674
|
+
const charAfter = index + keyword.length < text.length ? text[index + keyword.length] : " ";
|
|
52675
|
+
const isWordBoundaryBefore = !/[a-z0-9]/i.test(charBefore);
|
|
52676
|
+
const isWordBoundaryAfter = !/[a-z0-9]/i.test(charAfter);
|
|
52677
|
+
return isWordBoundaryBefore && isWordBoundaryAfter;
|
|
52678
|
+
}
|
|
52679
|
+
/**
|
|
52680
|
+
* Extracts plain text content from any AST node recursively.
|
|
52681
|
+
*/ function extractTextFromNode(node) {
|
|
52682
|
+
if (!isAstNodeLike(node)) {
|
|
52683
|
+
return "";
|
|
52684
|
+
}
|
|
52685
|
+
if (node.value && typeof node.value === "string") {
|
|
52686
|
+
return node.value;
|
|
52687
|
+
}
|
|
52688
|
+
if (Array.isArray(node.children)) {
|
|
52689
|
+
return node.children.map((child)=>extractTextFromNode(child)).join(" ");
|
|
52690
|
+
}
|
|
52691
|
+
return "";
|
|
52692
|
+
}
|
|
52693
|
+
/**
|
|
52694
|
+
* Creates and returns the default Markdown AST gateway.
|
|
52695
|
+
*/ function createMarkdownAstGateway() {
|
|
52696
|
+
return new RemarkMarkdownAstGateway();
|
|
52697
|
+
}
|
|
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
|
+
|
|
52813
|
+
;// CONCATENATED MODULE: ../core/src/entities/lint-rules.ts
|
|
52814
|
+
/**
|
|
52815
|
+
* Lint rule registry entity.
|
|
52816
|
+
* Defines all known lint rule IDs with their default severities, organized by target.
|
|
52817
|
+
*/ /** Valid severity values for rule configuration */ /** Default severity levels for all known lint rules */ const DEFAULT_LINT_RULES = {
|
|
52818
|
+
agents: {
|
|
52819
|
+
"agent/missing-frontmatter": "error",
|
|
52820
|
+
"agent/invalid-frontmatter": "error",
|
|
52821
|
+
"agent/missing-name": "error",
|
|
52822
|
+
"agent/invalid-name-format": "error",
|
|
52823
|
+
"agent/name-mismatch": "error",
|
|
52824
|
+
"agent/missing-description": "error",
|
|
52825
|
+
"agent/invalid-description": "error",
|
|
52826
|
+
"agent/invalid-field": "warn"
|
|
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
|
+
},
|
|
52848
|
+
hooks: {
|
|
52849
|
+
"hook/invalid-json": "error",
|
|
52850
|
+
"hook/invalid-config": "error",
|
|
52851
|
+
"hook/missing-command": "error",
|
|
52852
|
+
"hook/missing-matcher": "warn",
|
|
52853
|
+
"hook/missing-timeout": "warn"
|
|
52854
|
+
},
|
|
52855
|
+
instructions: {
|
|
52856
|
+
"instruction/parse-error": "warn",
|
|
52857
|
+
"instruction/command-not-in-code-block": "warn",
|
|
52858
|
+
"instruction/command-outside-section": "warn",
|
|
52859
|
+
"instruction/missing-error-handling": "warn",
|
|
52860
|
+
"instruction/missing-structural-heading": "warn"
|
|
52861
|
+
},
|
|
52862
|
+
skills: {
|
|
52863
|
+
"skill/invalid-frontmatter": "error",
|
|
52864
|
+
"skill/missing-frontmatter": "error",
|
|
52865
|
+
"skill/missing-name": "error",
|
|
52866
|
+
"skill/invalid-name-format": "error",
|
|
52867
|
+
"skill/name-mismatch": "error",
|
|
52868
|
+
"skill/missing-description": "error",
|
|
52869
|
+
"skill/invalid-description": "error",
|
|
52870
|
+
"skill/missing-allowed-tools": "warn",
|
|
52871
|
+
"skill/missing-argument-hint": "warn"
|
|
52872
|
+
}
|
|
52873
|
+
};
|
|
52874
|
+
|
|
52875
|
+
;// CONCATENATED MODULE: ../core/src/lib/lint-config.ts
|
|
52876
|
+
/**
|
|
52877
|
+
* Lint configuration loader.
|
|
52878
|
+
* Loads lint rule severity overrides from c12 config and merges with defaults.
|
|
52879
|
+
*/
|
|
52880
|
+
|
|
52881
|
+
|
|
52882
|
+
/** Zod schema for a rule config map: rule IDs validated with regex to prevent prototype pollution */ const RuleConfigMapSchema = schemas_record(schemas_string().regex(/^[a-z]+\/[a-z]+(?:-[a-z]+)*$/), schemas_enum([
|
|
52883
|
+
"error",
|
|
52884
|
+
"warn",
|
|
52885
|
+
"off"
|
|
52886
|
+
]));
|
|
52887
|
+
/** Zod schema for the lint.rules section of the config */ const LintRulesConfigSchema = schemas_object({
|
|
52888
|
+
agents: RuleConfigMapSchema.optional(),
|
|
52889
|
+
subagents: RuleConfigMapSchema.optional(),
|
|
52890
|
+
hooks: RuleConfigMapSchema.optional(),
|
|
52891
|
+
instructions: RuleConfigMapSchema.optional(),
|
|
52892
|
+
skills: RuleConfigMapSchema.optional(),
|
|
52893
|
+
mcpServers: RuleConfigMapSchema.optional()
|
|
52894
|
+
});
|
|
52895
|
+
/** Zod schema for the lint section of the config */ const LintConfigSchema = schemas_object({
|
|
52896
|
+
lint: schemas_object({
|
|
52897
|
+
rules: LintRulesConfigSchema.optional()
|
|
52898
|
+
}).optional()
|
|
52899
|
+
});
|
|
52900
|
+
/**
|
|
52901
|
+
* Merges user overrides with defaults for a single target.
|
|
52902
|
+
* Only known rule IDs (present in defaults) are applied; unknown IDs are discarded.
|
|
52903
|
+
*/ function mergeTargetRules(defaults, overrides) {
|
|
52904
|
+
if (!overrides) {
|
|
52905
|
+
return defaults;
|
|
52906
|
+
}
|
|
52907
|
+
const merged = {
|
|
52908
|
+
...defaults
|
|
52909
|
+
};
|
|
52910
|
+
for (const [ruleId, severity] of Object.entries(overrides)){
|
|
52911
|
+
if (Object.hasOwn(defaults, ruleId)) {
|
|
52912
|
+
merged[ruleId] = severity;
|
|
52913
|
+
}
|
|
52914
|
+
}
|
|
52915
|
+
return merged;
|
|
52916
|
+
}
|
|
52917
|
+
/**
|
|
52918
|
+
* Loads lint configuration from the target directory using c12.
|
|
52919
|
+
* Merges user overrides with default rule severities.
|
|
52920
|
+
* Throws on config load failures (syntax errors, permission denied, validation errors).
|
|
52921
|
+
*/ async function loadLintConfig(targetDir) {
|
|
52922
|
+
const { config } = await loadConfig({
|
|
52923
|
+
name: "lousy-agents",
|
|
52924
|
+
cwd: targetDir
|
|
52925
|
+
});
|
|
52926
|
+
if (!config) {
|
|
52927
|
+
return DEFAULT_LINT_RULES;
|
|
52928
|
+
}
|
|
52929
|
+
const parsed = LintConfigSchema.parse(config);
|
|
52930
|
+
const rules = parsed.lint?.rules;
|
|
52931
|
+
if (!rules) {
|
|
52932
|
+
return DEFAULT_LINT_RULES;
|
|
52933
|
+
}
|
|
52934
|
+
return {
|
|
52935
|
+
agents: mergeTargetRules(DEFAULT_LINT_RULES.agents, rules.agents),
|
|
52936
|
+
subagents: mergeTargetRules(DEFAULT_LINT_RULES.subagents, rules.subagents),
|
|
52937
|
+
hooks: mergeTargetRules(DEFAULT_LINT_RULES.hooks, rules.hooks),
|
|
52938
|
+
instructions: mergeTargetRules(DEFAULT_LINT_RULES.instructions, rules.instructions),
|
|
52939
|
+
skills: mergeTargetRules(DEFAULT_LINT_RULES.skills, rules.skills),
|
|
52940
|
+
mcpServers: mergeTargetRules(DEFAULT_LINT_RULES.mcpServers, rules.mcpServers)
|
|
52941
|
+
};
|
|
52942
|
+
}
|
|
52943
|
+
|
|
52681
52944
|
;// CONCATENATED MODULE: ../core/src/use-cases/apply-severity-filter.ts
|
|
52682
52945
|
/**
|
|
52683
52946
|
* Shared severity filtering for lint outputs.
|
|
@@ -52686,8 +52949,10 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
|
|
|
52686
52949
|
*/ /** Maps a lint target to its config key */ const TARGET_TO_CONFIG_KEY = {
|
|
52687
52950
|
skill: "skills",
|
|
52688
52951
|
agent: "agents",
|
|
52952
|
+
subagent: "subagents",
|
|
52689
52953
|
hook: "hooks",
|
|
52690
|
-
instruction: "instructions"
|
|
52954
|
+
instruction: "instructions",
|
|
52955
|
+
"mcp-server": "mcpServers"
|
|
52691
52956
|
};
|
|
52692
52957
|
/**
|
|
52693
52958
|
* Maps config severity to diagnostic severity.
|
|
@@ -53087,6 +53352,172 @@ const INVALID_JSON_MESSAGE_PREFIX = "Invalid JSON in hook configuration file";
|
|
|
53087
53352
|
}
|
|
53088
53353
|
}
|
|
53089
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
|
+
|
|
53090
53521
|
;// CONCATENATED MODULE: ../core/src/use-cases/lint-skill-frontmatter.ts
|
|
53091
53522
|
|
|
53092
53523
|
const AgentSkillFrontmatterSchema = schemas_object({
|
|
@@ -53242,6 +53673,138 @@ function hasFrontmatterDelimiters(content) {
|
|
|
53242
53673
|
return false;
|
|
53243
53674
|
}
|
|
53244
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
|
+
|
|
53245
53808
|
;// CONCATENATED MODULE: ../lint/src/lint-errors.ts
|
|
53246
53809
|
/**
|
|
53247
53810
|
* Public lint API error types.
|
|
@@ -53382,11 +53945,17 @@ function sanitizeForErrorMessage(value) {
|
|
|
53382
53945
|
|
|
53383
53946
|
|
|
53384
53947
|
|
|
53948
|
+
|
|
53949
|
+
|
|
53950
|
+
|
|
53951
|
+
|
|
53385
53952
|
const LintTargetsSchema = schemas_object({
|
|
53386
53953
|
skills: schemas_boolean().optional(),
|
|
53387
53954
|
agents: schemas_boolean().optional(),
|
|
53955
|
+
subagents: schemas_boolean().optional(),
|
|
53388
53956
|
hooks: schemas_boolean().optional(),
|
|
53389
|
-
instructions: schemas_boolean().optional()
|
|
53957
|
+
instructions: schemas_boolean().optional(),
|
|
53958
|
+
mcpServers: schemas_boolean().optional()
|
|
53390
53959
|
}).strict().optional();
|
|
53391
53960
|
const LintOptionsSchema = schemas_object({
|
|
53392
53961
|
directory: schemas_string().min(1, "directory must not be empty"),
|
|
@@ -53442,6 +54011,22 @@ async function lintAgents(targetDir) {
|
|
|
53442
54011
|
});
|
|
53443
54012
|
return toLintOutput(output, "agent", output.totalAgents);
|
|
53444
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
|
+
}
|
|
53445
54030
|
async function lintHooks(targetDir) {
|
|
53446
54031
|
const gateway = createHookConfigGateway();
|
|
53447
54032
|
const useCase = new LintHookConfigUseCase(gateway);
|
|
@@ -53472,10 +54057,15 @@ async function lintInstructions(targetDir, logger) {
|
|
|
53472
54057
|
}
|
|
53473
54058
|
};
|
|
53474
54059
|
}
|
|
53475
|
-
function isTargetEnabled(
|
|
53476
|
-
if (!targets)
|
|
54060
|
+
function isTargetEnabled(target, targets) {
|
|
54061
|
+
if (!targets) {
|
|
54062
|
+
return target.defaultEnabled;
|
|
54063
|
+
}
|
|
53477
54064
|
const hasAnyEnabled = Object.values(targets).some(Boolean);
|
|
53478
|
-
|
|
54065
|
+
if (!hasAnyEnabled) {
|
|
54066
|
+
return target.defaultEnabled;
|
|
54067
|
+
}
|
|
54068
|
+
return targets[target.key] === true;
|
|
53479
54069
|
}
|
|
53480
54070
|
/**
|
|
53481
54071
|
* Run lint checks on a project directory.
|
|
@@ -53483,7 +54073,10 @@ function isTargetEnabled(key, targets) {
|
|
|
53483
54073
|
* Orchestrates all lint targets (skills, agents, hooks, instructions),
|
|
53484
54074
|
* applies lint rule configuration, and returns structured results.
|
|
53485
54075
|
*
|
|
53486
|
-
* When no targets are specified (or all are false),
|
|
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.
|
|
53487
54080
|
*
|
|
53488
54081
|
* @example
|
|
53489
54082
|
* ```typescript
|
|
@@ -53518,22 +54111,36 @@ function isTargetEnabled(key, targets) {
|
|
|
53518
54111
|
const lintTargets = [
|
|
53519
54112
|
{
|
|
53520
54113
|
key: "skills",
|
|
54114
|
+
defaultEnabled: true,
|
|
53521
54115
|
execute: lintSkills
|
|
53522
54116
|
},
|
|
53523
54117
|
{
|
|
53524
54118
|
key: "agents",
|
|
54119
|
+
defaultEnabled: true,
|
|
53525
54120
|
execute: lintAgents
|
|
53526
54121
|
},
|
|
54122
|
+
{
|
|
54123
|
+
key: "subagents",
|
|
54124
|
+
defaultEnabled: false,
|
|
54125
|
+
execute: lintSubagents
|
|
54126
|
+
},
|
|
53527
54127
|
{
|
|
53528
54128
|
key: "hooks",
|
|
54129
|
+
defaultEnabled: true,
|
|
53529
54130
|
execute: lintHooks
|
|
53530
54131
|
},
|
|
53531
54132
|
{
|
|
53532
54133
|
key: "instructions",
|
|
54134
|
+
defaultEnabled: true,
|
|
53533
54135
|
execute: (dir)=>lintInstructions(dir, parsed.logger)
|
|
54136
|
+
},
|
|
54137
|
+
{
|
|
54138
|
+
key: "mcpServers",
|
|
54139
|
+
defaultEnabled: false,
|
|
54140
|
+
execute: lintMcpServers
|
|
53534
54141
|
}
|
|
53535
54142
|
];
|
|
53536
|
-
const enabledTargets = lintTargets.filter((t)=>isTargetEnabled(t
|
|
54143
|
+
const enabledTargets = lintTargets.filter((t)=>isTargetEnabled(t, parsed.targets));
|
|
53537
54144
|
const outputs = [];
|
|
53538
54145
|
for (const target of enabledTargets){
|
|
53539
54146
|
const rawOutput = await target.execute(targetDir);
|
|
@@ -53568,185 +54175,277 @@ function isTargetEnabled(key, targets) {
|
|
|
53568
54175
|
|
|
53569
54176
|
|
|
53570
54177
|
|
|
53571
|
-
;// CONCATENATED MODULE: ./src/commands/lint.ts
|
|
54178
|
+
;// CONCATENATED MODULE: ./src/commands/lint-instruction-display.ts
|
|
53572
54179
|
/**
|
|
53573
|
-
*
|
|
53574
|
-
* Delegates to the lint package facade and handles CLI display concerns.
|
|
54180
|
+
* Instruction-quality display for the lint CLI command.
|
|
53575
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
|
+
}
|
|
53576
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
|
+
}
|
|
53577
54222
|
|
|
54223
|
+
;// CONCATENATED MODULE: ./src/commands/lint-display.ts
|
|
53578
54224
|
/**
|
|
53579
|
-
*
|
|
53580
|
-
*/
|
|
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) {
|
|
53581
54251
|
if (output.summary.totalFiles === 0) {
|
|
53582
54252
|
consola.info(`No ${label} found`);
|
|
53583
54253
|
return;
|
|
53584
54254
|
}
|
|
53585
54255
|
consola.info(`Discovered ${output.summary.totalFiles} ${label}`);
|
|
53586
|
-
|
|
54256
|
+
displayOkFiles(output);
|
|
53587
54257
|
for (const d of output.diagnostics){
|
|
53588
|
-
|
|
54258
|
+
logDiagnostic(d);
|
|
53589
54259
|
}
|
|
53590
|
-
|
|
53591
|
-
|
|
53592
|
-
|
|
54260
|
+
}
|
|
54261
|
+
function displayHumanOutputs(outputs) {
|
|
54262
|
+
for (const output of outputs){
|
|
54263
|
+
if (output.target === "instruction") {
|
|
54264
|
+
displayInstructionQuality(output);
|
|
54265
|
+
continue;
|
|
53593
54266
|
}
|
|
54267
|
+
displayLintOutput(output, targetLabel(output.target));
|
|
53594
54268
|
}
|
|
53595
|
-
|
|
53596
|
-
|
|
53597
|
-
|
|
53598
|
-
|
|
53599
|
-
|
|
53600
|
-
|
|
53601
|
-
|
|
53602
|
-
|
|
53603
|
-
|
|
53604
|
-
}
|
|
54269
|
+
}
|
|
54270
|
+
function displayFormattedOutputs(outputs, format) {
|
|
54271
|
+
if (format === "human") {
|
|
54272
|
+
displayHumanOutputs(outputs);
|
|
54273
|
+
return;
|
|
54274
|
+
}
|
|
54275
|
+
const formatted = createFormatter(format).format(outputs);
|
|
54276
|
+
if (!formatted) {
|
|
54277
|
+
return;
|
|
53605
54278
|
}
|
|
54279
|
+
process.stdout.write(`${formatted}\n`);
|
|
53606
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
|
|
53607
54286
|
/**
|
|
53608
|
-
*
|
|
53609
|
-
*/
|
|
53610
|
-
|
|
53611
|
-
|
|
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") {
|
|
53612
54293
|
return;
|
|
53613
54294
|
}
|
|
53614
|
-
|
|
53615
|
-
|
|
53616
|
-
|
|
53617
|
-
|
|
53618
|
-
|
|
53619
|
-
|
|
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;
|
|
53620
54376
|
}
|
|
53621
|
-
|
|
54377
|
+
throw error;
|
|
53622
54378
|
}
|
|
53623
|
-
|
|
53624
|
-
|
|
54379
|
+
}
|
|
54380
|
+
async function runLintCommand(context) {
|
|
54381
|
+
if (isLessonsSubcommand(context.rawArgs ?? [])) {
|
|
54382
|
+
return;
|
|
53625
54383
|
}
|
|
54384
|
+
const format = resolveFormat(context);
|
|
54385
|
+
const result = await executeLint(context);
|
|
54386
|
+
if (result === null) {
|
|
54387
|
+
return;
|
|
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);
|
|
53626
54397
|
}
|
|
53627
54398
|
/**
|
|
53628
54399
|
* The `lint` command for validating agent skills, custom agents, and instruction files.
|
|
53629
54400
|
* Also exposes `lint lessons` as a subcommand for lesson frontmatter validation.
|
|
53630
|
-
*/
|
|
54401
|
+
*/ const lintArgs = {
|
|
54402
|
+
skills: {
|
|
54403
|
+
type: "boolean",
|
|
54404
|
+
description: "Lint skill frontmatter in .github/skills/ and .claude/skills/",
|
|
54405
|
+
default: false
|
|
54406
|
+
},
|
|
54407
|
+
agents: {
|
|
54408
|
+
type: "boolean",
|
|
54409
|
+
description: "Lint custom agent frontmatter in .github/agents/",
|
|
54410
|
+
default: false
|
|
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
|
+
},
|
|
54417
|
+
hooks: {
|
|
54418
|
+
type: "boolean",
|
|
54419
|
+
description: "Lint pre-tool-use hook configurations in .github/hooks/agent-shell/hooks.json, .claude/settings.json, and .claude/settings.local.json",
|
|
54420
|
+
default: false
|
|
54421
|
+
},
|
|
54422
|
+
instructions: {
|
|
54423
|
+
type: "boolean",
|
|
54424
|
+
description: "Analyze instruction quality across all instruction file formats",
|
|
54425
|
+
default: false
|
|
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
|
+
},
|
|
54432
|
+
format: {
|
|
54433
|
+
type: "string",
|
|
54434
|
+
description: "Output format: human (default), json, or rdjsonl",
|
|
54435
|
+
default: "human"
|
|
54436
|
+
}
|
|
54437
|
+
};
|
|
54438
|
+
function createLintCommand(lintLessonsCmd) {
|
|
53631
54439
|
return defineCommand({
|
|
53632
54440
|
meta: {
|
|
53633
54441
|
name: "lint",
|
|
53634
|
-
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."
|
|
53635
54443
|
},
|
|
53636
54444
|
subCommands: {
|
|
53637
54445
|
lessons: lintLessonsCmd
|
|
53638
54446
|
},
|
|
53639
|
-
args:
|
|
53640
|
-
|
|
53641
|
-
type: "boolean",
|
|
53642
|
-
description: "Lint skill frontmatter in .github/skills/ and .claude/skills/",
|
|
53643
|
-
default: false
|
|
53644
|
-
},
|
|
53645
|
-
agents: {
|
|
53646
|
-
type: "boolean",
|
|
53647
|
-
description: "Lint custom agent frontmatter in .github/agents/",
|
|
53648
|
-
default: false
|
|
53649
|
-
},
|
|
53650
|
-
hooks: {
|
|
53651
|
-
type: "boolean",
|
|
53652
|
-
description: "Lint pre-tool-use hook configurations in .github/hooks/agent-shell/hooks.json, .claude/settings.json, and .claude/settings.local.json",
|
|
53653
|
-
default: false
|
|
53654
|
-
},
|
|
53655
|
-
instructions: {
|
|
53656
|
-
type: "boolean",
|
|
53657
|
-
description: "Analyze instruction quality across all instruction file formats",
|
|
53658
|
-
default: false
|
|
53659
|
-
},
|
|
53660
|
-
format: {
|
|
53661
|
-
type: "string",
|
|
53662
|
-
description: "Output format: human (default), json, or rdjsonl",
|
|
53663
|
-
default: "human"
|
|
53664
|
-
}
|
|
53665
|
-
},
|
|
53666
|
-
run: async (context)=>{
|
|
53667
|
-
const rawTargetDir = typeof context.data?.targetDir === "string" ? context.data.targetDir : process.cwd();
|
|
53668
|
-
const rawArgs_ = context.rawArgs ?? [];
|
|
53669
|
-
const lessonsIsSubcommand = rawArgs_.some((token, idx)=>token === "lessons" && rawArgs_[idx - 1] !== "--format");
|
|
53670
|
-
if (lessonsIsSubcommand) {
|
|
53671
|
-
return;
|
|
53672
|
-
}
|
|
53673
|
-
const lintSkillsFlag = context.args?.skills === true || context.data?.skills === true;
|
|
53674
|
-
const lintAgentsFlag = context.args?.agents === true || context.data?.agents === true;
|
|
53675
|
-
const lintHooksFlag = context.args?.hooks === true || context.data?.hooks === true;
|
|
53676
|
-
const lintInstructionsFlag = context.args?.instructions === true || context.data?.instructions === true;
|
|
53677
|
-
const rawFormat = typeof context.args?.format === "string" ? context.args.format : typeof context.data?.format === "string" ? context.data.format : "human";
|
|
53678
|
-
const validFormats = new Set([
|
|
53679
|
-
"human",
|
|
53680
|
-
"json",
|
|
53681
|
-
"rdjsonl"
|
|
53682
|
-
]);
|
|
53683
|
-
function isLintFormatType(value) {
|
|
53684
|
-
return validFormats.has(value);
|
|
53685
|
-
}
|
|
53686
|
-
const format = isLintFormatType(rawFormat) ? rawFormat : "human";
|
|
53687
|
-
let result;
|
|
53688
|
-
try {
|
|
53689
|
-
result = await runLint({
|
|
53690
|
-
directory: rawTargetDir,
|
|
53691
|
-
targets: {
|
|
53692
|
-
skills: lintSkillsFlag,
|
|
53693
|
-
agents: lintAgentsFlag,
|
|
53694
|
-
hooks: lintHooksFlag,
|
|
53695
|
-
instructions: lintInstructionsFlag
|
|
53696
|
-
}
|
|
53697
|
-
});
|
|
53698
|
-
} catch (error) {
|
|
53699
|
-
if (error instanceof LintValidationError) {
|
|
53700
|
-
consola.error(`Lint failed: ${error.message}`);
|
|
53701
|
-
process.exitCode = 1;
|
|
53702
|
-
return;
|
|
53703
|
-
}
|
|
53704
|
-
throw error;
|
|
53705
|
-
}
|
|
53706
|
-
const { outputs, hasErrors } = result;
|
|
53707
|
-
let totalWarnings = 0;
|
|
53708
|
-
for (const output of outputs){
|
|
53709
|
-
totalWarnings += output.summary.totalWarnings;
|
|
53710
|
-
}
|
|
53711
|
-
const targetLabels = {
|
|
53712
|
-
skill: "skill(s)",
|
|
53713
|
-
agent: "agent(s)",
|
|
53714
|
-
hook: "hook config(s)",
|
|
53715
|
-
instruction: "instruction file(s)"
|
|
53716
|
-
};
|
|
53717
|
-
if (format !== "human") {
|
|
53718
|
-
const formatter = createFormatter(format);
|
|
53719
|
-
const formatted = formatter.format(outputs);
|
|
53720
|
-
if (formatted) {
|
|
53721
|
-
process.stdout.write(`${formatted}\n`);
|
|
53722
|
-
}
|
|
53723
|
-
} else {
|
|
53724
|
-
for (const output of outputs){
|
|
53725
|
-
const label = targetLabels[output.target] ?? output.target;
|
|
53726
|
-
if (output.target === "instruction") {
|
|
53727
|
-
displayInstructionQuality(output);
|
|
53728
|
-
} else {
|
|
53729
|
-
displayLintOutput(output, label);
|
|
53730
|
-
}
|
|
53731
|
-
}
|
|
53732
|
-
}
|
|
53733
|
-
if (hasErrors) {
|
|
53734
|
-
process.exitCode = 1;
|
|
53735
|
-
if (format === "human") {
|
|
53736
|
-
const totalErrors = outputs.reduce((sum, o)=>sum + o.summary.totalErrors, 0);
|
|
53737
|
-
consola.error(`lint failed: ${totalErrors} error(s), ${totalWarnings} warning(s)`);
|
|
53738
|
-
}
|
|
53739
|
-
return;
|
|
53740
|
-
}
|
|
53741
|
-
if (format === "human") {
|
|
53742
|
-
if (totalWarnings > 0) {
|
|
53743
|
-
consola.warn(`Lint passed with ${totalWarnings} warning(s)`);
|
|
53744
|
-
} else {
|
|
53745
|
-
const targets = outputs.map((o)=>targetLabels[o.target] ?? o.target).join(", ");
|
|
53746
|
-
consola.success(`All ${targets} passed lint checks`);
|
|
53747
|
-
}
|
|
53748
|
-
}
|
|
53749
|
-
}
|
|
54447
|
+
args: lintArgs,
|
|
54448
|
+
run: runLintCommand
|
|
53750
54449
|
});
|
|
53751
54450
|
}
|
|
53752
54451
|
|
|
@@ -63012,4 +63711,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
|
|
|
63012
63711
|
// module factories are used so entry inlining is disabled
|
|
63013
63712
|
// startup
|
|
63014
63713
|
// Load entry module and return exports
|
|
63015
|
-
var __webpack_exports__ = __webpack_require__(
|
|
63714
|
+
var __webpack_exports__ = __webpack_require__(4140);
|