@lousy-agents/cli 2.10.1 → 3.0.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 +312 -65
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +50 -7
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32630,6 +32630,15 @@ const RulesetSchema = schemas_object({
|
|
|
32630
32630
|
enforcement: schemas_string(),
|
|
32631
32631
|
rules: schemas_array(RulesetRuleSchema).optional()
|
|
32632
32632
|
});
|
|
32633
|
+
const RepoSecuritySchema = schemas_object({
|
|
32634
|
+
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
32635
|
+
security_and_analysis: schemas_object({
|
|
32636
|
+
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
32637
|
+
advanced_security: schemas_object({
|
|
32638
|
+
status: schemas_string()
|
|
32639
|
+
})
|
|
32640
|
+
}).optional()
|
|
32641
|
+
});
|
|
32633
32642
|
/**
|
|
32634
32643
|
* Parses a GitHub remote URL to extract owner and repo name
|
|
32635
32644
|
*/ function parseRepoFromRemoteUrl(remoteUrl) {
|
|
@@ -32704,6 +32713,24 @@ function defaultExec(command, args, options) {
|
|
|
32704
32713
|
return null;
|
|
32705
32714
|
}
|
|
32706
32715
|
}
|
|
32716
|
+
async hasAdvancedSecurity(owner, repo) {
|
|
32717
|
+
if (!this.octokit) {
|
|
32718
|
+
return false;
|
|
32719
|
+
}
|
|
32720
|
+
try {
|
|
32721
|
+
const { data } = await this.octokit.rest.repos.get({
|
|
32722
|
+
owner,
|
|
32723
|
+
repo
|
|
32724
|
+
});
|
|
32725
|
+
const parsed = RepoSecuritySchema.safeParse(data);
|
|
32726
|
+
if (!parsed.success) {
|
|
32727
|
+
return false;
|
|
32728
|
+
}
|
|
32729
|
+
return parsed.data.security_and_analysis?.advanced_security?.status === "enabled";
|
|
32730
|
+
} catch {
|
|
32731
|
+
return false;
|
|
32732
|
+
}
|
|
32733
|
+
}
|
|
32707
32734
|
async listRulesets(owner, repo) {
|
|
32708
32735
|
if (!this.octokit) {
|
|
32709
32736
|
throw new Error("Not authenticated");
|
|
@@ -33671,8 +33698,44 @@ function isCopilotCodeScanningRule(rule) {
|
|
|
33671
33698
|
return findCopilotRuleset(rulesets) !== undefined;
|
|
33672
33699
|
}
|
|
33673
33700
|
/**
|
|
33674
|
-
* Builds a ruleset payload for enabling Copilot code review
|
|
33675
|
-
|
|
33701
|
+
* Builds a ruleset payload for enabling Copilot code review.
|
|
33702
|
+
* Includes code_scanning rules configured with CodeQL and Copilot Autofix when GitHub Advanced Security is enabled.
|
|
33703
|
+
*/ function buildCopilotReviewRulesetPayload(options) {
|
|
33704
|
+
const rules = [
|
|
33705
|
+
{
|
|
33706
|
+
type: "copilot_code_review",
|
|
33707
|
+
parameters: {
|
|
33708
|
+
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33709
|
+
review_on_push: true,
|
|
33710
|
+
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33711
|
+
review_draft_pull_requests: true
|
|
33712
|
+
}
|
|
33713
|
+
}
|
|
33714
|
+
];
|
|
33715
|
+
if (options.advancedSecurityEnabled) {
|
|
33716
|
+
rules.push({
|
|
33717
|
+
type: "code_scanning",
|
|
33718
|
+
parameters: {
|
|
33719
|
+
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33720
|
+
code_scanning_tools: [
|
|
33721
|
+
{
|
|
33722
|
+
tool: "CodeQL",
|
|
33723
|
+
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33724
|
+
security_alerts_threshold: "high_or_higher",
|
|
33725
|
+
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33726
|
+
alerts_threshold: "errors"
|
|
33727
|
+
},
|
|
33728
|
+
{
|
|
33729
|
+
tool: "Copilot Autofix",
|
|
33730
|
+
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33731
|
+
security_alerts_threshold: "high_or_higher",
|
|
33732
|
+
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33733
|
+
alerts_threshold: "errors"
|
|
33734
|
+
}
|
|
33735
|
+
]
|
|
33736
|
+
}
|
|
33737
|
+
});
|
|
33738
|
+
}
|
|
33676
33739
|
return {
|
|
33677
33740
|
name: "Copilot Code Review",
|
|
33678
33741
|
enforcement: "active",
|
|
@@ -33688,32 +33751,7 @@ function isCopilotCodeScanningRule(rule) {
|
|
|
33688
33751
|
exclude: []
|
|
33689
33752
|
}
|
|
33690
33753
|
},
|
|
33691
|
-
rules
|
|
33692
|
-
{
|
|
33693
|
-
type: "copilot_code_review",
|
|
33694
|
-
parameters: {
|
|
33695
|
-
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33696
|
-
review_on_push: true,
|
|
33697
|
-
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33698
|
-
review_draft_pull_requests: true
|
|
33699
|
-
}
|
|
33700
|
-
},
|
|
33701
|
-
{
|
|
33702
|
-
type: "code_scanning",
|
|
33703
|
-
parameters: {
|
|
33704
|
-
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33705
|
-
code_scanning_tools: [
|
|
33706
|
-
{
|
|
33707
|
-
tool: "Copilot Autofix",
|
|
33708
|
-
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33709
|
-
security_alerts_threshold: "high_or_higher",
|
|
33710
|
-
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
33711
|
-
alerts_threshold: "errors"
|
|
33712
|
-
}
|
|
33713
|
-
]
|
|
33714
|
-
}
|
|
33715
|
-
}
|
|
33716
|
-
]
|
|
33754
|
+
rules
|
|
33717
33755
|
};
|
|
33718
33756
|
}
|
|
33719
33757
|
/**
|
|
@@ -34683,7 +34721,10 @@ async function checkAndPromptRuleset(rulesetGateway, targetDir, prompt) {
|
|
|
34683
34721
|
return;
|
|
34684
34722
|
}
|
|
34685
34723
|
try {
|
|
34686
|
-
const
|
|
34724
|
+
const advancedSecurityEnabled = await rulesetGateway.hasAdvancedSecurity(repoInfo.owner, repoInfo.repo);
|
|
34725
|
+
const payload = buildCopilotReviewRulesetPayload({
|
|
34726
|
+
advancedSecurityEnabled
|
|
34727
|
+
});
|
|
34687
34728
|
await rulesetGateway.createRuleset(repoInfo.owner, repoInfo.repo, payload);
|
|
34688
34729
|
consola.success(`Created Copilot PR review ruleset: "${payload.name}"`);
|
|
34689
34730
|
} catch (error) {
|
|
@@ -55118,6 +55159,102 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55118
55159
|
return new RemarkMarkdownAstGateway();
|
|
55119
55160
|
}
|
|
55120
55161
|
|
|
55162
|
+
;// CONCATENATED MODULE: ./src/entities/lint-rules.ts
|
|
55163
|
+
/**
|
|
55164
|
+
* Lint rule registry entity.
|
|
55165
|
+
* Defines all known lint rule IDs with their default severities, organized by target.
|
|
55166
|
+
*/ /** Valid severity values for rule configuration */ /** Default severity levels for all known lint rules */ const DEFAULT_LINT_RULES = {
|
|
55167
|
+
agents: {
|
|
55168
|
+
"agent/missing-frontmatter": "error",
|
|
55169
|
+
"agent/invalid-frontmatter": "error",
|
|
55170
|
+
"agent/missing-name": "error",
|
|
55171
|
+
"agent/invalid-name-format": "error",
|
|
55172
|
+
"agent/name-mismatch": "error",
|
|
55173
|
+
"agent/missing-description": "error",
|
|
55174
|
+
"agent/invalid-description": "error",
|
|
55175
|
+
"agent/invalid-field": "warn"
|
|
55176
|
+
},
|
|
55177
|
+
instructions: {
|
|
55178
|
+
"instruction/parse-error": "warn",
|
|
55179
|
+
"instruction/command-not-in-code-block": "warn",
|
|
55180
|
+
"instruction/command-outside-section": "warn",
|
|
55181
|
+
"instruction/missing-error-handling": "warn"
|
|
55182
|
+
},
|
|
55183
|
+
skills: {
|
|
55184
|
+
"skill/invalid-frontmatter": "error",
|
|
55185
|
+
"skill/missing-frontmatter": "error",
|
|
55186
|
+
"skill/missing-name": "error",
|
|
55187
|
+
"skill/invalid-name-format": "error",
|
|
55188
|
+
"skill/name-mismatch": "error",
|
|
55189
|
+
"skill/missing-description": "error",
|
|
55190
|
+
"skill/invalid-description": "error",
|
|
55191
|
+
"skill/missing-allowed-tools": "warn"
|
|
55192
|
+
}
|
|
55193
|
+
};
|
|
55194
|
+
|
|
55195
|
+
;// CONCATENATED MODULE: ./src/lib/lint-config.ts
|
|
55196
|
+
/**
|
|
55197
|
+
* Lint configuration loader.
|
|
55198
|
+
* Loads lint rule severity overrides from c12 config and merges with defaults.
|
|
55199
|
+
*/
|
|
55200
|
+
|
|
55201
|
+
|
|
55202
|
+
/** Zod schema for a rule config map: rule IDs validated with regex to prevent prototype pollution */ const RuleConfigMapSchema = record(schemas_string().regex(/^[a-z]+\/[a-z]+(?:-[a-z]+)*$/), schemas_enum([
|
|
55203
|
+
"error",
|
|
55204
|
+
"warn",
|
|
55205
|
+
"off"
|
|
55206
|
+
]));
|
|
55207
|
+
/** Zod schema for the lint.rules section of the config */ const LintRulesConfigSchema = schemas_object({
|
|
55208
|
+
agents: RuleConfigMapSchema.optional(),
|
|
55209
|
+
instructions: RuleConfigMapSchema.optional(),
|
|
55210
|
+
skills: RuleConfigMapSchema.optional()
|
|
55211
|
+
});
|
|
55212
|
+
/** Zod schema for the lint section of the config */ const LintConfigSchema = schemas_object({
|
|
55213
|
+
lint: schemas_object({
|
|
55214
|
+
rules: LintRulesConfigSchema.optional()
|
|
55215
|
+
}).optional()
|
|
55216
|
+
});
|
|
55217
|
+
/**
|
|
55218
|
+
* Merges user overrides with defaults for a single target.
|
|
55219
|
+
* Only known rule IDs (present in defaults) are applied; unknown IDs are discarded.
|
|
55220
|
+
*/ function mergeTargetRules(defaults, overrides) {
|
|
55221
|
+
if (!overrides) {
|
|
55222
|
+
return defaults;
|
|
55223
|
+
}
|
|
55224
|
+
const merged = {
|
|
55225
|
+
...defaults
|
|
55226
|
+
};
|
|
55227
|
+
for (const [ruleId, severity] of Object.entries(overrides)){
|
|
55228
|
+
if (Object.hasOwn(defaults, ruleId)) {
|
|
55229
|
+
merged[ruleId] = severity;
|
|
55230
|
+
}
|
|
55231
|
+
}
|
|
55232
|
+
return merged;
|
|
55233
|
+
}
|
|
55234
|
+
/**
|
|
55235
|
+
* Loads lint configuration from the target directory using c12.
|
|
55236
|
+
* Merges user overrides with default rule severities.
|
|
55237
|
+
* Throws on config load failures (syntax errors, permission denied, validation errors).
|
|
55238
|
+
*/ async function loadLintConfig(targetDir) {
|
|
55239
|
+
const { config } = await loadConfig({
|
|
55240
|
+
name: "lousy-agents",
|
|
55241
|
+
cwd: targetDir
|
|
55242
|
+
});
|
|
55243
|
+
if (!config) {
|
|
55244
|
+
return DEFAULT_LINT_RULES;
|
|
55245
|
+
}
|
|
55246
|
+
const parsed = LintConfigSchema.parse(config);
|
|
55247
|
+
const rules = parsed.lint?.rules;
|
|
55248
|
+
if (!rules) {
|
|
55249
|
+
return DEFAULT_LINT_RULES;
|
|
55250
|
+
}
|
|
55251
|
+
return {
|
|
55252
|
+
agents: mergeTargetRules(DEFAULT_LINT_RULES.agents, rules.agents),
|
|
55253
|
+
instructions: mergeTargetRules(DEFAULT_LINT_RULES.instructions, rules.instructions),
|
|
55254
|
+
skills: mergeTargetRules(DEFAULT_LINT_RULES.skills, rules.skills)
|
|
55255
|
+
};
|
|
55256
|
+
}
|
|
55257
|
+
|
|
55121
55258
|
;// CONCATENATED MODULE: ./src/entities/instruction-quality.ts
|
|
55122
55259
|
/**
|
|
55123
55260
|
* Core domain entities for instruction quality analysis.
|
|
@@ -55178,7 +55315,9 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55178
55315
|
commandScores: [],
|
|
55179
55316
|
overallQualityScore: 0,
|
|
55180
55317
|
suggestions: [
|
|
55181
|
-
|
|
55318
|
+
{
|
|
55319
|
+
message: "No agent instruction files found. Supported formats: .github/copilot-instructions.md, .github/instructions/*.md, .github/agents/*.md, AGENTS.md, CLAUDE.md"
|
|
55320
|
+
}
|
|
55182
55321
|
],
|
|
55183
55322
|
parsingErrors: []
|
|
55184
55323
|
},
|
|
@@ -55245,7 +55384,10 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55245
55384
|
const suggestions = this.generateSuggestions(commandScores);
|
|
55246
55385
|
if (parsingErrors.length > 0) {
|
|
55247
55386
|
const skippedFiles = parsingErrors.map((pe)=>pe.filePath).join(", ");
|
|
55248
|
-
suggestions.push(
|
|
55387
|
+
suggestions.push({
|
|
55388
|
+
message: `${parsingErrors.length} file(s) could not be parsed and were skipped: ${skippedFiles}. Analysis may be incomplete.`,
|
|
55389
|
+
ruleId: "instruction/parse-error"
|
|
55390
|
+
});
|
|
55249
55391
|
}
|
|
55250
55392
|
return {
|
|
55251
55393
|
result: {
|
|
@@ -55459,22 +55601,33 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55459
55601
|
const lowStructural = commandScores.filter((s)=>s.structuralContext === 0 && s.bestSourceFile !== "");
|
|
55460
55602
|
if (lowStructural.length > 0) {
|
|
55461
55603
|
const names = lowStructural.map((s)=>s.commandName).join(", ");
|
|
55462
|
-
suggestions.push(
|
|
55604
|
+
suggestions.push({
|
|
55605
|
+
message: `Commands not under a dedicated section: ${names}. Add a heading like "## Validation" or "## Feedback Loop" above these commands.`,
|
|
55606
|
+
ruleId: "instruction/command-outside-section"
|
|
55607
|
+
});
|
|
55463
55608
|
}
|
|
55464
55609
|
const lowExecution = commandScores.filter((s)=>s.executionClarity === 0 && s.bestSourceFile !== "");
|
|
55465
55610
|
if (lowExecution.length > 0) {
|
|
55466
55611
|
const names = lowExecution.map((s)=>s.commandName).join(", ");
|
|
55467
|
-
suggestions.push(
|
|
55612
|
+
suggestions.push({
|
|
55613
|
+
message: `Commands not in code blocks: ${names}. Document these commands in fenced code blocks for clarity.`,
|
|
55614
|
+
ruleId: "instruction/command-not-in-code-block"
|
|
55615
|
+
});
|
|
55468
55616
|
}
|
|
55469
55617
|
const lowLoop = commandScores.filter((s)=>s.loopCompleteness === 0 && s.executionClarity === 1 && s.bestSourceFile !== "");
|
|
55470
55618
|
if (lowLoop.length > 0) {
|
|
55471
55619
|
const names = lowLoop.map((s)=>s.commandName).join(", ");
|
|
55472
|
-
suggestions.push(
|
|
55620
|
+
suggestions.push({
|
|
55621
|
+
message: `Commands missing error handling guidance: ${names}. Add instructions for what to do if the command fails.`,
|
|
55622
|
+
ruleId: "instruction/missing-error-handling"
|
|
55623
|
+
});
|
|
55473
55624
|
}
|
|
55474
55625
|
const notFound = commandScores.filter((s)=>s.bestSourceFile === "");
|
|
55475
55626
|
if (notFound.length > 0) {
|
|
55476
55627
|
const names = notFound.map((s)=>s.commandName).join(", ");
|
|
55477
|
-
suggestions.push(
|
|
55628
|
+
suggestions.push({
|
|
55629
|
+
message: `Commands not found in any instruction file: ${names}. Document these feedback loop commands in your instruction files.`
|
|
55630
|
+
});
|
|
55478
55631
|
}
|
|
55479
55632
|
return suggestions;
|
|
55480
55633
|
}
|
|
@@ -55612,14 +55765,8 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55612
55765
|
}
|
|
55613
55766
|
|
|
55614
55767
|
;// CONCATENATED MODULE: ./src/use-cases/lint-skill-frontmatter.ts
|
|
55615
|
-
|
|
55616
|
-
|
|
55617
|
-
* Validates required and recommended fields, name format, and directory naming.
|
|
55618
|
-
*/
|
|
55619
|
-
/**
|
|
55620
|
-
* Zod schema for validating agent skill frontmatter.
|
|
55621
|
-
* Based on the agentskills.io specification.
|
|
55622
|
-
*/ const AgentSkillFrontmatterSchema = schemas_object({
|
|
55768
|
+
|
|
55769
|
+
const AgentSkillFrontmatterSchema = schemas_object({
|
|
55623
55770
|
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. It cannot start/end with a hyphen or contain consecutive hyphens."),
|
|
55624
55771
|
description: schemas_string().min(1, "Description is required").max(1024, "Description must be 1024 characters or fewer").refine((s)=>s.trim().length > 0, {
|
|
55625
55772
|
message: "Description cannot be empty or whitespace-only"
|
|
@@ -55629,14 +55776,13 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55629
55776
|
metadata: record(schemas_string(), schemas_string()).optional(),
|
|
55630
55777
|
"allowed-tools": schemas_string().optional()
|
|
55631
55778
|
});
|
|
55632
|
-
|
|
55633
|
-
* Recommended (optional) fields that produce warnings when missing.
|
|
55634
|
-
*/ const RECOMMENDED_FIELDS = [
|
|
55779
|
+
const RECOMMENDED_FIELDS = [
|
|
55635
55780
|
"allowed-tools"
|
|
55636
55781
|
];
|
|
55637
|
-
|
|
55638
|
-
|
|
55639
|
-
|
|
55782
|
+
const RECOMMENDED_FIELD_RULE_IDS = {
|
|
55783
|
+
"allowed-tools": "skill/missing-allowed-tools"
|
|
55784
|
+
};
|
|
55785
|
+
class LintSkillFrontmatterUseCase {
|
|
55640
55786
|
gateway;
|
|
55641
55787
|
constructor(gateway){
|
|
55642
55788
|
this.gateway = gateway;
|
|
@@ -55672,16 +55818,20 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55672
55818
|
diagnostics.push({
|
|
55673
55819
|
line: 1,
|
|
55674
55820
|
severity: "error",
|
|
55675
|
-
message: errorMessage
|
|
55821
|
+
message: errorMessage,
|
|
55822
|
+
ruleId: "skill/invalid-frontmatter"
|
|
55676
55823
|
});
|
|
55677
55824
|
}
|
|
55678
55825
|
if (!parsed) {
|
|
55679
55826
|
if (diagnostics.length === 0) {
|
|
55680
|
-
const
|
|
55827
|
+
const hasDelimiters = hasFrontmatterDelimiters(content);
|
|
55828
|
+
const message = hasDelimiters ? "Invalid YAML frontmatter. The content between --- delimiters could not be parsed as valid YAML." : "Missing YAML frontmatter. Skill files must begin with --- delimited YAML frontmatter.";
|
|
55829
|
+
const ruleId = hasDelimiters ? "skill/invalid-frontmatter" : "skill/missing-frontmatter";
|
|
55681
55830
|
diagnostics.push({
|
|
55682
55831
|
line: 1,
|
|
55683
55832
|
severity: "error",
|
|
55684
|
-
message
|
|
55833
|
+
message,
|
|
55834
|
+
ruleId
|
|
55685
55835
|
});
|
|
55686
55836
|
}
|
|
55687
55837
|
return {
|
|
@@ -55702,47 +55852,58 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55702
55852
|
}
|
|
55703
55853
|
validateFrontmatter(parsed, parentDirName) {
|
|
55704
55854
|
const diagnostics = [];
|
|
55705
|
-
// Validate against Zod schema
|
|
55706
55855
|
const result = AgentSkillFrontmatterSchema.safeParse(parsed.data);
|
|
55707
55856
|
if (!result.success) {
|
|
55708
55857
|
for (const issue of result.error.issues){
|
|
55709
55858
|
const fieldName = issue.path[0]?.toString();
|
|
55710
55859
|
const line = fieldName ? parsed.fieldLines.get(fieldName) ?? parsed.frontmatterStartLine : parsed.frontmatterStartLine;
|
|
55860
|
+
const ruleId = this.getRuleIdForField(fieldName, issue.code, parsed.data);
|
|
55711
55861
|
diagnostics.push({
|
|
55712
55862
|
line,
|
|
55713
55863
|
severity: "error",
|
|
55714
55864
|
message: issue.message,
|
|
55715
|
-
field: fieldName
|
|
55865
|
+
field: fieldName,
|
|
55866
|
+
ruleId
|
|
55716
55867
|
});
|
|
55717
55868
|
}
|
|
55718
55869
|
}
|
|
55719
|
-
// Check name matches parent directory
|
|
55720
55870
|
if (result.success && result.data.name !== parentDirName) {
|
|
55721
55871
|
const nameLine = parsed.fieldLines.get("name") ?? parsed.frontmatterStartLine;
|
|
55722
55872
|
diagnostics.push({
|
|
55723
55873
|
line: nameLine,
|
|
55724
55874
|
severity: "error",
|
|
55725
55875
|
message: `Frontmatter name '${result.data.name}' must match parent directory name '${parentDirName}'`,
|
|
55726
|
-
field: "name"
|
|
55876
|
+
field: "name",
|
|
55877
|
+
ruleId: "skill/name-mismatch"
|
|
55727
55878
|
});
|
|
55728
55879
|
}
|
|
55729
|
-
// Check recommended fields
|
|
55730
55880
|
for (const field of RECOMMENDED_FIELDS){
|
|
55731
55881
|
if (parsed.data[field] === undefined) {
|
|
55732
55882
|
diagnostics.push({
|
|
55733
55883
|
line: parsed.frontmatterStartLine,
|
|
55734
55884
|
severity: "warning",
|
|
55735
55885
|
message: `Recommended field '${field}' is missing`,
|
|
55736
|
-
field
|
|
55886
|
+
field,
|
|
55887
|
+
ruleId: RECOMMENDED_FIELD_RULE_IDS[field]
|
|
55737
55888
|
});
|
|
55738
55889
|
}
|
|
55739
55890
|
}
|
|
55740
55891
|
return diagnostics;
|
|
55741
55892
|
}
|
|
55893
|
+
getRuleIdForField(fieldName, issueCode, inputData) {
|
|
55894
|
+
// Check the actual input data for field presence rather than
|
|
55895
|
+
// relying on Zod message text which can change across versions.
|
|
55896
|
+
const isMissing = issueCode === "invalid_type" && (fieldName === undefined || !Object.hasOwn(inputData, fieldName));
|
|
55897
|
+
if (fieldName === "name") {
|
|
55898
|
+
return isMissing ? "skill/missing-name" : "skill/invalid-name-format";
|
|
55899
|
+
}
|
|
55900
|
+
if (fieldName === "description") {
|
|
55901
|
+
return isMissing ? "skill/missing-description" : "skill/invalid-description";
|
|
55902
|
+
}
|
|
55903
|
+
return "skill/invalid-frontmatter";
|
|
55904
|
+
}
|
|
55742
55905
|
}
|
|
55743
|
-
|
|
55744
|
-
* Checks whether content has opening and closing --- frontmatter delimiters.
|
|
55745
|
-
*/ function hasFrontmatterDelimiters(content) {
|
|
55906
|
+
function hasFrontmatterDelimiters(content) {
|
|
55746
55907
|
const lines = content.split("\n");
|
|
55747
55908
|
if (lines[0]?.trim() !== "---") {
|
|
55748
55909
|
return false;
|
|
@@ -55772,6 +55933,7 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55772
55933
|
|
|
55773
55934
|
|
|
55774
55935
|
|
|
55936
|
+
|
|
55775
55937
|
/** Schema for validating target directory */ const TargetDirSchema = schemas_string().min(1, "Target directory is required");
|
|
55776
55938
|
/**
|
|
55777
55939
|
* Validates the target directory.
|
|
@@ -55796,6 +55958,7 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55796
55958
|
severity: d.severity,
|
|
55797
55959
|
message: d.message,
|
|
55798
55960
|
field: d.field,
|
|
55961
|
+
ruleId: d.ruleId,
|
|
55799
55962
|
target: "skill"
|
|
55800
55963
|
});
|
|
55801
55964
|
}
|
|
@@ -55932,8 +56095,80 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55932
56095
|
consola.info(`Overall instruction quality score: ${result.overallQualityScore}%`);
|
|
55933
56096
|
}
|
|
55934
56097
|
for (const suggestion of result.suggestions){
|
|
55935
|
-
consola.warn(suggestion);
|
|
56098
|
+
consola.warn(suggestion.message);
|
|
56099
|
+
}
|
|
56100
|
+
}
|
|
56101
|
+
/** Maps a lint target to its config key */ const TARGET_TO_CONFIG_KEY = {
|
|
56102
|
+
skill: "skills",
|
|
56103
|
+
agent: "agents",
|
|
56104
|
+
instruction: "instructions"
|
|
56105
|
+
};
|
|
56106
|
+
/**
|
|
56107
|
+
* Maps config-facing severity to diagnostic-facing severity.
|
|
56108
|
+
* "warn" → "warning", "error" → "error", "off" → null (drop).
|
|
56109
|
+
*/ function lint_mapSeverity(configSeverity) {
|
|
56110
|
+
if (configSeverity === "off") {
|
|
56111
|
+
return null;
|
|
56112
|
+
}
|
|
56113
|
+
if (configSeverity === "warn") {
|
|
56114
|
+
return "warning";
|
|
55936
56115
|
}
|
|
56116
|
+
return configSeverity;
|
|
56117
|
+
}
|
|
56118
|
+
/**
|
|
56119
|
+
* Filters instruction suggestions based on rule severity configuration.
|
|
56120
|
+
* Drops suggestions whose corresponding rule is "off".
|
|
56121
|
+
* Suggestions without a ruleId pass through unchanged.
|
|
56122
|
+
*/ function filterInstructionSuggestions(suggestions, rules) {
|
|
56123
|
+
return suggestions.filter((suggestion)=>{
|
|
56124
|
+
if (!suggestion.ruleId) {
|
|
56125
|
+
return true;
|
|
56126
|
+
}
|
|
56127
|
+
return rules[suggestion.ruleId] !== "off";
|
|
56128
|
+
});
|
|
56129
|
+
}
|
|
56130
|
+
/**
|
|
56131
|
+
* Applies severity filtering to a LintOutput based on rule configuration.
|
|
56132
|
+
* Drops diagnostics for "off" rules, remaps severity for "warn"/"error" rules.
|
|
56133
|
+
* Diagnostics without a ruleId pass through unchanged.
|
|
56134
|
+
* For instruction targets, also filters qualityResult.suggestions.
|
|
56135
|
+
*/ function applySeverityFilter(output, rulesConfig) {
|
|
56136
|
+
const configKey = TARGET_TO_CONFIG_KEY[output.target];
|
|
56137
|
+
const targetRules = rulesConfig[configKey];
|
|
56138
|
+
const filteredDiagnostics = [];
|
|
56139
|
+
for (const diagnostic of output.diagnostics){
|
|
56140
|
+
const configuredSeverity = diagnostic.ruleId ? targetRules[diagnostic.ruleId] : undefined;
|
|
56141
|
+
if (!configuredSeverity) {
|
|
56142
|
+
filteredDiagnostics.push(diagnostic);
|
|
56143
|
+
continue;
|
|
56144
|
+
}
|
|
56145
|
+
const mappedSeverity = lint_mapSeverity(configuredSeverity);
|
|
56146
|
+
if (mappedSeverity === null) {
|
|
56147
|
+
continue;
|
|
56148
|
+
}
|
|
56149
|
+
filteredDiagnostics.push({
|
|
56150
|
+
...diagnostic,
|
|
56151
|
+
severity: mappedSeverity
|
|
56152
|
+
});
|
|
56153
|
+
}
|
|
56154
|
+
const totalErrors = filteredDiagnostics.filter((d)=>d.severity === "error").length;
|
|
56155
|
+
const totalWarnings = filteredDiagnostics.filter((d)=>d.severity === "warning").length;
|
|
56156
|
+
const totalInfos = filteredDiagnostics.filter((d)=>d.severity === "info").length;
|
|
56157
|
+
const filteredQualityResult = output.qualityResult && configKey === "instructions" ? {
|
|
56158
|
+
...output.qualityResult,
|
|
56159
|
+
suggestions: filterInstructionSuggestions(output.qualityResult.suggestions, targetRules)
|
|
56160
|
+
} : output.qualityResult;
|
|
56161
|
+
return {
|
|
56162
|
+
...output,
|
|
56163
|
+
diagnostics: filteredDiagnostics,
|
|
56164
|
+
qualityResult: filteredQualityResult,
|
|
56165
|
+
summary: {
|
|
56166
|
+
...output.summary,
|
|
56167
|
+
totalErrors,
|
|
56168
|
+
totalWarnings,
|
|
56169
|
+
totalInfos
|
|
56170
|
+
}
|
|
56171
|
+
};
|
|
55937
56172
|
}
|
|
55938
56173
|
/**
|
|
55939
56174
|
* The `lint` command for validating agent skills, custom agents, and instruction files.
|
|
@@ -55967,6 +56202,15 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55967
56202
|
run: async (context)=>{
|
|
55968
56203
|
const rawTargetDir = typeof context.data?.targetDir === "string" ? context.data.targetDir : process.cwd();
|
|
55969
56204
|
const targetDir = validateTargetDir(rawTargetDir);
|
|
56205
|
+
let rulesConfig;
|
|
56206
|
+
try {
|
|
56207
|
+
rulesConfig = await loadLintConfig(targetDir);
|
|
56208
|
+
} catch (error) {
|
|
56209
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56210
|
+
consola.error(`Failed to load lint configuration: ${message}`);
|
|
56211
|
+
process.exitCode = 1;
|
|
56212
|
+
return;
|
|
56213
|
+
}
|
|
55970
56214
|
const lintSkillsFlag = context.args?.skills === true || context.data?.skills === true;
|
|
55971
56215
|
const lintAgentsFlag = context.args?.agents === true || context.data?.agents === true;
|
|
55972
56216
|
const lintInstructionsFlag = context.args?.instructions === true || context.data?.instructions === true;
|
|
@@ -55981,19 +56225,22 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55981
56225
|
let totalWarnings = 0;
|
|
55982
56226
|
const allOutputs = [];
|
|
55983
56227
|
if (noFlagProvided || lintSkillsFlag) {
|
|
55984
|
-
const
|
|
56228
|
+
const rawOutput = await lintSkills(targetDir);
|
|
56229
|
+
const skillOutput = applySeverityFilter(rawOutput, rulesConfig);
|
|
55985
56230
|
allOutputs.push(skillOutput);
|
|
55986
56231
|
totalErrors += skillOutput.summary.totalErrors;
|
|
55987
56232
|
totalWarnings += skillOutput.summary.totalWarnings;
|
|
55988
56233
|
}
|
|
55989
56234
|
if (noFlagProvided || lintAgentsFlag) {
|
|
55990
|
-
const
|
|
56235
|
+
const rawOutput = await lintAgents(targetDir);
|
|
56236
|
+
const agentOutput = applySeverityFilter(rawOutput, rulesConfig);
|
|
55991
56237
|
allOutputs.push(agentOutput);
|
|
55992
56238
|
totalErrors += agentOutput.summary.totalErrors;
|
|
55993
56239
|
totalWarnings += agentOutput.summary.totalWarnings;
|
|
55994
56240
|
}
|
|
55995
56241
|
if (noFlagProvided || lintInstructionsFlag) {
|
|
55996
|
-
const
|
|
56242
|
+
const rawOutput = await lintInstructions(targetDir);
|
|
56243
|
+
const instructionOutput = applySeverityFilter(rawOutput, rulesConfig);
|
|
55997
56244
|
allOutputs.push(instructionOutput);
|
|
55998
56245
|
totalErrors += instructionOutput.summary.totalErrors;
|
|
55999
56246
|
totalWarnings += instructionOutput.summary.totalWarnings;
|