@weareikko/eslint-plugin-skill 0.0.1 → 0.1.1
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/README.md +162 -2
- package/configs/check-refs.workflow.yml +25 -0
- package/configs/eslint.config.example.js +23 -0
- package/configs/lint-skills.workflow.yml +26 -0
- package/dist/dedup/analyze.d.ts +26 -0
- package/dist/dedup/analyze.d.ts.map +1 -0
- package/dist/dedup/cli.d.ts +10 -0
- package/dist/dedup/cli.d.ts.map +1 -0
- package/dist/dedup/cli.js +37 -0
- package/dist/dedup/cli.js.map +1 -0
- package/dist/dedup/discover.d.ts +16 -0
- package/dist/dedup/discover.d.ts.map +1 -0
- package/dist/dedup/hash.d.ts +17 -0
- package/dist/dedup/hash.d.ts.map +1 -0
- package/dist/dedup/index.d.ts +4 -0
- package/dist/dedup/index.d.ts.map +1 -0
- package/dist/dedup/index.js +2 -0
- package/dist/dedup/report.d.ts +6 -0
- package/dist/dedup/report.d.ts.map +1 -0
- package/dist/dedup/types.d.ts +92 -0
- package/dist/dedup/types.d.ts.map +1 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1236 -0
- package/dist/index.js.map +1 -0
- package/dist/report-ClPKWYJy.js +266 -0
- package/dist/report-ClPKWYJy.js.map +1 -0
- package/dist/rules/body-max-length.d.ts +16 -0
- package/dist/rules/body-max-length.d.ts.map +1 -0
- package/dist/rules/description-has-trigger.d.ts +16 -0
- package/dist/rules/description-has-trigger.d.ts.map +1 -0
- package/dist/rules/description-length.d.ts +16 -0
- package/dist/rules/description-length.d.ts.map +1 -0
- package/dist/rules/description-no-html.d.ts +13 -0
- package/dist/rules/description-no-html.d.ts.map +1 -0
- package/dist/rules/descriptive-reference-filenames.d.ts +15 -0
- package/dist/rules/descriptive-reference-filenames.d.ts.map +1 -0
- package/dist/rules/h1-matches-name.d.ts +13 -0
- package/dist/rules/h1-matches-name.d.ts.map +1 -0
- package/dist/rules/large-reference-toc.d.ts +16 -0
- package/dist/rules/large-reference-toc.d.ts.map +1 -0
- package/dist/rules/meta-prefers-dispatch-table.d.ts +19 -0
- package/dist/rules/meta-prefers-dispatch-table.d.ts.map +1 -0
- package/dist/rules/multi-domain-has-variants.d.ts +16 -0
- package/dist/rules/multi-domain-has-variants.d.ts.map +1 -0
- package/dist/rules/name-kebab-case.d.ts +10 -0
- package/dist/rules/name-kebab-case.d.ts.map +1 -0
- package/dist/rules/name-matches-dir.d.ts +10 -0
- package/dist/rules/name-matches-dir.d.ts.map +1 -0
- package/dist/rules/no-broken-relative-links.d.ts +14 -0
- package/dist/rules/no-broken-relative-links.d.ts.map +1 -0
- package/dist/rules/no-multiple-h1.d.ts +15 -0
- package/dist/rules/no-multiple-h1.d.ts.map +1 -0
- package/dist/rules/no-orphan-references.d.ts +15 -0
- package/dist/rules/no-orphan-references.d.ts.map +1 -0
- package/dist/rules/no-unknown-frontmatter-keys.d.ts +18 -0
- package/dist/rules/no-unknown-frontmatter-keys.d.ts.map +1 -0
- package/dist/rules/procedural-has-steps.d.ts +15 -0
- package/dist/rules/procedural-has-steps.d.ts.map +1 -0
- package/dist/rules/reference-has-content.d.ts +15 -0
- package/dist/rules/reference-has-content.d.ts.map +1 -0
- package/dist/rules/refs-one-level-deep.d.ts +15 -0
- package/dist/rules/refs-one-level-deep.d.ts.map +1 -0
- package/dist/rules/require-frontmatter.d.ts +11 -0
- package/dist/rules/require-frontmatter.d.ts.map +1 -0
- package/dist/rules/valid-allowed-tools.d.ts +15 -0
- package/dist/rules/valid-allowed-tools.d.ts.map +1 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/ast.d.ts +21 -0
- package/dist/utils/ast.d.ts.map +1 -0
- package/dist/utils/frontmatter.d.ts +24 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/kind.d.ts +41 -0
- package/dist/utils/kind.d.ts.map +1 -0
- package/dist/utils/links.d.ts +23 -0
- package/dist/utils/links.d.ts.map +1 -0
- package/dist/utils/references.d.ts +7 -0
- package/dist/utils/references.d.ts.map +1 -0
- package/dist/utils/skill-file.d.ts +32 -0
- package/dist/utils/skill-file.d.ts.map +1 -0
- package/dist/utils/skill-fs.d.ts +30 -0
- package/dist/utils/skill-fs.d.ts.map +1 -0
- package/docs/check-refs.md +60 -0
- package/docs/rules/body-max-length.md +23 -0
- package/docs/rules/description-has-trigger.md +28 -0
- package/docs/rules/description-length.md +27 -0
- package/docs/rules/description-no-html.md +22 -0
- package/docs/rules/descriptive-reference-filenames.md +26 -0
- package/docs/rules/h1-matches-name.md +36 -0
- package/docs/rules/large-reference-toc.md +26 -0
- package/docs/rules/meta-prefers-dispatch-table.md +30 -0
- package/docs/rules/multi-domain-has-variants.md +26 -0
- package/docs/rules/name-kebab-case.md +31 -0
- package/docs/rules/name-matches-dir.md +29 -0
- package/docs/rules/no-broken-relative-links.md +25 -0
- package/docs/rules/no-multiple-h1.md +31 -0
- package/docs/rules/no-orphan-references.md +25 -0
- package/docs/rules/no-unknown-frontmatter-keys.md +37 -0
- package/docs/rules/procedural-has-steps.md +43 -0
- package/docs/rules/reference-has-content.md +26 -0
- package/docs/rules/refs-one-level-deep.md +26 -0
- package/docs/rules/require-frontmatter.md +34 -0
- package/docs/rules/valid-allowed-tools.md +27 -0
- package/package.json +55 -3
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Require the body H1 to correspond to the skill `name`.
|
|
3
|
+
*
|
|
4
|
+
* The comparison is normalized so both `# my-skill` and `# My Skill` are
|
|
5
|
+
* accepted for `name: my-skill`.
|
|
6
|
+
*/
|
|
7
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
8
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
9
|
+
RuleOptions: [];
|
|
10
|
+
MessageIds: "mismatch";
|
|
11
|
+
}>;
|
|
12
|
+
export default rule;
|
|
13
|
+
//# sourceMappingURL=h1-matches-name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"h1-matches-name.d.ts","sourceRoot":"","sources":["../../src/rules/h1-matches-name.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAc/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;CACvB,CAiDA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Require large reference files to include a table of contents.
|
|
3
|
+
*
|
|
4
|
+
* Official guidance: reference files over ~300 lines should include a table of
|
|
5
|
+
* contents so Claude can navigate to the relevant section instead of reading
|
|
6
|
+
* (or partially reading) the whole file.
|
|
7
|
+
*/
|
|
8
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
9
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
10
|
+
RuleOptions: [{
|
|
11
|
+
maxLines?: number;
|
|
12
|
+
}?];
|
|
13
|
+
MessageIds: "missingToc";
|
|
14
|
+
}>;
|
|
15
|
+
export default rule;
|
|
16
|
+
//# sourceMappingURL=large-reference-toc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"large-reference-toc.d.ts","sourceRoot":"","sources":["../../src/rules/large-reference-toc.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAS/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,CAAC;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACtC,UAAU,EAAE,YAAY,CAAC;CACzB,CAyDA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Suggest a markdown table for a meta/router skill's dispatch list.
|
|
3
|
+
*
|
|
4
|
+
* Opt-in: only fires when the skill explicitly declares itself as
|
|
5
|
+
* `metadata.kind: meta`. A meta (router) skill exists to dispatch requests to
|
|
6
|
+
* other skills. A single-scenario empirical test (see
|
|
7
|
+
* docs/research/meta-dispatch-table-vs-prose.md) found a markdown table costs
|
|
8
|
+
* ~21% fewer tokens than equivalent prose for this kind of dispatch content,
|
|
9
|
+
* with no measured routing-accuracy difference — so this is a cost-only
|
|
10
|
+
* suggestion, not a correctness rule. Given that single-test evidence base,
|
|
11
|
+
* this rule is deliberately kept out of every preset.
|
|
12
|
+
*/
|
|
13
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
14
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
15
|
+
RuleOptions: [];
|
|
16
|
+
MessageIds: "preferTable";
|
|
17
|
+
}>;
|
|
18
|
+
export default rule;
|
|
19
|
+
//# sourceMappingURL=meta-prefers-dispatch-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta-prefers-dispatch-table.d.ts","sourceRoot":"","sources":["../../src/rules/meta-prefers-dispatch-table.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAU/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,aAAa,CAAC;CAC1B,CAqCA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Expect a multi-domain skill to carry at least two variant references.
|
|
3
|
+
*
|
|
4
|
+
* Opt-in: only fires when the skill explicitly declares itself as
|
|
5
|
+
* `metadata.kind: multi-domain`. The multi-domain pattern (e.g.
|
|
6
|
+
* `references/{aws,gcp,azure}.md`, with `SKILL.md` holding the selection
|
|
7
|
+
* logic) only earns its complexity when there is more than one variant to
|
|
8
|
+
* select between.
|
|
9
|
+
*/
|
|
10
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
11
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
12
|
+
RuleOptions: [];
|
|
13
|
+
MessageIds: "tooFewVariants";
|
|
14
|
+
}>;
|
|
15
|
+
export default rule;
|
|
16
|
+
//# sourceMappingURL=multi-domain-has-variants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-domain-has-variants.d.ts","sourceRoot":"","sources":["../../src/rules/multi-domain-has-variants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAY/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,gBAAgB,CAAC;CAC7B,CA6CA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Require the skill `name` to be letter-start kebab-case.
|
|
3
|
+
*/
|
|
4
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
5
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
6
|
+
RuleOptions: [];
|
|
7
|
+
MessageIds: "notKebab" | "tooLong" | "reservedWord";
|
|
8
|
+
}>;
|
|
9
|
+
export default rule;
|
|
10
|
+
//# sourceMappingURL=name-kebab-case.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name-kebab-case.d.ts","sourceRoot":"","sources":["../../src/rules/name-kebab-case.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAW/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,cAAc,CAAC;CACpD,CAgEA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Require the skill `name` to match its containing directory.
|
|
3
|
+
*/
|
|
4
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
5
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
6
|
+
RuleOptions: [];
|
|
7
|
+
MessageIds: "mismatch";
|
|
8
|
+
}>;
|
|
9
|
+
export default rule;
|
|
10
|
+
//# sourceMappingURL=name-matches-dir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name-matches-dir.d.ts","sourceRoot":"","sources":["../../src/rules/name-matches-dir.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAM/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;CACvB,CA+CA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Flag relative links/images whose target does not exist.
|
|
3
|
+
*
|
|
4
|
+
* Skills lean on referenced files for progressive disclosure; a dead relative
|
|
5
|
+
* link means the agent will try to open something that isn't there. Absolute
|
|
6
|
+
* URLs (`https:`, `mailto:`) and pure anchors (`#section`) are ignored.
|
|
7
|
+
*/
|
|
8
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
9
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
10
|
+
RuleOptions: [];
|
|
11
|
+
MessageIds: "broken";
|
|
12
|
+
}>;
|
|
13
|
+
export default rule;
|
|
14
|
+
//# sourceMappingURL=no-broken-relative-links.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-broken-relative-links.d.ts","sourceRoot":"","sources":["../../src/rules/no-broken-relative-links.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAM/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,QAAQ,CAAC;CACrB,CA8CA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Disallow more than one top-level H1 in the skill body.
|
|
3
|
+
*
|
|
4
|
+
* The standard skill format does not require an H1 — the official examples use
|
|
5
|
+
* `##`-first bodies — so this rule does not demand a title. But two `#` H1s in
|
|
6
|
+
* one body reads like separate documents merged together, so any H1 after the
|
|
7
|
+
* first is flagged.
|
|
8
|
+
*/
|
|
9
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
10
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
11
|
+
RuleOptions: [];
|
|
12
|
+
MessageIds: "multiple";
|
|
13
|
+
}>;
|
|
14
|
+
export default rule;
|
|
15
|
+
//# sourceMappingURL=no-multiple-h1.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-multiple-h1.d.ts","sourceRoot":"","sources":["../../src/rules/no-multiple-h1.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAM/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;CACvB,CAwCA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Flag reference files not linked from SKILL.md.
|
|
3
|
+
*
|
|
4
|
+
* A `.md` file sitting in `references/` that nothing in SKILL.md links to is
|
|
5
|
+
* dead weight: Claude is told to load reference files via the pointers in
|
|
6
|
+
* SKILL.md, so an unlinked one is never read. It's usually a leftover, a
|
|
7
|
+
* rename that broke the link, or a missing pointer.
|
|
8
|
+
*/
|
|
9
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
10
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
11
|
+
RuleOptions: [];
|
|
12
|
+
MessageIds: "orphan";
|
|
13
|
+
}>;
|
|
14
|
+
export default rule;
|
|
15
|
+
//# sourceMappingURL=no-orphan-references.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-orphan-references.d.ts","sourceRoot":"","sources":["../../src/rules/no-orphan-references.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAS/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,QAAQ,CAAC;CACrB,CA6CA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Flag unknown top-level frontmatter keys on a skill.
|
|
3
|
+
*
|
|
4
|
+
* The Agent Skills standard defines a fixed set of frontmatter fields
|
|
5
|
+
* (`name` + `description` required; a handful of optional ones). An unexpected
|
|
6
|
+
* key is almost always a typo (`descriptoin`) or a misplaced field, which the
|
|
7
|
+
* runtime silently ignores — so it never takes effect and is easy to miss.
|
|
8
|
+
* Extra keys can be allowed explicitly via the `allow` option.
|
|
9
|
+
*/
|
|
10
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
11
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
12
|
+
RuleOptions: [{
|
|
13
|
+
allow?: string[];
|
|
14
|
+
}?];
|
|
15
|
+
MessageIds: "unknownKey";
|
|
16
|
+
}>;
|
|
17
|
+
export default rule;
|
|
18
|
+
//# sourceMappingURL=no-unknown-frontmatter-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-unknown-frontmatter-keys.d.ts","sourceRoot":"","sources":["../../src/rules/no-unknown-frontmatter-keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAuB/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC,CAAC;IACrC,UAAU,EAAE,YAAY,CAAC;CACzB,CAuDA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Expect a procedural skill's body to contain ordered steps.
|
|
3
|
+
*
|
|
4
|
+
* Opt-in: only fires when the skill explicitly declares itself as
|
|
5
|
+
* `metadata.kind: procedural`. A procedural (task/workflow) skill exists to
|
|
6
|
+
* walk the agent through a sequence of actions; a declared procedural skill
|
|
7
|
+
* with no ordered list at all is either mis-declared or missing its steps.
|
|
8
|
+
*/
|
|
9
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
10
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
11
|
+
RuleOptions: [];
|
|
12
|
+
MessageIds: "noSteps";
|
|
13
|
+
}>;
|
|
14
|
+
export default rule;
|
|
15
|
+
//# sourceMappingURL=procedural-has-steps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procedural-has-steps.d.ts","sourceRoot":"","sources":["../../src/rules/procedural-has-steps.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAU/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,SAAS,CAAC;CACtB,CAuCA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Expect a reference skill to carry at least one reference file.
|
|
3
|
+
*
|
|
4
|
+
* Opt-in: only fires when the skill explicitly declares itself as
|
|
5
|
+
* `metadata.kind: reference`. A reference (domain-knowledge) skill exists to
|
|
6
|
+
* supply background material on demand; a declared reference skill with an
|
|
7
|
+
* empty or missing `references/` directory has nothing to supply.
|
|
8
|
+
*/
|
|
9
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
10
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
11
|
+
RuleOptions: [];
|
|
12
|
+
MessageIds: "noReferences";
|
|
13
|
+
}>;
|
|
14
|
+
export default rule;
|
|
15
|
+
//# sourceMappingURL=reference-has-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference-has-content.d.ts","sourceRoot":"","sources":["../../src/rules/reference-has-content.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAW/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,cAAc,CAAC;CAC3B,CAyCA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Require reference links to stay one level deep from SKILL.md.
|
|
3
|
+
*
|
|
4
|
+
* Official guidance: every reference file should link directly from SKILL.md
|
|
5
|
+
* and live at most one directory below it. Deeply nested reference chains are
|
|
6
|
+
* an anti-pattern — Claude may only partially read (e.g. `head -100`) files
|
|
7
|
+
* reached indirectly, so it can act on incomplete information.
|
|
8
|
+
*/
|
|
9
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
10
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
11
|
+
RuleOptions: [];
|
|
12
|
+
MessageIds: "tooDeep" | "escapes";
|
|
13
|
+
}>;
|
|
14
|
+
export default rule;
|
|
15
|
+
//# sourceMappingURL=refs-one-level-deep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refs-one-level-deep.d.ts","sourceRoot":"","sources":["../../src/rules/refs-one-level-deep.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAK/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,SAAS,GAAG,SAAS,CAAC;CAClC,CAiDA,CAAC;eAEa,IAAI"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Require `name` and `description` frontmatter on a skill.
|
|
3
|
+
*/
|
|
4
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
5
|
+
type MessageIds = "missingFrontmatter" | "invalidFrontmatter" | "missingKey" | "emptyKey";
|
|
6
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
7
|
+
RuleOptions: [];
|
|
8
|
+
MessageIds: MessageIds;
|
|
9
|
+
}>;
|
|
10
|
+
export default rule;
|
|
11
|
+
//# sourceMappingURL=require-frontmatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-frontmatter.d.ts","sourceRoot":"","sources":["../../src/rules/require-frontmatter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAQ/D,KAAK,UAAU,GACZ,oBAAoB,GACpB,oBAAoB,GACpB,YAAY,GACZ,UAAU,CAAC;AAEd,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAAE,WAAW,EAAE,EAAE,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CAiE5E,CAAC;eAEY,IAAI"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Validate the shape of the optional `allowed-tools` frontmatter field.
|
|
3
|
+
*
|
|
4
|
+
* `allowed-tools` (a Claude Code extension to the Agent Skills standard) must
|
|
5
|
+
* be a non-empty array of non-empty strings when present. An empty array
|
|
6
|
+
* grants no tools at all — almost certainly not the intent — and a non-array
|
|
7
|
+
* value is silently ignored by the runtime.
|
|
8
|
+
*/
|
|
9
|
+
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
10
|
+
declare const rule: MarkdownRuleDefinition<{
|
|
11
|
+
RuleOptions: [];
|
|
12
|
+
MessageIds: "notArray" | "empty" | "invalidEntry";
|
|
13
|
+
}>;
|
|
14
|
+
export default rule;
|
|
15
|
+
//# sourceMappingURL=valid-allowed-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"valid-allowed-tools.d.ts","sourceRoot":"","sources":["../../src/rules/valid-allowed-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAM/D,QAAA,MAAM,IAAI,EAAE,sBAAsB,CAAC;IAClC,WAAW,EAAE,EAAE,CAAC;IAChB,UAAU,EAAE,UAAU,GAAG,OAAO,GAAG,cAAc,CAAC;CAClD,CAiDA,CAAC;eAEa,IAAI"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the plugin's rules.
|
|
3
|
+
*/
|
|
4
|
+
/** A 1-based source position (line/column). */
|
|
5
|
+
export interface Point {
|
|
6
|
+
line: number;
|
|
7
|
+
column: number;
|
|
8
|
+
offset?: number;
|
|
9
|
+
}
|
|
10
|
+
/** A source range, compatible with mdast/unist `Position`. */
|
|
11
|
+
export interface SourceRange {
|
|
12
|
+
start: Point;
|
|
13
|
+
end: Point;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,+CAA+C;AAC/C,MAAM,WAAW,KAAK;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,KAAK,CAAC;CACX"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny mdast traversal helpers.
|
|
3
|
+
*
|
|
4
|
+
* Rules use a single `root(node)` visitor and walk from there, which keeps them
|
|
5
|
+
* independent of visitor ordering and free of `:exit`/esquery selectors — the
|
|
6
|
+
* subset that ports directly to oxlint's JS plugin runtime.
|
|
7
|
+
*/
|
|
8
|
+
import type { Nodes, Root, Yaml } from "mdast";
|
|
9
|
+
/**
|
|
10
|
+
* The frontmatter `yaml` node, if present as a direct child of root.
|
|
11
|
+
*/
|
|
12
|
+
export declare function findFrontmatter(root: Root): Yaml | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* All nodes of a given type anywhere in the tree, in document order.
|
|
15
|
+
*/
|
|
16
|
+
export declare function collect<T extends Nodes = Nodes>(node: Nodes, type: T["type"]): T[];
|
|
17
|
+
/**
|
|
18
|
+
* Concatenated text content of a node's subtree.
|
|
19
|
+
*/
|
|
20
|
+
export declare function textContent(node: Nodes): string;
|
|
21
|
+
//# sourceMappingURL=ast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/utils/ast.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AAE/C;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,CAE5D;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAC9C,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GACb,CAAC,EAAE,CAcL;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,CAQ/C"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the YAML frontmatter of a skill and resolve source locations for
|
|
3
|
+
* individual keys, so rules can report precise `loc` ranges.
|
|
4
|
+
*
|
|
5
|
+
* `@eslint/markdown` (with `languageOptions.frontmatter: "yaml"`) surfaces
|
|
6
|
+
* frontmatter as a single `yaml` mdast node whose `.value` is the raw YAML text
|
|
7
|
+
* *without* the enclosing `---` fences. The node's `position.start.line` points
|
|
8
|
+
* at the opening fence, so a key found on line `i` (0-indexed) of `.value` sits
|
|
9
|
+
* on file line `node.position.start.line + 1 + i`.
|
|
10
|
+
*/
|
|
11
|
+
import type { Yaml } from "mdast";
|
|
12
|
+
import type { SourceRange } from "../types";
|
|
13
|
+
export interface Frontmatter {
|
|
14
|
+
/** Parsed frontmatter object, or `null` when it isn't a mapping. */
|
|
15
|
+
data: Record<string, unknown> | null;
|
|
16
|
+
/** Parse error message, if any. */
|
|
17
|
+
error: string | null;
|
|
18
|
+
/** The raw YAML text (fences excluded). */
|
|
19
|
+
raw: string;
|
|
20
|
+
/** Best-effort source location for a top-level key. */
|
|
21
|
+
keyLoc: (key: string) => SourceRange;
|
|
22
|
+
}
|
|
23
|
+
export declare function parseFrontmatter(node: Yaml): Frontmatter;
|
|
24
|
+
//# sourceMappingURL=frontmatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/utils/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AAGlC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,MAAM,WAAW,WAAW;IAC3B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACrC,mCAAmC;IACnC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,WAAW,CAAC;CACrC;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,CA4CxD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill KIND: a taxonomy for applying different lint expectations to
|
|
3
|
+
* different shapes of skill.
|
|
4
|
+
*
|
|
5
|
+
* Grounded in official Claude Code guidance, which distinguishes "Reference
|
|
6
|
+
* content" (inline domain knowledge) from "Task content" (step-by-step
|
|
7
|
+
* actions, often gated with `disable-model-invocation`), plus the
|
|
8
|
+
* `skill-creator` meta-skill's multi-domain/variant pattern
|
|
9
|
+
* (`references/{aws,gcp,azure}.md`). `tool-wrapper` and `meta` are defensible
|
|
10
|
+
* extensions, not explicitly named in any single source — see
|
|
11
|
+
* docs/design/skill-taxonomy-and-rules.md.
|
|
12
|
+
*
|
|
13
|
+
* A skill may declare its kind explicitly via `metadata.kind` in frontmatter;
|
|
14
|
+
* this is the only signal the lint rules act on, since heuristic detection is
|
|
15
|
+
* inherently fuzzy and a wrong guess should never cause a false positive.
|
|
16
|
+
* `detectKind` is exposed separately as an informative, best-effort signal —
|
|
17
|
+
* it never gates a rule.
|
|
18
|
+
*/
|
|
19
|
+
export declare const SKILL_KINDS: readonly ["procedural", "reference", "multi-domain", "tool-wrapper", "meta"];
|
|
20
|
+
export type SkillKind = (typeof SKILL_KINDS)[number];
|
|
21
|
+
/**
|
|
22
|
+
* The skill's explicitly declared kind, via `metadata.kind` in frontmatter.
|
|
23
|
+
* Returns `undefined` when absent, malformed, or not a recognized kind.
|
|
24
|
+
*/
|
|
25
|
+
export declare function declaredKind(data: Record<string, unknown> | null | undefined): SkillKind | undefined;
|
|
26
|
+
export interface DetectKindInput {
|
|
27
|
+
data: Record<string, unknown> | null | undefined;
|
|
28
|
+
/** Number of ordered/numbered lists found in the body. */
|
|
29
|
+
orderedListCount: number;
|
|
30
|
+
/** Number of `.md` files under the skill's `references/` directory. */
|
|
31
|
+
referenceFileCount: number;
|
|
32
|
+
/** Whether the body links to another skill (the `[[skill-name]]` convention). */
|
|
33
|
+
linksToOtherSkills: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Best-effort auto-detected kind from observable signals, for tooling and
|
|
37
|
+
* documentation purposes. This is advisory only — no rule fails or warns
|
|
38
|
+
* based on a guess, only on an explicit `metadata.kind` declaration.
|
|
39
|
+
*/
|
|
40
|
+
export declare function detectKind(input: DetectKindInput): SkillKind | undefined;
|
|
41
|
+
//# sourceMappingURL=kind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kind.d.ts","sourceRoot":"","sources":["../../src/utils/kind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,WAAW,YACvB,YAAY,EACZ,WAAW,EACX,cAAc,EACd,cAAc,EACd,MAAM,CACG,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAQrD;;;GAGG;AACH,wBAAgB,YAAY,CAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAC9C,SAAS,GAAG,SAAS,CAOvB;AAED,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IACjD,0DAA0D;IAC1D,gBAAgB,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iFAAiF;IACjF,kBAAkB,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,GAAG,SAAS,CA8BxE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for working with links and images in a skill body.
|
|
3
|
+
*/
|
|
4
|
+
import type { Image, Link, Root } from "mdast";
|
|
5
|
+
export interface LinkTarget {
|
|
6
|
+
url: string;
|
|
7
|
+
kind: "link" | "image";
|
|
8
|
+
node: Link | Image;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Whether a URL points at a repo-relative path (not an anchor, scheme, or
|
|
12
|
+
* protocol-relative URL).
|
|
13
|
+
*/
|
|
14
|
+
export declare function isRelativeUrl(url: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Strip a URL's fragment and query so it can be resolved to a filesystem path.
|
|
17
|
+
*/
|
|
18
|
+
export declare function urlToPath(url: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* All link and image targets in the tree, in document order.
|
|
21
|
+
*/
|
|
22
|
+
export declare function collectLinkTargets(root: Root): LinkTarget[];
|
|
23
|
+
//# sourceMappingURL=links.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../src/utils/links.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AAI/C,MAAM,WAAW,UAAU;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQlD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAe7C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,EAAE,CAS3D"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for a skill's `references/` directory.
|
|
3
|
+
*/
|
|
4
|
+
import type { SkillFs } from "./skill-fs";
|
|
5
|
+
/** Every `.md` file under a skill's `references/` directory, recursively. */
|
|
6
|
+
export declare function referenceMarkdownFiles(skillDir: string, fs: SkillFs): string[];
|
|
7
|
+
//# sourceMappingURL=references.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"references.d.ts","sourceRoot":"","sources":["../../src/utils/references.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,6EAA6E;AAC7E,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,MAAM,EAAE,CAE9E"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for recognizing and locating Claude Code skill files.
|
|
3
|
+
*
|
|
4
|
+
* Kept dependency-free and side-effect-free so the compiled output ports
|
|
5
|
+
* cleanly to oxlint's ESLint-compatible JS plugin runtime.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* The canonical filename of a Claude Code Agent Skill.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SKILL_FILENAME = "SKILL.md";
|
|
11
|
+
/**
|
|
12
|
+
* Extract the basename of a POSIX or Windows path without relying on `node:path`,
|
|
13
|
+
* so the logic is identical regardless of the host runtime's path semantics.
|
|
14
|
+
*/
|
|
15
|
+
export declare function basename(filename: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* The name of the directory that directly contains the given file.
|
|
18
|
+
*/
|
|
19
|
+
export declare function dirName(filename: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the linted file is a skill definition (`SKILL.md`).
|
|
22
|
+
*
|
|
23
|
+
* Rules call this first and no-op when it returns `false`, so the plugin is
|
|
24
|
+
* safe even when its config globs every `*.md` file in a repository.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isSkillFile(filename: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the linted file is a bundled reference document — a `.md` file
|
|
29
|
+
* (other than `SKILL.md`) living under a `references/` directory.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isReferenceFile(filename: string): boolean;
|
|
32
|
+
//# sourceMappingURL=skill-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-file.d.ts","sourceRoot":"","sources":["../../src/utils/skill-file.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,eAAO,MAAM,cAAc,aAAa,CAAC;AAEzC;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUjD;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAahD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQzD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file access seam of the rules that look past the linted file
|
|
3
|
+
* (`no-broken-relative-links`, `no-orphan-references`, the `kind` rules).
|
|
4
|
+
*
|
|
5
|
+
* By default they read the disk. A host that keeps its skills somewhere else
|
|
6
|
+
* (a database, a remote store) passes every file of the linted skills in the
|
|
7
|
+
* shared setting `settings.skill.files` — a map from path to content — and
|
|
8
|
+
* lints them in memory with ESLint's `Linter`. The paths are resolved like
|
|
9
|
+
* the linted `filename`, so `/skills/demo/SKILL.md` and
|
|
10
|
+
* `/skills/demo/references/a.md` are siblings exactly as on disk. A flat
|
|
11
|
+
* config matches only files under the `Linter`'s `cwd`, so pass the virtual
|
|
12
|
+
* root as `cwd` (`new Linter({ cwd: "/skills" })`).
|
|
13
|
+
*/
|
|
14
|
+
/** The shape of `settings.skill`. */
|
|
15
|
+
export interface SkillSettings {
|
|
16
|
+
/** Every file of the linted skills, keyed by path. Replaces the disk when present. */
|
|
17
|
+
files?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
/** What the rules need from a file system. */
|
|
20
|
+
export interface SkillFs {
|
|
21
|
+
/** Whether a file or a directory exists at an absolute path. */
|
|
22
|
+
exists(path: string): boolean;
|
|
23
|
+
/** Every `.md` file under a directory, recursively, as absolute paths. */
|
|
24
|
+
markdownFilesUnder(dir: string): string[];
|
|
25
|
+
}
|
|
26
|
+
/** A file system over an in-memory map of path → content. */
|
|
27
|
+
export declare function memoryFs(files: Record<string, string>): SkillFs;
|
|
28
|
+
/** The file system a rule must use, chosen from the shared settings. */
|
|
29
|
+
export declare function skillFs(settings: Readonly<Record<string, unknown>> | undefined): SkillFs;
|
|
30
|
+
//# sourceMappingURL=skill-fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-fs.d.ts","sourceRoot":"","sources":["../../src/utils/skill-fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC7B,sFAAsF;IACtF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,8CAA8C;AAC9C,MAAM,WAAW,OAAO;IACvB,gEAAgE;IAChE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,0EAA0E;IAC1E,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1C;AAgCD,6DAA6D;AAC7D,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAc/D;AAED,wEAAwE;AACxE,wBAAgB,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,GAAG,OAAO,CAGxF"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# `skill-check-refs` — cross-skill reference de-duplication
|
|
2
|
+
|
|
3
|
+
`SKILL.md` rules run one file at a time, so they cannot see that two *different* skills carry duplicated or drifted copies of the same reference content. This is a repo/collection-level analysis, shipped as a separate CLI (and library export) rather than an ESLint rule.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Reference files are meant to live one level below their own `SKILL.md` (see [`refs-one-level-deep`](rules/refs-one-level-deep.md)) and each skill bundle is self-contained — which means sibling skills that need the same background material end up **copying** it. Over time those copies:
|
|
8
|
+
|
|
9
|
+
- stay byte-identical → a **maintenance burden** (every fix has to be applied N times), or
|
|
10
|
+
- drift apart → a **silent correctness hazard** (a fix lands in one copy; the other quietly keeps behaving on stale guidance).
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npx @weareikko/eslint-plugin-skill skill-check-refs [root] [options]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
(Or, once installed as a dependency, just `skill-check-refs`.)
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
skill-check-refs [root] # default: current directory
|
|
22
|
+
--scope=plugin|repo # default: plugin
|
|
23
|
+
--near-duplicates # also detect similar content under different filenames
|
|
24
|
+
--threshold=0.75 # near-duplicate similarity threshold (0-1)
|
|
25
|
+
--ignore=a.md,b.md # reference filenames to exclude (deliberate divergence)
|
|
26
|
+
--json # machine-readable output
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Exit code is **1** if any divergence is found, **0** otherwise (duplicates and near-duplicates are advisory and never fail the run on their own).
|
|
30
|
+
|
|
31
|
+
## How it works
|
|
32
|
+
|
|
33
|
+
1. Discovers every `SKILL.md` under `root`.
|
|
34
|
+
2. Groups sibling skills into a **scope** — by default, the parent of the nearest ancestor directory literally named `skills` (so `plugins/cro/skills/{a,b}` are compared together, scoped to `plugins/cro`). Skills with no such ancestor fall back to one scope keyed by `root`. Pass `--scope=repo` to compare every discovered skill in one global scope instead.
|
|
35
|
+
3. Within each scope, hashes every `references/**/*.md` file (content is normalized first: CRLF → LF, trailing whitespace stripped, runs of blank lines collapsed) and groups by filename.
|
|
36
|
+
4. Classifies each same-filename group:
|
|
37
|
+
- **all hashes match** → `duplicate` (warn) — byte-identical across skills, consider consolidating.
|
|
38
|
+
- **hashes differ** → `divergence` (error) — the copies forked; reconcile or rename.
|
|
39
|
+
5. With `--near-duplicates`, also compares files with *different* filenames pairwise (word-shingle Jaccard similarity) and reports pairs above the threshold as `near-duplicate` (warn) — a likely copy that was renamed instead of shared.
|
|
40
|
+
|
|
41
|
+
## Suppressing deliberate divergence
|
|
42
|
+
|
|
43
|
+
If two skills are supposed to carry different content under the same reference filename, exclude it:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
skill-check-refs . --ignore=output-template.md
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Library usage
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { analyzeReferences, formatReport } from "@weareikko/eslint-plugin-skill/dedup";
|
|
53
|
+
|
|
54
|
+
const result = analyzeReferences("./plugins/cro");
|
|
55
|
+
console.log(formatReport(result));
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## What this does NOT do (yet)
|
|
59
|
+
|
|
60
|
+
It only *reports* duplication/drift — it does not propose or apply a shared-location refactor. There is currently no official Agent Skills convention for sharing reference content across skills (see the [design doc](design/skill-taxonomy-and-rules.md)); consolidating a flagged duplicate today means picking your own shared location and updating each skill's links by hand.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# skill/body-max-length
|
|
2
|
+
|
|
3
|
+
Keep the skill body short to preserve progressive disclosure.
|
|
4
|
+
|
|
5
|
+
A `SKILL.md` is an entry point, not a manual. Heavy detail belongs in referenced files that are loaded on demand, so the skill itself stays cheap to keep in context. This rule flags bodies (frontmatter excluded) that grow past a line budget. It is a `warn` by default.
|
|
6
|
+
|
|
7
|
+
## Fails
|
|
8
|
+
|
|
9
|
+
A body longer than `maxLines` lines.
|
|
10
|
+
|
|
11
|
+
## Passes
|
|
12
|
+
|
|
13
|
+
A concise body that links out to references for detail.
|
|
14
|
+
|
|
15
|
+
## Options
|
|
16
|
+
|
|
17
|
+
| Option | Type | Default | Description |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| `maxLines` | integer ≥ 1 | `500` | Maximum body lines, excluding frontmatter. |
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
"skill/body-max-length": ["warn", { maxLines: 400 }]
|
|
23
|
+
```
|